From 582b43865c4421a0e0580135f19d95c88f8496a1 Mon Sep 17 00:00:00 2001 From: Pablo Greco Date: Thu, 29 Jun 2023 10:43:49 -0700 Subject: [PATCH 1/9] Avoid pruning headers that have not been saved to disk yet --- src/chain.h | 15 ++++++++++++++- src/txdb.cpp | 1 + src/validation.cpp | 5 +++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/chain.h b/src/chain.h index 40ec115728e..e6c51dd419c 100644 --- a/src/chain.h +++ b/src/chain.h @@ -201,6 +201,7 @@ class CBlockIndex bool m_trimmed{false}; bool m_trimmed_dynafed_block{false}; + bool m_stored_lvl{false}; friend class CBlockTreeDB; @@ -208,19 +209,28 @@ class CBlockIndex // Irrevocably remove blocksigning and dynafed-related stuff from this // in-memory copy of the block header. - void trim() { + bool trim() { assert_untrimmed(); + if (!m_stored_lvl) { + // We can't trim in-memory data if it's not on disk yet, but we can if it's already been recovered once + return false; + } m_trimmed = true; m_trimmed_dynafed_block = !m_dynafed_params.value().IsNull(); proof = std::nullopt; m_dynafed_params = std::nullopt; m_signblock_witness = std::nullopt; + return true; } + void untrim(); inline bool trimmed() const { return m_trimmed; } + inline void set_stored() { + m_stored_lvl = true; + } inline void assert_untrimmed() const { assert(!m_trimmed); } @@ -463,6 +473,9 @@ class CDiskBlockIndex : public CBlockIndex // For compatibility with elements 0.14 based chains if (g_signed_blocks) { + if (!ser_action.ForRead()) { + obj.assert_untrimmed(); + } if (is_dyna) { READWRITE(obj.m_dynafed_params.value()); READWRITE(obj.m_signblock_witness.value().stack); diff --git a/src/txdb.cpp b/src/txdb.cpp index a679b0f5429..aca584ebf67 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -373,6 +373,7 @@ bool CBlockTreeDB::LoadBlockIndexGuts(const Consensus::Params& consensusParams, pindexNew->nStatus = diskindex.nStatus; pindexNew->nTx = diskindex.nTx; + pindexNew->set_stored(); n_total++; if (diskindex.nHeight >= trimBelowHeight) { n_untrimmed++; diff --git a/src/validation.cpp b/src/validation.cpp index f3cf1e25722..c8ff3f5b757 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2355,6 +2355,11 @@ bool CChainState::FlushStateToDisk( return AbortNode(state, "Failed to write to block index database"); } + // This should be done inside WriteBatchSync, but CBlockIndex is const there + for (std::set::iterator it = setTrimmableBlockIndex.begin(); it != setTrimmableBlockIndex.end(); it++) { + (*it)->set_stored(); + } + if (fTrimHeaders) { LogPrintf("Flushing block index, trimming headers, setTrimmableBlockIndex.size(): %d\n", setTrimmableBlockIndex.size()); int trim_height = m_chain.Height() - nMustKeepFullHeaders; From e2f90e627a1d91ffe14bb6bcc2965d9983b91c24 Mon Sep 17 00:00:00 2001 From: Pablo Greco Date: Thu, 29 Jun 2023 10:47:16 -0700 Subject: [PATCH 2/9] Avoid trimming on shutdown --- src/validation.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/validation.cpp b/src/validation.cpp index c8ff3f5b757..1a8f59cb820 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2360,7 +2360,7 @@ bool CChainState::FlushStateToDisk( (*it)->set_stored(); } - if (fTrimHeaders) { + if (fTrimHeaders && !ShutdownRequested()) { LogPrintf("Flushing block index, trimming headers, setTrimmableBlockIndex.size(): %d\n", setTrimmableBlockIndex.size()); int trim_height = m_chain.Height() - nMustKeepFullHeaders; int min_height = std::numeric_limits::max(); From d83c25e5c1776cb83fa2a51b03c7bb25c5ee7535 Mon Sep 17 00:00:00 2001 From: Pablo Greco Date: Thu, 29 Jun 2023 10:52:08 -0700 Subject: [PATCH 3/9] Validate trimmed headers too while loading --- src/txdb.cpp | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/txdb.cpp b/src/txdb.cpp index aca584ebf67..5a79407cdca 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -373,24 +373,26 @@ bool CBlockTreeDB::LoadBlockIndexGuts(const Consensus::Params& consensusParams, pindexNew->nStatus = diskindex.nStatus; pindexNew->nTx = diskindex.nTx; + pindexNew->proof = diskindex.proof; + pindexNew->m_dynafed_params = diskindex.m_dynafed_params; + pindexNew->m_signblock_witness = diskindex.m_signblock_witness; + + assert(!(g_signed_blocks && diskindex.m_dynafed_params.value().IsNull() && diskindex.proof.value().IsNull())); + pindexNew->set_stored(); n_total++; + + const uint256 block_hash = pindexNew->GetBlockHash(); + // Only validate one of every 1000 block header for sanity check + if (pindexNew->nHeight % 1000 == 0 && + block_hash != consensusParams.hashGenesisBlock && + !CheckProof(pindexNew->GetBlockHeader(), consensusParams)) { + return error("%s: CheckProof: %s, %s", __func__, block_hash.ToString(), pindexNew->ToString()); + } if (diskindex.nHeight >= trimBelowHeight) { n_untrimmed++; - pindexNew->proof = diskindex.proof; - pindexNew->m_dynafed_params = diskindex.m_dynafed_params; - pindexNew->m_signblock_witness = diskindex.m_signblock_witness; - - const uint256 block_hash = pindexNew->GetBlockHash(); - // Only validate one of every 1000 block header for sanity check - if (pindexNew->nHeight % 1000 == 0 && - block_hash != consensusParams.hashGenesisBlock && - !CheckProof(pindexNew->GetBlockHeader(), consensusParams)) { - return error("%s: CheckProof: %s, %s", __func__, block_hash.ToString(), pindexNew->ToString()); - } } else { - pindexNew->m_trimmed = true; - pindexNew->m_trimmed_dynafed_block = !diskindex.m_dynafed_params.value().IsNull(); + pindexNew->trim(); } pcursor->Next(); From 2f7430626b622a7c02c0fd0ab153b8859da4f623 Mon Sep 17 00:00:00 2001 From: Pablo Greco Date: Thu, 29 Jun 2023 11:51:24 -0700 Subject: [PATCH 4/9] Allow gaps while trimming headers --- src/validation.cpp | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/validation.cpp b/src/validation.cpp index 1a8f59cb820..f1264ffaf51 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2360,29 +2360,31 @@ bool CChainState::FlushStateToDisk( (*it)->set_stored(); } - if (fTrimHeaders && !ShutdownRequested()) { + int trim_height = pindexBestHeader ? pindexBestHeader->nHeight - nMustKeepFullHeaders : 0; + if (fTrimHeaders && trim_height > 0 && !ShutdownRequested()) { + static int nMinTrimHeight{0}; LogPrintf("Flushing block index, trimming headers, setTrimmableBlockIndex.size(): %d\n", setTrimmableBlockIndex.size()); - int trim_height = m_chain.Height() - nMustKeepFullHeaders; - int min_height = std::numeric_limits::max(); - CBlockIndex* min_index = nullptr; for (std::set::iterator it = setTrimmableBlockIndex.begin(); it != setTrimmableBlockIndex.end(); it++) { (*it)->assert_untrimmed(); if ((*it)->nHeight < trim_height) { (*it)->trim(); - if ((*it)->nHeight < min_height) { - min_height = (*it)->nHeight; - min_index = *it; - } } } - + CBlockIndex* min_index = pindexBestHeader->GetAncestor(trim_height-1); // Handle any remaining untrimmed blocks that were too recent for trimming last time we flushed. if (min_index) { - min_index = min_index->pprev; - while (min_index && !min_index->trimmed()) { - min_index->trim(); + int nMaxTrimHeightRound = std::max(nMinTrimHeight, min_index->nHeight + 1); + while (min_index && min_index->nHeight >= nMinTrimHeight) { + if (!min_index->trimmed()) { + // there may be gaps due to untrimmed blocks, we need to check them all + if (!min_index->trim()) { + // Header could not be trimmed, we'll need to try again next round + nMaxTrimHeightRound = min_index->nHeight; + } + } min_index = min_index->pprev; } + nMinTrimHeight = nMaxTrimHeightRound; } } } From bc385cbec9a808e3cb66058b89e9b720d012dd1a Mon Sep 17 00:00:00 2001 From: Pablo Greco Date: Thu, 29 Jun 2023 12:10:09 -0700 Subject: [PATCH 5/9] Reload trimmed header from index instead of block, block may have been pruned --- src/chain.cpp | 7 ++++++ src/chain.h | 2 ++ src/node/blockstorage.cpp | 11 ---------- src/node/blockstorage.h | 1 - src/rest.cpp | 21 +++++------------- src/rpc/blockchain.cpp | 21 +++++++++--------- src/txdb.cpp | 46 +++++++++++++++++++++++++++++++++++++++ src/txdb.h | 1 + 8 files changed, 72 insertions(+), 38 deletions(-) diff --git a/src/chain.cpp b/src/chain.cpp index 6323aaf0dfb..fad127889f1 100644 --- a/src/chain.cpp +++ b/src/chain.cpp @@ -5,6 +5,8 @@ #include +#include // pblocktree + /** * CChain implementation */ @@ -48,6 +50,11 @@ CBlockLocator CChain::GetLocator(const CBlockIndex *pindex) const { return CBlockLocator(vHave); } +const CBlockIndex *CBlockIndex::untrim_to(CBlockIndex *pindexNew) const +{ + return pblocktree->RegenerateFullIndex(this, pindexNew); +} + const CBlockIndex *CChain::FindFork(const CBlockIndex *pindex) const { if (pindex == nullptr) { return nullptr; diff --git a/src/chain.h b/src/chain.h index e6c51dd419c..d804690a4e0 100644 --- a/src/chain.h +++ b/src/chain.h @@ -224,6 +224,8 @@ class CBlockIndex } void untrim(); + const CBlockIndex *untrim_to(CBlockIndex *pindexNew) const; + inline bool trimmed() const { return m_trimmed; } diff --git a/src/node/blockstorage.cpp b/src/node/blockstorage.cpp index fa1890a5244..89ad1fba564 100644 --- a/src/node/blockstorage.cpp +++ b/src/node/blockstorage.cpp @@ -408,17 +408,6 @@ bool ReadBlockFromDisk(CBlock& block, const CBlockIndex* pindex, const Consensus return true; } -bool ReadBlockHeaderFromDisk(CBlockHeader& header, const CBlockIndex* pindex, const Consensus::Params& consensusParams) -{ - // Not very efficient: read a block and throw away all but the header. - CBlock tmp; - if (!ReadBlockFromDisk(tmp, pindex, consensusParams)) { - return false; - } - header = tmp.GetBlockHeader(); - return true; -} - bool ReadRawBlockFromDisk(std::vector& block, const FlatFilePos& pos, const CMessageHeader::MessageStartChars& message_start) { FlatFilePos hpos = pos; diff --git a/src/node/blockstorage.h b/src/node/blockstorage.h index 0a632eae0fe..404ec4d52c9 100644 --- a/src/node/blockstorage.h +++ b/src/node/blockstorage.h @@ -79,7 +79,6 @@ bool ReadBlockFromDisk(CBlock& block, const CBlockIndex* pindex, const Consensus bool ReadRawBlockFromDisk(std::vector& block, const FlatFilePos& pos, const CMessageHeader::MessageStartChars& message_start); bool ReadRawBlockFromDisk(std::vector& block, const CBlockIndex* pindex, const CMessageHeader::MessageStartChars& message_start); // ELEMENTS: -bool ReadBlockHeaderFromDisk(class CBlockHeader& header, const CBlockIndex* pindex, const Consensus::Params& consensusParams); bool UndoReadFromDisk(CBlockUndo& blockundo, const CBlockIndex* pindex); bool WriteUndoDataForBlock(const CBlockUndo& blockundo, BlockValidationState& state, CBlockIndex* pindex, const CChainParams& chainparams); diff --git a/src/rest.cpp b/src/rest.cpp index 519412e2287..fd384d0ea9c 100644 --- a/src/rest.cpp +++ b/src/rest.cpp @@ -221,13 +221,9 @@ static bool rest_headers(const std::any& context, case RetFormat::BINARY: { CDataStream ssHeader(SER_NETWORK, PROTOCOL_VERSION); for (const CBlockIndex *pindex : headers) { - if (pindex->trimmed()) { - CBlockHeader tmp; - ReadBlockHeaderFromDisk(tmp, pindex, Params().GetConsensus()); - ssHeader << tmp; - } else { - ssHeader << pindex->GetBlockHeader(); - } + CBlockIndex tmpBlockIndexFull; + const CBlockIndex* pindexfull = pindex->untrim_to(&tmpBlockIndexFull); + ssHeader << pindexfull->GetBlockHeader(); } std::string binaryHeader = ssHeader.str(); @@ -239,14 +235,9 @@ static bool rest_headers(const std::any& context, case RetFormat::HEX: { CDataStream ssHeader(SER_NETWORK, PROTOCOL_VERSION); for (const CBlockIndex *pindex : headers) { - if (pindex->trimmed()) { - CBlockHeader tmp; - ReadBlockHeaderFromDisk(tmp, pindex, Params().GetConsensus()); - ssHeader << tmp; - - } else { - ssHeader << pindex->GetBlockHeader(); - } + CBlockIndex tmpBlockIndexFull; + const CBlockIndex* pindexfull = pindex->untrim_to(&tmpBlockIndexFull); + ssHeader << pindexfull->GetBlockHeader(); } std::string strHex = HexStr(ssHeader) + "\n"; diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 61713f1ee18..611ed21f0bf 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -226,15 +226,18 @@ CBlockIndex* ParseHashOrHeight(const UniValue& param, ChainstateManager& chainma } } -UniValue blockheaderToJSON(const CBlockIndex* tip, const CBlockIndex* blockindex) +UniValue blockheaderToJSON(const CBlockIndex* tip, const CBlockIndex* blockindex_) { // Serialize passed information without accessing chain state of the active chain! AssertLockNotHeld(cs_main); // For performance reasons + CBlockIndex tmpBlockIndexFull; + const CBlockIndex* blockindex = blockindex_->untrim_to(&tmpBlockIndexFull); + UniValue result(UniValue::VOBJ); result.pushKV("hash", blockindex->GetBlockHash().GetHex()); const CBlockIndex* pnext; - int confirmations = ComputeNextBlockAndDepth(tip, blockindex, pnext); + int confirmations = ComputeNextBlockAndDepth(tip, blockindex_, pnext); result.pushKV("confirmations", confirmations); result.pushKV("height", blockindex->nHeight); result.pushKV("version", blockindex->nVersion); @@ -271,7 +274,7 @@ UniValue blockheaderToJSON(const CBlockIndex* tip, const CBlockIndex* blockindex } } result.pushKV("nTx", (uint64_t)blockindex->nTx); - if (blockindex->pprev) + if (blockindex_->pprev) result.pushKV("previousblockhash", blockindex->pprev->GetBlockHash().GetHex()); if (pnext) result.pushKV("nextblockhash", pnext->GetBlockHash().GetHex()); @@ -966,7 +969,7 @@ static RPCHelpMan getblockheader() if (!request.params[1].isNull()) fVerbose = request.params[1].get_bool(); - const CBlockIndex* pblockindex; + CBlockIndex* pblockindex; const CBlockIndex* tip; { ChainstateManager& chainman = EnsureAnyChainman(request.context); @@ -982,13 +985,9 @@ static RPCHelpMan getblockheader() if (!fVerbose) { CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION); - if (pblockindex->trimmed()) { - CBlockHeader tmp; - ReadBlockHeaderFromDisk(tmp, pblockindex, Params().GetConsensus()); - ssBlock << tmp; - } else { - ssBlock << pblockindex->GetBlockHeader(); - } + CBlockIndex tmpBlockIndexFull; + const CBlockIndex* pblockindexfull = pblockindex->untrim_to(&tmpBlockIndexFull); + ssBlock << pblockindexfull->GetBlockHeader(); std::string strHex = HexStr(ssBlock); return strHex; } diff --git a/src/txdb.cpp b/src/txdb.cpp index 5a79407cdca..4b334b8cb37 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -18,6 +18,7 @@ // ELEMENTS #include // CheckProof +#include // Params() static constexpr uint8_t DB_COIN{'C'}; static constexpr uint8_t DB_COINS{'c'}; @@ -342,6 +343,51 @@ bool CBlockTreeDB::WalkBlockIndexGutsForMaxHeight(int* nHeight) { return true; } +const CBlockIndex *CBlockTreeDB::RegenerateFullIndex(const CBlockIndex *pindexTrimmed, CBlockIndex *pindexNew) const +{ + if(!pindexTrimmed->trimmed()) { + return pindexTrimmed; + } + CBlockHeader tmp; + bool BlockRead = false; + { + // At this point we can either be locked or unlocked depending on where we're being called + // but cs_main is a RecursiveMutex, so it doesn't matter + LOCK(cs_main); + // In unpruned nodes, same data could be read from blocks using ReadBlockFromDisk, but that turned out to + // be about 6x slower than reading from the index + std::pair key(DB_BLOCK_INDEX, pindexTrimmed->GetBlockHash()); + CDiskBlockIndex diskindex; + BlockRead = this->Read(key, diskindex); + tmp = diskindex.GetBlockHeader(); + } + assert(BlockRead); + // Clone the needed data from the original trimmed block + pindexNew->pprev = pindexTrimmed->pprev; + pindexNew->phashBlock = pindexTrimmed->phashBlock; + // Construct block index object + pindexNew->nHeight = pindexTrimmed->nHeight; + pindexNew->nFile = pindexTrimmed->nFile; + pindexNew->nDataPos = pindexTrimmed->nDataPos; + pindexNew->nUndoPos = pindexTrimmed->nUndoPos; + pindexNew->nVersion = pindexTrimmed->nVersion; + pindexNew->hashMerkleRoot = pindexTrimmed->hashMerkleRoot; + pindexNew->nTime = pindexTrimmed->nTime; + pindexNew->nBits = pindexTrimmed->nBits; + pindexNew->nNonce = pindexTrimmed->nNonce; + pindexNew->nStatus = pindexTrimmed->nStatus; + pindexNew->nTx = pindexTrimmed->nTx; + + pindexNew->proof = tmp.proof; + pindexNew->m_dynafed_params = tmp.m_dynafed_params; + pindexNew->m_signblock_witness = tmp.m_signblock_witness; + + if (pindexTrimmed->nHeight && pindexTrimmed->nHeight % 1000 == 0) { + assert(CheckProof(pindexNew->GetBlockHeader(), Params().GetConsensus())); + } + return pindexNew; +} + bool CBlockTreeDB::LoadBlockIndexGuts(const Consensus::Params& consensusParams, std::function insertBlockIndex, int trimBelowHeight) { std::unique_ptr pcursor(NewIterator()); diff --git a/src/txdb.h b/src/txdb.h index 9d2461d4732..b1e386b2293 100644 --- a/src/txdb.h +++ b/src/txdb.h @@ -87,6 +87,7 @@ class CBlockTreeDB : public CDBWrapper bool ReadFlag(const std::string &name, bool &fValue); bool LoadBlockIndexGuts(const Consensus::Params& consensusParams, std::function insertBlockIndex, int trimBelowHeight); // ELEMENTS: + const CBlockIndex* RegenerateFullIndex(const CBlockIndex *pindexTrimmed, CBlockIndex *pindexNew) const; bool WalkBlockIndexGutsForMaxHeight(int* nHeight); bool ReadPAKList(std::vector >& offline_list, std::vector >& online_list, bool& reject); bool WritePAKList(const std::vector >& offline_list, const std::vector >& online_list, bool reject); From bf5a50fa90434341b01b41ff0b7e222420e69758 Mon Sep 17 00:00:00 2001 From: Pablo Greco Date: Fri, 14 Jul 2023 20:59:44 -0700 Subject: [PATCH 6/9] Allow untrimming headers when needed --- src/chain.cpp | 13 +++++++++++++ src/dynafed.cpp | 4 ++++ src/pegins.cpp | 3 +++ src/validation.cpp | 15 +++++++++++++++ src/validation.h | 1 + 5 files changed, 36 insertions(+) diff --git a/src/chain.cpp b/src/chain.cpp index fad127889f1..4d694a06c99 100644 --- a/src/chain.cpp +++ b/src/chain.cpp @@ -50,6 +50,19 @@ CBlockLocator CChain::GetLocator(const CBlockIndex *pindex) const { return CBlockLocator(vHave); } +void CBlockIndex::untrim() { + if (!trimmed()) + return; + CBlockIndex tmp; + const CBlockIndex* pindexfull = untrim_to(&tmp); + assert(pindexfull!=this); + m_trimmed = false; + set_stored(); + proof = pindexfull->proof; + m_dynafed_params = pindexfull->m_dynafed_params; + m_signblock_witness = pindexfull->m_signblock_witness; +} + const CBlockIndex *CBlockIndex::untrim_to(CBlockIndex *pindexNew) const { return pblocktree->RegenerateFullIndex(this, pindexNew); diff --git a/src/dynafed.cpp b/src/dynafed.cpp index 8ef680e2962..29a20294837 100644 --- a/src/dynafed.cpp +++ b/src/dynafed.cpp @@ -1,6 +1,7 @@ #include #include +#include bool NextBlockIsParameterTransition(const CBlockIndex* pindexPrev, const Consensus::Params& consensus, DynaFedParamEntry& winning_entry) { @@ -15,6 +16,7 @@ bool NextBlockIsParameterTransition(const CBlockIndex* pindexPrev, const Consens for (int32_t height = next_height - 1; height >= (int32_t)(next_height - consensus.dynamic_epoch_length); --height) { const CBlockIndex* p_epoch_walk = pindexPrev->GetAncestor(height); assert(p_epoch_walk); + ForceUntrimHeader(p_epoch_walk); const DynaFedParamEntry& proposal = p_epoch_walk->dynafed_params().m_proposed; const uint256 proposal_root = proposal.CalculateRoot(); vote_tally[proposal_root]++; @@ -60,6 +62,7 @@ DynaFedParamEntry ComputeNextBlockFullCurrentParameters(const CBlockIndex* pinde // may be pre-dynafed params const CBlockIndex* p_epoch_start = pindexPrev->GetAncestor(epoch_start_height); assert(p_epoch_start); + ForceUntrimHeader(p_epoch_start); if (p_epoch_start->dynafed_params().IsNull()) { // We need to construct the "full" current parameters of pre-dynafed // consensus @@ -93,6 +96,7 @@ DynaFedParamEntry ComputeNextBlockCurrentParameters(const CBlockIndex* pindexPre { assert(pindexPrev); + ForceUntrimHeader(pindexPrev); DynaFedParamEntry entry = ComputeNextBlockFullCurrentParameters(pindexPrev, consensus); uint32_t next_height = pindexPrev->nHeight+1; diff --git a/src/pegins.cpp b/src/pegins.cpp index dc133d1fbce..da47025d438 100644 --- a/src/pegins.cpp +++ b/src/pegins.cpp @@ -26,6 +26,8 @@ // ELEMENTS // +#include + namespace { static secp256k1_context* secp256k1_ctx_validation; @@ -487,6 +489,7 @@ std::vector> GetValidFedpegScripts(const CBlockIndex break; } + ForceUntrimHeader(p_epoch_start); if (!p_epoch_start->dynafed_params().IsNull()) { fedpegscripts.push_back(std::make_pair(p_epoch_start->dynafed_params().m_current.m_fedpeg_program, p_epoch_start->dynafed_params().m_current.m_fedpegscript)); } else { diff --git a/src/validation.cpp b/src/validation.cpp index f1264ffaf51..e1804338012 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2348,6 +2348,7 @@ bool CChainState::FlushStateToDisk( vBlocks.reserve(setDirtyBlockIndex.size()); std::set setTrimmableBlockIndex(setDirtyBlockIndex); for (std::set::iterator it = setDirtyBlockIndex.begin(); it != setDirtyBlockIndex.end(); ) { + (*it)->untrim(); vBlocks.push_back(*it); setDirtyBlockIndex.erase(it++); } @@ -2460,6 +2461,18 @@ static void AppendWarning(bilingual_str& res, const bilingual_str& warn) res += warn; } +void ForceUntrimHeader(const CBlockIndex *pindex_) +{ + assert(pindex_); + if (!pindex_->trimmed()) { + return; + } + AssertLockHeld(cs_main); + CBlockIndex* pindex = const_cast(pindex_); + pindex->untrim(); + setDirtyBlockIndex.insert(pindex); +} + void CChainState::UpdateTip(const CBlockIndex* pindexNew) { // New best block @@ -2497,11 +2510,13 @@ void CChainState::UpdateTip(const CBlockIndex* pindexNew) this->CoinsTip().DynamicMemoryUsage() * (1.0 / (1<<20)), this->CoinsTip().GetCacheSize(), !warning_messages.empty() ? strprintf(" warning='%s'", warning_messages.original) : ""); + ForceUntrimHeader(pindexNew); // Do some logging if dynafed parameters changed. if (pindexNew->pprev && !pindexNew->dynafed_params().IsNull()) { int height = pindexNew->nHeight; uint256 hash = pindexNew->GetBlockHash(); uint256 root = pindexNew->dynafed_params().m_current.CalculateRoot(); + ForceUntrimHeader(pindexNew->pprev); if (pindexNew->pprev->dynafed_params().IsNull()) { LogPrintf("Dynafed activated in block %d:%s: %s\n", height, hash.GetHex(), root.GetHex()); } else if (root != pindexNew->pprev->dynafed_params().m_current.CalculateRoot()) { diff --git a/src/validation.h b/src/validation.h index f4ebc713d9d..81a80590c6e 100644 --- a/src/validation.h +++ b/src/validation.h @@ -1068,4 +1068,5 @@ bool LoadMempool(CTxMemPool& pool, CChainState& active_chainstate, FopenFn mocka */ const AssumeutxoData* ExpectedAssumeutxo(const int height, const CChainParams& params); +void ForceUntrimHeader(const CBlockIndex *pindex_); #endif // BITCOIN_VALIDATION_H From eb8735bbaacd17690a36982636d78b0ddc5def70 Mon Sep 17 00:00:00 2001 From: Pablo Greco Date: Thu, 29 Jun 2023 12:46:53 -0700 Subject: [PATCH 7/9] Reduce the number of untrimmed headers in memory, since they can be untrimmed on demand now --- src/init.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/init.cpp b/src/init.cpp index 1fc22643ffd..60a42c718f4 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -993,7 +993,7 @@ bool AppInitParameterInteraction(const ArgsManager& args) // back to current epoch start, and then an additional total_valid_epochs on top of that. // We add one epoch here for the current partial epoch, and then another one for good luck. - nMustKeepFullHeaders = (chainparams.GetConsensus().total_valid_epochs + 2) * epoch_length; + nMustKeepFullHeaders = chainparams.GetConsensus().total_valid_epochs * epoch_length; // This is the number of headers we can have in flight downloading at a time, beyond the // set of blocks we've already validated. Capping this is necessary to keep memory usage // bounded during IBD. From 7b20b412eb9857cd2112e2726d63bf177deedd0d Mon Sep 17 00:00:00 2001 From: Pablo Greco Date: Thu, 29 Jun 2023 12:26:22 -0700 Subject: [PATCH 8/9] Restore NODE_NETWORK functionality with trim_headers --- src/init.cpp | 9 ++------- src/net_processing.cpp | 11 ++++++----- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index 60a42c718f4..05872c4e551 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -987,7 +987,7 @@ bool AppInitParameterInteraction(const ArgsManager& args) } if (args.GetBoolArg("-trim_headers", false)) { - LogPrintf("Configured for header-trimming mode. This will reduce memory usage substantially, but we will be unable to serve as a full P2P peer, and certain header fields may be missing from JSON RPC output.\n"); + LogPrintf("Configured for header-trimming mode. This will reduce memory usage substantially, but will increase IO usage when the headers need to be temporarily untrimmed.\n"); fTrimHeaders = true; // This calculation is driven by GetValidFedpegScripts in pegins.cpp, which walks the chain // back to current epoch start, and then an additional total_valid_epochs on top of that. @@ -1711,7 +1711,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) // if pruning, unset the service bit and perform the initial blockstore prune // after any wallet rescanning has taken place. - if (fPruneMode || fTrimHeaders) { + if (fPruneMode) { LogPrintf("Unsetting NODE_NETWORK on prune mode\n"); nLocalServices = ServiceFlags(nLocalServices & ~NODE_NETWORK); if (!fReindex) { @@ -1723,11 +1723,6 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) } } - if (fTrimHeaders) { - LogPrintf("Unsetting NODE_NETWORK_LIMITED on header trim mode\n"); - nLocalServices = ServiceFlags(nLocalServices & ~NODE_NETWORK_LIMITED); - } - if (DeploymentEnabled(chainparams.GetConsensus(), Consensus::DEPLOYMENT_SEGWIT)) { // Advertise witness capabilities. // The option to not set NODE_WITNESS is only used in the tests and should be removed. diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 021e54d4b7e..d4b4bd94d44 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -3183,12 +3183,13 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, for (; pindex; pindex = m_chainman.ActiveChain().Next(pindex)) { if (pindex->trimmed()) { - // For simplicity, if any of the headers they're asking for are trimmed, - // just drop the request. - LogPrint(BCLog::NET, "%s: ignoring getheaders from peer=%i which would return at least one trimmed header\n", __func__, pfrom.GetId()); - return; + // Header is trimmed, reload from disk before sending + CBlockIndex tmpBlockIndexFull; + const CBlockIndex* pindexfull = pindex->untrim_to(&tmpBlockIndexFull); + vHeaders.push_back(pindexfull->GetBlockHeader()); + } else { + vHeaders.push_back(pindex->GetBlockHeader()); } - vHeaders.push_back(pindex->GetBlockHeader()); if (--nLimit <= 0 || pindex->GetBlockHash() == hashStop) break; } From 45e9fcb166dfc54dcc8b6685e1accf99d4c154d7 Mon Sep 17 00:00:00 2001 From: Pablo Greco Date: Fri, 14 Jul 2023 20:44:44 -0700 Subject: [PATCH 9/9] Extreme trimming on startup --- src/txdb.cpp | 35 ----------------------------------- src/txdb.h | 1 - src/validation.cpp | 13 ++++--------- 3 files changed, 4 insertions(+), 45 deletions(-) diff --git a/src/txdb.cpp b/src/txdb.cpp index 4b334b8cb37..146dd501798 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -308,41 +308,6 @@ bool CBlockTreeDB::WritePAKList(const std::vector >& return Write(std::make_pair(DB_PAK, uint256S("1")), offline_list) && Write(std::make_pair(DB_PAK, uint256S("2")), online_list) && Write(std::make_pair(DB_PAK, uint256S("3")), reject); } -/** Note that we only get a conservative (lower) estimate of the max header height here, - * obtained by sampling the first 10,000 headers on disk (which are in random order) and - * taking the highest block we see. */ -bool CBlockTreeDB::WalkBlockIndexGutsForMaxHeight(int* nHeight) { - std::unique_ptr pcursor(NewIterator()); - *nHeight = 0; - int i = 0; - pcursor->Seek(std::make_pair(DB_BLOCK_INDEX, uint256())); - while (pcursor->Valid()) { - if (ShutdownRequested()) return false; - std::pair key; - if (pcursor->GetKey(key) && key.first == DB_BLOCK_INDEX) { - i++; - if (i > 10'000) { - // Under the (accurate) assumption that the headers on disk are effectively in random height order, - // we have a good-enough (conservative) estimate of the max height very quickly, and don't need to - // waste more time. Shortcutting like this will cause us to keep a few extra headers, which is fine. - break; - } - CDiskBlockIndex diskindex; - if (pcursor->GetValue(diskindex)) { - if (diskindex.nHeight > *nHeight) { - *nHeight = diskindex.nHeight; - } - pcursor->Next(); - } else { - return error("%s: failed to read value", __func__); - } - } else { - break; - } - } - return true; -} - const CBlockIndex *CBlockTreeDB::RegenerateFullIndex(const CBlockIndex *pindexTrimmed, CBlockIndex *pindexNew) const { if(!pindexTrimmed->trimmed()) { diff --git a/src/txdb.h b/src/txdb.h index b1e386b2293..2ec256c2337 100644 --- a/src/txdb.h +++ b/src/txdb.h @@ -88,7 +88,6 @@ class CBlockTreeDB : public CDBWrapper bool LoadBlockIndexGuts(const Consensus::Params& consensusParams, std::function insertBlockIndex, int trimBelowHeight); // ELEMENTS: const CBlockIndex* RegenerateFullIndex(const CBlockIndex *pindexTrimmed, CBlockIndex *pindexNew) const; - bool WalkBlockIndexGutsForMaxHeight(int* nHeight); bool ReadPAKList(std::vector >& offline_list, std::vector >& online_list, bool& reject); bool WritePAKList(const std::vector >& offline_list, const std::vector >& online_list, bool reject); }; diff --git a/src/validation.cpp b/src/validation.cpp index e1804338012..749c0f98a5c 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -4161,15 +4161,7 @@ bool BlockManager::LoadBlockIndex( { int trim_below_height = 0; if (fTrimHeaders) { - int max_height = 0; - if (!blocktree.WalkBlockIndexGutsForMaxHeight(&max_height)) { - LogPrintf("LoadBlockIndex: Failed to WalkBlockIndexGutsForMaxHeight.\n"); - return false; - } - - int must_keep_headers = (consensus_params.total_valid_epochs + 2) * consensus_params.dynamic_epoch_length; - int extra_headers_buffer = consensus_params.dynamic_epoch_length * 2; // XXX arbitrary - trim_below_height = max_height - must_keep_headers - extra_headers_buffer; + trim_below_height = std::numeric_limits::max(); } if (!blocktree.LoadBlockIndexGuts(consensus_params, [this](const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return this->InsertBlockIndex(hash); }, trim_below_height)) return false; @@ -4218,6 +4210,9 @@ bool BlockManager::LoadBlockIndex( pindexBestHeader = pindex; } + if (pindexBestHeader) { + ForceUntrimHeader(pindexBestHeader); + } return true; }