feat(collection): cost-bounded auto-batching for large mints, resumable, terminal oversize error - #12
Merged
Conversation
…le, terminal oversize error Large `collection mint` built one spend bundle for all items; past ~200 items it exceeds Chia's per-block CLVM cost limit, push_tx is rejected, and coinset's aborted body surfaced as "error decoding response body" — wrongly retried as transient (#84). Reported by dkackman on a 200-item collection. Auto-split into cost-bounded batches (size COMPUTED from the CLVM cost model, not hard-coded; --batch-size override), each a self-contained bundle proven under the block limit, funded per batch (1 mojo/item + change), broadcast sequentially, all attributed to the same DID. Resumable: write-ahead per-batch progress under ~/.dig/collection-mints, reconciled against chain on re-run — no re-mint, no double-spend. New terminal ChainError::BundleTooLarge / CliError::TooLarge (exit 17): never retried, never a coinset-connectivity misread. Proven on the chia-sdk-test Simulator + the consensus cost model (run_spendbundle / MAINNET_CONSTANTS); never auto-broadcast in CI. Closes #231. Refs #221.
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
digstore collection mintnow succeeds on arbitrarily large collections. A large mint is auto-split into cost-bounded batches (each a self-contained spend bundle proven under Chia's per-block CLVM cost limit), broadcast sequentially and all attributed to the same creator DID, with resumable per-batch progress (no re-mint / no double-spend on re-run) and a terminal, actionable oversize error that is never misreported as a coinset.org connectivity problem.Fixes the live-user failure dkackman reported on a 200-item collection:
error: chain error: push_tx: error decoding response body.Closes #231. Refs #221.
Root cause
digstore-chain/src/collection.rsbuilt ONESpendBundlefor all items. For large N the bundle exceeds Chia's per-block CLVM cost / generator-size limit, so the full node rejectspush_tx; coinset then returns an aborted / non-JSON body thatreqwestsurfaces aserror decoding response body.digstore-chain/src/coinset.rsclassifiederror decoding response bodyas transient (the #84 retry) and retried it — but an oversized bundle is terminal (retry can't help), and the message read as a coinset connectivity problem, not "your tx is too big".Fix
Cost-bounded auto-batching (
collection.rs). Batch size is computed from the CLVM cost model, not a hard-coded count:MAX_BLOCK_COST_CLVM = 11_000_000_000(Chia mainnetmax_block_cost_clvm).1/4of the ceiling =2_750_000_000(margin for estimate error, block contention, gateway request-size limits).EST_COST_PER_ITEM_CLVM = 80_000_000,EST_BATCH_BASE_COST_CLVM = 300_000_000.default_batch_size()= (2.75e9 − 300M) / 80M = 30 items/batch; a 200-item mint → 7 batches.--batch-size <n>overrides, validated against the same budget (a too-large value is a terminal error naming the max).The per-item constant is proven conservative against the real Chia consensus cost model:
est_cost_per_item_is_conservativeruns built batches throughchia::consensus::run_spendbundleunderMAINNET_CONSTANTSand fails if the measured marginal per-item cost (~69.76M) ever exceeds the constant.Per-batch launcher funding — reuses the existing
build_collection_mint_fundedpath (#199): each batch spends a separate XCH funding coin contributing 1 mojo per item, change returned (never burned as fee). digstore-chain builds the mint independently of chip35, so the funding fix lives here; #221 is the sibling twin onchip35_dl_coin::build_bulk_mint(its own lane, unchanged by this PR).Resumable (
commands/collection.rs). Per-batch progress is persisted to~/.dig/collection-mints/<manifest-fingerprint>.json, keyed by a SHA-256 fingerprint of the manifest bytes (resume applies only to the same collection + DID + manifest + batch size). The record is write-ahead — persisted BEFORE broadcast — so a crash in the window between a successful broadcast and the file write cannot strand a landed batch as unrecorded (which a resume, re-fetching the now-advanced DID, would otherwise re-mint against the next generation — a double-spend). On re-run, confirmed batches are skipped and a pushed-but-unconfirmed tail is reconciled against chain (its recreated DID coin present ⇒ it landed ⇒ mark confirmed). Correctness rests on the DID being single-use per generation: at most one mint confirms per DID generation, so a rebuilt batch can never double-mint; re-broadcasting the identical (deterministic) bundle is idempotent at the mempool.Terminal error classification (
coinset.rs+error.rs). A newChainError::BundleTooLarge(→ CLITooLarge, exit 17 / codeTOO_LARGE).Coinset::pushpre-flights the bundle: if its serialized generator-byte cost alone (bytes × 12_000) meets/exceeds the block limit it is refused up-front, terminally, with an actionable "transaction SIZE limit — split into smaller batches" message — never retried, never "check your connection to coinset.org".Proof (Simulator-first, never auto-broadcast in CI)
est_cost_per_item_is_conservative— measures real batch cost viarun_spendbundle/MAINNET_CONSTANTS; asserts the estimate bounds it and a default-sized batch stays under the block limit.build_collection_batch_chains_across_batches_on_simulator— a multi-batch mint chained across batches on thechia-sdk-testSimulator: each batch is a separate self-contained bundle that validates under consensus (cost + value conservation), the DID advances one generation per batch, and every item across all batches gets a distinct launcher id.plan_batches_covers_all_items_contiguously(200 items → >1 batch, gapless cover),cost_model_default_batch_size_fits_budget,validate_batch_size_rejects_zero_and_oversized.mint_progress_tracks_confirmed_and_pending,mint_progress_record_pending_is_idempotent,mint_progress_matches_guards_stale_records,manifest_fingerprint_is_stable_and_content_addressed.oversize_reason_flags_only_bundles_over_the_block_limit,push_refuses_oversized_bundle_terminally_without_network(no network attempted).cli_assets.rs): a 203-item manifest (dkackman's size) parses + plans without the old refusal, reaching the same DID-ownership check as the single-item path.Real mainnet broadcast is a separate, explicitly user-confirmed pass — never in CI.
Blast radius
digstore-chain::collectionis consumed only bydigstore-cliwithin this repo (dig-node depends on digstore's.digstore-format libs, notdigstore-chain). The public buildersbuild_collection_mint_in/build_collection_mint_funded_inkeep identical signatures (internally delegate to new*_core_invariants that also return the recreated DID); all batching/progress APIs are additive.ChainError::BundleTooLargeandCliError::TooLargeare additive variants.Coinset::pushgains a pre-flight guard that only triggers on genuinely oversized bundles (~>916 KB generator) — normal init/commit/offer/send spends are unaffected. Doc-comment references incollection_index.rs/nft.rsare unaffected.Also in this unit
SPEC.md§11.3 — normative batching + resumability + oversize-guard contract (and fixes a stray orphan code fence).--batch-size/resumability — filed as a separate PR in that repo (different submodule/release).Version
SemVer minor:
0.10.1 → 0.11.0(new--batch-sizecapability + additive APIs, no breaking change).