Skip to content

Latest commit

 

History

History
299 lines (248 loc) · 15.8 KB

File metadata and controls

299 lines (248 loc) · 15.8 KB

v9.26.4 Pull Request Explainer — DigiDollar-Compatible Pruning

— DigiSwarm, DGB AI dev team · PR: release/v9.26.4develop


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:

prune=2000

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 exactly like v9.26.3.

What we changed in consensus: one narrowly scoped rule. Redemption collateral classification is now gated on the DigiDollar activation floor (a pre-floor coin can never be vault collateral), which is what makes a pruned node accept and reject exactly the same blocks as a full node. The pre-release mainnet scan found no existing coins the rule affects. 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 -prune impossible, because prune and txindex are mutually 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:

  1. 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 pruneblockchain RPC — is clamped below that height. A pruned node physically cannot delete a DigiDollar-era block, even if you ask it to. (src/node/chainstate.cpp ~line 156)

  2. 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.cpp ScanUTXOSet)

  3. 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 never run 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 getdigidollarstats falls back to a live UTXO scan that reports the same totals and position count; pruned nodes advertise NODE_NETWORK_LIMITED like any pruned Bitcoin-lineage node.

What did NOT change

  • Consensus, except the redeem collateral floor gate. Mint/transfer rules, oracle bundle rules, lock tiers — all byte-identical. The one change is in redemption collateral classification (the activation-floor gate above).
  • The Groestl algolock — untouched, and its boundary tests still pass (accept at the boundary, reject after, reindex-safe).
  • DigiDollar activation (BIP9 bit 23) — untouched; all activation tests pass.
  • Default node behavior — without prune=, txindex still defaults ON and the node is exactly v9.26.3. Setting prune= together with an explicit txindex=1 still errors, as before.

How to enable it (digibyte.conf)

Upgrading an existing v9.26.3 node (the pool path):

  1. Stop the node.

  2. In digibyte.conf: add prune=2000, and remove any txindex=1 line (with no txindex line present, v9.26.4 turns it off automatically under prune).

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

    DigiDollar: pruning enabled; retaining all blocks at/above height 23627520 (DigiDollar activation floor)
    

Fresh node: same config; it syncs and prunes as it goes.

Notes: prune=N is a disk target in MiB (minimum 550; 2000 gives headroom). prune=1 = manual mode (prune only via the pruneblockchain RPC). Wallets and getblocktemplate mining work normally. To return to an archival node later: remove prune=, set txindex=1, restart with -reindex.

How we tested it

  • Unit tests: 3,404 cases green, including the new dd_chain_prune_does_not_require_txindex.
  • Full functional suite: 380 tests green via 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 latent v9.26.3 issue where -prune=-1 reported the wrong startup error.
  • New 14-phase functional test (test/functional/feature_digidollar_pruning.py), written test-first (TDD). It proves, on real nodes:
    • a pruned node (no txindex) boots, crosses BIP9 DigiDollar activation, and mines a DigiDollar block that a full node accepts;
    • a full mint → send (self and cross-node) → redeem lifecycle on the pruned node;
    • pruning deletes pre-activation history while the DD-era window survives, and the prune lock is the binding constraint (prune-to-tip gets clamped);
    • a fresh pruned node cold-syncs the entire DD-era chain over P2P and reaches identical DigiDollar state;
    • a full node migrates in place to pruned (the exact pool upgrade path, automatic prune mode) and stays in parity;
    • reorgs across DigiDollar blocks on the pruned node reconcile exactly;
    • an offline pruned miner that mined its own stale chain (with a DD mint on it!) while the network advanced past the 288-block window abandons the stale chain on reconnect and returns to exact parity;
    • a pruned datadir cannot silently become a full node again (dropping -prune requires -reindex);
    • a deliberately damaged datadir triggers the startup guard, and the guard's prescribed -reindex recovery restores full parity;
    • a truncated/partially-restored DD-era block file (which passes the index-flag guard) is caught by the fail-closed startup reconstruction and refuses to start — then recovers with -reindex.
  • Live mainnet validation (full record: V9.26.4_MAINNET_VALIDATION.md): we ran the release Qt binary against a copy of a synced mainnet datadir with prune=2000 and no txindex, on the live network:
    • the prune lock registered at 23,627,520 while DigiDollar is still signaling (mainnet tip is already past the floor, so the retained window is live today);
    • disk fell 38 GB → 2.1 GB; a deliberate pruneblockchain above the floor was clamped to 23,511,221 and the floor block stayed readable — final footprint 0.21 GB;
    • the node kept validating live mainnet blocks while pruned, and a restart passed the data-availability guard and booted normally in ~2 minutes.

Adversarial audit — what we tried to break (and what we fixed)

Three independent deep audits ran against this branch: pruned-miner operational edge cases, guard-bypass attempts, and fuzz-surface review. Headline results:

  • No wrong-chain path exists. In every traced scenario where a pruned node lacks data (deep reorg beyond retained blocks, damaged files, unreadable history) it halts loudly — FatalError, startup refusal, or clean RPC errors — with -reindex as the recovery. There is no code path where missing pruned data causes it to accept or follow an invalid or lower-work chain.
  • Reorg capacity: before the tip reaches the activation floor, a pruned node can disconnect up to 288 blocks (~72 min) like any Bitcoin-lineage pruned node; once the tip passes the floor (mainnet: already true) capacity becomes the whole retained window — ~147,000 blocks (~25 days) today, and growing. Deeper-than-retained reorgs halt the node; they cannot mislead it.
  • Guard-bypass attempts all blocked: -txindex=1+prune, -reindex-chainstate +prune, dropping -prune on a pruned datadir, conf-section mistakes, regtest knobs on mainnet (impossible — regtest-only args), assumeutxo ordering, and the pruneblockchain RPC racing the lock registration.
  • Two real gaps found and FIXED in this branch:
    1. A truncated or partially-restored DD-era block file passes the startup guard (it checks index flags; whole-file deletion is already caught at index load) and previously let the startup health/price reconstruction continue silently with partial data — consensus-relevant DCA/volatility state could diverge. Now LoadPricesFromChain and the health seed fail closed: the node refuses to start with the "block data is incomplete… restart with -reindex" guidance, pinned by test phase F14 (written RED-first against the old binary, GREEN after the fix).
    2. The final consensus audit found that ValidateCollateralReleaseAmount classified a redeem's collateral input (and positive-value fee inputs) via a block lookup with no activation-floor gate — unlike its sibling SpendsDigiDollarCollateralVault. Because that lookup reads a block a pruned node may have pruned (pre-floor), a full node could flag a deliberately pre-planted, DD-mint-shaped pre-floor coin as collateral while a pruned node could not → an attacker-crafted pruned-vs-full split on a redeem. Fixed: a coin below the activation floor can never be real collateral, so both sites now reject/ignore pre-floor coins before the lookup, on every node. Only pre-floor coins are affected; no legitimate redeem changes. Pinned by unit test redteam_t2_06d (asserts a pruned node's failing lookup reaches the identical verdict).
  • Cross-version compatibility: a pruned v9.26.4 node speaks the same protocol as v9.26.3/v9.26.2/v8.22 peers; NODE_NETWORK_LIMITED (service bit 1024) predates both lineages, deep block requests are refused-and-disconnected (never banned), and the Groestl algolock is enforced from headers/heights only — pruning cannot affect it.

Operational guidance for pools (from the audit):

  • After a multi-hour outage, restart the node before resuming mining; a restart correctly holds getblocktemplate until the node is back in sync (a node that stayed up through an outage can briefly serve stale templates — an upstream Bitcoin Core behavior, orphan-risk only, never consensus).
  • -reindex on a pruned node redownloads from the network — it needs connectivity. Keep one archival node (or a datadir snapshot) in your fleet.
  • Watch getblockchaininfo: headers == blocks is the cheap "safe to mine" check.

How reviewers can test it themselves

# Build
./autogen.sh && ./configure && make -j$(nproc)

# Unit tests (all, then the targeted suite)
./src/test/test_digibyte
./src/test/test_digibyte --run_test=digidollar_txindex_tests

# The headline functional test (14 phases, ~2 min)
test/functional/feature_digidollar_pruning.py

# Full functional suite
test/functional/test_runner.py --jobs=8

# Optional live mainnet spot-check (non-destructive — uses a COPY):
#   1. stop your node; copy ~/.digibyte/{blocks,chainstate} to a test datadir
#   2. test datadir digibyte.conf:  prune=2000 / server=1 / rpcport=14022 / port=12124
#   3. start digibyted/Qt with -datadir=<test dir>, then:
#      - debug.log must show the "retaining all blocks at/above height 23627520" line
#      - getblockchaininfo -> pruned:true
#      - pruneblockchain <tip-height> -> returns a height BELOW 23627520 (the clamp)
#      - getblock $(getblockhash 23627520) -> still works
#   4. your real datadir is untouched; delete the copy when done

TESTNET validation — first pass DONE (live results below)

Why testnet matters: DigiDollar is already ACTIVE on testnet26 (activated at height 599/600). That makes it the only live, multi-node network where a pruned node operates in the exact situation mainnet will be in after activation — real DD positions in the UTXO set, real oracle data — which regtest can only simulate.

Config gotcha (important): testnet options go under the [test] section of digibyte.conf. If your existing [test] section contains txindex=1 (it often does — it was required before v9.26.4), you must remove it before adding prune=550, or the node will refuse to start with "Prune mode is incompatible with -txindex."

What we ran (2026-07-02, live testnet26): a pruned v9.26.4 node (prune=550, no txindex, no indexes) from a copied testnet26 datadir, alongside a full v9.26.4 node (txindex + stats index) — separate ports, same live network:

  • The pruned node logged DigiDollar: pruning enabled; retaining all blocks at/above height 600, synced to the live tip with 6 peers, and reported status: active for the deployment. Being ~3 weeks behind at start, this also exercised the offline-node catch-up on a chain where DigiDollar is live.
  • getdigidollarstats on the pruned node (UTXO-scan fallback, no index, 47 ms) returned the real network state — $4,720.77 DD supply, 18 active positions, 6.63M DGB collateral, 337% health, oracle price available — and resolving each vault's exact amount used the block-db read path on real testnet blocks.
  • Full-vs-pruned parity, live: identical tips (66,983) and identical total_dd_supply / total_collateral_dgb / active_positions / health_percentage between the pruned node's scan and the full node's stats index. New network blocks arriving during the test were validated by both.
  • Floor block 600 readable on the pruned node; NODE_NETWORK_LIMITED advertised; getindexinfo empty.

Remaining testnet steps (need a funded testnet wallet / miner):

  1. Mint, send, and redeem DigiDollar from the pruned node's wallet; confirm the full node accepts every block and stats stay in parity. (Regtest phases F4/lifecycle cover this logic; testnet re-checks it against the real oracle roster.)
  2. Mine testnet blocks with the pruned node's getblocktemplate (needs the team's miner rig pointed at the pruned node).
  3. Restart the pruned node after those operations; confirm the guard passes and stats still match.

Where everything lives

Document Content
V9.26.4_PRUNING_PLAN.md Design + full TDD test plan
V9.26.4_PRUNING_EXPLAINER.md Short shareable community explainer
V9.26.4_MAINNET_VALIDATION.md The live mainnet test record
doc/release-notes/release-notes-9.26.4.md Operator-facing release notes & limitations

Fine print: pruned nodes don't serve deep history to new peers (archival nodes still do that); getrawtransaction for pre-DigiDollar history needs an archival node; and after activation the retained DigiDollar-era window grows with the chain — prune=N deletes the 12 years of history, it isn't a permanent size cap.