Skip to content

merchant: fee precheck stalls minutes on a wedged mint — blocks the payment lane before any timeout applies #525

Closed

Description

@Amperstrand

Found integration-testing #511 (frozen-mint scenario in the cloud-lab).

What

A mint that is wedged or partitioned (accepts nothing / never answers — docker pause reproduces it) stalls the fee precheck for ~5+ minutes per payment, and the request goroutine never reaches anything with a deadline:

HandleRootPost → PurchaseSession (merchant.go:482) → SwapFeeSats
→ mintKeysetFees (gonuts_wallet.go:128) → GetMintActiveKeyset
→ getMintActiveKeysetFallback → client.go:134   [blocked]

Goroutine dumps (SIGQUIT, two payments, 3–4 minutes in flight, both parked at the same call): captured in the #511 verification run.

Why it's minutes, not seconds

The wallet client's per-call timeout is 30 s (client.go:34) but getWithRetry retries up to maxRetries = 4 with exponential backoff — ~5 attempts ≈ 150 s+ per endpoint — and GetMintActiveKeyset chains a second endpoint on total failure (getMintActiveKeysetFallback), doubling the worst case before the error ever returns.

Consequences

  • The customer's request hangs for minutes; the splash page gives up. The token is not burned (verified: after thawing, the same token paid in 0.1 s), but the lane is frozen for that mint and each attempt stacks a stuck goroutine.
  • The precheck's intended "fall through and let Receive classify the error" path only works once the calls return — against a wedge they effectively don't.
  • It also defeats fix(merchant): reconcile late Receive completions after the response deadline (#498) #511's receiveTimeout reconciliation for this mint-failure mode: the timeout wraps Receive, and PurchaseSession never gets there. (PRTA Self signed certificates / .local #95 documented wedged cdk-mintds accepting quotes but never settling — this is the same mint class.)

Fix candidates

  1. Bound the precheck: run SwapFeeSats under a short context deadline (a few seconds) and reuse the existing error fall-through — fees are an optimization check, not a gate.
  2. Avoid the network entirely: InputFeePpk for the active keyset is already in the wallet's local DB (bolt, kept warm by loadWalletMints); mintKeysetFees could read the cached keyset instead of GetMintActiveKeyset's fetch. This also makes the precheck offline-capable.
  3. Separate matter, upstream: keyset fetches probably don't want the full 5-attempt retry ladder — they're on the hot payment path.

(2) looks strictly better: no deadline tuning, no extra failure mode.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions