v9.26.4: DigiDollar-compatible pruning — run pruned nodes with DigiDollar#418
Conversation
Lets mining pools and users run pruned mainnet nodes with DigiDollar, so they no longer have to store the full ~12-year block history. No consensus rule changes: a pruned node validates every block identically to a full node, and every new behavior is opt-in behind -prune. A DigiDollar output can only be created at/after activation, and activation cannot happen below the deployment's minimum activation height, so every block DigiDollar validation ever reads lives in [activation floor, tip]. A pruned node keeps that window and deletes the older history: - init: IsDigiDollarTxIndexRequired returns false under -prune (a pruned node resolves a DD input's amount/lock by reading the creating tx from the retained block at the coin's height instead of the transaction index); soft-disable the default-on DigiDollar stats index under prune so its from-genesis sync does not demand pruned blocks and refuse startup. Full nodes are unchanged and still require txindex; -prune -txindex=1 still errors. - node/chainstate: register a "digidollar" prune lock at the activation floor during chainstate load, before the first prune, so automatic pruning and the pruneblockchain RPC never delete a DigiDollar-era block; plus a reindex guard that refuses to start (asking for -reindex) if a DigiDollar-era block is already missing, rather than validating with incomplete data. - digidollar/health: SystemHealthMonitor::ScanUTXOSet seeds network DD supply/collateral (which feed consensus DCA/ERR health) by reading each vault's creating tx from the retained block at the coin's height rather than the txindex, and skips pre-floor coins. Result-identical on a full node, correct on a pruned one. - oracle: flag an unexpected block-read failure during startup price reconstruction instead of silently reconstructing from partial data. Tests: - unit: dd_chain_prune_does_not_require_txindex. - functional: feature_digidollar_pruning.py runs a full node and a pruned (no-txindex) node side by side and asserts they agree through BIP9 DigiDollar activation, a DigiDollar mint block mined on the pruned node, a full mint/send/redeem lifecycle (including a redeem 339 blocks after the mint, reading the creating block from the retained window), pruning the pre-activation history, and restart reconstruction. Reference: V9.26.4_PRUNING_PLAN.md; operator notes in doc/release-notes/release-notes-9.26.4.md.
They are registered in test_runner.py (which invokes them via the Python interpreter, so CI was unaffected), but running one directly as ./test/functional/<name>.py failed with 'Permission denied'. Mode-only change (100644 -> 100755); no file contents are touched.
V9.26.4_PRUNING_EXPLAINER.md: shareable summary of how DigiDollar-compatible pruning works and how it was tested. V9.26.4_MAINNET_VALIDATION.md: record of the live mainnet prune validation (42 GB -> 0.21 GB, prune lock clamped at the activation floor, restart guard).
…phases F12: a pruned mining node goes offline, mines a stale chain carrying a DigiDollar mint, while the network advances past the 288-block window; on reconnect it abandons the stale chain and returns to exact DigiDollar parity. F13: dropping -prune on a pruned datadir refuses to start without -reindex. Also record the first-pass live testnet26 validation results in the PR explainer (pruned node active-DD parity with a full node on the live network) and document the [test]-section txindex=1 conf gotcha.
There was a problem hiding this comment.
Pull request overview
This PR implements DigiDollar-compatible pruning for DigiByte Core v9.26.4, enabling pruned nodes to fully validate DigiDollar without requiring txindex, by retaining the DigiDollar-era block window and guarding against incomplete pruned data directories. It also adds extensive functional/regression coverage and operator-facing documentation for the new pruning behavior.
Changes:
- Add a DigiDollar activation-floor prune lock and a startup data-availability guard for pruned nodes.
- Rewire DigiDollar UTXO scanning/amount resolution to work without
txindexby reading creating transactions from retained blocks. - Add new functional/regression tests + release notes and bump the client build version to 9.26.4.
Reviewed changes
Copilot reviewed 16 out of 38 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
V9.26.4_PRUNING_PLAN.md |
New design + TDD plan documenting the pruning approach and verification strategy. |
V9.26.4_PRUNING_EXPLAINER.md |
New short explainer describing pruning behavior and how it was proven. |
V9.26.4_PR_EXPLAINER.md |
New PR explainer mirroring the operator/reviewer narrative for the changes. |
V9.26.4_MAINNET_VALIDATION.md |
New mainnet validation record for pruned/no-txindex operation. |
test/functional/feature_digidollar_pruning.py |
New end-to-end functional test covering activation, mint/send/redeem, pruning, restart parity, IBD, reorg, and guard behavior. |
test/functional/test_runner.py |
Registers the new pruning functional test in the functional suite list. |
test/functional/rpc_getblockreward.py |
Adds a functional test for the getblockreward RPC (currently not wired into the runner list). |
test/functional/digidollar_verifychain_cache_side_effect.py |
Regression test ensuring verifychain doesn’t mutate live DigiDollar/oracle caches. |
test/functional/digidollar_transaction_fees.py |
Regression test ensuring DigiDollar send fee reporting is non-zero. |
test/functional/digidollar_testnet26_oracle_roster_rpc.py |
Regression test for testnet26 oracle roster/name exposure via RPC. |
test/functional/digidollar_stats_reorg.py |
Regression test for DigiDollar stats index rewind correctness across reorg. |
test/functional/digidollar_stats_reordered_mint.py |
Regression test for mint output ordering edge cases affecting wallet/index accounting. |
test/functional/digidollar_send.py |
Regression test covering send response schema and sub-dollar/fractional input handling. |
test/functional/digidollar_rpc_amount_filters.py |
Regression test for DigiDollar RPC amount parsing, filters, and boundary defaults. |
test/functional/digidollar_protection_status.py |
Regression test ensuring getprotectionstatus matches getdigidollarstats health. |
test/functional/digidollar_pending_position_status.py |
Regression test for pending mint position reporting when walletbroadcast is disabled. |
test/functional/digidollar_oracle_signers.py |
Regression test for exposing decoded MuSig2 bundle participants via RPC. |
test/functional/digidollar_oracle_price.py |
Regression test for oracle price precision and non-hardcoded volatility/high-low fields. |
test/functional/digidollar_listunspent.py |
Regression test pinning DigiDollar list-unspent semantics and filters. |
test/functional/digidollar_listoracle_schema.py |
Regression test pinning listoracle schema when a local oracle is running. |
test/functional/digidollar_health_restart_consensus.py |
Regression test ensuring system health reconstruction survives restart (consensus safety). |
test/functional/wallet_digidollar_reorg.py |
Wallet regression test covering DigiDollar bookkeeping across disconnect/restart flows. |
test/functional/wallet_digidollar_transfer_reorg.py |
Wallet regression test ensuring abandoned disconnected transfers restore original DD UTXOs. |
test/functional/wallet_digidollar_transfer_ancestor_reorg.py |
Wallet regression test ensuring descendants don’t resurrect via stale txindex after reorg. |
test/functional/wallet_digidollar_rc33_regressions.py |
Wallet regression coverage for several RC33-reported mint/send/redeem edge cases. |
test/functional/wallet_digidollar_mixed_output_accounting.py |
Wallet regression test for multi-recipient send output accounting order. |
test/functional/wallet_digidollar_encrypted_received_redeem.py |
Wallet regression test for redeeming collateral using received DD in an encrypted wallet. |
src/init.cpp |
Disables -txindex/DD stats index under prune by default and relaxes DigiDollar txindex requirement in prune mode. |
src/node/chainstate.cpp |
Registers the "digidollar" prune lock and adds the pruned-datadir DigiDollar window guard. |
src/digidollar/health.h |
Extends ScanUTXOSet signature and metrics to track active position count and support chain/consensus inputs. |
src/digidollar/health.cpp |
Implements prune-compatible UTXO scan by skipping pre-floor coins and using block-db transaction reads. |
src/rpc/digidollar.cpp |
Passes chain/consensus into UTXO scan and provides active position count fallback when stats index is disabled. |
src/oracle/bundle_manager.cpp |
Enhances handling/logging for startup block read failures during oracle price reconstruction. |
src/test/digidollar_txindex_tests.cpp |
Adds unit coverage proving txindex is not required under prune for DigiDollar nodes. |
doc/release-notes/release-notes-9.26.4.md |
New operator-facing release notes documenting prune mode behavior/limitations. |
DIGIDOLLAR_ARCHITECTURE.md |
Updates architecture documentation to reflect prune-compatible UTXO scanning. |
configure.ac |
Bumps build version from 9.26.3 to 9.26.4. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Audit finding: a truncated or partially-restored DD-era block file passes the startup data-availability guard (which checks block-index flags; whole-file deletion is already caught when the index verifies every flagged file opens) and previously let two startup reconstruction paths continue silently with partial data: - SystemHealthMonitor::ScanUTXOSet skipped unreadable vaults, undercounting the supply/collateral baseline that feeds consensus DCA/ERR health; - OracleBundleManager::LoadPricesFromChain rebuilt the price cache and consensus volatility-freeze history with gaps. Either could make the damaged node disagree with the network on DigiDollar mint validity. Both now return false on an unreadable post-floor block and init aborts with the incomplete-data -reindex error; the getdigidollarstats / protection-status RPC fallbacks throw instead of reporting undercounted totals. Healthy nodes are unaffected (the condition can only fire when a block that must be readable is not). New test phase F14 (written RED-first) truncates DD-era block files on the pruned node, asserts startup is refused, and recovers via -reindex to full parity. Release notes and PR explainer document the audit results and pool operational guidance (restart after long outages; -reindex needs network).
Follow-up to the fail-closed startup change: only treat an unreadable block as fatal when it is at/above the DigiDollar activation floor (the guaranteed- retained window). Below the floor — or when the floor is 0 (default regtest ALWAYS_ACTIVE, and the assumeutxo background-sync case) — a missing block is a legitimate prune/snapshot state, not damage, so skip it instead of aborting. Restores feature_assumeutxo.py and feature_remove_pruned_files_on_startup.py, which legitimately start with gaps below the floor.
…, coin gating) Three targets in one file, validated with a full clang-20 --enable-fuzz build (~15M executions, ASan clean): - dd_extract_amount_blockdb: ExtractDDAmountFromBlockDb over arbitrary and DD-shaped transactions with fuzzed outpoints/coin-heights and four TxLookupFn flavors; asserts fail-closed (ok <=> amount > 0) and pruned/archival determinism. - dd_prune_activation_floor: the activation-floor expression and the FlushStateToDisk prune-lock clamp model over extreme deployment params; asserts the floor always equals validation's earliest-DD height and nothing at/above floor-10 is prunable. - dd_prune_coin_gating: mainnet-params coin gating through SpendsDigiDollarCollateralVault/RequiresDigiDollarValidation with a real coins view; asserts pre-floor coins are never classified as DD vault spends. Note for runners: unset DEBUGINFOD_URLS or libFuzzer coverage symbolization makes network fetches and throughput collapses.
Imports the deterministic regtest oracle key into the pruned node's wallet, starts the oracle (startoracle succeeds, listoracle/getoraclepubkey report it running), and stops it cleanly. The oracle daemon needs a wallet key, live price input, and P2P — never the txindex or pre-activation blocks — and this pins that a node whose pre-DD history is pruned away operates one identically to a full node.
v9.26.4 pruning — testnet26 test results 🍃— DigiSwarm, DGB AI dev team · live testnet26, 2026-07-02 We ran the v9.26.4 pruned build against live testnet26, where DigiDollar is Setup: two nodes from the same synced testnet26 data — one full (txindex, What we confirmed
One honest caveat about testnettestnet26's entire chain is tiny (~24 MB, a single block file), so it is below Every scenario we ran (all passed)
Nothing is left on the testnet list. The only behavior testnet can't show is Bottom lineA pruned v9.26.4 node on the live, DigiDollar-active testnet validates |
JaredTate
left a comment
There was a problem hiding this comment.
ACK I have tested on main net and testnet, it looks good to go.
Final-audit finding: ValidateCollateralReleaseAmount classified a redeem's input 0 (collateral) and its positive-value fee inputs as DigiDollar collateral via LookupPreviousTransaction + IsMintCollateralOutput, with no activation-floor gate — unlike the sibling SpendsDigiDollarCollateralVault (which already skips pre-floor coins). Because LookupPreviousTransaction reads the creating tx from a block that a pruned node may have pruned (pre-floor), a full node (txindex) could classify a deliberately pre-planted, DD-mint-structured pre-floor coin as collateral while a pruned node could not — a pruned-vs-full consensus split on an attacker-crafted redeem (reject vs accept). A collateral vault can only be created at/after activation, so a coin below the floor is never real collateral regardless of byte structure. Both sites now reject (input 0) or ignore (fee input) pre-floor coins on every node before the structural lookup, so pruned and full reach identical verdicts by construction. Only pre-floor coins are affected; no legitimate redeem (whose collateral is a real post-floor mint) changes behavior. ExtractRedemptionAccountingAmounts is unaffected: ConnectBlock rejects a pre-floor-input-0 redeem at ValidateDigiDollarTransaction before the accounting step runs. Verified: full unit suite (redteam/rh07/burn/validation redeem tests) + redeem and pruning functional tests all green.
…cords - release-notes-9.26.4.md: full, plain-language notes — the pruning feature, how to enable it, the v9.26 line context (v9=BC v26.2+DigiDollar, BIP9 activation, the activation floor, Groestl algolock, txindex default), pruned limitations, carried-forward-unchanged, and the validation summary. - V9.26.4_MAINNET_VALIDATION.md / V9.26.4_TESTNET_PRUNE_TESTS.md / V9.26.4_TESTNET_PRUNE_SUMMARY.md: live re-validation on the tagged binary (mainnet 42 GB -> 0.21 GB with the lock clamped at the floor; testnet reindex re-validated every redeem under the new collateral gate with zero rejects, pruned == full to the cent).
…on-floor helper Triple-review remediations for the v9.26.4 pruning PR: - docs: the release notes, PR/pruning explainers, and pruning plan no longer claim "zero consensus changes"; the redeem collateral floor gate (bad-collateral-release-not-vault on input 0, pre-floor fee-input classification) is disclosed as one narrowly scoped consensus rule that applies to every node, with the mainnet scan result referenced. - consensus: add DigiDollar::EarliestActivationFloor() in consensus/digidollar as the single source of truth for the DigiDollar activation floor. Callers: validation's EarliestDigiDollarActivationHeight, the "digidollar" prune-lock registration in node/chainstate.cpp, the health-scan seed in digidollar/health.cpp, and startup oracle price reconstruction in oracle/bundle_manager.cpp — previously four hand-duplicated copies of the same expression. Behavioral note: the helper returns 0 for NEVER_ACTIVE deployments, which the three startup copies already did; validation's copy previously returned min(nDDActivationHeight, min_activation_height) there. Unreachable on any configured network (main/testnet/regtest all set real start/timeout values, and DigiDollar validation never runs when the deployment can never activate). - fuzz: dd_prune_activation_floor and dd_prune_coin_gating now exercise the real shared helper via a constructed Consensus::Params instead of hand-copied models of the deleted inline expressions. - health: ScanUTXOSet's mempool/chain/consensus parameters lose their nullptr defaults; the pre-floor skip and the fail-closed incomplete-data check are gated on a non-null chain, so every caller must now opt in explicitly instead of silently reverting to the old silent-undercount behavior. - plan doc: sections 3b/3c annotated as-shipped (the promised unit-test files were not created; coverage lives in the fuzz target and functional phases F6/F9/F10/F14, and the tip-below-floor no-op case has no direct unit test).
…activation-floor helper" This reverts commit 596bd1f.
…on-floor helper Triple-review remediations for the v9.26.4 pruning PR: - docs: the release notes, PR/pruning explainers, and pruning plan no longer claim "zero consensus changes"; the redeem collateral floor gate (bad-collateral-release-not-vault on input 0, pre-floor fee-input classification) is disclosed as one narrowly scoped consensus rule that applies to every node, with the mainnet scan result referenced. - consensus: add DigiDollar::EarliestActivationFloor() in consensus/digidollar as the single source of truth for the DigiDollar activation floor. Callers: validation's EarliestDigiDollarActivationHeight, the "digidollar" prune-lock registration in node/chainstate.cpp, the health-scan seed in digidollar/health.cpp, and startup oracle price reconstruction in oracle/bundle_manager.cpp — previously four hand-duplicated copies of the same expression. Behavioral note: the helper returns 0 for NEVER_ACTIVE deployments, which the three startup copies already did; validation's copy previously returned min(nDDActivationHeight, min_activation_height) there. Unreachable on any configured network (main/testnet/regtest all set real start/timeout values, and DigiDollar validation never runs when the deployment can never activate). - fuzz: dd_prune_activation_floor and dd_prune_coin_gating now exercise the real shared helper via a constructed Consensus::Params instead of hand-copied models of the deleted inline expressions. - health: ScanUTXOSet's mempool/chain/consensus parameters lose their nullptr defaults; the pre-floor skip and the fail-closed incomplete-data check are gated on a non-null chain, so every caller must now opt in explicitly instead of silently reverting to the old silent-undercount behavior. - plan doc: sections 3b/3c annotated as-shipped (the promised unit-test files were not created; coverage lives in the fuzz target and functional phases F6/F9/F10/F14, and the tip-below-floor no-op case has no direct unit test).
The simple summary (read this if you read nothing else)
What you get: you can now run a full-validating DigiByte node — including
everything DigiDollar will need — on about 2 GB of disk instead of ~42 GB.
How: add one line to
digibyte.conf:Who should care: mining pools and anyone who found the v9.26.3 disk/RAM
footprint too heavy. Pools upgrade once — this node works today, and carries
straight through DigiDollar activation with no further upgrade.
What changes if you don't set
prune=: nothing. Your node behaves exactlylike v9.26.3.
What we changed in consensus: nothing. Zero consensus changes. A pruned node
accepts and rejects exactly the same blocks as a full node. The Groestl algolock
and the DigiDollar activation schedule are untouched.
The problem this solves
Since v9.26.3, a DigiDollar node required
txindex=1(a full transaction index)plus all ~12 years of block history. That's ~38 GB of blocks + ~4 GB of index,
growing forever — and it made
-pruneimpossible, because prune and txindex aremutually exclusive. Pools asked for something lighter. This release delivers it
without touching consensus.
The key insight (one paragraph, no jargon)
DigiDollar coins can only be created after DigiDollar activates, and
activation cannot happen before block 23,627,520 on mainnet. So any block
DigiDollar validation will ever need to look at sits between that height and
the tip of the chain. Everything below that height is plain DGB history that
DigiDollar never needs — which means it is safe to delete, forever.
How it works (technical, kept simple)
Three small pieces of wiring, all opt-in behind
-prune:A prune lock at the activation floor. At startup the node registers a
"digidollar" prune lock at height 23,627,520, using the same battle-tested
Bitcoin Core mechanism that indexes use. Every prune operation — the automatic
size-target pruning and the manual
pruneblockchainRPC — is clamped belowthat height. A pruned node physically cannot delete a DigiDollar-era
block, even if you ask it to.
(
src/node/chainstate.cpp~line 156)DigiDollar no longer needs txindex under prune. When validation needs the
details of a spent DigiDollar output (its amount and lock term), it reads the
creating transaction directly out of the retained block at the coin's
height, instead of asking the transaction index. Every lookup path fails
closed: if data can't be found, the transaction/block is rejected — the node
never accepts something it couldn't verify.
(
src/init.cpp~914,src/digidollar/health.cppScanUTXOSet)A startup safety guard. If a pruned data directory is somehow missing a
DigiDollar-era block (e.g. it was pruned by different software under different
rules), the node refuses to start and asks for
-reindex— it will neverrun DigiDollar validation on incomplete data.
(
src/node/chainstate.cpp~line 184)Side effects handled: the DigiDollar stats index (which syncs from genesis) is
auto-disabled under prune, and
getdigidollarstatsfalls back to a live UTXOscan that reports the same totals and position count; pruned nodes advertise
NODE_NETWORK_LIMITEDlike any pruned Bitcoin-lineage node.What did NOT change
bundle rules, lock tiers — all byte-identical.
(accept at the boundary, reject after, reindex-safe).
prune=, txindex still defaults ON and thenode is exactly v9.26.3. Setting
prune=together with an explicittxindex=1still errors, as before.
How to enable it (
digibyte.conf)Upgrading an existing v9.26.3 node (the pool path):
Stop the node.
In
digibyte.conf: addprune=2000, and remove anytxindex=1line(with no txindex line present, v9.26.4 turns it off automatically under prune).
Swap in the v9.26.4 binaries and start. The node prunes in place — no
resync, no reindex. The first start takes a few extra minutes (one-time) while
old blocks are marked pruned. Confirm in
debug.log:Fresh node: same config; it syncs and prunes as it goes.
Notes:
prune=Nis a disk target in MiB (minimum 550; 2000 gives headroom).prune=1= manual mode (prune only via thepruneblockchainRPC). Wallets andgetblocktemplatemining work normally. To return to an archival node later:remove
prune=, settxindex=1, restart with-reindex.How we tested it
dd_chain_prune_does_not_require_txindex.test_runner.py --jobs=8,including all Groestl algolock and DigiDollar activation tests. The extended
pruning suite (
feature_pruning.py,feature_index_prune.py,wallet_pruning.py) was run green as well — which surfaced and fixed a latentv9.26.3 issue where
-prune=-1reported the wrong startup error.test/functional/feature_digidollar_pruning.py),written test-first (TDD). It proves, on real nodes:
mines a DigiDollar block that a full node accepts;
the prune lock is the binding constraint (prune-to-tip gets clamped);
reaches identical DigiDollar state;
automatic prune mode) and stays in parity;
prescribed
-reindexrecovery restores full parity.V9.26.4_MAINNET_VALIDATION.md):we ran the release Qt binary against a copy of a synced mainnet datadir with
prune=2000and no txindex, on the live network:signaling (mainnet tip is already past the floor, so the retained window
is live today);
pruneblockchainabove the floorwas clamped to 23,511,221 and the floor block stayed readable — final
footprint 0.21 GB;
passed the data-availability guard and booted normally in ~2 minutes.
How reviewers can test it themselves
Still to do before release: TESTNET validation
We have not yet run this on testnet26 — that is the remaining validation
step, and it matters for a specific reason: DigiDollar is expected to already be
ACTIVE on testnet (activation floor 600 — confirm with
getdigidollardeploymentinfo). That makes testnet the only live, multi-nodenetwork where a pruned node can perform real DigiDollar mints, transfers and
redeems against the real 35-oracle roster and MuSig2 price quotes — i.e. the
exact situation mainnet will be in after activation, which regtest can only
simulate.
Testnet checklist (config note: testnet options go under the
[test]section ofdigibyte.conf, or use-testneton the command line):prune=550, no txindex); confirm the log lineretaining all blocks at/above height 600. (Because the floor is 600, atestnet node saves little disk — the point here is functional validation,
not space.)
node accepts every block, and
getdigidollarstatsmatches between the two.pruneblockchain <tip>on the pruned node; confirm the clamp and that thefloor block stays readable.
Where everything lives
V9.26.4_PRUNING_PLAN.mdV9.26.4_PRUNING_EXPLAINER.mdV9.26.4_MAINNET_VALIDATION.mddoc/release-notes/release-notes-9.26.4.mdFine print: pruned nodes don't serve deep history to new peers (archival nodes
still do that);
getrawtransactionfor pre-DigiDollar history needs an archivalnode; and after activation the retained DigiDollar-era window grows with the
chain —
prune=Ndeletes the 12 years of history, it isn't a permanent size cap.