Skip to content

Instantly share code, notes, and snippets.

@jamesob
Created May 27, 2021 20:50
Show Gist options
  • Save jamesob/264f4bbb402b3c03c8dda241f83e5ae5 to your computer and use it in GitHub Desktop.
Save jamesob/264f4bbb402b3c03c8dda241f83e5ae5 to your computer and use it in GitHub Desktop.
utxo-dumpload.61 -> .62
$ git range-diff master utxo-dumpload.61 utxo-dumpload.62
1: a5d6f365c < -: --------- validation: fix up LoadBlockIndex and RewindBlockIndex for multiple chainstates
2: 356ad5d86 < -: --------- validation: parameterize VerifyDB by chainstate
-: --------- > 1: 24df1d6af validation: change UpdateTip for multiple chainstates
-: --------- > 2: f1c2115cf validation: fix CheckBlockIndex for multiple chainstates
-: --------- > 3: a1b9a476f move-only: unittest: add test/util/chainstate.h
-: --------- > 4: c42dc9aa9 test: refactor: separate CreateBlock in TestChain100Setup
-: --------- > 5: e70c94715 refactor: remove assertions preventing multiple chainstates
-: --------- > 6: 0e06cd361 test: validation: add unittest for UpdateTip behavior
3: f7ee44878 ! 7: 83863ae0a validation: have LoadGenesisBlock work on all chainstates
@@ -2,18 +2,19 @@
validation: have LoadGenesisBlock work on all chainstates
- diff --git a/src/init.cpp b/src/init.cpp
- --- a/src/init.cpp
- +++ b/src/init.cpp
+ diff --git a/src/node/blockstorage.cpp b/src/node/blockstorage.cpp
+ --- a/src/node/blockstorage.cpp
+ +++ b/src/node/blockstorage.cpp
@@
- fReindex = false;
- LogPrintf("Reindexing finished\n");
- // To avoid ending up in a situation without genesis block, re-try initializing (no-op if reindexing worked):
-- LoadGenesisBlock(chainparams);
-+ WITH_LOCK(::cs_main, LoadGenesisBlock(chainparams));
- }
+ fReindex = false;
+ LogPrintf("Reindexing finished\n");
+ // To avoid ending up in a situation without genesis block, re-try initializing (no-op if reindexing worked):
+- chainman.ActiveChainstate().LoadGenesisBlock(chainparams);
++ WITH_LOCK(::cs_main,
++ chainman.ActiveChainstate().LoadGenesisBlock(chainparams));
+ }
- // -loadblock=
+ // -loadblock=
diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp
--- a/src/test/util/setup_common.cpp
@@ -22,11 +23,11 @@
assert(!::ChainstateActive().CanFlushToDisk());
::ChainstateActive().InitCoinsCache(1 << 23);
assert(::ChainstateActive().CanFlushToDisk());
-- if (!LoadGenesisBlock(chainparams)) {
+- if (!::ChainstateActive().LoadGenesisBlock(chainparams)) {
- throw std::runtime_error("LoadGenesisBlock failed.");
+ {
+ LOCK(::cs_main);
-+ if (!LoadGenesisBlock(chainparams)) {
++ if (!::ChainstateActive().LoadGenesisBlock(chainparams)) {
+ throw std::runtime_error("LoadGenesisBlock failed.");
+ }
}
@@ -70,9 +71,9 @@
+ if (g_chainman.BlockIndex().count(chainparams.GenesisBlock().GetHash()))
return true;
- try {
+ assert(std::addressof(::ChainActive()) == std::addressof(m_chain));
@@
- FlatFilePos blockPos = SaveBlockToDisk(block, 0, chainparams, nullptr);
+ FlatFilePos blockPos = SaveBlockToDisk(block, 0, m_chain, chainparams, nullptr);
if (blockPos.IsNull())
return error("%s: writing genesis block to disk failed", __func__);
- CBlockIndex *pindex = m_blockman.AddToBlockIndex(block);
@@ -84,46 +85,24 @@
} catch (const std::runtime_error& e) {
return error("%s: failed to write genesis block: %s", __func__, e.what());
}
-@@
-
- bool LoadGenesisBlock(const CChainParams& chainparams)
- {
-- return ::ChainstateActive().LoadGenesisBlock(chainparams);
-+ bool okay = true;
-+ for (CChainState* chainstate : g_chainman.GetAll()) {
-+ okay &= chainstate->LoadGenesisBlock(chainparams);
-+ }
-+ return okay;
- }
-
- void CChainState::LoadExternalBlockFile(const CChainParams& chainparams, FILE* fileIn, FlatFilePos* dbp)
diff --git a/src/validation.h b/src/validation.h
--- a/src/validation.h
+++ b/src/validation.h
@@
- /** Translation to a filesystem path */
- fs::path GetBlockPosFilename(const FlatFilePos &pos);
- /** Ensures we have a genesis block in the block tree, possibly writing one to disk. */
--bool LoadGenesisBlock(const CChainParams& chainparams);
-+bool LoadGenesisBlock(const CChainParams& chainparams) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
- /** Unload database information */
- void UnloadBlockIndex(CTxMemPool* mempool, ChainstateManager& chainman);
- /** Run instances of script checking worker threads */
-@@
- /** Replay blocks that aren't fully applied to the database. */
- bool ReplayBlocks(const CChainParams& params);
- bool RewindBlockIndex(const CChainParams& params) LOCKS_EXCLUDED(cs_main);
+ /** Whether the chain state needs to be redownloaded due to lack of witness data */
+ [[nodiscard]] bool NeedsRedownload(const CChainParams& params) const EXCLUSIVE_LOCKS_REQUIRED(cs_main);
+ /** Ensures we have a genesis block in the block tree, possibly writing one to disk. */
- bool LoadGenesisBlock(const CChainParams& chainparams);
+ bool LoadGenesisBlock(const CChainParams& chainparams) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
void PruneBlockIndexCandidates();
@@
- void EraseBlockData(CBlockIndex* index) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
+ bool LoadBlockIndexDB(const CChainParams& chainparams) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
friend ChainstateManager;
+ friend bool LoadGenesisBlock(const CChainParams&);
};
- /** Mark a block as precious and reorganize.
+ /**
4: bcf73dec4 < -: --------- validation: have UpdateTip only act on ActiveChainstate
5: 169ca6b99 ! 8: 060d862e1 wallet: avoid rescans if under the snapshot
@@ -6,6 +6,8 @@
validation chainstates. Also refuse to load a wallet if it requires a rescan
lower than the height of an unvalidated snapshot we're running.
+ TODO: more method definitions like GetSnapshotNChainTx() to a more appropriate commit
+
diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h
--- a/src/interfaces/chain.h
+++ b/src/interfaces/chain.h
@@ -24,18 +26,15 @@
--- a/src/node/interfaces.cpp
+++ b/src/node/interfaces.cpp
@@
- LOCK(cs_main);
- return CheckFinalTx(tx);
+ assert(std::addressof(::ChainActive()) == std::addressof(chainman().ActiveChain()));
+ return CheckFinalTx(chainman().ActiveChain().Tip(), tx);
}
+ int getLowestBlockDataHeight() override
+ {
+ LOCK(cs_main);
-+ if (g_chainman.IsSnapshotActive() && !g_chainman.IsSnapshotValidated()) {
-+ return g_chainman.SnapshotHeight();
-+ }
-+ return 0;
++ return g_chainman.GetSnapshotNChainTx().value_or(0);
+ }
- Optional<int> findLocatorFork(const CBlockLocator& locator) override
+ std::optional<int> findLocatorFork(const CBlockLocator& locator) override
{
LOCK(cs_main);
@@ -43,22 +42,52 @@
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@
- vSortedByHeight.push_back(std::make_pair(pindex->nHeight, pindex));
+ }
}
- sort(vSortedByHeight.begin(), vSortedByHeight.end());
-+ const uint256 snapshot_blockhash =
-+ g_chainman.SnapshotBlockhash().value_or(uint256());
- const bool using_unvalidated_snapshot =
- g_chainman.IsSnapshotActive() && !g_chainman.IsSnapshotValidated();
- bool pindex_assumed_valid = false;
+ }
++
++std::optional<unsigned int> ChainstateManager::GetSnapshotNChainTx()
++{
++ auto height = this->GetSnapshotHeight();
++ if (!height) {
++ return std::nullopt;
++ }
++
++ auto au_data = ExpectedAssumeutxo(*height, ::Params());
++ if (!au_data) {
++ return std::nullopt;
++ }
++
++ return au_data->nChainTx;
++}
++
++std::optional<CBlockIndex*> ChainstateManager::GetSnapshotBaseBlock()
++ {
++ auto blockhash_op = SnapshotBlockhash();
++ if (!blockhash_op) {
++ return std::nullopt;
++ }
++ return m_blockman.LookupBlockIndex(*blockhash_op);
++ }
++
++//! @returns height at which the active UTXO snapshot was taken.
++std::optional<int> ChainstateManager::GetSnapshotHeight()
++{
++ std::optional<CBlockIndex*> base = this->GetSnapshotBaseBlock();
++ if (!base) {
++ return std::nullopt;
++ }
++ return (*base)->nHeight;
++}
diff --git a/src/validation.h b/src/validation.h
--- a/src/validation.h
+++ b/src/validation.h
@@
+ //! Check to see if caches are out of balance and if so, call
//! ResizeCoinsCaches() as needed.
void MaybeRebalanceCaches() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
-
++
+ //! Returns true if any chainstate in use is in initial block download.
+ bool IsAnyChainInIBD() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
+
@@ -71,15 +100,24 @@
+ //! the background validation chainstate is marked accordingly.
+ void CheckForUncleanShutdown() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
+
- //! Return the cached nChainTx value for the snapshot (per the chainparams assumeutxo data),
- //! if one exists
- std::optional<unsigned int> GetSnapshotNChainTx() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
++ //! Return the cached nChainTx value for the snapshot (per the chainparams assumeutxo data),
++ //! if one exists
++ std::optional<unsigned int> GetSnapshotNChainTx() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
++
++ //! @returns height at which the active UTXO snapshot was taken, if applicable.
++ std::optional<CBlockIndex*> GetSnapshotBaseBlock() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
++
++ //! @returns height at which the active UTXO snapshot was taken, if a snapshot is being used.
++ std::optional<int> GetSnapshotHeight() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
+ };
+
+ /** DEPRECATED! Please use node.chainman instead. May only be used in validation.cpp internally */
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@
- return nullptr;
+ return false;
}
}
+ // Otherwise refuse to rescan if we're operating on a snapshot and the
@@ -92,5 +130,5 @@
+ return nullptr;
+ }
- chain.initMessage(_("Rescanning...").translated);
+ chain.initMessage(_("Rescanning…").translated);
walletInstance->WalletLogPrintf("Rescanning last %i blocks (from block %i)...\n", *tip_height - rescan_height, rescan_height);
6: 1cf452104 ! 9: 4e456ab0f rpc: add loadtxoutset
@@ -11,7 +11,10 @@
#include <core_io.h>
+#include <fs.h>
#include <hash.h>
- #include <index/blockfilterindex.h>
+-#include <index/blockfilterindex.h>
+ #include <index/coinstatsindex.h>
++#include <index/blockfilterindex.h>
+ #include <node/blockstorage.h>
+#include <logging/timer.h>
#include <node/coinstats.h>
#include <node/context.h>
@@ -19,7 +22,7 @@
@@
FILE* file{fsbridge::fopen(temppath, "wb")};
CAutoFile afile{file, SER_DISK, CLIENT_VERSION};
- NodeContext& node = EnsureNodeContext(request.context);
+ NodeContext& node = EnsureAnyNodeContext(request.context);
- UniValue result = CreateUTXOSnapshot(node, node.chainman->ActiveChainstate(), afile);
+ UniValue result = CreateUTXOSnapshot(
+ node, node.chainman->ActiveChainstate(), afile, path, temppath);
@@ -39,7 +42,7 @@
+ const fs::path temppath)
{
std::unique_ptr<CCoinsViewCursor> pcursor;
- CCoinsStats stats;
+ CCoinsStats stats{CoinStatsHashType::NONE};
@@
CHECK_NONFATAL(tip);
}
@@ -82,7 +85,7 @@
+{
+ fs::path path = request.params[0].get_str();
+ if (path.is_relative()) {
-+ path = fs::absolute(path, GetDataDir());
++ path = fs::absolute(path, gArgs.GetDataDirNet());
+ }
+ FILE* file{fsbridge::fopen(path, "rb")};
+ CAutoFile afile{file, SER_DISK, CLIENT_VERSION};
@@ -154,9 +157,9 @@
--- a/src/rpc/blockchain.h
+++ b/src/rpc/blockchain.h
@@
- #define BITCOIN_RPC_BLOCKCHAIN_H
#include <amount.h>
+ #include <core_io.h>
+#include <fs.h>
#include <streams.h>
#include <sync.h>
@@ -179,12 +182,40 @@
--- a/src/test/validation_chainstatemanager_tests.cpp
+++ b/src/test/validation_chainstatemanager_tests.cpp
@@
- FILE* outfile{fsbridge::fopen(snapshot_path, "wb")};
- CAutoFile auto_outfile{outfile, SER_DISK, CLIENT_VERSION};
+ BOOST_CHECK_CLOSE(c2.m_coinsdb_cache_size_bytes, max_cache * 0.95, 1);
+ }
-- UniValue result = CreateUTXOSnapshot(node, node.chainman->ActiveChainstate(), auto_outfile);
++auto NoMalleation = [](CAutoFile& file, SnapshotMetadata& meta){};
++
++template<typename F = decltype(NoMalleation)>
++static bool
++CreateAndActivateUTXOSnapshot(NodeContext& node, const fs::path root, F malleation = NoMalleation)
++{
++ // Write out a snapshot to the test's tempdir.
++ //
++ int height;
++ WITH_LOCK(::cs_main, height = node.chainman->ActiveHeight());
++ fs::path snapshot_path = root / tfm::format("test_snapshot.%d.dat", height);
++ FILE* outfile{fsbridge::fopen(snapshot_path, "wb")};
++ CAutoFile auto_outfile{outfile, SER_DISK, CLIENT_VERSION};
++
+ UniValue result = CreateUTXOSnapshot(
+ node, node.chainman->ActiveChainstate(), auto_outfile, snapshot_path, snapshot_path);
- BOOST_TEST_MESSAGE(
- "Wrote UTXO snapshot to " << snapshot_path.make_preferred().string() << ": " << result.write());
-
++ BOOST_TEST_MESSAGE(
++ "Wrote UTXO snapshot to " << snapshot_path.make_preferred().string() << ": " << result.write());
++
++ // Read the written snapshot in and then activate it.
++ //
++ FILE* infile{fsbridge::fopen(snapshot_path, "rb")};
++ CAutoFile auto_infile{infile, SER_DISK, CLIENT_VERSION};
++ SnapshotMetadata metadata;
++ auto_infile >> metadata;
++
++ malleation(auto_infile, metadata);
++
++ return node.chainman->ActivateSnapshot(auto_infile, metadata, /*in_memory*/ true);
++}
++
+ //! Test basic snapshot activation.
+ BOOST_FIXTURE_TEST_CASE(chainstatemanager_activate_snapshot, TestChain100Setup)
+ {
7: 128d4fe42 ! 10: 57912ecdf validation: only send ValidationInterface callbacks for active chain
@@ -15,7 +15,7 @@
GetMainSignals().ChainStateFlushed(m_chain.GetLocator());
}
@@
- UpdateTip(m_mempool, pindexDelete->pprev, chainparams, CoinsTip());
+ UpdateTip(m_mempool, pindexDelete->pprev, chainparams, *this);
// Let wallets know transactions went from 1-confirmed to
// 0-confirmed or conflicted:
- GetMainSignals().BlockDisconnected(pblock, pindexDelete);
8: f35c8abf9 ! 11: 53fea4a38 validation: add BackgroundBlockConnected and use it for indexing
@@ -51,7 +51,7 @@
+
void ChainStateFlushed(const CBlockLocator& locator) override;
- /// Initialize internal state from the database and block index.
+ const CBlockIndex* CurrentIndex() { return m_best_block_index.load(); };
diff --git a/src/validation.cpp b/src/validation.cpp
--- a/src/validation.cpp
9: c9500b54d ! 12: 845e20786 validation: introduce ChainstateManager::GetChainstateForNewBlock
@@ -52,7 +52,7 @@
@@
}
- auto snapshot_chainstate = WITH_LOCK(::cs_main, return MakeUnique<CChainState>(
+ auto snapshot_chainstate = WITH_LOCK(::cs_main, return std::make_unique<CChainState>(
- this->ActiveChainstate().m_mempool, m_blockman, base_blockhash));
+ this->ActiveChainstate().m_mempool, m_blockman, base_blockhash));
10: 0456819dc ! 13: 2a001faa0 net_processing: work with multiple chainstates
@@ -25,11 +25,23 @@
std::map<uint256, std::pair<NodeId, std::list<QueuedBlock>::iterator> > mapBlocksInFlight GUARDED_BY(cs_main);
@@
- const CBlockIndex *pindexBestKnownBlock;
+ void ProcessBlockAvailability(NodeId nodeid) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
+ /** Update tracking information about which blocks a peer is assumed to have. */
+ void UpdateBlockAvailability(NodeId nodeid, const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
++
++ /**
++ * Allow the direct fetch of block data if our tip is recent within the past
++ * 200 minutes.
++ */
+ bool CanDirectFetch() EXCLUSIVE_LOCKS_REQUIRED(cs_main);
+
+ /**
+@@
+ const CBlockIndex* pindexBestKnownBlock{nullptr};
//! The hash of the last unknown block this peer has announced.
- uint256 hashLastUnknownBlock;
+ uint256 hashLastUnknownBlock{};
- //! The last full block we both have.
-- const CBlockIndex *pindexLastCommonBlock;
+- const CBlockIndex* pindexLastCommonBlock{nullptr};
+
+ //! The last full block we both have (per chainstate).
+ //!
@@ -40,42 +52,22 @@
+ //! any CChainState objects which were in use at any point (e.g. a background
+ //! validation chainstate which has completed) until the end of
+ //! init.cpp:Shutdown(), else we'll have bad pointers here.
-+ //
+ std::map<const CChainState* const, const CBlockIndex*> chainstate_to_last_common_block = {};
+
//! The best header we have sent our peer.
- const CBlockIndex *pindexBestHeaderSent;
+ const CBlockIndex* pindexBestHeaderSent{nullptr};
//! Length of current-streak of unconnecting headers announcements
@@
- {
- pindexBestKnownBlock = nullptr;
- hashLastUnknownBlock.SetNull();
-- pindexLastCommonBlock = nullptr;
- pindexBestHeaderSent = nullptr;
- nUnconnectingHeaders = 0;
- fSyncStarted = false;
-@@
- return m_last_tip_update < GetTime() - consensusParams.nPowTargetSpacing * 3 && mapBlocksInFlight.empty();
- }
-
-+/**
-+ * Allow the direct fetch of block data if our tip is recent within the past
-+ * 200 minutes.
-+ */
- static bool CanDirectFetch(const Consensus::Params &consensusParams) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
- {
- return ::ChainActive().Tip()->GetBlockTime() > GetAdjustedTime() - consensusParams.nPowTargetSpacing * 20;
-@@
- return false;
+ }
}
--void PeerManagerImpl::FindNextBlocksToDownload(NodeId nodeid, unsigned int count, std::vector<const CBlockIndex*>& vBlocks, NodeId& nodeStaller) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
+-void PeerManagerImpl::FindNextBlocksToDownload(NodeId nodeid, unsigned int count, std::vector<const CBlockIndex*>& vBlocks, NodeId& nodeStaller)
+void PeerManagerImpl::FindNextBlocksToDownload(
+ const CChainState* const chainstate,
+ NodeId nodeid,
+ unsigned int count,
+ std::vector<const CBlockIndex*>& vBlocks,
-+ NodeId& nodeStaller) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
++ NodeId& nodeStaller)
{
if (count == 0)
return;
@@ -83,7 +75,7 @@
// Make sure pindexBestKnownBlock is up to date, we'll need it.
ProcessBlockAvailability(nodeid);
-- if (state->pindexBestKnownBlock == nullptr || state->pindexBestKnownBlock->nChainWork < ::ChainActive().Tip()->nChainWork || state->pindexBestKnownBlock->nChainWork < nMinimumChainWork) {
+- if (state->pindexBestKnownBlock == nullptr || state->pindexBestKnownBlock->nChainWork < m_chainman.ActiveChain().Tip()->nChainWork || state->pindexBestKnownBlock->nChainWork < nMinimumChainWork) {
+ const CChain& our_chain = chainstate->m_chain;
+ const CBlockIndex* our_tip = our_chain.Tip();
+
@@ -98,11 +90,10 @@
+ if (!state->chainstate_to_last_common_block.count(chainstate)) {
// Bootstrap quickly by guessing a parent of our best tip is the forking point.
// Guessing wrong in either direction is not a problem.
-- state->pindexLastCommonBlock = ::ChainActive()[std::min(state->pindexBestKnownBlock->nHeight, ::ChainActive().Height())];
+- state->pindexLastCommonBlock = m_chainman.ActiveChain()[std::min(state->pindexBestKnownBlock->nHeight, m_chainman.ActiveChain().Height())];
+ //
+ // Namespace this by chainstate so that we can simultaneously sync two
+ // separate chainstates at different heights.
-+ //
+ state->chainstate_to_last_common_block[chainstate] = our_chain[
+ std::min(state->pindexBestKnownBlock->nHeight, our_chain.Height())];
}
@@ -147,7 +138,7 @@
// We wouldn't download this block or its descendants from this peer.
return;
}
-- if (pindex->nStatus & BLOCK_HAVE_DATA || ::ChainActive().Contains(pindex)) {
+- if (pindex->nStatus & BLOCK_HAVE_DATA || m_chainman.ActiveChain().Contains(pindex)) {
+ if (pindex->nStatus & BLOCK_HAVE_DATA || our_chain.Contains(pindex)) {
if (pindex->HaveTxsDownloaded())
- state->pindexLastCommonBlock = pindex;
@@ -175,19 +166,19 @@
if (queue.pindex)
stats.vHeightInFlight.push_back(queue.pindex->nHeight);
@@
- LogPrint(BCLog::MEMPOOL, "Erased %d orphan tx included or conflicted by block\n", nErased);
- }
+ void PeerManagerImpl::BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindex)
+ {
+ m_orphanage.EraseForBlock(*pblock);
++ // TODO jamesob: do we need to namespace this per chainstate? Will we accidentally
++ // evict outbound peers we're IBDing from for the background validation chain?
+ m_last_tip_update = GetTime();
-+ // TODO jamesob: do we need to namespace this per chainstate? Will we accidentally
-+ // evict outbound peers we're IBDing from for the background validation chain?
- m_last_tip_update = GetTime();
- }
{
@@
/**
* Update our best height and announce any block hashes which weren't previously
-- * in ::ChainActive() to our peers.
+- * in m_chainman.ActiveChain() to our peers.
+ * in the active chain to our peers.
*/
void PeerManagerImpl::UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload)
@@ -200,22 +191,11 @@
uint256 hashLastBlock;
for (const CBlockHeader& header : headers) {
if (!hashLastBlock.IsNull() && header.hashPrevBlock != hashLastBlock) {
-@@
- // disk-space attacks), but this should be safe due to the
- // protections in the compact block handler -- see related comment
- // in compact block optimistic reconstruction handling.
-+ //
-+ // It's okay to restrict this to the active chainstate because we'll
-+ // never receive compact blocks when building the snapshot
-+ // verification chain.
- m_chainman.ProcessNewBlock(m_chainparams, pblock, /*fForceProcessing=*/true, &fNewBlock);
- if (fNewBlock) {
- pfrom.nLastBlockTime = GetTime();
@@
// Message: getdata (blocks)
//
std::vector<CInv> vGetData;
-- if (!pto->fClient && ((fFetch && !pto->m_limited_node) || !::ChainstateActive().IsInitialBlockDownload()) && state.nBlocksInFlight < MAX_BLOCKS_IN_TRANSIT_PER_PEER) {
+- if (!pto->fClient && ((fFetch && !pto->m_limited_node) || !m_chainman.ActiveChainstate().IsInitialBlockDownload()) && state.nBlocksInFlight < MAX_BLOCKS_IN_TRANSIT_PER_PEER) {
- std::vector<const CBlockIndex*> vToDownload;
- NodeId staller = -1;
- FindNextBlocksToDownload(pto->GetId(), MAX_BLOCKS_IN_TRANSIT_PER_PEER - state.nBlocksInFlight, vToDownload, staller);
@@ -227,8 +207,8 @@
- pindex->nHeight, pto->GetId());
- }
- if (state.nBlocksInFlight == 0 && staller != -1) {
-- if (State(staller)->nStallingSince == 0) {
-- State(staller)->nStallingSince = count_microseconds(current_time);
+- if (State(staller)->m_stalling_since == 0us) {
+- State(staller)->m_stalling_since = current_time;
- LogPrint(BCLog::NET, "Stall started peer=%d\n", staller);
+
+ // The first chainstate in line for processing will likely exhaust this
@@ -257,8 +237,8 @@
+
+ requests_available -= vToDownload.size();
+ if (state.nBlocksInFlight == 0 && staller != -1) {
-+ if (State(staller)->nStallingSince == 0) {
-+ State(staller)->nStallingSince = count_microseconds(current_time);
++ if (State(staller)->m_stalling_since == 0us) {
++ State(staller)->m_stalling_since = current_time;
+ LogPrint(BCLog::NET, "Stall started peer=%d\n", staller);
+ }
}
@@ -289,9 +269,9 @@
+ return out;
+}
+
- CChainState& ChainstateManager::InitializeChainstate(CTxMemPool& mempool, const uint256& snapshot_blockhash)
+ CChainState& ChainstateManager::InitializeChainstate(CTxMemPool& mempool, const std::optional<uint256>& snapshot_blockhash)
{
- bool is_snapshot = !snapshot_blockhash.IsNull();
+ bool is_snapshot = snapshot_blockhash.has_value();
diff --git a/src/validation.h b/src/validation.h
--- a/src/validation.h
11: 4dce8a304 ! 14: cb5f5b7d9 p2p: don't advertise until we finish all IBDs
@@ -24,7 +24,7 @@
//
// We skip this for block-relay-only peers to avoid potentially leaking
// information about our block-relay-only connections via address relay.
-- if (fListen && !::ChainstateActive().IsInitialBlockDownload())
+- if (fListen && !m_chainman.ActiveChainstate().IsInitialBlockDownload())
+ if (fListen && !is_ibd)
{
CAddress addr = GetLocalAddress(&pfrom.addr, pfrom.GetLocalServices());
@@ -33,27 +33,31 @@
}
LOCK(cs_main);
-- if (::ChainstateActive().IsInitialBlockDownload() && !pfrom.HasPermission(PF_DOWNLOAD)) {
-+ if (g_chainman.IsAnyChainInIBD() && !pfrom.HasPermission(PF_DOWNLOAD)) {
+- if (m_chainman.ActiveChainstate().IsInitialBlockDownload() && !pfrom.HasPermission(NetPermissionFlags::Download)) {
++ if (g_chainman.IsAnyChainInIBD() && !pfrom.HasPermission(NetPermissionFlags::Download)) {
LogPrint(BCLog::NET, "Ignoring getheaders from peer=%d because node is in initial block download\n", pfrom.GetId());
return;
}
@@
- // Address refresh broadcast
- auto current_time = GetTime<std::chrono::microseconds>();
+ if (!RelayAddrsWithPeer(peer)) return;
-- if (pto->RelayAddrsWithConn() && !::ChainstateActive().IsInitialBlockDownload() && pto->m_next_local_addr_send < current_time) {
-+ if (pto->RelayAddrsWithConn() && !g_chainman.IsAnyChainInIBD() && pto->m_next_local_addr_send < current_time) {
- // If we've sent before, clear the bloom filter for the peer, so that our
- // self-announcement will actually go out.
- // This might be unnecessary if the bloom filter has already rolled
+ LOCK(peer.m_addr_send_times_mutex);
++ bool is_ibd = WITH_LOCK(::cs_main, return m_chainman.IsAnyChainInIBD());
++
+ // Periodically advertise our local address to the peer.
+- if (fListen && !m_chainman.ActiveChainstate().IsInitialBlockDownload() &&
+- peer.m_next_local_addr_send < current_time) {
++ if (fListen && !is_ibd && peer.m_next_local_addr_send < current_time) {
+ // If we've sent before, clear the bloom filter for the peer, so that our
+ // self-announcement will actually go out.
+ // This might be unnecessary if the bloom filter has already rolled
diff --git a/src/validation.cpp b/src/validation.cpp
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@
-
- return au_data->nChainTx;
+ }
+ return (*base)->nHeight;
}
+
+bool ChainstateManager::IsAnyChainInIBD()
12: 0e299eb49 ! 15: 7e59aaad1 init: fix up for multiple chainstates
@@ -1,9 +1,6 @@
Author: James O'Beirne <james.obeirne@gmail.com>
- init: fix up for multiple chainstates
-
- TODO: maybe separate out snapshot chainstate init detection into a separate
- commit?
+ add utxo snapshot detection and add to init
diff --git a/src/init.cpp b/src/init.cpp
--- a/src/init.cpp
@@ -25,20 +22,11 @@
@@
UnregisterAllValidationInterfaces();
GetMainSignals().UnregisterBackgroundSignalScheduler();
- globalVerifyHandle.reset();
+ init::UnsetGlobals();
+ WITH_LOCK(::cs_main, g_chainman.Reset());
- ECC_Stop();
node.mempool.reset();
node.fee_estimator.reset();
-@@
- // the relevant pointers before the ABC call.
- for (CChainState* chainstate : WITH_LOCK(::cs_main, return chainman.GetAll())) {
- BlockValidationState state;
-- if (!chainstate->ActivateBestChain(state, chainparams, nullptr)) {
-+ if (!chainstate->ActivateBestChain(state, chainparams)) {
- LogPrintf("Failed to connect best block (%s)\n", state.ToString());
- StartShutdown();
- return;
+ node.chainman = nullptr;
@@
const int64_t load_block_index_start_time = GetTimeMillis();
try {
@@ -117,8 +105,8 @@
#include <flatfile.h>
+#include <fs.h>
#include <hash.h>
+ #include <index/blockfilterindex.h>
#include <index/txindex.h>
- #include <logging.h>
@@
}
return false;
@@ -128,7 +116,7 @@
+{
+ constexpr int SNAPSHOT_NAME_LEN = 75; // "chainstate_" + 64 hex characters for blockhash.
+
-+ for (fs::directory_iterator it(GetDataDir()); it != fs::directory_iterator(); it++) {
++ for (fs::directory_iterator it(gArgs.GetDataDirNet()); it != fs::directory_iterator(); it++) {
+ if (fs::is_directory(*it) &&
+ !fs::is_empty(*it) &&
+ it->path().filename().string().length() == SNAPSHOT_NAME_LEN &&
13: 3b9385fcf ! 16: 9286ce21d validation: add ChainstateManager logic for completing UTXO snapshot validation
@@ -55,14 +55,6 @@
}
}
::pblocktree.reset();
-@@
- GetMainSignals().UnregisterBackgroundSignalScheduler();
- globalVerifyHandle.reset();
- WITH_LOCK(::cs_main, g_chainman.Reset());
-+
- ECC_Stop();
- node.mempool.reset();
- node.fee_estimator.reset();
@@
break; // out of the chainstate activation do-while
}
@@ -105,11 +97,11 @@
+ // time we compare the UTXO set hash to the base of our active snapshot.
+ //
+ if (g_chainman.IsBackgroundIBD(this)) {
-+ if (pindexNew->nHeight > g_chainman.SnapshotHeight()) {
++ if (pindexNew->nHeight > g_chainman.GetSnapshotHeight()) {
+ // TODO jamesob: better handling?
+ LogPrintf("[snapshot] something is wrong! validation chain " /* Continued */
+ "should not have continued past the snapshot origin\n");
-+ } else if (pindexNew->nHeight == g_chainman.SnapshotHeight()) {
++ } else if (pindexNew->nHeight == g_chainman.GetSnapshotHeight()) {
+ // This may set `m_stop_use`.
+ g_chainman.CompleteSnapshotValidation(this);
+ }
@@ -151,15 +143,6 @@
// Write changes periodically to disk, after relay.
if (!FlushStateToDisk(chainparams, state, FlushStateMode::PERIODIC)) {
return false;
-@@
- return std::min<double>(pindex->nChainTx / fTxTotal, 1.0);
- }
-
--Optional<uint256> ChainstateManager::SnapshotBlockhash() const {
-+std::optional<uint256> ChainstateManager::SnapshotBlockhash() const {
- LOCK(::cs_main);
- if (m_active_chainstate != nullptr &&
- !m_active_chainstate->m_from_snapshot_blockhash.IsNull()) {
@@
return true;
}
@@ -174,10 +157,10 @@
+ CCoinsViewDB& ibd_coins_db = validation_chainstate->CoinsDB();
+ validation_chainstate->ForceFlushStateToDisk();
+
-+ CCoinsStats ibd_stats;
++ CCoinsStats ibd_stats{CoinStatsHashType::HASH_SERIALIZED};
+ auto breakpoint_fnc = [] { /* TODO insert breakpoint here? */ };
+
-+ if (!GetUTXOStats(&ibd_coins_db, ibd_stats, CoinStatsHashType::HASH_SERIALIZED, breakpoint_fnc)) {
++ if (!GetUTXOStats(&ibd_coins_db, WITH_LOCK(::cs_main, return std::ref(m_blockman)), ibd_stats, breakpoint_fnc)) {
+ LogPrintf("[snapshot] failed to generate stats for validation coins db\n");
+ return false;
+ }
@@ -208,15 +191,15 @@
+ // TODO: For belt-and-suspenders, we should cache an obfuscated version of the UTXO set
+ // hash for the snapshot when it's loaded in its chainstate's leveldb. We should then
+ // reference that here for an additional check.
-+ if (ibd_stats.hashSerialized != au_data.hash_serialized) {
++ if (AssumeutxoHash{ibd_stats.hashSerialized} != au_data.hash_serialized) {
+ LogPrintf("[snapshot] hash mismatch: actual=%s, expected=%s\n",
+ ibd_stats.hashSerialized.ToString(),
+ expected_contents_hash.ToString());
+ snapshot_invalid = true;
+ }
-+ if (validation_chainstate->m_chain.Height() != SnapshotHeight()) {
++ if (validation_chainstate->m_chain.Height() != GetSnapshotHeight()) {
+ LogPrintf("[snapshot] height mismatch: actual=%d expected=%d\n",
-+ validation_chainstate->m_chain.Height(), SnapshotHeight());
++ validation_chainstate->m_chain.Height(), GetSnapshotHeight().value_or(-1));
+ snapshot_invalid = true;
+ }
+
@@ -313,28 +296,13 @@
+ if (!(m_snapshot_chainstate && m_ibd_chainstate)) {
+ return;
+ }
-+ if (m_ibd_chainstate->m_chain.Height() != this->SnapshotHeight()) {
++ if (m_ibd_chainstate->m_chain.Height() != this->GetSnapshotHeight()) {
+ return;
+ }
+ LogPrintf(
+ "[snapshot] unclean shutdown detected - background validation chain needs to " /* Continued */
+ "be checked against the loaded snapshot and cleaned up.\n");
+ this->CompleteSnapshotValidation(m_ibd_chainstate.get());
-+}
-+
-+std::optional<unsigned int> ChainstateManager::GetSnapshotNChainTx()
-+{
-+ auto height = this->SnapshotHeight();
-+ if (height == -1) {
-+ return std::nullopt;
-+ }
-+
-+ auto au_data = ExpectedAssumeutxo(height, ::Params());
-+ if (!au_data) {
-+ return std::nullopt;
-+ }
-+
-+ return au_data->nChainTx;
+}
diff --git a/src/validation.h b/src/validation.h
@@ -375,8 +343,8 @@
+ void ValidatedSnapshotShutdownCleanup(fs::path new_chainstate, fs::path old_chainstate);
+
public:
+ std::thread m_load_block;
//! A single BlockManager instance is shared across each constructed
- //! chainstate to avoid duplicating block metadata.
@@
[[nodiscard]] bool ActivateSnapshot(
CAutoFile& coins_file, const SnapshotMetadata& metadata, bool in_memory);
@@ -395,31 +363,6 @@
//!
//! Because the use of UTXO snapshots requires the simultaneous maintenance
@@
-
- bool IsSnapshotActive() const;
-
-- Optional<uint256> SnapshotBlockhash() const;
-+ std::optional<uint256> SnapshotBlockhash() const;
-+
-+ CBlockIndex* SnapshotBaseBlock() EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
-+ {
-+ auto blockhash_op = SnapshotBlockhash();
-+ if (!blockhash_op) {
-+ return nullptr;
-+ }
-+ return m_blockman.LookupBlockIndex(*blockhash_op);
-+ }
-+
-+ //! @returns height at which the active UTXO snapshot was taken.
-+ int SnapshotHeight() EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
-+ {
-+ CBlockIndex* base = SnapshotBaseBlock();
-+ return base ? base->nHeight : -1;
-+ }
-
- CBlockIndex* SnapshotBaseBlock() EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
- {
-@@
void Unload() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
//! Clear (deconstruct) chainstate data.
14: 330d6fb61 = 17: adb04416f doc: add note about snapshots and index building
15: a3695d8e2 ! 18: ff1fdff42 validation: pruning for multiple chainstates
@@ -9,8 +9,8 @@
} else {
LOG_TIME_MILLIS_WITH_CATEGORY("find files to prune", BCLog::BENCH);
-- m_blockman.FindFilesToPrune(setFilesToPrune, chainparams.PruneAfterHeight(), m_chain.Height(), IsInitialBlockDownload());
-+ m_blockman.FindFilesToPrune(setFilesToPrune, chainparams.PruneAfterHeight());
+- m_blockman.FindFilesToPrune(setFilesToPrune, chainparams.PruneAfterHeight(), m_chain.Height(), last_prune, IsInitialBlockDownload());
++ m_blockman.FindFilesToPrune(setFilesToPrune, chainparams.PruneAfterHeight(), last_prune);
fCheckForPruning = false;
}
if (!setFilesToPrune.empty()) {
@@ -42,10 +42,7 @@
+ nManualPruneHeight,
+ // TODO jamesob: is this an off-by-one?
+ chainstate->m_chain.Height() - 1);
- }
-- PruneOneBlockFile(fileNumber);
-- setFilesToPrune.insert(fileNumber);
-- count++;
++ }
+
+ // last block to prune is the lesser of (user-specified height, MIN_BLOCKS_TO_KEEP from the tip)
+
@@ -59,7 +56,10 @@
+ PruneOneBlockFile(fileNumber);
+ setFilesToPrune.insert(fileNumber);
+ count++;
-+ }
+ }
+- PruneOneBlockFile(fileNumber);
+- setFilesToPrune.insert(fileNumber);
+- count++;
+ LogPrintf("Prune (Manual) (%s): prune_height=%d removed %d blk/rev pairs\n",
+ chainstate->ToString(), nLastBlockWeCanPrune, count);
}
@@ -71,8 +71,8 @@
}
}
--void BlockManager::FindFilesToPrune(std::set<int>& setFilesToPrune, uint64_t nPruneAfterHeight, int chain_tip_height, bool is_ibd)
-+void BlockManager::FindFilesToPrune(std::set<int>& setFilesToPrune, uint64_t nPruneAfterHeight)
+-void BlockManager::FindFilesToPrune(std::set<int>& setFilesToPrune, uint64_t nPruneAfterHeight, int chain_tip_height, int prune_height, bool is_ibd)
++void BlockManager::FindFilesToPrune(std::set<int>& setFilesToPrune, uint64_t nPruneAfterHeight, int prune_height)
{
LOCK2(cs_main, cs_LastBlockFile);
- if (chain_tip_height < 0 || nPruneTarget == 0) {
@@ -83,7 +83,7 @@
- return;
- }
-
-- unsigned int nLastBlockWeCanPrune = chain_tip_height - MIN_BLOCKS_TO_KEEP;
+- unsigned int nLastBlockWeCanPrune = std::min(prune_height, chain_tip_height - static_cast<int>(MIN_BLOCKS_TO_KEEP));
- 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
@@ -108,7 +108,8 @@
- for (int fileNumber = 0; fileNumber < nLastBlockFile; fileNumber++) {
- nBytesToPrune = vinfoBlockFile[fileNumber].nSize + vinfoBlockFile[fileNumber].nUndoSize;
-+ unsigned int nLastBlockWeCanPrune = height - MIN_BLOCKS_TO_KEEP;
++ unsigned int nLastBlockWeCanPrune =
++ std::min(prune_height, static_cast<int>(height) - static_cast<int>(MIN_BLOCKS_TO_KEEP));
+ // Depending on which chainstate we're pruning, we may have a differet
+ // start height.
+ unsigned int start_height = g_chainman.PruneStartHeight(chainstate);
@@ -192,10 +193,10 @@
+ }
}
- static FlatFileSeq BlockFileSeq()
+ CBlockIndex * BlockManager::InsertBlockIndex(const uint256& hash)
@@
-
- return au_data->nChainTx;
+ "be checked against the loaded snapshot and cleaned up.\n");
+ this->CompleteSnapshotValidation(m_ibd_chainstate.get());
}
+
+unsigned int ChainstateManager::PruneStartHeight(CChainState* chainstate)
@@ -213,8 +214,8 @@
*
* @param[out] setFilesToPrune The set of file indices that can be unlinked will be returned
*/
-- void FindFilesToPrune(std::set<int>& setFilesToPrune, uint64_t nPruneAfterHeight, int chain_tip_height, bool is_ibd);
-+ void FindFilesToPrune(std::set<int>& setFilesToPrune, uint64_t nPruneAfterHeight);
+- void FindFilesToPrune(std::set<int>& setFilesToPrune, uint64_t nPruneAfterHeight, int chain_tip_height, int prune_height, bool is_ibd);
++ void FindFilesToPrune(std::set<int>& setFilesToPrune, uint64_t nPruneAfterHeight, int prune_height);
public:
BlockMap m_block_index GUARDED_BY(cs_main);
@@ -229,9 +230,9 @@
bool RollforwardBlock(const CBlockIndex* pindex, CCoinsViewCache& inputs, const CChainParams& params) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
@@
- //! Return the cached nChainTx value for the snapshot (per the chainparams assumeutxo data),
- //! if one exists
- std::optional<unsigned int> GetSnapshotNChainTx() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
+
+ //! @returns height at which the active UTXO snapshot was taken, if a snapshot is being used.
+ std::optional<int> GetSnapshotHeight() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
+
+ //! If we're pruning the snapshot chainstate, be sure not to
+ //! step on the toes of the background validation by pruning blocks it
16: b2caf2a04 ! 19: ac2ae19c7 doc: misc. validation doc updates
@@ -12,15 +12,16 @@
- // Check whether ::ChainActive() is an extension of the block at which the LockPoints
+ // Check whether the active chain is an extension of the block at which the LockPoints
// calculation was valid. If not LockPoints are no longer valid
- if (!::ChainActive().Contains(lp->maxInputBlock)) {
- return false;
+ assert(std::addressof(::ChainActive()) == std::addressof(active_chain));
+ if (!active_chain.Contains(lp->maxInputBlock)) {
@@
lockPair.second = lp->time;
}
else {
-- // CoinsTip() contains the UTXO set for ::ChainActive().Tip()
+- // CoinsTip() contains the UTXO set for active_chainstate.m_chain.Tip()
++ // CoinsTip() contains the UTXO set for active_chainstate.m_chain.Tip().
+ // Since this function is only used for mempool maintenance, we don't
+ // need to be concerned with parameterizing the UTXO set.
- CCoinsViewMemPool viewMemPool(&::ChainstateActive().CoinsTip(), pool);
+ CCoinsViewMemPool viewMemPool(&active_chainstate.CoinsTip(), pool);
std::vector<int> prevheights;
prevheights.resize(tx.vin.size());
17: 518d19eb7 ! 20: 51f4f8a41 validation: only clean up mempool for active chainstate
@@ -21,4 +21,4 @@
+
// Update m_chain & related variables.
m_chain.SetTip(pindexNew);
- UpdateTip(m_mempool, pindexNew, chainparams, CoinsTip());
+ UpdateTip(m_mempool, pindexNew, chainparams, *this);
18: 1d50b301a = 21: 192c3e302 validation: run CheckBlockIndex on all chainstates during ProcessNewHeaders
19: c0028e509 = 22: 59472e816 validation: annotation for LoadBlockIndex holding cs_main
20: 3e040cc70 = 23: 7c4a91d61 validation: LoadExternalBlockFile comment and chainstate prefix
21: 5586e2dee < -: --------- doc: add some minor CChainState doc
-: --------- > 24: ce02daa33 doc: add some minor CChainState doc
22: 8154db090 = 25: b1b007f8d rpc: add monitorsnapshot
23: ee29a7f31 = 26: 89ed062d9 dumptxoutset: add assumeutxo key to output
24: 29387bab2 < -: --------- validation: add logging for background validation in UpdateTip
25: 285d5dd3e < -: --------- fixup: don't flush if nothing to flush
-: --------- > 27: 1b6eea1fd validation: add logging for background validation in UpdateTip
-: --------- > 28: eb1c1eefc fixup: don't flush if nothing to flush
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment