diff --git a/doc/release-notes-7861.md b/doc/release-notes-7861.md new file mode 100644 index 000000000000..cfa126788d91 --- /dev/null +++ b/doc/release-notes-7861.md @@ -0,0 +1,12 @@ +Pruning +------- + +- Pruned nodes on mainnet, testnet and devnets now keep at least four days of + blocks (2304) below the tip instead of 288. Validation reads older blocks + from disk: the credit pool is rebuilt from its newest daily snapshot after a + restart, and rotated quorum membership reads the work blocks of earlier DKG + cycles. `pruneblockchain` clamps its height accordingly, and on testnet and + devnets pruning starts once the chain is longer than 3000 blocks + (previously 1000). Blocks that an earlier version already pruned are not + recovered; a node that fails to validate a block after upgrading needs + `-reindex`. diff --git a/src/evo/creditpool.h b/src/evo/creditpool.h index e7676beace54..060a570e17ca 100644 --- a/src/evo/creditpool.h +++ b/src/evo/creditpool.h @@ -123,9 +123,8 @@ class CCreditPoolManager CEvoDB& evoDb; const ChainstateManager& m_chainman; - static constexpr int DISK_SNAPSHOT_PERIOD = 576; // once per day - public: + static constexpr int DISK_SNAPSHOT_PERIOD = 576; // once per day static constexpr CAmount LimitAmountLow = 100 * COIN; static constexpr CAmount LimitAmountHigh = 1000 * COIN; static constexpr CAmount LimitAmountV22 = 2000 * COIN; diff --git a/src/init.cpp b/src/init.cpp index a495ac1ec6a3..224d3267b22e 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -2106,9 +2106,9 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) } if (status == node::ChainstateLoadStatus::SUCCESS) { uiInterface.InitMessage(_("Verifying blocks…").translated); - if (chainman.m_blockman.m_have_pruned && options.check_blocks > MIN_BLOCKS_TO_KEEP) { + if (chainman.m_blockman.m_have_pruned && options.check_blocks > chainman.GetParams().MinBlocksToKeep()) { LogWarning("pruned datadir may not have more than %d blocks; only checking available blocks\n", - MIN_BLOCKS_TO_KEEP); + chainman.GetParams().MinBlocksToKeep()); } std::tie(status, error) = catch_exceptions([&]{ return VerifyLoadedChainstate(chainman, options, *Assert(node.evodb), [](bool bls_state) { LogPrintf("AppInitMain: bls_legacy_scheme=%d\n", bls_state); diff --git a/src/kernel/chainparams.cpp b/src/kernel/chainparams.cpp index a9310b5245a6..225268291879 100644 --- a/src/kernel/chainparams.cpp +++ b/src/kernel/chainparams.cpp @@ -255,6 +255,7 @@ class CMainParams : public CChainParams { nDefaultPlatformP2PPort = 26656; nDefaultPlatformHTTPPort = 443; nPruneAfterHeight = 100000; + m_min_blocks_to_keep = 2304; // four days m_assumed_blockchain_size = 57; m_assumed_chain_state_size = 1; @@ -458,7 +459,8 @@ class CTestNetParams : public CChainParams { nDefaultPort = 19999; nDefaultPlatformP2PPort = 22000; nDefaultPlatformHTTPPort = 22001; - nPruneAfterHeight = 1000; + nPruneAfterHeight = 3000; + m_min_blocks_to_keep = 2304; // four days m_assumed_blockchain_size = 10; m_assumed_chain_state_size = 1; @@ -640,7 +642,8 @@ class CDevNetParams : public CChainParams { nDefaultPort = 19799; nDefaultPlatformP2PPort = 22100; nDefaultPlatformHTTPPort = 22101; - nPruneAfterHeight = 1000; + nPruneAfterHeight = 3000; + m_min_blocks_to_keep = 2304; // four days m_assumed_blockchain_size = 0; m_assumed_chain_state_size = 0; @@ -936,6 +939,7 @@ class CRegTestParams : public CChainParams { nDefaultPlatformP2PPort = 22200; nDefaultPlatformHTTPPort = 22201; nPruneAfterHeight = opts.fastprune ? 100 : 1000; + m_min_blocks_to_keep = 288; m_assumed_blockchain_size = 0; m_assumed_chain_state_size = 0; diff --git a/src/kernel/chainparams.h b/src/kernel/chainparams.h index 073442ddd9e9..777dc15d9627 100644 --- a/src/kernel/chainparams.h +++ b/src/kernel/chainparams.h @@ -118,6 +118,8 @@ class CChainParams /** If this chain is exclusively used for testing */ bool IsTestChain() const { return m_is_test_chain; } uint64_t PruneAfterHeight() const { return nPruneAfterHeight; } + /** Block files containing a block-height within MinBlocksToKeep() of ActiveChain().Tip() will not be pruned. */ + int MinBlocksToKeep() const { return m_min_blocks_to_keep; } /** Minimum free space (in GB) needed for data directory */ uint64_t AssumedBlockchainSize() const { return m_assumed_blockchain_size; } /** Minimum free space (in GB) needed for data directory when pruned; Does not include prune target*/ @@ -252,6 +254,11 @@ class CChainParams CMessageHeader::MessageStartChars pchMessageStart; uint16_t nDefaultPort; uint64_t nPruneAfterHeight; + /// Four days of blocks (2304) on every network but regtest. Validation reads older blocks from disk: + /// a credit pool is rebuilt from its newest daily snapshot, reading the block one withdrawal window + /// before each block since it, and rotated LLMQ modifiers read the work block of earlier cycles. + /// Regtest keeps Bitcoin Core's 288 for the upstream pruning tests. + int m_min_blocks_to_keep{288}; uint64_t m_assumed_blockchain_size; uint64_t m_assumed_chain_state_size; std::vector vSeeds; diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 1ff06b047097..9ca230901f5f 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -161,7 +161,7 @@ static constexpr auto BLOCK_STALLING_TIMEOUT_MAX{64s}; static const int MAX_CMPCTBLOCK_DEPTH = 5; /** Maximum depth of blocks we're willing to respond to GETBLOCKTXN requests for. */ static const int MAX_BLOCKTXN_DEPTH = 10; -static_assert(MAX_BLOCKTXN_DEPTH <= MIN_BLOCKS_TO_KEEP, "MAX_BLOCKTXN_DEPTH too high"); +static_assert(MAX_BLOCKTXN_DEPTH <= BLOCKS_NEAR_TIP, "MAX_BLOCKTXN_DEPTH too high"); /** Size of the "block download window": how far ahead of our current height do we fetch? * Larger windows tolerate larger download speed differences between peer, but increase the potential * degree of disordering of blocks on disk (which make reindexing and pruning harder). We'll probably @@ -4716,7 +4716,7 @@ void PeerManagerImpl::ProcessMessage( } // If pruning, don't inv blocks unless we have on disk and are likely to still have // for some reasonable time window (1 hour) that block relay might require. - const int nPrunedBlocksLikelyToHave = MIN_BLOCKS_TO_KEEP - 3600 / m_chainparams.GetConsensus().nPowTargetSpacing; + const int nPrunedBlocksLikelyToHave = BLOCKS_NEAR_TIP - 3600 / m_chainparams.GetConsensus().nPowTargetSpacing; if (m_chainman.m_blockman.IsPruneMode() && (!(pindex->nStatus & BLOCK_HAVE_DATA) || pindex->nHeight <= m_chainman.ActiveChain().Tip()->nHeight - nPrunedBlocksLikelyToHave)) { LogPrint(BCLog::NET, " getblocks stopping, pruned or too old block at %d %s\n", pindex->nHeight, pindex->GetBlockHash().ToString()); break; diff --git a/src/node/blockstorage.cpp b/src/node/blockstorage.cpp index 71553ad41ba8..822e983573cb 100644 --- a/src/node/blockstorage.cpp +++ b/src/node/blockstorage.cpp @@ -172,8 +172,9 @@ void BlockManager::FindFilesToPruneManual(std::set& setFilesToPrune, int nM return; } - // last block to prune is the lesser of (user-specified height, MIN_BLOCKS_TO_KEEP from the tip) - unsigned int nLastBlockWeCanPrune = std::min((unsigned)nManualPruneHeight, chain_tip_height - MIN_BLOCKS_TO_KEEP); + // last block to prune is the lesser of (user-specified height, MinBlocksToKeep() from the tip) + unsigned int nLastBlockWeCanPrune = std::min((unsigned)nManualPruneHeight, + static_cast(chain_tip_height - GetParams().MinBlocksToKeep())); int count = 0; for (int fileNumber = 0; fileNumber < m_last_blockfile; fileNumber++) { if (m_blockfile_info[fileNumber].nSize == 0 || m_blockfile_info[fileNumber].nHeightLast > nLastBlockWeCanPrune) { @@ -196,7 +197,7 @@ void BlockManager::FindFilesToPrune(std::set& setFilesToPrune, uint64_t nPr return; } - unsigned int nLastBlockWeCanPrune{(unsigned)std::min(prune_height, chain_tip_height - static_cast(MIN_BLOCKS_TO_KEEP))}; + unsigned int nLastBlockWeCanPrune{(unsigned)std::min(prune_height, chain_tip_height - GetParams().MinBlocksToKeep())}; uint64_t nCurrentUsage = CalculateCurrentUsage(); // We don't check to prune until after we've allocated new space for files // So we should leave a buffer under our target to account for another allocation @@ -226,7 +227,7 @@ void BlockManager::FindFilesToPrune(std::set& setFilesToPrune, uint64_t nPr break; } - // don't prune files that could have a block within MIN_BLOCKS_TO_KEEP of the main chain's tip but keep scanning + // don't prune files that could have a block within MinBlocksToKeep() of the main chain's tip but keep scanning if (m_blockfile_info[fileNumber].nHeightLast > nLastBlockWeCanPrune) { continue; } diff --git a/src/node/blockstorage.h b/src/node/blockstorage.h index 907baa2bd1b5..66eb4bfc35dd 100644 --- a/src/node/blockstorage.h +++ b/src/node/blockstorage.h @@ -118,7 +118,7 @@ class BlockManager * Pruning functions are called from FlushStateToDisk when the m_check_for_pruning flag has been set. * Block and undo files are deleted in lock-step (when blk00003.dat is deleted, so is rev00003.dat.) * Pruning cannot take place until the longest chain is at least a certain length (CChainParams::nPruneAfterHeight). - * Pruning will never delete a block within a defined distance (currently 288) from the active chain's tip. + * Pruning will never delete a block within CChainParams::MinBlocksToKeep() of the active chain's tip. * The block index is updated by unsetting HAVE_DATA and HAVE_UNDO for any blocks that were stored in the deleted files. * A db flag records the fact that at least some block files have been pruned. * diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index cbad463336b6..e3f7a02a6bc7 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1122,9 +1122,9 @@ static RPCHelpMan pruneblockchain() throw JSONRPCError(RPC_MISC_ERROR, "Blockchain is too short for pruning."); } else if (height > chainHeight) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Blockchain is shorter than the attempted prune height."); - } else if (height > chainHeight - MIN_BLOCKS_TO_KEEP) { + } else if (height > chainHeight - chainman.GetParams().MinBlocksToKeep()) { LogPrint(BCLog::RPC, "Attempt to prune blocks close to the tip. Retaining the minimum number of blocks.\n"); - height = chainHeight - MIN_BLOCKS_TO_KEEP; + height = chainHeight - chainman.GetParams().MinBlocksToKeep(); } PruneBlockFilesManual(active_chainstate, height); diff --git a/src/test/evo_assetlocks_tests.cpp b/src/test/evo_assetlocks_tests.cpp index 8db84d70d92a..aa23c80aa846 100644 --- a/src/test/evo_assetlocks_tests.cpp +++ b/src/test/evo_assetlocks_tests.cpp @@ -4,6 +4,7 @@ #include +#include #include #include #include @@ -858,6 +859,17 @@ BOOST_FIXTURE_TEST_CASE(credit_pool_snapshot_persisted_after_transactionless_con BOOST_CHECK_EQUAL(snapshot.currentLimit, pool.currentLimit); } +BOOST_FIXTURE_TEST_CASE(min_blocks_to_keep_covers_credit_pool_rebuild, BasicTestingSetup) +{ + // A pool is rebuilt from a snapshot up to one period old, reading the block one window before + // each block since it. Regtest deliberately keeps Bitcoin Core's 288 and is not checked. + for (const auto& chain : {CBaseChainParams::MAIN, CBaseChainParams::TESTNET, CBaseChainParams::DEVNET}) { + const auto params = CreateChainParams(*m_node.args, chain); + BOOST_CHECK_GE(params->MinBlocksToKeep(), + CCreditPoolManager::DISK_SNAPSHOT_PERIOD + params->CreditPoolPeriodBlocks()); + } +} + BOOST_AUTO_TEST_CASE(credit_pool_unlock_limit_v24) { // From v24 the limit is relative and net: 20% of the balance at the window start, never less diff --git a/src/validation.cpp b/src/validation.cpp index 796553c1b79f..03daf29ef5ce 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -5008,7 +5008,7 @@ bool ChainstateManager::AcceptBlock(const std::shared_ptr& pblock, // blocks which are too close in height to the tip. Apply this test // regardless of whether pruning is enabled; it should generally be safe to // not process unrequested blocks. - bool fTooFarAhead{pindex->nHeight > ActiveHeight() + int(MIN_BLOCKS_TO_KEEP)}; + bool fTooFarAhead{pindex->nHeight > ActiveHeight() + int(BLOCKS_NEAR_TIP)}; // TODO: Decouple this function from the block download logic by removing fRequested // This requires some new chain data structure to efficiently look up if a diff --git a/src/validation.h b/src/validation.h index f5aea3c036ed..566837c4b1fc 100644 --- a/src/validation.h +++ b/src/validation.h @@ -85,8 +85,9 @@ static const unsigned int MAX_HEADERS_COMPRESSED_RESULT = 8000; /** Default for -stopatheight */ static const int DEFAULT_STOPATHEIGHT = 0; -/** Block files containing a block-height within MIN_BLOCKS_TO_KEEP of ActiveChain().Tip() will not be pruned. */ -static const unsigned int MIN_BLOCKS_TO_KEEP = 288; +/** Blocks within this distance of ActiveChain().Tip() are accepted unrequested and announced by pruned nodes. + * Bitcoin Core's MIN_BLOCKS_TO_KEEP; pruning retention is CChainParams::MinBlocksToKeep(). */ +static const unsigned int BLOCKS_NEAR_TIP = 288; static const signed int DEFAULT_CHECKBLOCKS = 6; static constexpr int DEFAULT_CHECKLEVEL{3};