#!/bin/bash # Extreme amount tests for GOA: largest practical price vs atomic/tiny coin. # # Live denoms (exchange.hacktivism.ch): GOA:0.000001 … GOA:10 # Currency TINY / round unit: GOA:0.00000001 (Atomic-GOA) # # Run on the local machine (wallet) with ssh BatchMode to koopa for bank/merchant ops. # Requires: monorepo taler-wallet-cli, passwords on koopa /root/*-password.txt # # Usage: # ./run-extreme-amount-tests.sh # HIGH_AMOUNT=GOA:500 TINY_AMOUNT=GOA:0.000001 ./run-extreme-amount-tests.sh set -euo pipefail ROOT=$(cd "$(dirname "$0")/../.." && pwd) SCRATCH="${ROOT}/.tmp/extreme-$(date -u +%Y%m%d-%H%M%S)" mkdir -p "$SCRATCH" chmod 700 "$SCRATCH" MONO="${MONO:-/Users/newkamek/src/taler/taler-typescript-core/packages/taler-wallet-cli/bin/taler-wallet-cli.mjs}" WDB="${WDB:-$SCRATCH/wallet.sqlite3}" EX="${EXCHANGE_URL:-https://exchange.hacktivism.ch/}" KOOPA="${KOOPA_HOST:-koopa}" INST="${MERCHANT_INSTANCE:-goa-demo-cp4zqk}" BANK_PUBLIC="${BANK_PUBLIC:-https://bank.hacktivism.ch}" MER_PUBLIC="${MER_PUBLIC:-https://taler.hacktivism.ch}" # High price: many max-denom (GOA:10) coins. Tiny: smallest issued coin. HIGH_AMOUNT="${HIGH_AMOUNT:-GOA:1000}" TINY_AMOUNT="${TINY_AMOUNT:-GOA:0.000001}" # True atomic (round unit) — may fail if no coin below 1e-6 ATOMIC_AMOUNT="${ATOMIC_AMOUNT:-GOA:0.00000001}" # Fat bank balance for withdraw buffer FAT_CREDIT="${FAT_CREDIT:-GOA:50000}" FAT_USER="${FAT_USER:-extreme-fat}" # withdraw a bit more than HIGH WITHDRAW_AMOUNT="${WITHDRAW_AMOUNT:-GOA:1500}" wcli() { node "$MONO" --wallet-db="$WDB" --no-throttle --skip-defaults "$@"; } log() { printf '%s %s\n' "$(date -u +%H:%M:%S)" "$*"; } die() { echo "FAIL: $*" >&2; exit 1; } command -v node >/dev/null || die "node missing" [ -f "$MONO" ] || die "wallet missing: $MONO" ssh -o BatchMode=yes -o ConnectTimeout=10 "$KOOPA" 'echo ok' >/dev/null || die "ssh $KOOPA failed" log "scratch=$SCRATCH" log "HIGH=$HIGH_AMOUNT TINY=$TINY_AMOUNT ATOMIC=$ATOMIC_AMOUNT WITHDRAW=$WITHDRAW_AMOUNT FAT=$FAT_USER+$FAT_CREDIT" # --- 1) koopa: bank account + credit + templates + orders --- log "=== koopa setup (admin credit, templates, orders) ===" # Generate bank password locally (not committed) FAT_PW=$(python3 -c 'import secrets; print(secrets.token_urlsafe(12))') echo "$FAT_PW" >"$SCRATCH/fat-bank.password" chmod 600 "$SCRATCH/fat-bank.password" cat >"$SCRATCH/koopa-setup.sh" < /tmp/extreme-setup.out 2>&1 set -x INST=$INST FAT_USER=$FAT_USER FAT_PW='$(printf %s "$FAT_PW" | sed "s/'/'\\\\''/g")' FAT_CREDIT=$FAT_CREDIT HIGH_AMOUNT=$HIGH_AMOUNT TINY_AMOUNT=$TINY_AMOUNT ATOMIC_AMOUNT=$ATOMIC_AMOUNT BANK=http://127.0.0.1:9012 MER=https://127.0.0.1:9010 ADMIN_PASS=\$(tr -d '\\n' =5: bits-=5; o.append(a[(v>>bits)&31]) if bits: o.append(a[(v<<(5-bits))&31]) print("".join(o))') curl -sS -H "Authorization: Bearer \${AT}" -H 'Content-Type: application/json' \\ -d "{\\"payto_uri\\":\\"\${PAYTO}\\",\\"amount\\":\\"\${FAT_CREDIT}\\",\\"request_uid\\":\\"\${UID}\\"}" \\ "\${BANK}/accounts/admin/transactions" | tee /tmp/credit.out echo fi # balance curl -sS -H "Authorization: Bearer \${AT}" "\${BANK}/accounts/\${FAT_USER}" | tee /tmp/fat-bal.json echo python3 -c 'import json;d=json.load(open("/tmp/fat-bal.json"));print("FAT_BALANCE", (d.get("balance") or {}).get("amount"), (d.get("balance") or {}).get("credit_debit_indicator"))' # store bank pw for host-side withdraw printf '%s' "\$FAT_PW" > /root/bank-\${FAT_USER}-password.txt chmod 600 /root/bank-\${FAT_USER}-password.txt # templates for id_summary_amount in \\ "extreme-high|extreme high price|\${HIGH_AMOUNT}" \\ "extreme-tiny|extreme tiny coin|\${TINY_AMOUNT}" \\ "extreme-atomic|true atomic round unit|\${ATOMIC_AMOUNT}" do IFS='|' read -r TID SUM AMT <<<"\$id_summary_amount" body=\$(python3 -c "import json; print(json.dumps({ 'template_id': '\$TID', 'template_description': '\$SUM', 'editable_defaults': False, 'template_contract': { 'summary': '\$SUM', 'amount': '\$AMT', 'minimum_age': 0, }, }))") # try POST then PATCH code=\$(curl -sk -o /tmp/tpl.json -w '%{http_code}' -X POST -H "\$AUTH" -H 'Content-Type: application/json' \\ -d "\$body" "\${MER}/instances/\${INST}/private/templates") echo "template_\$TID post=\$code \$(head -c 120 /tmp/tpl.json)" if [ "\$code" = "409" ] || [ "\$code" = "400" ]; then curl -sk -X PATCH -H "\$AUTH" -H 'Content-Type: application/json' \\ -d "\$body" "\${MER}/instances/\${INST}/private/templates/\${TID}" -w " patch=%{http_code}\\n" fi done # also list templates curl -sk -H "\$AUTH" "\${MER}/instances/\${INST}/private/templates" | python3 -m json.tool | head -80 # create two firm orders (high + tiny) for deterministic pay URIs create_order() { local sum="\$1" amt="\$2" tag="\$3" curl -sk -X POST -H "\$AUTH" -H 'Content-Type: application/json' \\ -d "{\\"order\\":{\\"summary\\":\\"\${sum}\\",\\"amount\\":\\"\${amt}\\",\\"fulfillment_message\\":\\"extreme-ok\\"},\\"create_token\\":true}" \\ "\${MER}/instances/\${INST}/private/orders" > "/tmp/ord-\${tag}.json" python3 - "\$tag" <<'PY' import json,sys tag=sys.argv[1] d=json.load(open(f"/tmp/ord-{tag}.json")) print(f"ORDER_{tag.upper()}", d.get("order_id"), "token", (d.get("token") or "")[:12]) open(f"/tmp/oid-{tag}.txt","w").write(d.get("order_id") or "") open(f"/tmp/otok-{tag}.txt","w").write(d.get("token") or "") PY } create_order "extreme-high-order" "\$HIGH_AMOUNT" high create_order "extreme-tiny-order" "\$TINY_AMOUNT" tiny create_order "extreme-atomic-order" "\$ATOMIC_AMOUNT" atomic # create bank integrated withdrawal for fat user BT=\$(curl -sS -u "\${FAT_USER}:\${FAT_PW}" -H 'Content-Type: application/json' \\ -d '{"scope":"readwrite"}' "\${BANK}/accounts/\${FAT_USER}/token" \\ | python3 -c 'import sys,json;print(json.load(sys.stdin).get("access_token",""))') curl -sS -H "Authorization: Bearer \${BT}" -H 'Content-Type: application/json' \\ -d "{\\"amount\\":\\"\$WITHDRAW_AMOUNT\\",\\"exchange_url\\":\\"https://exchange.hacktivism.ch/\\"}" \\ "\${BANK}/accounts/\${FAT_USER}/withdrawals" | tee /tmp/wd-create.json echo python3 - <<'PY' import json d=json.load(open("/tmp/wd-create.json")) wid=d.get("withdrawal_id") or d.get("id") uri=d.get("taler_withdraw_uri") print("WID", wid) print("WURI", uri) open("/tmp/wid.txt","w").write(wid or "") open("/tmp/wuri.txt","w").write(uri or "") PY # publish artifacts for hernani-readable pull cp /tmp/oid-*.txt /tmp/otok-*.txt /tmp/wuri.txt /tmp/wid.txt /tmp/fat-bal.json /tmp/extreme-setup.out /home/hernani/ 2>/dev/null || true chmod 644 /home/hernani/oid-*.txt /home/hernani/otok-*.txt /home/hernani/wuri.txt /home/hernani/wid.txt 2>/dev/null || true echo SETUP_EXTREME_OK EOS # inject WITHDRAW_AMOUNT into script (heredoc already expanded partially - fix) # The setup script uses $WITHDRAW_AMOUNT as shell var on koopa - need to export into script sed -i.bak "s|\$WITHDRAW_AMOUNT|$WITHDRAW_AMOUNT|g" "$SCRATCH/koopa-setup.sh" 2>/dev/null || \ sed -i '' "s|\$WITHDRAW_AMOUNT|$WITHDRAW_AMOUNT|g" "$SCRATCH/koopa-setup.sh" scp -o BatchMode=yes "$SCRATCH/koopa-setup.sh" "${KOOPA}:/tmp/extreme-setup.sh" # also scp credit-account for reliable credit scp -o BatchMode=yes "$ROOT/scripts/taler-bank/credit-account.sh" "${KOOPA}:/tmp/credit-account.sh" || true # run via screen window 0 (root@koopa) — screen-run cannot source local-machine paths SESSION=$(screen -ls 2>/dev/null | awk 'match($0, /[0-9]+\.[A-Za-z0-9._-]+/) { print substr($0, RSTART, RLENGTH); exit }') [ -n "$SESSION" ] || die "no screen session" screen -S "$SESSION" -p 0 -X stuff $'bash /tmp/extreme-setup.sh; chmod 644 /tmp/extreme-setup.out /home/hernani/*.txt 2>/dev/null\n' for i in $(seq 1 90); do if ssh -o BatchMode=yes "$KOOPA" 'grep -q SETUP_EXTREME_OK /tmp/extreme-setup.out 2>/dev/null'; then log "setup ready @${i}s" break fi sleep 1 [ "$i" -eq 90 ] && { ssh -o BatchMode=yes "$KOOPA" 'tail -80 /tmp/extreme-setup.out' || true; die "setup timeout"; } done ssh -o BatchMode=yes "$KOOPA" 'cat /tmp/extreme-setup.out' >"$SCRATCH/koopa-setup.out" || true # pull artifacts (hernani-readable) scp -o BatchMode=yes "${KOOPA}:/home/hernani/wuri.txt" "${KOOPA}:/home/hernani/wid.txt" \ "${KOOPA}:/home/hernani/oid-high.txt" "${KOOPA}:/home/hernani/oid-tiny.txt" \ "${KOOPA}:/home/hernani/oid-atomic.txt" \ "${KOOPA}:/home/hernani/otok-high.txt" "${KOOPA}:/home/hernani/otok-tiny.txt" \ "${KOOPA}:/home/hernani/otok-atomic.txt" \ "$SCRATCH/" 2>/dev/null || true WURI=$(tr -d '\n' <"$SCRATCH/wuri.txt" 2>/dev/null || true) WID=$(tr -d '\n' <"$SCRATCH/wid.txt" 2>/dev/null || true) OID_HIGH=$(tr -d '\n' <"$SCRATCH/oid-high.txt" 2>/dev/null || true) OID_TINY=$(tr -d '\n' <"$SCRATCH/oid-tiny.txt" 2>/dev/null || true) OID_ATOMIC=$(tr -d '\n' <"$SCRATCH/oid-atomic.txt" 2>/dev/null || true) TOK_HIGH=$(tr -d '\n' <"$SCRATCH/otok-high.txt" 2>/dev/null || true) TOK_TINY=$(tr -d '\n' <"$SCRATCH/otok-tiny.txt" 2>/dev/null || true) TOK_ATOMIC=$(tr -d '\n' <"$SCRATCH/otok-atomic.txt" 2>/dev/null || true) log "WURI=$WURI" log "orders high=$OID_HIGH tiny=$OID_TINY atomic=$OID_ATOMIC" [ -n "$WURI" ] || die "no withdraw URI" [ -n "$OID_HIGH" ] && [ -n "$OID_TINY" ] || die "orders missing" # ensure auto-confirm watches this withdrawal ssh -o BatchMode=yes "$KOOPA" "echo $WID >> /var/www/bank-landing/withdraw-watch.ids 2>/dev/null; sort -u /var/www/bank-landing/withdraw-watch.ids -o /var/www/bank-landing/withdraw-watch.ids 2>/dev/null; true" # root confirm via screen screen -S "$SESSION" -p 0 -X stuff $'BPW=$(tr -d "\\n" &1 | tee "$SCRATCH/ex-add.out" || true wcli exchanges update "$EX" 2>&1 | tee "$SCRATCH/ex-upd.out" || true wcli exchanges tos "$EX" 2>&1 | tee "$SCRATCH/ex-tos.out" || true wcli exchanges accept-tos "$EX" 2>&1 | tee "$SCRATCH/ex-accept.out" || true log "=== withdraw accept-uri ===" wcli withdraw accept-uri --exchange "$EX" "$WURI" 2>&1 | tee "$SCRATCH/accept-uri.out" || true for round in $(seq 1 20); do log "run-until-done round $round" wcli run-until-done 2>&1 | tee -a "$SCRATCH/rud.out" || true wcli balance 2>&1 | tee "$SCRATCH/bal-$round.out" if grep -qE "GOA:" "$SCRATCH/bal-$round.out"; then # check non-zero available if python3 -c 'import re,sys; t=open(sys.argv[1]).read(); m=re.search(r"available[^0-9A-Z]*GOA:([0-9.]+)", t) print(m.group(1) if m else ""); sys.exit(0 if m and float(m.group(1))>0 else 1)' "$SCRATCH/bal-$round.out" 2>/dev/null; then log "coins available" break fi fi # re-confirm withdraw on bank screen -S "$SESSION" -p 0 -X stuff $'BPW=$(tr -d "\\n" /dev/null; echo\n' sleep 3 done wcli balance 2>&1 | tee "$SCRATCH/balance-after-withdraw.out" wcli transactions 2>&1 | tee "$SCRATCH/tx-after-withdraw.out" | head -100 # --- 3) pay high + tiny (+ try atomic) --- pay_order() { local tag="$1" oid="$2" tok="$3" local uri="taler://pay/taler.hacktivism.ch/instances/${INST}/${oid}/?c=${tok}" echo "$uri" >"$SCRATCH/pay-${tag}.uri" log "=== pay $tag order=$oid ===" log "uri=$uri" wcli handle-uri "$uri" 2>&1 | tee "$SCRATCH/pay-${tag}-handle.out" || true # some wallets use payments prepare/confirm wcli run-until-done 2>&1 | tee -a "$SCRATCH/pay-${tag}-rud.out" || true # check merchant ssh -o BatchMode=yes "$KOOPA" "MPW=\$(tr -d '\\n' "$SCRATCH/order-${tag}-status.json" 2>/dev/null || true python3 - "$SCRATCH/order-${tag}-status.json" "$tag" <<'PY' || true import json,sys p,tag=sys.argv[1],sys.argv[2] try: d=json.load(open(p)) except Exception as e: print(f"STATUS_{tag} unreadable", e); raise SystemExit print(f"STATUS_{tag}", "order_status=", d.get("order_status"), "paid=", d.get("paid"), "wired=", d.get("wired"), "deposit_total=", d.get("deposit_total"), "amount=", (d.get("contract_terms") or {}).get("amount")) PY } pay_order high "$OID_HIGH" "$TOK_HIGH" wcli balance 2>&1 | tee "$SCRATCH/balance-after-high.out" pay_order tiny "$OID_TINY" "$TOK_TINY" wcli balance 2>&1 | tee "$SCRATCH/balance-after-tiny.out" if [ -n "$OID_ATOMIC" ] && [ -n "$TOK_ATOMIC" ]; then pay_order atomic "$OID_ATOMIC" "$TOK_ATOMIC" || true wcli balance 2>&1 | tee "$SCRATCH/balance-after-atomic.out" || true fi # template URIs for docs { echo "taler://pay-template/taler.hacktivism.ch/instances/${INST}/extreme-high/" echo "taler://pay-template/taler.hacktivism.ch/instances/${INST}/extreme-tiny/" echo "taler://pay-template/taler.hacktivism.ch/instances/${INST}/extreme-atomic/" } | tee "$SCRATCH/template-uris.txt" log "=== summary ===" python3 - <