mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-06 03:38:23 +00:00
Audit of the paths the proof backfill makes hot, plus two bugs it makes reachable. /v1/checkstate went out unchunked. scrubStaleProofs checks every proof held at a mint in one call and that set is unbounded — it grows with the wallet's history, and a client that pages its whole proof set back off the relays reaches four figures in one sweep. Mints run the same Pydantic list caps there that they do on /v1/restore, which this file already caps at 500 for exactly that reason, so the sweep failed with a validation error at the moment the wallet had the most to reconcile. Chunked, and the hash-to-curve derivation now happens once per proof instead of twice (it was computed separately for the request list and the response lookup — a discarded EC operation per proof, every sweep). The auto-redeem sweep paid two NIP-44 decrypts of kind:17375 before checking whether it had anything to redeem, and p2pkPubkeyHex re-decrypts the same event walletPrivkeyHex just read. That sweep fires from every relevant cache bundle, so a wallet whose nutzaps were all redeemed months ago still paid two out-of-process round-trips per bundle on a NIP-46 bunker or a NIP-55 external signer. The candidate filter needs no key, so it now runs first, and the pubkey is derived from the privkey in hand. A kind:7375 we cannot decrypt hides money exactly as effectively as one a relay never delivered, and looked identical to an empty wallet. recomputeUnspent caches only successes, so failures are retried — but only when something else marks tokens dirty, which in a quiet wallet may be never. Failures are now counted and logged, and a forced resync retries them even when the relay walk found nothing new. Two quadratic scans that were invisible while truncation kept the entry list tiny: peekNutzapFunding filtered the whole entry list once per shared mint, allocating a list each time, from inside a composable remember (so per rendered note); and cleanupDuplicateProofs compared all pairs before every Resync. Both are single-pass/indexed now — a superset of B must share all of B's secrets, so only entries indexed under B's first secret can cover it. Finally, scanning every keyset made Resync N times slower by construction: each keyset costs at least three /v1/restore round-trips with 500-item bodies, so a mint that has rotated ten times turned a three-request scan into thirty run end to end. The walks are independent and read-only, so they run three at a time — bounded to stay polite to the mint's rate limiter. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HaZ8RprmKC3sidsq6W8dKY