bank: standalone GOA withdraw ladder helper script.
Fixed zero and max endpoints with random intermediate amounts for local tests.
This commit is contained in:
parent
36ef637501
commit
4dbede51b4
1 changed files with 305 additions and 0 deletions
305
scripts/taler-bank/goa-withdraw-ladder.sh
Executable file
305
scripts/taler-bank/goa-withdraw-ladder.sh
Executable file
|
|
@ -0,0 +1,305 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Systematic GOA withdraw ladder on bank.hacktivism.ch
|
||||||
|
#
|
||||||
|
# Per landing / intro docs:
|
||||||
|
# 1) GET /intro/auto-account.json → personal goa-account-* (+ first pool URI)
|
||||||
|
# 2) Shared pool withdrawals (explorer) with server auto-confirm
|
||||||
|
# 3) wallet-cli: ToS + accept-uri + run-until-done each rung
|
||||||
|
# 4) amounts from atomic-GOA up until bank/wallet fails
|
||||||
|
#
|
||||||
|
# Prefer the monitoring phase (random increasing ranges + timings + report):
|
||||||
|
# ../taler-monitoring/taler-monitoring.sh ladder
|
||||||
|
# LADDER_MAX_RUNGS=10 ../taler-monitoring/check_goa_ladder.sh
|
||||||
|
#
|
||||||
|
# Usage (standalone, fixed ladder):
|
||||||
|
# ./goa-withdraw-ladder.sh
|
||||||
|
# MAX_RUNGS=12 ./goa-withdraw-ladder.sh
|
||||||
|
# AMOUNTS='GOA:0.000001 GOA:0.01 GOA:1 GOA:10 GOA:100' ./goa-withdraw-ladder.sh
|
||||||
|
#
|
||||||
|
# Env:
|
||||||
|
# BANK default https://bank.hacktivism.ch
|
||||||
|
# EXCHANGE default https://exchange.hacktivism.ch/
|
||||||
|
# EXP_USER default explorer
|
||||||
|
# EXP_PW_FILE explorer password file (for mint + confirm)
|
||||||
|
# WALLET_CLI path to taler-wallet-cli.mjs or binary
|
||||||
|
# WDB wallet sqlite path
|
||||||
|
# SHOTDIR result directory
|
||||||
|
set -euo pipefail
|
||||||
|
export PATH="/tmp/py313bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:${PATH}"
|
||||||
|
|
||||||
|
BANK="${BANK:-https://bank.hacktivism.ch}"
|
||||||
|
BANK="${BANK%/}"
|
||||||
|
EXCHANGE="${EXCHANGE:-https://exchange.hacktivism.ch/}"
|
||||||
|
EXP_USER="${EXP_USER:-explorer}"
|
||||||
|
EXP_PW_FILE="${EXP_PW_FILE:-/Users/newkamek/src/koopa/koopa-admin-secrets/koopa/host-root/taler-bank/bank-explorer-password.txt}"
|
||||||
|
SHOTDIR="${SHOTDIR:-/tmp/goa-ladder-$(date +%Y%m%d-%H%M%S)}"
|
||||||
|
WDB="${WDB:-$SHOTDIR/wallet.db}"
|
||||||
|
MAX_RUNGS="${MAX_RUNGS:-24}"
|
||||||
|
CLI_JS="${WALLET_CLI_JS:-/Users/newkamek/src/taler-typescript-core/packages/taler-wallet-cli/bin/taler-wallet-cli.mjs}"
|
||||||
|
mkdir -p "$SHOTDIR"
|
||||||
|
LOG="$SHOTDIR/ladder.log"
|
||||||
|
RESULTS="$SHOTDIR/results.tsv"
|
||||||
|
echo -e "rung\tamount\tstatus\twid\tnote" >"$RESULTS"
|
||||||
|
|
||||||
|
log() { printf '%s\n' "$*" | tee -a "$LOG"; }
|
||||||
|
die() { log "ERROR: $*"; exit 1; }
|
||||||
|
|
||||||
|
wcli() {
|
||||||
|
if [ -f "$CLI_JS" ]; then
|
||||||
|
node "$CLI_JS" --wallet-db="$WDB" --no-throttle "$@"
|
||||||
|
else
|
||||||
|
taler-wallet-cli --wallet-db="$WDB" --no-throttle "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Default ladder via Python: fixed 0 + random low picks + random high ranges + fixed max.
|
||||||
|
# Prefer taler-monitoring check_goa_ladder.sh for full control (LADDER_* env).
|
||||||
|
default_amounts() {
|
||||||
|
python3 - <<'PY'
|
||||||
|
import math, random
|
||||||
|
from decimal import Decimal, ROUND_HALF_UP
|
||||||
|
|
||||||
|
MAX = Decimal("4503599627370496")
|
||||||
|
HIGH_FROM = Decimal("1000000")
|
||||||
|
HIGH_RUNGS = 12
|
||||||
|
CUR = "GOA"
|
||||||
|
|
||||||
|
def fmt(v: Decimal) -> str:
|
||||||
|
q = v.quantize(Decimal("0.00000001"), rounding=ROUND_HALF_UP)
|
||||||
|
if q == q.to_integral():
|
||||||
|
return "%s:%s" % (CUR, format(int(q), "d"))
|
||||||
|
return "%s:%s" % (CUR, format(q, "f").rstrip("0").rstrip("."))
|
||||||
|
|
||||||
|
def logu(lo, hi):
|
||||||
|
lo = max(lo, 1e-12)
|
||||||
|
if hi <= lo:
|
||||||
|
return hi
|
||||||
|
return math.exp(random.uniform(math.log(lo), math.log(hi)))
|
||||||
|
|
||||||
|
out = [fmt(Decimal(0))]
|
||||||
|
prev = Decimal(0)
|
||||||
|
# low bands (same idea as monitoring LADDER_RANGES without 0:0)
|
||||||
|
bands = [
|
||||||
|
(1e-6, 9e-6), (1e-5, 9e-5), (1e-4, 9e-4), (1e-3, 9e-3), (0.01, 0.09),
|
||||||
|
(0.1, 0.9), (1, 9), (10, 49), (50, 99), (100, 499), (500, 999),
|
||||||
|
(1000, 4999), (5000, 9999), (1e4, 5e4), (5e4, 1e5), (1e5, 5e5), (5e5, 2e6),
|
||||||
|
]
|
||||||
|
for lo, hi in bands:
|
||||||
|
lo2 = max(lo, float(prev) * 1.0000001 if prev > 0 else lo)
|
||||||
|
if lo2 > hi:
|
||||||
|
continue
|
||||||
|
v = Decimal(str(logu(lo2, hi))).quantize(Decimal("0.00000001"))
|
||||||
|
if v <= prev:
|
||||||
|
continue
|
||||||
|
if v >= MAX:
|
||||||
|
continue
|
||||||
|
out.append(fmt(v))
|
||||||
|
prev = v
|
||||||
|
|
||||||
|
band_lo = max(float(HIGH_FROM), float(prev) * 1.0000001)
|
||||||
|
band_hi = float(MAX - 1)
|
||||||
|
if band_lo < band_hi and HIGH_RUNGS > 0:
|
||||||
|
cuts = [band_lo, band_hi]
|
||||||
|
for _ in range(HIGH_RUNGS - 1):
|
||||||
|
cuts.append(logu(band_lo, band_hi))
|
||||||
|
cuts = sorted(set(cuts))
|
||||||
|
while len(cuts) < HIGH_RUNGS + 1:
|
||||||
|
cuts.append(logu(band_lo, band_hi))
|
||||||
|
cuts = sorted(set(cuts))
|
||||||
|
segs = [(cuts[i], cuts[i + 1]) for i in range(len(cuts) - 1) if cuts[i + 1] > cuts[i] * 1.001]
|
||||||
|
segs.sort(key=lambda t: t[0])
|
||||||
|
for r_lo, r_hi in segs[:HIGH_RUNGS]:
|
||||||
|
floor = max(r_lo, float(prev) * 1.0000001)
|
||||||
|
if floor >= r_hi:
|
||||||
|
continue
|
||||||
|
v = Decimal(str(logu(floor, r_hi))).quantize(Decimal(1))
|
||||||
|
if v <= prev or v >= MAX:
|
||||||
|
continue
|
||||||
|
out.append(fmt(v))
|
||||||
|
prev = v
|
||||||
|
|
||||||
|
out.append(fmt(MAX))
|
||||||
|
print("\n".join(out))
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ -n "${AMOUNTS:-}" ]; then
|
||||||
|
# shellcheck disable=SC2206
|
||||||
|
LADDER=($AMOUNTS)
|
||||||
|
else
|
||||||
|
LADDER=()
|
||||||
|
while IFS= read -r line; do
|
||||||
|
[ -n "$line" ] || continue
|
||||||
|
LADDER+=("$line")
|
||||||
|
[ "${#LADDER[@]}" -ge "$MAX_RUNGS" ] && break
|
||||||
|
done < <(default_amounts)
|
||||||
|
fi
|
||||||
|
|
||||||
|
[ -f "$EXP_PW_FILE" ] || die "missing explorer password: $EXP_PW_FILE"
|
||||||
|
EXP_PW="$(tr -d '\n' <"$EXP_PW_FILE")"
|
||||||
|
|
||||||
|
explorer_token() {
|
||||||
|
curl -sS -m 20 -u "${EXP_USER}:${EXP_PW}" \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-d '{"scope":"readwrite","duration":{"d_us":3600000000}}' \
|
||||||
|
"${BANK}/accounts/${EXP_USER}/token" \
|
||||||
|
| python3 -c 'import json,sys; print(json.load(sys.stdin)["access_token"])'
|
||||||
|
}
|
||||||
|
|
||||||
|
confirm_when_selected() {
|
||||||
|
local wid="$1" tok="$2"
|
||||||
|
local i st
|
||||||
|
for i in $(seq 1 30); do
|
||||||
|
st=$(curl -sS -m 12 "${BANK}/taler-integration/withdrawal-operation/${wid}" \
|
||||||
|
| python3 -c 'import json,sys; print(json.load(sys.stdin).get("status",""))' 2>/dev/null || true)
|
||||||
|
case "$st" in
|
||||||
|
selected)
|
||||||
|
code=$(curl -sS -m 20 -o "$SHOTDIR/conf-${wid}.json" -w '%{http_code}' -X POST \
|
||||||
|
-H "Authorization: Bearer ${tok}" \
|
||||||
|
-H 'Content-Type: application/json' -d '{}' \
|
||||||
|
"${BANK}/accounts/${EXP_USER}/withdrawals/${wid}/confirm")
|
||||||
|
log " auto-confirm HTTP $code (status was selected)"
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
confirmed)
|
||||||
|
log " already confirmed"
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
aborted)
|
||||||
|
log " aborted"
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
log " timeout waiting for selected (last=$st)"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
mint_withdraw() {
|
||||||
|
local amount="$1"
|
||||||
|
curl -sS -m 30 -H "Authorization: Bearer ${TOK}" \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-d "{\"amount\":\"${amount}\"}" \
|
||||||
|
"${BANK}/accounts/${EXP_USER}/withdrawals"
|
||||||
|
}
|
||||||
|
|
||||||
|
wallet_avail() {
|
||||||
|
wcli balance 2>/dev/null | python3 -c '
|
||||||
|
import json,sys,re
|
||||||
|
t=sys.stdin.read()
|
||||||
|
i=t.find("{")
|
||||||
|
if i<0: print("0"); raise SystemExit
|
||||||
|
d=json.loads(t[i:t.rfind("}")+1])
|
||||||
|
for b in d.get("balances") or []:
|
||||||
|
a=b.get("available") or ""
|
||||||
|
if a.startswith("GOA:"):
|
||||||
|
print(a.split(":",1)[1]); raise SystemExit
|
||||||
|
print("0")
|
||||||
|
' 2>/dev/null || echo "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- main ---
|
||||||
|
log "SHOTDIR=$SHOTDIR"
|
||||||
|
log "BANK=$BANK EXCHANGE=$EXCHANGE"
|
||||||
|
log "ladder (${#LADDER[@]} rungs): ${LADDER[*]}"
|
||||||
|
|
||||||
|
log "=== 1) auto-account (bank.hacktivism.ch/intro) ==="
|
||||||
|
curl -sS -m 30 "${BANK}/intro/auto-account.json" | tee "$SHOTDIR/auto-account.json" | python3 -m json.tool | tee -a "$LOG" | head -40
|
||||||
|
ACCT_USER=$(python3 -c 'import json; print(json.load(open("'"$SHOTDIR"'/auto-account.json"))["username"])')
|
||||||
|
ACCT_PASS=$(python3 -c 'import json; print(json.load(open("'"$SHOTDIR"'/auto-account.json"))["password"])')
|
||||||
|
log "personal account: $ACCT_USER (balance GOA:0 — pool withdraws use explorer + auto-confirm)"
|
||||||
|
echo "$ACCT_USER" >"$SHOTDIR/account-user.txt"
|
||||||
|
echo "$ACCT_PASS" >"$SHOTDIR/account-pass.txt"
|
||||||
|
|
||||||
|
log "=== 2) explorer token + wallet init ==="
|
||||||
|
TOK=$(explorer_token)
|
||||||
|
log "explorer token ok"
|
||||||
|
|
||||||
|
wcli exchanges add "$EXCHANGE" 2>&1 | tee "$SHOTDIR/ex-add.out" | tail -5 || true
|
||||||
|
wcli exchanges update "$EXCHANGE" 2>&1 | tee "$SHOTDIR/ex-upd.out" | tail -5 || true
|
||||||
|
wcli exchanges accept-tos "$EXCHANGE" 2>&1 | tee "$SHOTDIR/ex-tos.out" | tail -5 || true
|
||||||
|
|
||||||
|
n=0
|
||||||
|
fail_rung=""
|
||||||
|
for amt in "${LADDER[@]}"; do
|
||||||
|
n=$((n + 1))
|
||||||
|
log ""
|
||||||
|
log "======== rung $n / ${#LADDER[@]} amount=$amt ========"
|
||||||
|
WD=$(mint_withdraw "$amt" 2>&1) || true
|
||||||
|
echo "$WD" | tee "$SHOTDIR/wd-${n}.json" >/dev/null
|
||||||
|
if ! echo "$WD" | python3 -c 'import json,sys; json.load(sys.stdin)' 2>/dev/null; then
|
||||||
|
log "FAIL mint: $WD"
|
||||||
|
echo -e "${n}\t${amt}\tFAIL_MINT\t-\t$(echo "$WD" | tr '\n' ' ' | head -c 200)" >>"$RESULTS"
|
||||||
|
fail_rung="$amt"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
WID=$(echo "$WD" | python3 -c 'import json,sys; print(json.load(sys.stdin).get("withdrawal_id",""))')
|
||||||
|
URI=$(echo "$WD" | python3 -c 'import json,sys; u=json.load(sys.stdin).get("taler_withdraw_uri",""); print(u.replace(":443/","/"))')
|
||||||
|
[ -n "$WID" ] && [ -n "$URI" ] || {
|
||||||
|
log "FAIL parse mint: $WD"
|
||||||
|
echo -e "${n}\t${amt}\tFAIL_PARSE\t-\t" >>"$RESULTS"
|
||||||
|
fail_rung="$amt"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
log " WID=$WID"
|
||||||
|
log " URI=$URI"
|
||||||
|
|
||||||
|
before=$(wallet_avail)
|
||||||
|
if ! wcli withdraw accept-uri --exchange "$EXCHANGE" "$URI" 2>&1 | tee "$SHOTDIR/accept-${n}.out" | tail -20; then
|
||||||
|
# still try confirm if selected
|
||||||
|
true
|
||||||
|
fi
|
||||||
|
if ! confirm_when_selected "$WID" "$TOK"; then
|
||||||
|
log "FAIL confirm $amt"
|
||||||
|
echo -e "${n}\t${amt}\tFAIL_CONFIRM\t${WID}\t" >>"$RESULTS"
|
||||||
|
fail_rung="$amt"
|
||||||
|
# keep going? user asked until it no longer works — stop
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
settled=0
|
||||||
|
for r in $(seq 1 20); do
|
||||||
|
wcli run-until-done 2>&1 | tee -a "$SHOTDIR/rud-${n}.out" >/dev/null || true
|
||||||
|
after=$(wallet_avail)
|
||||||
|
# progress if available increased (float-safe string compare via python)
|
||||||
|
if python3 -c "import sys; sys.exit(0 if float(sys.argv[1])>float(sys.argv[2]) else 1)" "$after" "$before" 2>/dev/null; then
|
||||||
|
settled=1
|
||||||
|
log " settled avail GOA:$after (was $before)"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
after=$(wallet_avail)
|
||||||
|
if [ "$settled" = "1" ]; then
|
||||||
|
log "OK $amt → wallet GOA:$after"
|
||||||
|
echo -e "${n}\t${amt}\tOK\t${WID}\tavail=${after}" >>"$RESULTS"
|
||||||
|
else
|
||||||
|
# bank may be confirmed but wire lag — check transfer_done
|
||||||
|
xfer=$(curl -sS -m 10 "${BANK}/taler-integration/withdrawal-operation/${WID}" \
|
||||||
|
| python3 -c 'import json,sys; d=json.load(sys.stdin); print(d.get("transfer_done"), d.get("status"))')
|
||||||
|
log " not settled yet transfer_done/status=$xfer avail=$after"
|
||||||
|
if echo "$xfer" | grep -q True; then
|
||||||
|
echo -e "${n}\t${amt}\tOK_BANK_LAG\t${WID}\tavail=${after}" >>"$RESULTS"
|
||||||
|
log "OK bank confirmed (wallet lag) $amt"
|
||||||
|
else
|
||||||
|
echo -e "${n}\t${amt}\tFAIL_SETTLE\t${WID}\tavail=${after} $xfer" >>"$RESULTS"
|
||||||
|
fail_rung="$amt"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
log ""
|
||||||
|
log "=== final balance ==="
|
||||||
|
wcli balance 2>&1 | tee "$SHOTDIR/balance-final.out"
|
||||||
|
log ""
|
||||||
|
log "=== results ==="
|
||||||
|
column -t -s $'\t' "$RESULTS" 2>/dev/null || cat "$RESULTS"
|
||||||
|
log "SHOTDIR=$SHOTDIR"
|
||||||
|
if [ -n "$fail_rung" ]; then
|
||||||
|
log "STOPPED at first failure: $fail_rung"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
log "All rungs OK"
|
||||||
|
exit 0
|
||||||
Loading…
Add table
Add a link
Reference in a new issue