fix(chain): retry transient coinset failures + resume pending mint (#84) - #3
Merged
Conversation
coinset.org intermittently truncates an HTTP response body under load
("error decoding response body"); the chain-read layer had no retry/timeout,
so a single hiccup aborted `digstore doctor` (fund scan) and `digstore init`
(mint) — once even AFTER the mint spend was broadcast, leaving the user's XCH
spent while the CLI errored out.
- Wrap every coinset read (Coinset::call -> retry_core) in
retry-with-exponential-backoff + jitter + a per-attempt timeout.
Transient failures (truncated body, transport, timeout, 5xx, 429) are
retried; a definitive not-found / other 4xx is terminal (never a hang).
Reads are issued sequentially, so concurrency is already bounded to 1.
- Post-submit resilience: confirmation polling now survives a transient error
(it goes through the same retry). Re-running `init <name>` after a
submitted-but-unconfirmed mint RESUMES confirmation of the existing coin
instead of minting a second singleton — it never double-spends real XCH.
- Tests: retry core (recover/terminal/exhaust/timeout), end-to-end
truncated-body-then-success + not-found-terminal via a local server,
confirm-survives-transient, and init-rerun-resumes-without-reminting.
No endpoint change — chain reads stay on coinset.org (rpc.dig.net is not a
coinset proxy, #62). Version 0.7.0 -> 0.7.1 (patch: bug fix, no API change).
Closes #84
…argo-deny advisories)
MichaelTaylor3d
added a commit
that referenced
this pull request
Jul 10, 2026
… deviation #3) Co-Authored-By: Claude <noreply@anthropic.com>
MichaelTaylor3d
added a commit
that referenced
this pull request
Jul 10, 2026
…viation #3) Adapts to real digstore_crypto::bls API (bls_sign/bls_verify free fns, to_bytes returns Bytes48/Bytes96) and digstore_core codec (Encoder/Decoder). Co-Authored-By: Claude <noreply@anthropic.com>
MichaelTaylor3d
added a commit
that referenced
this pull request
Jul 10, 2026
…dual #3) SECURITY.md residual #3: the chain source, clock, and proof-backend selection on the blind serve path are now real and injectable. The default build stays toolchain-free; real RISC0 proving is "flip the feature + install the toolchain". - serve_blind: add BlindServeDeps (Arc<dyn Prover/ChainSource/Clock>) and serve_blind_with(). Defaults to the mock/fixed trio (MockProver + MockChainSource + FixedClock) so existing serve tests + the default build stay green. with_real_chain_clock() swaps in a real ChainSource + SystemClock; with_risc0_prover() (cfg(feature="risc0")) builds a real Risc0Prover bound to the chain peak. Backend selection compiles in both modes: risc0 OFF => MockProver, risc0 ON => Risc0Prover. - digstore-host: add `risc0` feature forwarding to digstore-prover/risc0 (NOT in the default build — it pulls risc0-build/embed_methods which needs the r0vm toolchain). - CoinsetChainSource (digstore-prover) is the real coinset.org chain source; add HTTP-mocked tests + an #[ignore]d live test (tests/coinset_live.rs) and a module-doc note. clock.rs: note SystemClock as the real injectable clock. - SECURITY.md: rewrite residual #3 — chain/clock/backend-selection done; real proofs still need the RISC0 toolchain via the risc0 feature. Default gate green: cargo test --workspace --locked (all pass, live coinset test ignored), clippy clean, cargo deny advisories/bans/sources ok. Co-Authored-By: Claude <noreply@anthropic.com>
MichaelTaylor3d
added a commit
that referenced
this pull request
Jul 10, 2026
The dig-node dig.getContent live read path now carries only REAL trust-bearing material and never presents a mock/absent proof AS verified: - inclusion proof — REAL: guest-computed from the module's MerkleNodes (build_real_proof), independent of the prover backend (MockProver never touches it). - chain-anchored root — REAL: chain-pinned and fail-closed (#127). - clock/freshness — INERT on this path: getContent sends window:None with require_attestation=false, so the guest temporal gate + the host-attestation gate never run; the default FixedClock cannot affect what is served. - execution proof — ABSENT, never faked: ContentResponse has no execution-proof field and dig-node does not implement dig.getProof (returns -32601, not a fabricated receipt). A real risc0 execution attestation is fully wired in code (with_risc0_prover behind the `risc0` cargo feature) but blocked ONLY on the RISC0 toolchain (r0vm/rzup) not being installed in CI/this environment — honestly deferred with that precise blocker (SECURITY.md residual #3), never reported as verified. The hub/browser shields render that mock/absent state honestly (#134); this keeps that contract. Regression tests pin the honest contract: - get_content_result_carries_real_inclusion_proof_and_no_execution_proof: build_result emits the real inclusion proof + the served (chain-pinned) root and NO execution_proof/attestation/proof_status/receipt/trusted field. - get_proof_is_not_served_as_a_verified_proof_by_the_node: dig.getProof returns -32601, never a fabricated proof. SECURITY.md residual #3 extended with the precise dig-node read-path statement. 59 dig-node lib tests green; clippy --all-targets and fmt clean. Co-Authored-By: Claude <noreply@anthropic.com>
MichaelTaylor3d
added a commit
that referenced
this pull request
Jul 10, 2026
…84) (#3) * fix(chain): retry transient coinset failures + resume pending mint (#84) coinset.org intermittently truncates an HTTP response body under load ("error decoding response body"); the chain-read layer had no retry/timeout, so a single hiccup aborted `digstore doctor` (fund scan) and `digstore init` (mint) — once even AFTER the mint spend was broadcast, leaving the user's XCH spent while the CLI errored out. - Wrap every coinset read (Coinset::call -> retry_core) in retry-with-exponential-backoff + jitter + a per-attempt timeout. Transient failures (truncated body, transport, timeout, 5xx, 429) are retried; a definitive not-found / other 4xx is terminal (never a hang). Reads are issued sequentially, so concurrency is already bounded to 1. - Post-submit resilience: confirmation polling now survives a transient error (it goes through the same retry). Re-running `init <name>` after a submitted-but-unconfirmed mint RESUMES confirmation of the existing coin instead of minting a second singleton — it never double-spends real XCH. - Tests: retry core (recover/terminal/exhaust/timeout), end-to-end truncated-body-then-success + not-found-terminal via a local server, confirm-survives-transient, and init-rerun-resumes-without-reminting. No endpoint change — chain reads stay on coinset.org (rpc.dig.net is not a coinset proxy, #62). Version 0.7.0 -> 0.7.1 (patch: bug fix, no API change). Closes #84 * fix(deps): update num-bigint 0.4.7 -> 0.4.8 (0.4.7 yanked; unblocks cargo-deny advisories) Co-Authored-By: Claude <noreply@anthropic.com>
MichaelTaylor3d
added a commit
that referenced
this pull request
Jul 10, 2026
… deviation #3) Co-Authored-By: Claude <noreply@anthropic.com>
MichaelTaylor3d
added a commit
that referenced
this pull request
Jul 10, 2026
…viation #3) Adapts to real digstore_crypto::bls API (bls_sign/bls_verify free fns, to_bytes returns Bytes48/Bytes96) and digstore_core codec (Encoder/Decoder). Co-Authored-By: Claude <noreply@anthropic.com>
MichaelTaylor3d
added a commit
that referenced
this pull request
Jul 10, 2026
…dual #3) SECURITY.md residual #3: the chain source, clock, and proof-backend selection on the blind serve path are now real and injectable. The default build stays toolchain-free; real RISC0 proving is "flip the feature + install the toolchain". - serve_blind: add BlindServeDeps (Arc<dyn Prover/ChainSource/Clock>) and serve_blind_with(). Defaults to the mock/fixed trio (MockProver + MockChainSource + FixedClock) so existing serve tests + the default build stay green. with_real_chain_clock() swaps in a real ChainSource + SystemClock; with_risc0_prover() (cfg(feature="risc0")) builds a real Risc0Prover bound to the chain peak. Backend selection compiles in both modes: risc0 OFF => MockProver, risc0 ON => Risc0Prover. - digstore-host: add `risc0` feature forwarding to digstore-prover/risc0 (NOT in the default build — it pulls risc0-build/embed_methods which needs the r0vm toolchain). - CoinsetChainSource (digstore-prover) is the real coinset.org chain source; add HTTP-mocked tests + an #[ignore]d live test (tests/coinset_live.rs) and a module-doc note. clock.rs: note SystemClock as the real injectable clock. - SECURITY.md: rewrite residual #3 — chain/clock/backend-selection done; real proofs still need the RISC0 toolchain via the risc0 feature. Default gate green: cargo test --workspace --locked (all pass, live coinset test ignored), clippy clean, cargo deny advisories/bans/sources ok. Co-Authored-By: Claude <noreply@anthropic.com>
MichaelTaylor3d
added a commit
that referenced
this pull request
Jul 10, 2026
The dig-node dig.getContent live read path now carries only REAL trust-bearing material and never presents a mock/absent proof AS verified: - inclusion proof — REAL: guest-computed from the module's MerkleNodes (build_real_proof), independent of the prover backend (MockProver never touches it). - chain-anchored root — REAL: chain-pinned and fail-closed (#127). - clock/freshness — INERT on this path: getContent sends window:None with require_attestation=false, so the guest temporal gate + the host-attestation gate never run; the default FixedClock cannot affect what is served. - execution proof — ABSENT, never faked: ContentResponse has no execution-proof field and dig-node does not implement dig.getProof (returns -32601, not a fabricated receipt). A real risc0 execution attestation is fully wired in code (with_risc0_prover behind the `risc0` cargo feature) but blocked ONLY on the RISC0 toolchain (r0vm/rzup) not being installed in CI/this environment — honestly deferred with that precise blocker (SECURITY.md residual #3), never reported as verified. The hub/browser shields render that mock/absent state honestly (#134); this keeps that contract. Regression tests pin the honest contract: - get_content_result_carries_real_inclusion_proof_and_no_execution_proof: build_result emits the real inclusion proof + the served (chain-pinned) root and NO execution_proof/attestation/proof_status/receipt/trusted field. - get_proof_is_not_served_as_a_verified_proof_by_the_node: dig.getProof returns -32601, never a fabricated proof. SECURITY.md residual #3 extended with the precise dig-node read-path statement. 59 dig-node lib tests green; clippy --all-targets and fmt clean. Co-Authored-By: Claude <noreply@anthropic.com>
MichaelTaylor3d
added a commit
that referenced
this pull request
Jul 10, 2026
…84) (#3) * fix(chain): retry transient coinset failures + resume pending mint (#84) coinset.org intermittently truncates an HTTP response body under load ("error decoding response body"); the chain-read layer had no retry/timeout, so a single hiccup aborted `digstore doctor` (fund scan) and `digstore init` (mint) — once even AFTER the mint spend was broadcast, leaving the user's XCH spent while the CLI errored out. - Wrap every coinset read (Coinset::call -> retry_core) in retry-with-exponential-backoff + jitter + a per-attempt timeout. Transient failures (truncated body, transport, timeout, 5xx, 429) are retried; a definitive not-found / other 4xx is terminal (never a hang). Reads are issued sequentially, so concurrency is already bounded to 1. - Post-submit resilience: confirmation polling now survives a transient error (it goes through the same retry). Re-running `init <name>` after a submitted-but-unconfirmed mint RESUMES confirmation of the existing coin instead of minting a second singleton — it never double-spends real XCH. - Tests: retry core (recover/terminal/exhaust/timeout), end-to-end truncated-body-then-success + not-found-terminal via a local server, confirm-survives-transient, and init-rerun-resumes-without-reminting. No endpoint change — chain reads stay on coinset.org (rpc.dig.net is not a coinset proxy, #62). Version 0.7.0 -> 0.7.1 (patch: bug fix, no API change). Closes #84 * fix(deps): update num-bigint 0.4.7 -> 0.4.8 (0.4.7 yanked; unblocks cargo-deny advisories) Co-Authored-By: Claude <noreply@anthropic.com>
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.
TLDR
Fixes DIG-Network/dig_ecosystem#84 — a live user (dkackman) was blocked minting a store on mainnet because coinset.org intermittently truncates an HTTP response body under load (
error decoding response body) and digstore's chain-read layer had no retry/backoff/timeout. A single hiccup aborteddigstore doctor(fund scan) anddigstore init(mint) — once even AFTER the mint spend was already broadcast (submitted mint …), so the flow died with the user's XCH already spent.What changed
crates/digstore-chain/src/coinset.rs): every read now goes throughCoinset::call→retry_core, wrapping the underlying RPC in retry-with-exponential-backoff + full jitter + a per-attempt timeout. Classification: truncated body (is_decode), transport/connect/request/body errors, timeouts, 5xx, 429 are retryable; a definitive not-found / other 4xx is terminal (returns immediately, never a hang). Centralized so every call site benefits — coversget_coin_records_by_puzzle_hashes,_by_hint,_by_puzzle_hash,_by_parent_ids,get_coin_record_by_name,get_puzzle_and_solution,get_blockchain_state, andpush_tx.ChainAnchor::confirm→coin_record) now survives a transient error via the same retry. Re-runningdigstore init <name>after a submitted-but-unconfirmed mint RESUMES confirmation of the existing coin (reusing the exactdigstore anchorresume logic) instead of minting a second singleton — never double-spends real XCH. A confirmed/non-anchored store on disk is still a genuine name collision (exit 2).push_txretry is safe: the tx id is deterministic, so re-submitting the identical bundle is idempotent at the mempool.Tests (TDD, all new, kept permanently)
retry_recovers_from_transient_then_succeeds— a transient failure then success returns success, not an abort (acceptance a).retry_does_not_retry_terminal_error/coinset_not_found_is_terminal_single_call— a definitive not-found is NOT retried into a hang (acceptance b).retry_exhausts_attempts_on_persistent_transient,retry_recovers_from_per_attempt_timeout— bounded attempts + timeout-is-retried.coinset_retries_truncated_body_then_succeeds— end-to-end through the REAL reqwest path via a local TCP server that truncates the first body then serves valid JSON.confirm_survives_transient_then_confirms— post-submit confirmation survives a transient error →Confirmed(acceptance c).init_rerun_after_pending_mint_resumes_without_reminting— re-running init after a submitted mint resumes; coin_id unchanged proves no re-mint / double-spend (acceptance d).Verification (this session)
cargo test -p digstore-chain: 188 passed, 0 failed (3 ignored live).cargo test -p digstore-cli: 453 passed, 0 failed (2 ignored live) — full integration suite, incl. all init/doctor/anchor tests.cargo fmt --allclean;cargo clippy -p digstore-chain -p digstore-cli --all-targets --all-features -- -D warningsclean.cargo install --path crates/digstore-cli --force --locked→digstore 0.7.1; installed-binary integration tests green.cargo clippy --all-targetsfor the whole workspace hits a pre-existing risc0-zkvm host-linker error indigstore-prover's build script on Windows — unrelated to this change and unaffected by it; CI runs on Linux.)SemVer
patch 0.7.0 → 0.7.1 — a backwards-compatible bug fix (
fix:), no public-API or behavior change beyond added resilience.