Skip to content

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
fix/concurrent-duplicate-grantfrom
fix/640-ambiguous-swap-outcome
Closed

Amperstrand wants to merge 2 commits into
fix/concurrent-duplicate-grantfrom
fix/640-ambiguous-swap-outcome

Conversation

@Amperstrand

Copy link
Copy Markdown
Collaborator

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.

  • Repins gonuts-tollgate to v0.13.0 (fork PR Ensure that the proffit shares add up to 1 #35): a transport error on a state-changing POST returns *AmbiguousOutcomeError after 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.
  • tollwallet maps it to an exported ErrOutcomeUnknown at the Receive boundary (the same seam ErrTokenAlreadySpent uses).
  • The merchant classifies it as 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

Fixes #640.

Amperstrand 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).
@Amperstrand
Amperstrand deleted the branch fix/concurrent-duplicate-grant October 6, 2026 12:32
@Amperstrand Amperstrand closed this Oct 6, 2026
@Amperstrand
Amperstrand deleted the fix/640-ambiguous-swap-outcome branch October 6, 2026 12:36
@Amperstrand

Copy link
Copy Markdown
Collaborator Author

Closed by a base-branch-deletion race during the train merge (this PR was based on #641's branch). Content landed unchanged as #660 — tree-identical, verified.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant