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