Repository navigation
fix(wallet): no same-body retry after an ambiguous mint outcome — repin gonuts v0.13.0, classify as outcome-unknown (#640) - #650
Closed
Amperstrand wants to merge 2 commits into
Conversation
added 2 commits
October 6, 2026 14:08
…customer is told not to resend (repin gonuts v0.12.2) The mint client re-sent an identical money-moving POST body up to four more times on a network error, so a mint that processed a swap and whose response was dropped received the same blinded messages again — the deterministic-derivation re-exposure that strict mints answer with error 10002 and that has bricked wallets before (#257/#266/#480). Measured on main by the #535 conformance lane as the swap-timeout-retry row (#640); the lab mint tolerates duplicates, which is why payments kept working while the invariant was violated. The fix lands in gonuts-tollgate v0.12.2 (network errors return *AmbiguousResponseError immediately; the 429 same-body retry is kept because a rate-limit answer precedes processing), repinned in the four go.mod files. The repo-side pins that fail the release gate on a bad repin: a full-wallet fault test whose fake mint processes the swap, drops the response, and must sight every blinded output exactly once (fails on v0.12.1, passes on v0.12.2); a wallet-usable-after-recovery test; and the merchant classification mapping tollwallet.ErrOutcomeUnknown to the existing payment-outcome-unknown notice (do-not-resend guidance plus the operator reference) instead of a retry-flavoured error, without condemning the mint in the health tracker.
…ix supersedes the local v0.12.2 The fork landed the same no-re-exposure policy as a pull request (OpenTollGate/gonuts-tollgate#35, tagged v0.13.0) and it is a superset of the local fix: checkstate keeps its read-only transport retry (it is the reconciliation primitive and must survive the outage that made the money-moving call ambiguous), while swap/mint/melt POSTs are sent exactly once and surface AmbiguousOutcomeError. The public tag dissolves the fork-tag handoff step: the module builds — and the docker conformance lane builds its daemon image — from the public proxy with no local module proxy. The local branch fix/no-same-body-retry-on-ambiguous-post and its v0.12.2 tag are superseded and must NOT be pushed (a second, competing fix landing would collide with #35). The tollwallet boundary now maps client.AmbiguousOutcomeError to ErrOutcomeUnknown; the repo-side invariant tests pass unchanged on v0.13.0 (they fail on v0.12.1).
Collaborator
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
One layer above the wallet: after any ambiguous Cashu operation, deterministic derivation outputs must never be regenerated or re-exposed — and the customer must never be told to resend.
gonuts-tollgateto v0.13.0 (fork PR Ensure that the proffit shares add up to 1 #35): a transport error on a state-changing POST returns*AmbiguousOutcomeErrorafter exactly one attempt (the 429 same-body retry is kept — a rate-limit answer precedes processing; checkstate keeps its read-only retry as the reconciliation primitive). Go's net/http never replays a POST itself, so the client's retry ladder was the only re-exposure site.tollwalletmaps it to an exportedErrOutcomeUnknownat the Receive boundary (the same seamErrTokenAlreadySpentuses).payment-outcome-unknown— the do-not-resend guidance plus the operator-quotable reference, never a retry-flavoured error — and it does not condemn the mint in the health tracker (the mint may be healthy and processing).Evidence
swap-timeout-retry→ no-output-reuse=pass (reused={}),restart-converges=pass. The same lane measured the re-exposure on v0.12.1 (issue wallet: swap-timeout retry re-exposes derivation outputs (measured by the #535 conformance lane on current main) — the #257/#266/#480 brick class #640).Fixes #640.