Skip to content

fix(wallet): bump gonuts to the #23/#24/#25 integration ref; make ErrTokenAlreadySpent reachable - #392

Merged
c03rad0r merged 4 commits into
mainfrom
bump/gonuts-integration
Sep 14, 2026
Merged

c03rad0r merged 4 commits into
mainfrom
bump/gonuts-integration

Conversation

@Amperstrand

Copy link
Copy Markdown
Collaborator

Stacked on #383 (the release cuts from that branch; this PR is the intended next commit before the tag).

What

Pins gonuts-tollgate to 717c886 (branch tmp/release-integration on the gonuts repo = main + #23 + #25 + #24) across every consuming module (root, merchant, cli, tollwallet), to be re-pinned to the tagged release the moment it exists. Riding in:

  • Empty-proofs tokens fail normally instead of a contained panic (gonuts check all fields of install config #23) — reachable today from the portal payment path; fix(merchant): contain payment-goroutine panics #360's recovery contains it, but a plain error is correct.
  • Mint swap rejections surface verbatim instead of empty could not swap proofs: errors (gonuts Refactor/merchant testcases #25) — and that is what finally makes ErrTokenAlreadySpent reachable, because:
  • this PR broadens the sentinel match. tollwallet matched only the exact string Token already spent; mints say inputs have already been spent. Now case-insensitive already spent / already been spent, pinned by a phrasing table and a two-layer test that drives a mock mint's rejection through gonuts into errors.Is(err, ErrTokenAlreadySpent) — failing if either layer regresses to swallowing.
  • Below-fee tokens fail fast; second in-process wallet load errors clearly instead of deadlocking (gonuts @Origami74's upstream-purchase implementation plan #24 — latent for tollgate, harmless).

Verification

  • gofmt / vet / build clean; root module -race -tags testenv green (15.3s); merchant (185s), cli, tollwallet modules green.
  • E2E on the build host: cloud-lab (cdk-mintd FakeWallet mint, smoke payments) + the reproducibility battery at this head — results posted below before this is mergeable.

@Amperstrand

Copy link
Copy Markdown
Collaborator Author

Review pass against PR-REVIEW.md — focusing on the actual new content (the tollwallet sentinel work), since ~2000 lines of this diff are #383's packaging/build content that is already on main via the #383 merge and will disappear on rebase.

What this PR actually changes (after the #383 noise is rebased away):

  1. gonuts-tollgate dependency pinned to 717c886 across all modules — the integration ref that fixes empty-proof panics (check all fields of install config #23), surfaces mint-swap rejections verbatim instead of swallowing them (Refactor/merchant testcases #25), and fixes a second-load deadlock (@Origami74's upstream-purchase implementation plan #24). The description is candid that this is a pre-tag pin to be re-pinned to a release.

  2. src/tollwallet/tollwallet.go — sentinel broadening: the old exact-match strings.Contains(err.Error(), "Token already spent") only caught Nutshell-family mints; CDK-family mints say "inputs have already been spent". The new isAlreadySpentError() helper matches both phrasings case-insensitively, and — critically — the match was unreachable before the gonuts bump because older gonuts swallowed the rejection into an empty error. This is a real bug fix: the ErrTokenAlreadySpent sentinel existed but could never fire for CDK mints.

  3. Test coverage is excellent — two layers, each catching a different regression:

    • TestIsAlreadySpentError_MintPhrasings (unit): phrasing table with Nutshell, CDK, case variations, plus the guards — empty swallowed errors must NOT match (or every failed swap becomes "already spent"), unrelated errors must not match, nil is handled.
    • TestReceive_SpentTokenMapsToSentinel (integration): mock mint serves CDK-style {"code":3,"detail":"inputs have already been spent"} on /v1/swap; the test drives the rejection through gonuts into errors.Is(err, ErrTokenAlreadySpent). Fails if either layer regresses (gonuts swallowing again, or match drift here).

Blocking item (mechanical, not content):

Rebase onto main. This PR was stacked on feat/reproducible-builds (#383), which merged as f2a4728. The diff against main currently shows ~2000 lines of #383 content (workflow pins, SDK digests, repro-test, portal pinning, --version flag, license fix) as if it were new. After rebasing onto main:

git rebase --onto main feat/reproducible-builds bump/gonuts-integration

the diff will show only the actual changes: the go.mod/sum bumps + the tollwallet sentinel work. Reviewers should not have to mentally subtract 35 files of noise.

Non-blocking notes:

  • The broad "already spent" substring match is intentionally permissive — any mint error containing that phrase maps to ErrTokenAlreadySpent. The empty-error guard is the right defense, and the phrasing table pins the boundary. Worth a one-line comment in a future PR if mints appear with false-positive phrasings.
  • The pre-tag pin (tmp/release-integration branch) is acknowledged in the description and carries a re-pin commitment. When the gonuts tag lands, that's a mechanical go.mod bump.

Disposition: land-after-rebase. The sentinel fix is real and the two-layer test design is exactly right — it catches regression at either the dependency layer or the integration layer, and the empty-guard prevents the broadened match from over-firing.

…TokenAlreadySpent reachable

Pins gonuts-tollgate to 717c886 (main + #23 + #25 + #24, branch
tmp/release-integration on the gonuts repo; re-pin to the tagged
release when it exists) across every consuming module (root, merchant,
cli, tollwallet).

With the bump, three wallet-layer fixes ride into tollgate: the
empty-proofs panic becomes a normal error, mint swap rejections
surface verbatim, and the below-fee swap fails fast. The surfaced
rejections are what finally make the ErrTokenAlreadySpent sentinel
reachable: tollwallet matched only the exact string 'Token already
spent', while the mint's rejection (previously swallowed entirely by
gonuts) says 'inputs have already been spent'. The match is broadened
(case-insensitive 'already spent' / 'already been spent') and pinned
two ways: a phrasing table, and a two-layer test driving a mock mint's
rejection through gonuts into the sentinel via errors.Is — which fails
if either layer regresses to swallowing.

Verified: gofmt/vet/build clean; root module -race testenv green
(15.3s); merchant (185s), cli, tollwallet modules green.
@Amperstrand
Amperstrand force-pushed the bump/gonuts-integration branch from 936e310 to 8a294b0 Compare September 13, 2026 23:37
@felixfelix-bot

Copy link
Copy Markdown
Contributor

Thanks @Amperstrand — clean bump, and the two-layer pin (phrasing table + mock-mint through gonuts) is exactly the right shape. Findings:

  1. [NIT] src/tollwallet/tollwallet.go:150 — the broadened match is a case-insensitive substring test on raw error text (already spent / already been spent against anything Receive's swap returns). A mint phrasing like "Token not already spent" would mis-map to ErrTokenAlreadySpent. Contrived today; if you ever see a false positive, gate the match to the swap/HTTP-rejection error type rather than any error.

  2. [NIT] src/merchant/go.mod:6 (and the other four pins) — v0.11.2-0.20260913230723-717c8863c8e5 is a pseudo-version on tmp/release-integration, not a tag. Fine as an integration ref (and the CHANGELOG says so), but: module proxies cache pseudo-versions against the branch tip, so if that branch is rewritten or deleted before the tag lands, fresh go mod downloads from the proxy stay resolvable but direct sum verification against the repo breaks. Re-pin to the tagged gonuts release before cutting the tollgate tag.

  3. [INFO] Positive: tollwallet_receive_sentinel_test.go failing on either regression layer (gonuts swallowing the rejection, or match drift here) plus the empty swallowed error / nil table cases in sentinel_test.go is solid coverage. Nothing else jumped out.

Two lab repairs found by running the suite for the gonuts-bump e2e:

- Dockerfile.mint pinned cdk-mintd to 0.17.6. The unpinned cargo install
  now fetches 0.18.0 (released 2026-09-02), which replaced env-var
  startup with a mandatory 'config init --file <toml>' flow — the mint
  container exited immediately ('mintd configuration is not initialized')
  and the whole lab was unrunnable. 0.17.6 (2026-08-26) is the version
  the lab was green on when it landed in #362 (2026-08-27).
- README quick-start used 'compose run --rm client pytest -sv …', but the
  client service's entrypoint IS pytest, so 'pytest' arrived as a test
  filename: 'ERROR: file or directory not found: pytest'. Drop the
  prefix.

With these, the full suite passes at this branch's head: smoke 6/6,
mint-failure 3 passed + 1 skipped, two-router 3/3.
@felixfelix-bot

Copy link
Copy Markdown
Contributor

Thanks @Amperstrand — delta review of the new head 9d9b058 (the two cloud-lab repairs; the sentinel/bump work was already reviewed at 8a294b0 and those findings still stand, not re-raised).

  1. [NIT] tests/cloud-lab/docker-compose.yml:14 — the header Usage comment still reads docker compose run --rm client pytest -sv, the exact stale pytest prefix this PR removes from README.md (the client entrypoint already IS pytest, so the prefix arrives as a test filename). Same one-line class of fix — drop it there too.

  2. [NIT] tests/cloud-lab/Dockerfile.mint:20 — the --version 0.17.6 pin is the right unblock, but the rationale lives only in the commit message: 0.18.0 switched to a mandatory config init --file <toml> flow and the mint container exits with mintd configuration is not initialized. Without an in-file note, a future reader sees an outdated pin and "helpfully" bumps it, resurrecting the breakage with a confusing error. Two lines in the Dockerfile ("pinned: 0.18.0 switched to config-init startup; migrate the lab when next touched") make the pin self-defending.

  3. [INFO] Positive: real suite evidence in the commit message (smoke 6/6, mint-failure 3 passed + 1 skipped, two-router 3/3), and the README invocation fix matches the compose entrypoint exactly. The pin choice (last green version from test(cloud-lab): Docker-based integration test environment #362) is sound. Land after the two NITs, or as-is — neither blocks.

@c03rad0r
c03rad0r self-requested a review September 14, 2026 06:07
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.

3 participants