Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion V9.26.4_PRUNING_EXPLAINER.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ and activation cannot happen below the deployment's minimum activation height: b
**23,627,520**. So every block DigiDollar validation will *ever* need lives between
that height and the tip. Everything below is plain DGB history — safe to delete.

**How v9.26.4 does it** (zero consensus changes, all opt-in behind `-prune`):
**How v9.26.4 does it** (one narrowly scoped consensus rule — the redeem collateral
floor gate that keeps pruned and full nodes in agreement — everything else opt-in
behind `-prune`):
Comment on lines +16 to +18

1. **Prune lock at the floor.** Startup registers a "digidollar" prune lock at
23,627,520. Automatic pruning *and* the `pruneblockchain` RPC are clamped below
Expand Down
16 changes: 15 additions & 1 deletion V9.26.4_PRUNING_PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
~23.6 million pre-DigiDollar blocks (12 years, ~32 GB) while keeping the small
DigiDollar-era window — so pools **upgrade once**, enforce the Groestl algolock at
block 23,808,000, signal and validate DigiDollar, and never need txindex.
**Zero consensus changes. Every new behavior is opt-in behind `-prune`.** The whole job
**One narrowly scoped consensus change (the redeem collateral floor gate, added by the
ship audit); every other new behavior is opt-in behind `-prune`.** The whole job
is plumbing: make sure a pruned node has every piece of data DigiDollar reads, and that
each piece is wired to the right source so the node computes the same answers as a full
node.
Expand Down Expand Up @@ -374,6 +375,14 @@ exercise our plumbing, not upstream's.
| `regtest_prune_still_honors_explicit_request` | regtest `-digidollar -prune` → false (relaxed); plain regtest → false | n/a |

### 3b. `digidollar_pruning_tests.cpp` (NEW)

> **As shipped:** this file was not created. The floor computation is covered by the
> `dd_prune_activation_floor` fuzz target (`src/test/fuzz/digidollar_prune_blockdb.cpp`),
> which exercises the shared `DigiDollar::EarliestActivationFloor` helper directly, and
> the reindex-guard cases (complete window, hole, truncated block file) are covered by
> `test/functional/feature_digidollar_pruning.py` phases F9/F14 rather than unit tests;
> the tip-below-floor no-op case has no direct unit test.

| Test | Asserts |
|---|---|
| `earliest_dd_activation_height_matches_consensus` | plan floor `== min(nDDActivationHeight, DEPLOYMENT_DIGIDOLLAR.min_activation_height)` for Main (23,627,520) / TestNet / RegTest — pin the exact number so a chainparams edit can't silently move the floor |
Expand All @@ -384,6 +393,11 @@ exercise our plumbing, not upstream's.
| `reindex_guard_hole_signals_reindex` | tip≥floor, a DD-era block lacks `BLOCK_HAVE_DATA` → guard returns the "needs reindex" result (not a crash) |

### 3c. `digidollar_health_pruned_tests.cpp` (NEW — the one wiring gap)

> **As shipped:** this file was not created. ScanUTXOSet pruned-vs-full parity and the
> pre-floor coin skip are covered by `test/functional/feature_digidollar_pruning.py`
> (phases F6 restart parity and F10 full-node-to-pruned migration) rather than unit tests.

Build a tiny regtest chain with one DD mint, then:
| Test | Asserts |
|---|---|
Expand Down
14 changes: 9 additions & 5 deletions V9.26.4_PR_EXPLAINER.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,12 @@ 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:** 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.
**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
Comment on lines 22 to +26
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
Comment on lines +25 to +29
schedule are untouched.

---

Expand Down Expand Up @@ -77,8 +80,9 @@ scan that reports the same totals *and* position count; pruned nodes advertise

## What did NOT change

- **No consensus changes.** Mint/transfer/redeem rules, collateral rules, oracle
bundle rules, lock tiers — all byte-identical.
- **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).
Comment on lines +83 to +85
- **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.
Expand Down
13 changes: 10 additions & 3 deletions doc/release-notes/release-notes-9.26.4.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,18 @@ only a few gigabytes of blocks instead of the entire ~12-year, ~40 GB block hist
**In one line:** add `prune=2000` to your `digibyte.conf`, upgrade, and your node
runs DigiByte + everything DigiDollar needs on a fraction of the disk.

**It contains no consensus rule changes for existing nodes.** v9.26.2's Groestl
**This release adds one narrowly scoped consensus rule.** Redemption collateral
classification is now gated on the DigiDollar activation floor: a coin created below
the floor is never treated as vault collateral, whether it appears as the redemption's
input 0 (rejected as `bad-collateral-release-not-vault`) or as a fee input (see
`ValidateCollateralReleaseAmount` in `src/digidollar/validation.cpp`). This rule is
what keeps pruned and full nodes in agreement, and the pre-release mainnet scan found
no existing coins it affects (`V9.26.4_MAINNET_VALIDATION.md`). v9.26.2's Groestl
algolock and the DigiDollar BIP9 deployment are carried forward unchanged, and a
pruned v9.26.4 node accepts and rejects exactly the same blocks and transactions as a
full node. Upgrading is optional — a node that does not set `-prune` behaves exactly
like v9.26.3.
full node. Upgrading is optional — a node that does not set `-prune` behaves like
v9.26.3 except for this one rule; node operators, pools, and exchanges should treat it
as a consensus rule addition when assessing upgrade urgency.


How to Upgrade
Expand Down
13 changes: 13 additions & 0 deletions src/consensus/digidollar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,19 @@

namespace DigiDollar {

int EarliestActivationFloor(const Consensus::Params& params)
{
const auto& deployment = params.vDeployments[Consensus::DEPLOYMENT_DIGIDOLLAR];
if (deployment.nStartTime == Consensus::BIP9Deployment::NEVER_ACTIVE ||
deployment.nTimeout == Consensus::BIP9Deployment::NEVER_ACTIVE) {
return 0;
}
if (deployment.nStartTime == Consensus::BIP9Deployment::ALWAYS_ACTIVE) {
return deployment.min_activation_height;
}
return std::min(params.nDDActivationHeight, deployment.min_activation_height);
}

int GetCollateralRatioForLockTime(int64_t lockBlocks, const ConsensusParams& params)
{
const auto it = params.collateralRatios.find(lockBlocks);
Expand Down
15 changes: 15 additions & 0 deletions src/consensus/digidollar.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,21 @@ enum DigiDollarTxType : uint8_t {
DD_TX_MAX = 4 // For validation
};

/**
* Earliest height at which a DigiDollar output can exist on this chain (the
* "activation floor"). Single source of truth shared by consensus validation
* (EarliestDigiDollarActivationHeight), the "digidollar" prune lock
* (src/node/chainstate.cpp), and the startup fail-closed guards
* (src/digidollar/health.cpp, src/oracle/bundle_manager.cpp) — these must
* never disagree, or a pruned node could delete a block validation still
* needs to read.
*
* Returns 0 when the deployment can never activate (NEVER_ACTIVE start or
* timeout); callers treat 0 as "no floor" (no prune lock, no fail-closed
* window, pre-floor gates disabled).
*/
int EarliestActivationFloor(const Consensus::Params& params);

/**
* Core consensus parameters for the DigiDollar stablecoin system.
* These parameters define the economic model, collateral requirements,
Expand Down
11 changes: 1 addition & 10 deletions src/digidollar/health.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -311,16 +311,7 @@ bool SystemHealthMonitor::ScanUTXOSet(CCoinsView* view, CCoinsView* validation_v
// Below the floor there are no DD vaults, so we skip those coins without reading a
// block — this is what lets the seed run identically on a pruned node (pre-floor
// blocks are gone) and a full node (pre-floor blocks are present but hold no vaults).
int dd_floor = 0;
if (consensus) {
const auto& dd_dep = consensus->vDeployments[Consensus::DEPLOYMENT_DIGIDOLLAR];
if (dd_dep.nStartTime != Consensus::BIP9Deployment::NEVER_ACTIVE &&
dd_dep.nTimeout != Consensus::BIP9Deployment::NEVER_ACTIVE) {
dd_floor = (dd_dep.nStartTime == Consensus::BIP9Deployment::ALWAYS_ACTIVE)
? dd_dep.min_activation_height
: std::min(consensus->nDDActivationHeight, dd_dep.min_activation_height);
}
}
const int dd_floor = consensus ? DigiDollar::EarliestActivationFloor(*consensus) : 0;

// Reset counters
s_currentMetrics.totalDDSupply = 0;
Expand Down
6 changes: 5 additions & 1 deletion src/digidollar/health.h
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,11 @@ class SystemHealthMonitor {
//! (block data incomplete/damaged) — callers seeding consensus-relevant
//! state must treat that as fatal (fail closed) rather than accept an
//! undercounted supply/collateral baseline.
static bool ScanUTXOSet(CCoinsView* view, CCoinsView* validation_view, const node::BlockManager* blockman, const CTxMemPool* mempool = nullptr, const CChain* chain = nullptr, const Consensus::Params* consensus = nullptr);
//! mempool/chain/consensus deliberately have no defaults: the pre-floor
//! coin skip and the fail-closed check are both gated on a non-null chain,
//! so a caller that silently omitted these arguments would revert to the
//! old silent-undercount behavior. Every caller must decide explicitly.
static bool ScanUTXOSet(CCoinsView* view, CCoinsView* validation_view, const node::BlockManager* blockman, const CTxMemPool* mempool, const CChain* chain, const Consensus::Params* consensus);

/**
* Reconstruct the cached system-health metrics (total DD supply + total
Expand Down
7 changes: 1 addition & 6 deletions src/digidollar/validation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,7 @@ static bool IsCanonicalP2TROutput(const CScript& script)

static int EarliestDigiDollarActivationHeight(const ValidationContext& ctx)
{
const Consensus::Params& cp = ctx.params.GetConsensus();
const auto& deployment = cp.vDeployments[Consensus::DEPLOYMENT_DIGIDOLLAR];
if (deployment.nStartTime == Consensus::BIP9Deployment::ALWAYS_ACTIVE) {
return deployment.min_activation_height;
}
return std::min(cp.nDDActivationHeight, deployment.min_activation_height);
return DigiDollar::EarliestActivationFloor(ctx.params.GetConsensus());
}

static bool CoinHeightMayCreateDigiDollar(const Coin& coin, const ValidationContext& ctx)
Expand Down
11 changes: 2 additions & 9 deletions src/node/chainstate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <arith_uint256.h>
#include <chain.h>
#include <coins.h>
#include <consensus/digidollar.h>
#include <consensus/params.h>
#include <logging.h>
#include <node/blockstorage.h>
Expand Down Expand Up @@ -165,15 +166,7 @@ static ChainstateLoadResult CompleteChainstateInitialization(
// DigiDollar with incomplete data. Registered here (cs_main held, tip loaded) before
// the first prune/flush can run.
{
const Consensus::Params& consensus = chainman.GetConsensus();
const auto& dd_dep = consensus.vDeployments[Consensus::DEPLOYMENT_DIGIDOLLAR];
int dd_floor = 0;
if (dd_dep.nStartTime != Consensus::BIP9Deployment::NEVER_ACTIVE &&
dd_dep.nTimeout != Consensus::BIP9Deployment::NEVER_ACTIVE) {
dd_floor = (dd_dep.nStartTime == Consensus::BIP9Deployment::ALWAYS_ACTIVE)
? dd_dep.min_activation_height
: std::min(consensus.nDDActivationHeight, dd_dep.min_activation_height);
}
const int dd_floor = DigiDollar::EarliestActivationFloor(chainman.GetConsensus());

if (options.prune && dd_floor > 0) {
PruneLockInfo dd_lock;
Expand Down
12 changes: 2 additions & 10 deletions src/oracle/bundle_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <chainparams.h>
#include <common/args.h>
#include <consensus/consensus.h>
#include <consensus/digidollar.h>
#include <consensus/volatility.h>
#include <digidollar/digidollar.h>
#include <kernel/chainparams.h>
Expand Down Expand Up @@ -1810,16 +1811,7 @@ bool OracleBundleManager::LoadPricesFromChain(ChainstateManager& chainman)
// data is damaged and we must fail closed. Below it — or when the floor is 0
// (default regtest ALWAYS_ACTIVE, where no retention is guaranteed and no lock is
// registered) — a missing block is a legitimate prune/assumeutxo state, not damage.
int dd_floor = 0;
{
const auto& dd_dep = consensus.vDeployments[Consensus::DEPLOYMENT_DIGIDOLLAR];
if (dd_dep.nStartTime != Consensus::BIP9Deployment::NEVER_ACTIVE &&
dd_dep.nTimeout != Consensus::BIP9Deployment::NEVER_ACTIVE) {
dd_floor = (dd_dep.nStartTime == Consensus::BIP9Deployment::ALWAYS_ACTIVE)
? dd_dep.min_activation_height
: std::min(consensus.nDDActivationHeight, dd_dep.min_activation_height);
}
}
const int dd_floor = DigiDollar::EarliestActivationFloor(consensus);

// Scan recent blocks for the live oracle cache and enough history to
// deterministically rebuild volatility state after restart/reindex.
Expand Down
2 changes: 1 addition & 1 deletion src/test/digidollar_redteam_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10382,7 +10382,7 @@ BOOST_AUTO_TEST_CASE(redteam_t5_06c_scan_utxo_set_race_condition)
DigiDollar::SystemHealthMonitor::Initialize();

// Now call ScanUTXOSet with null view — this resets metrics to 0
DigiDollar::SystemHealthMonitor::ScanUTXOSet(nullptr, nullptr, nullptr, nullptr);
DigiDollar::SystemHealthMonitor::ScanUTXOSet(nullptr, nullptr, nullptr, nullptr, nullptr, nullptr);

// At this point metrics are reset to 0 (scan found nothing with null view)
auto metrics = DigiDollar::SystemHealthMonitor::GetCachedMetrics();
Expand Down
Loading
Loading