Repository navigation
feat(scripts): tollgate-clientd laptop client + cloud-lab battery — rebase of #425 - #683
Conversation
Auto-top-up client for laptops behind a TollGate: discovers the gateway advertisement on :2121, registers with the captive portal (nodogsplash MAC workaround), polls /usage, and pays with ecash from cdk-cli or nutshell before the allotment runs out. Shows remaining bytes/seconds live; --status/--json one-shot output for status bars; --selftest runs the full loop against a built-in mock TollGate. (cherry picked from commit e0e39f0)
Extend the laptop client: waybar module JSON (--waybar) for status bars; cdk-cli adapter handles both modern (--v3 --amount) and legacy (stdin) CLIs and expands short keyset IDs for cdk-mintd swaps. Add a 31-test unit suite (tests/clientd/) over in-process mock TollGates and a cloud-lab e2e lane (test_clientd_autotopup.py) that pays the real backend, watches dry-run never pay, and observes threshold renewal. (cherry picked from commit 656c670)
… live, mint outage run-clientd-scenarios.sh (compose profile clientd) drives the laptop client through the hard paths: S2 data-metered lifecycle against a bytes upstream with live file-driven ndsctl counters (renewal before exhaustion, DEAUTH on exhaustion, re-payment), S3 a real nutshell wallet paying live (Dockerfile.client-ns pins marshmallow<4 — fresh pip installs of cashu crash on marshmallow 4.x — and the wallet persists via CASHU_DIR on a named volume), S4 mint outage mid-session with logged backoff and recovery (ms-expiry recorded as a NOTE: this lab expires ms sessions lazily), S5 keyset rotation + swap fees (auto-skipped unless the rotation mints are defined). client-ns shares the client static IP by design — never concurrent, and /usage must resolve the daemon MAC via dhcp.leases. Battery verified 9/9 green; clientd docs now carry the marshmallow pin. (cherry picked from commit afed056)
…mber (cherry picked from commit 86bac46)
…payment tokens recoverable The two money-path blockers from the review (OpenTollGate#422/OpenTollGate#423): - Blind repay loop: /usage returns -1/-1 whenever the router cannot map the poller's source IP to a MAC while payments still succeed, so the daemon re-paid every backoff cycle forever. Payments now count against --max-blind-payments (default 3) and only an observed /usage allotment growth resets the counter — the router's own 1022 event deliberately does NOT (that is exactly the runaway's shape). At the cap the daemon stops with a non-zero exit naming the state dir where the unacknowledged token is preserved. - Burned tokens with no reclaim path: the minted token is written to disk (0600, --state-dir, XDG default) BEFORE the POST and reused on retry instead of minting a fresh one; router notice code tags are read, and terminal codes (payment-error-below-swap-fee, -invalid-token, -token-spent) stop the daemon non-zero instead of feeding the backoff loop — a spent token is dropped, a below-fee token is kept on disk for manual recovery. The payment POST timeout is now --payment-timeout (default 30 s, was a hard 5 s that abandoned live synchronous swaps). Should-fixes: keyset-ID expansion failure now warns loudly on stderr (a bare short-ID token is a guaranteed cdk-mintd rejection); the scenario battery's S5b/S5c no longer PASS on a bare 'top-up failed' — they require a credited session (paid + observed allotment) or a coded terminal stop, so a wedged loop reports FAIL. Tests: 8 new cases (terminal parse/propagation, cap trip + reset, pending-token reuse, spent-token cleanup, timeout default) — 39/39 unit; --selftest PASS; live e2e 5/5 against the cloud lab including renewal; scenario marking logic verified against synthetic logs (7/7 pattern checks). (cherry picked from commit 8d0151c)
Self-review finding: the cap fires only after a payment the gateway ACCEPTED, and that path has already cleared the pending-token file — so 'any unacknowledged token is preserved under <state-dir>' was false in exactly the scenario it described. The sats sit in the gateway's wallet; recovery is a reconciliation with its operator. The message now says that, and clarifies that only rejected attempts leave a locally recoverable token (below-fee terminal refusals keep the file). (cherry picked from commit 5576c4c)
…set/outcome codes, fee-underbuy warning Finding 1 (block): the rebase surfaced OpenTollGate#541's fresh-session semantics — a post-expiry payment opens a NEW session whose allotment never exceeds the old high-water mark, so healthy post-expiry renewals tripped the blind cap after 3 payments (and a bytes laptop that exhausted once was on a 3-payment fuse forever). The counter now resets per session identity: a new 1022 start-time re-arms the comparison, run()'s no-session observation does the same, and the all-time max() is gone. The reset predicate lives in observe() so it is directly unit-testable; the selftest's mock implements the server's current semantics and pays a third, post-expiry step. Finding 2: payment-error-keyset-expired and payment-outcome-unknown join TERMINAL_PAYMENT_CODES (the router's own messages say do not retry; re-POSTing an outcome-unknown token is the double-spend it forbids). An outcome-unknown token is preserved on disk under <name>.token.outcome-unknown for operator reconciliation — never reused. Finding 3: the 1022 allotment tag is compared against steps x step_size; a smaller credited amount (fee-charging mint, S5 math) surfaces a per-top-up warning instead of a silent overpayment. Finding 4: --mac help now says it is informational (OpenTollGate#548 resolves clients from the socket). (cherry picked from commit 5f0dd38)
…NGELOG entries under [Unreleased] The S5 skip-guard grepped for mint-rotate, which now exists on main (OpenTollGate#413/OpenTollGate#416), so the guard passed while S5 actually drives the upstream-rotate service — defined neither here nor on main. An opt-in SCENARIOS="… S5" run then failed atomically at 'compose up' and burned the S5a/b/c wait timeouts marking FAIL instead of SKIPPED. Gate on the service S5 needs (upstream-rotate) and fix the header comment to match. Move the two tollgate-clientd CHANGELOG bullets from the finalized [v0.6.0-alpha3] section to [Unreleased]/Added per house style — they were born two days after that release was cut. Entries unchanged otherwise; both already carry the OpenTollGate#425 link. (cherry picked from commit 802be6f)
|
Review record — self-authored PR, so the approval has to be yours (GitHub will not let the author approve their own). Re-run at head Not run, stated plainly: the cloud-lab battery itself (S2–S5, Two |
…eview ruff: F401 `import shutil` in selftest(), RUF059 on three unpacked `payments` that the tests never read. The one test that does assert on `payments` keeps it. Made by the reviewer (felixfelix-bot) rather than requested: cosmetic only, the author's logic is untouched. Verified after the edit: ruff clean, `python3 -m pytest tests/clientd/ -q` -> 43 passed. Co-authored-by: Amperstrand
|
Tag legend: pre-release-safe = land before the pre-release you are testing · ready = no blocker, timing is yours · post-release = hold until after · needs-author-pass = back to the author. TAG: pre-release-safe — additive (new script + unit suite + a cloud-lab profile); nothing in the shipped binary changes. Changes I made, as asked (commit Re-verified after the edit: Not run, stated plainly: the cloud-lab battery (S2–S5, The head changed ( |
Rebase of #425's eight commits onto current
main(ac5811ff), authorship preserved. 11 files, +2136/−0 — the same file set and the same insertions-only shape as #425.Head:
pr/425-clientd-rebased(basemain).Conflicts (one file)
tests/cloud-lab/README.md— a table of independent file rows:mainadded aconformance/row (#503) where this branch addstest_clientd_autotopup.py. Both rows kept (union of the two sides); no other hunk needed a hand.CHANGELOG.mdand.gitignoreapplied clean.One placement fix worth knowing about
Replaying the branch commit-by-commit put the two clientd entries under an
### Addeddeep insidev0.6.0-rc1— the position the first of the branch's commits used. The branch's own last commit (CHANGELOG entries under [Unreleased]) moved them to## [Unreleased]→### Added, which is where they now sit (main gained an[Unreleased]section since this branch was written). Diff is@@ -32,6 +32,47 @@— purely additive, nothing removed.Verification (run here, on the rebased head)
Not run: the cloud-lab battery itself (
run-clientd-scenarios.sh, S2–S5,test_clientd_autotopup.py). It needs the docker lab up (mint + tollgate + client-ns images, live nutshell wallet), which is not available in this environment. The compose file parses; the scenarios are unexercised here.(The suite needs
tests/.env—tests/conftest.pyrefuses to collect without the four fleet variables (#509). Created locally fromtests/.env.example; it is gitignored and not part of this PR.)Review findings, reported not fixed
Two
rufffindings in the new files, left as the author wrote them:F401—scripts/tollgate-clientd.py:713:import shutilinsideselftest()is unused.RUF059—tests/clientd/test_tollgate_clientd.py:473: unpackedpaymentsis never used.Neither is a defect; both are one-line cleanups if you want them gone. Say so and I fold them in.
Merge this and #425 closes as superseded, or force-push
pr/425-clientd-rebasedontofeature/tollgate-clientd.