Skip to content

feat(collection): cost-bounded auto-batching for large mints, resumable, terminal oversize error - #12

Merged
MichaelTaylor3d merged 1 commit into
mainfrom
fix/231-collection-mint-batching
Jul 10, 2026
Merged

feat(collection): cost-bounded auto-batching for large mints, resumable, terminal oversize error#12
MichaelTaylor3d merged 1 commit into
mainfrom
fix/231-collection-mint-batching

Conversation

@MichaelTaylor3d

Copy link
Copy Markdown
Contributor

TLDR

digstore collection mint now 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.rs built ONE SpendBundle for all items. For large N the bundle exceeds Chia's per-block CLVM cost / generator-size limit, so the full node rejects push_tx; coinset then returns an aborted / non-JSON body that reqwest surfaces as error decoding response body.
  • digstore-chain/src/coinset.rs classified error decoding response body as 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

  1. 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 mainnet max_block_cost_clvm).
    • Per-batch budget = 1/4 of 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_conservative runs built batches through chia::consensus::run_spendbundle under MAINNET_CONSTANTS and fails if the measured marginal per-item cost (~69.76M) ever exceeds the constant.
  2. Per-batch launcher funding — reuses the existing build_collection_mint_funded path (#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 on chip35_dl_coin::build_bulk_mint (its own lane, unchanged by this PR).

  3. 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.

  4. Terminal error classification (coinset.rs + error.rs). A new ChainError::BundleTooLarge (→ CLI TooLarge, exit 17 / code TOO_LARGE). Coinset::push pre-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 via run_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 the chia-sdk-test Simulator: 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.
  • Resumability: 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 guard: oversize_reason_flags_only_bundles_over_the_block_limit, push_refuses_oversized_bundle_terminally_without_network (no network attempted).
  • CLI integration (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::collection is consumed only by digstore-cli within this repo (dig-node depends on digstore's .dig store-format libs, not digstore-chain). The public builders build_collection_mint_in / build_collection_mint_funded_in keep identical signatures (internally delegate to new *_core_in variants that also return the recreated DID); all batching/progress APIs are additive. ChainError::BundleTooLarge and CliError::TooLarge are additive variants. Coinset::push gains 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 in collection_index.rs / nft.rs are unaffected.

Also in this unit

  • SPEC.md §11.3 — normative batching + resumability + oversize-guard contract (and fixes a stray orphan code fence).
  • docs.dig.net (command reference + NFT-developers page) — batching/--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-size capability + additive APIs, no breaking change).

…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.
@MichaelTaylor3d
MichaelTaylor3d merged commit c143031 into main Jul 10, 2026
10 checks passed
@MichaelTaylor3d
MichaelTaylor3d deleted the fix/231-collection-mint-batching branch July 10, 2026 18:31
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