aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/Makefile.test_util.include1
-rw-r--r--src/chain.h24
-rw-r--r--src/test/util/chainstate.h54
-rw-r--r--src/test/util/setup_common.cpp21
-rw-r--r--src/test/util/setup_common.h13
-rw-r--r--src/test/validation_chainstate_tests.cpp76
-rw-r--r--src/test/validation_chainstatemanager_tests.cpp66
-rw-r--r--src/test/validation_flush_tests.cpp4
-rw-r--r--src/validation.cpp141
-rw-r--r--src/validation.h33
10 files changed, 314 insertions, 119 deletions
diff --git a/src/Makefile.test_util.include b/src/Makefile.test_util.include
index 85e50ebf70..0a3b99e7d2 100644
--- a/src/Makefile.test_util.include
+++ b/src/Makefile.test_util.include
@@ -9,6 +9,7 @@ EXTRA_LIBRARIES += \
TEST_UTIL_H = \
test/util/blockfilter.h \
+ test/util/chainstate.h \
test/util/logging.h \
test/util/mining.h \
test/util/net.h \
diff --git a/src/chain.h b/src/chain.h
index 84a3a4e1e7..365a7f79b6 100644
--- a/src/chain.h
+++ b/src/chain.h
@@ -126,7 +126,15 @@ enum BlockStatus: uint32_t {
BLOCK_FAILED_CHILD = 64, //!< descends from failed block
BLOCK_FAILED_MASK = BLOCK_FAILED_VALID | BLOCK_FAILED_CHILD,
- BLOCK_OPT_WITNESS = 128, //!< block data in blk*.data was received with a witness-enforcing client
+ BLOCK_OPT_WITNESS = 128, //!< block data in blk*.dat was received with a witness-enforcing client
+
+ /**
+ * If set, this indicates that the block index entry is assumed-valid.
+ * Certain diagnostics will be skipped in e.g. CheckBlockIndex().
+ * It almost certainly means that the block's full validation is pending
+ * on a background chainstate. See `doc/assumeutxo.md`.
+ */
+ BLOCK_ASSUMED_VALID = 256,
};
/** The block chain is a tree shaped structure starting with the
@@ -300,14 +308,24 @@ public:
return ((nStatus & BLOCK_VALID_MASK) >= nUpTo);
}
+ //! @returns true if the block is assumed-valid; this means it is queued to be
+ //! validated by a background chainstate.
+ bool IsAssumedValid() const { return nStatus & BLOCK_ASSUMED_VALID; }
+
//! Raise the validity level of this block index entry.
//! Returns true if the validity was changed.
bool RaiseValidity(enum BlockStatus nUpTo)
{
assert(!(nUpTo & ~BLOCK_VALID_MASK)); // Only validity flags allowed.
- if (nStatus & BLOCK_FAILED_MASK)
- return false;
+ if (nStatus & BLOCK_FAILED_MASK) return false;
+
if ((nStatus & BLOCK_VALID_MASK) < nUpTo) {
+ // If this block had been marked assumed-valid and we're raising
+ // its validity to a certain point, there is no longer an assumption.
+ if (nStatus & BLOCK_ASSUMED_VALID && nUpTo >= BLOCK_VALID_SCRIPTS) {
+ nStatus &= ~BLOCK_ASSUMED_VALID;
+ }
+
nStatus = (nStatus & ~BLOCK_VALID_MASK) | nUpTo;
return true;
}
diff --git a/src/test/util/chainstate.h b/src/test/util/chainstate.h
new file mode 100644
index 0000000000..81ea4c38f5
--- /dev/null
+++ b/src/test/util/chainstate.h
@@ -0,0 +1,54 @@
+// Copyright (c) 2021 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+//
+#ifndef BITCOIN_TEST_UTIL_CHAINSTATE_H
+#define BITCOIN_TEST_UTIL_CHAINSTATE_H
+
+#include <clientversion.h>
+#include <fs.h>
+#include <node/context.h>
+#include <node/utxo_snapshot.h>
+#include <rpc/blockchain.h>
+#include <validation.h>
+
+#include <univalue.h>
+
+#include <boost/test/unit_test.hpp>
+
+const auto NoMalleation = [](CAutoFile& file, SnapshotMetadata& meta){};
+
+/**
+ * Create and activate a UTXO snapshot, optionally providing a function to
+ * malleate the snapshot.
+ */
+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);
+ 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);
+}
+
+
+#endif // BITCOIN_TEST_UTIL_CHAINSTATE_H
diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp
index cabc4b3b49..df4f003227 100644
--- a/src/test/util/setup_common.cpp
+++ b/src/test/util/setup_common.cpp
@@ -234,11 +234,14 @@ void TestChain100Setup::mineBlocks(int num_blocks)
}
}
-CBlock TestChain100Setup::CreateAndProcessBlock(const std::vector<CMutableTransaction>& txns, const CScript& scriptPubKey)
+CBlock TestChain100Setup::CreateBlock(
+ const std::vector<CMutableTransaction>& txns,
+ const CScript& scriptPubKey,
+ CChainState& chainstate)
{
const CChainParams& chainparams = Params();
CTxMemPool empty_pool;
- CBlock block = BlockAssembler(m_node.chainman->ActiveChainstate(), empty_pool, chainparams).CreateNewBlock(scriptPubKey)->block;
+ CBlock block = BlockAssembler(chainstate, empty_pool, chainparams).CreateNewBlock(scriptPubKey)->block;
Assert(block.vtx.size() == 1);
for (const CMutableTransaction& tx : txns) {
@@ -248,6 +251,20 @@ CBlock TestChain100Setup::CreateAndProcessBlock(const std::vector<CMutableTransa
while (!CheckProofOfWork(block.GetHash(), block.nBits, chainparams.GetConsensus())) ++block.nNonce;
+ return block;
+}
+
+CBlock TestChain100Setup::CreateAndProcessBlock(
+ const std::vector<CMutableTransaction>& txns,
+ const CScript& scriptPubKey,
+ CChainState* chainstate)
+{
+ if (!chainstate) {
+ chainstate = &Assert(m_node.chainman)->ActiveChainstate();
+ }
+
+ const CChainParams& chainparams = Params();
+ const CBlock block = this->CreateBlock(txns, scriptPubKey, *chainstate);
std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(block);
Assert(m_node.chainman)->ProcessNewBlock(chainparams, shared_pblock, true, nullptr);
diff --git a/src/test/util/setup_common.h b/src/test/util/setup_common.h
index 5d12dc2323..acb96e54df 100644
--- a/src/test/util/setup_common.h
+++ b/src/test/util/setup_common.h
@@ -119,9 +119,20 @@ struct TestChain100Setup : public RegTestingSetup {
/**
* Create a new block with just given transactions, coinbase paying to
* scriptPubKey, and try to add it to the current chain.
+ * If no chainstate is specified, default to the active.
*/
CBlock CreateAndProcessBlock(const std::vector<CMutableTransaction>& txns,
- const CScript& scriptPubKey);
+ const CScript& scriptPubKey,
+ CChainState* chainstate = nullptr);
+
+ /**
+ * Create a new block with just given transactions, coinbase paying to
+ * scriptPubKey.
+ */
+ CBlock CreateBlock(
+ const std::vector<CMutableTransaction>& txns,
+ const CScript& scriptPubKey,
+ CChainState& chainstate);
//! Mine a series of new blocks on the active chain.
void mineBlocks(int num_blocks);
diff --git a/src/test/validation_chainstate_tests.cpp b/src/test/validation_chainstate_tests.cpp
index 315ef22599..726c9ebbb8 100644
--- a/src/test/validation_chainstate_tests.cpp
+++ b/src/test/validation_chainstate_tests.cpp
@@ -2,10 +2,13 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
//
+#include <chainparams.h>
#include <random.h>
#include <uint256.h>
#include <consensus/validation.h>
#include <sync.h>
+#include <rpc/blockchain.h>
+#include <test/util/chainstate.h>
#include <test/util/setup_common.h>
#include <validation.h>
@@ -73,4 +76,77 @@ BOOST_AUTO_TEST_CASE(validation_chainstate_resize_caches)
WITH_LOCK(::cs_main, manager.Unload());
}
+//! Test UpdateTip behavior for both active and background chainstates.
+//!
+//! When run on the background chainstate, UpdateTip should do a subset
+//! of what it does for the active chainstate.
+BOOST_FIXTURE_TEST_CASE(chainstate_update_tip, TestChain100Setup)
+{
+ ChainstateManager& chainman = *Assert(m_node.chainman);
+ uint256 curr_tip = ::g_best_block;
+
+ // Mine 10 more blocks, putting at us height 110 where a valid assumeutxo value can
+ // be found.
+ mineBlocks(10);
+
+ // After adding some blocks to the tip, best block should have changed.
+ BOOST_CHECK(::g_best_block != curr_tip);
+
+ BOOST_REQUIRE(CreateAndActivateUTXOSnapshot(m_node, m_path_root));
+
+ // Ensure our active chain is the snapshot chainstate.
+ BOOST_CHECK(chainman.IsSnapshotActive());
+
+ curr_tip = ::g_best_block;
+
+ // Mine a new block on top of the activated snapshot chainstate.
+ mineBlocks(1); // Defined in TestChain100Setup.
+
+ // After adding some blocks to the snapshot tip, best block should have changed.
+ BOOST_CHECK(::g_best_block != curr_tip);
+
+ curr_tip = ::g_best_block;
+
+ CChainState* background_cs;
+
+ BOOST_CHECK_EQUAL(chainman.GetAll().size(), 2);
+ for (CChainState* cs : chainman.GetAll()) {
+ if (cs != &chainman.ActiveChainstate()) {
+ background_cs = cs;
+ }
+ }
+ BOOST_CHECK(background_cs);
+
+ // Create a block to append to the validation chain.
+ std::vector<CMutableTransaction> noTxns;
+ CScript scriptPubKey = CScript() << ToByteVector(coinbaseKey.GetPubKey()) << OP_CHECKSIG;
+ CBlock validation_block = this->CreateBlock(noTxns, scriptPubKey, *background_cs);
+ auto pblock = std::make_shared<const CBlock>(validation_block);
+ BlockValidationState state;
+ CBlockIndex* pindex = nullptr;
+ const CChainParams& chainparams = Params();
+ bool newblock = false;
+
+ // TODO: much of this is inlined from ProcessNewBlock(); just reuse PNB()
+ // once it is changed to support multiple chainstates.
+ {
+ LOCK(::cs_main);
+ bool checked = CheckBlock(*pblock, state, chainparams.GetConsensus());
+ BOOST_CHECK(checked);
+ bool accepted = background_cs->AcceptBlock(
+ pblock, state, &pindex, true, nullptr, &newblock);
+ BOOST_CHECK(accepted);
+ }
+ // UpdateTip is called here
+ bool block_added = background_cs->ActivateBestChain(state, pblock);
+
+ // Ensure tip is as expected
+ BOOST_CHECK_EQUAL(background_cs->m_chain.Tip()->GetBlockHash(), validation_block.GetHash());
+
+ // g_best_block should be unchanged after adding a block to the background
+ // validation chain.
+ BOOST_CHECK(block_added);
+ BOOST_CHECK_EQUAL(curr_tip, ::g_best_block);
+}
+
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/validation_chainstatemanager_tests.cpp b/src/test/validation_chainstatemanager_tests.cpp
index 0bd378631b..be9e05a65e 100644
--- a/src/test/validation_chainstatemanager_tests.cpp
+++ b/src/test/validation_chainstatemanager_tests.cpp
@@ -8,13 +8,13 @@
#include <random.h>
#include <rpc/blockchain.h>
#include <sync.h>
+#include <test/util/chainstate.h>
#include <test/util/setup_common.h>
#include <uint256.h>
#include <validation.h>
#include <validationinterface.h>
#include <tinyformat.h>
-#include <univalue.h>
#include <vector>
@@ -44,7 +44,6 @@ BOOST_AUTO_TEST_CASE(chainstatemanager)
BOOST_CHECK(!manager.IsSnapshotActive());
BOOST_CHECK(!manager.IsSnapshotValidated());
- BOOST_CHECK(!manager.IsBackgroundIBD(&c1));
auto all = manager.GetAll();
BOOST_CHECK_EQUAL_COLLECTIONS(all.begin(), all.end(), chainstates.begin(), chainstates.end());
@@ -57,9 +56,6 @@ BOOST_AUTO_TEST_CASE(chainstatemanager)
auto exp_tip = c1.m_chain.Tip();
BOOST_CHECK_EQUAL(active_tip, exp_tip);
- auto& validated_cs = manager.ValidatedChainstate();
- BOOST_CHECK_EQUAL(&validated_cs, &c1);
-
BOOST_CHECK(!manager.SnapshotBlockhash().has_value());
// Create a snapshot-based chainstate.
@@ -81,8 +77,8 @@ BOOST_AUTO_TEST_CASE(chainstatemanager)
BOOST_CHECK(manager.IsSnapshotActive());
BOOST_CHECK(!manager.IsSnapshotValidated());
- BOOST_CHECK(manager.IsBackgroundIBD(&c1));
- BOOST_CHECK(!manager.IsBackgroundIBD(&c2));
+ BOOST_CHECK_EQUAL(&c2, &manager.ActiveChainstate());
+ BOOST_CHECK(&c1 != &manager.ActiveChainstate());
auto all2 = manager.GetAll();
BOOST_CHECK_EQUAL_COLLECTIONS(all2.begin(), all2.end(), chainstates.begin(), chainstates.end());
@@ -99,16 +95,6 @@ BOOST_AUTO_TEST_CASE(chainstatemanager)
// CCoinsViewCache instances.
BOOST_CHECK(exp_tip != exp_tip2);
- auto& validated_cs2 = manager.ValidatedChainstate();
- BOOST_CHECK_EQUAL(&validated_cs2, &c1);
-
- auto& validated_chain = manager.ValidatedChain();
- BOOST_CHECK_EQUAL(&validated_chain, &c1.m_chain);
-
- auto validated_tip = manager.ValidatedTip();
- exp_tip = c1.m_chain.Tip();
- BOOST_CHECK_EQUAL(validated_tip, exp_tip);
-
// Let scheduler events finish running to avoid accessing memory that is going to be unloaded
SyncWithValidationInterfaceQueue();
@@ -168,36 +154,6 @@ BOOST_AUTO_TEST_CASE(chainstatemanager_rebalance_caches)
BOOST_CHECK_CLOSE(c2.m_coinsdb_cache_size_bytes, max_cache * 0.95, 1);
}
-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);
- 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)
{
@@ -321,27 +277,27 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_activate_snapshot, TestChain100Setup)
{
LOCK(::cs_main);
size_t coins_in_active{0};
- size_t coins_in_ibd{0};
- size_t coins_missing_ibd{0};
+ size_t coins_in_background{0};
+ size_t coins_missing_from_background{0};
for (CChainState* chainstate : chainman.GetAll()) {
BOOST_TEST_MESSAGE("Checking coins in " << chainstate->ToString());
CCoinsViewCache& coinscache = chainstate->CoinsTip();
- bool is_ibd = chainman.IsBackgroundIBD(chainstate);
+ bool is_background = chainstate != &chainman.ActiveChainstate();
for (CTransactionRef& txn : m_coinbase_txns) {
COutPoint op{txn->GetHash(), 0};
if (coinscache.HaveCoin(op)) {
- (is_ibd ? coins_in_ibd : coins_in_active)++;
- } else if (is_ibd) {
- coins_missing_ibd++;
+ (is_background ? coins_in_background : coins_in_active)++;
+ } else if (is_background) {
+ coins_missing_from_background++;
}
}
}
BOOST_CHECK_EQUAL(coins_in_active, initial_total_coins + new_coins);
- BOOST_CHECK_EQUAL(coins_in_ibd, initial_total_coins);
- BOOST_CHECK_EQUAL(coins_missing_ibd, new_coins);
+ BOOST_CHECK_EQUAL(coins_in_background, initial_total_coins);
+ BOOST_CHECK_EQUAL(coins_missing_from_background, new_coins);
}
// Snapshot should refuse to load after one has already loaded.
diff --git a/src/test/validation_flush_tests.cpp b/src/test/validation_flush_tests.cpp
index 22aafcaa6c..9136c497ea 100644
--- a/src/test/validation_flush_tests.cpp
+++ b/src/test/validation_flush_tests.cpp
@@ -9,7 +9,7 @@
#include <boost/test/unit_test.hpp>
-BOOST_FIXTURE_TEST_SUITE(validation_flush_tests, BasicTestingSetup)
+BOOST_FIXTURE_TEST_SUITE(validation_flush_tests, ChainTestingSetup)
//! Test utilities for detecting when we need to flush the coins cache based
//! on estimated memory usage.
@@ -20,7 +20,7 @@ BOOST_AUTO_TEST_CASE(getcoinscachesizestate)
{
CTxMemPool mempool;
BlockManager blockman{};
- CChainState chainstate{&mempool, blockman};
+ CChainState chainstate{&mempool, blockman, *Assert(m_node.chainman)};
chainstate.InitCoinsDB(/*cache_size_bytes*/ 1 << 10, /*in_memory*/ true, /*should_wipe*/ false);
WITH_LOCK(::cs_main, chainstate.InitCoinsCache(1 << 10));
diff --git a/src/validation.cpp b/src/validation.cpp
index b052273b50..51cb1d373c 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -1095,10 +1095,15 @@ void CoinsViews::InitCache()
m_cacheview = std::make_unique<CCoinsViewCache>(&m_catcherview);
}
-CChainState::CChainState(CTxMemPool* mempool, BlockManager& blockman, std::optional<uint256> from_snapshot_blockhash)
+CChainState::CChainState(
+ CTxMemPool* mempool,
+ BlockManager& blockman,
+ ChainstateManager& chainman,
+ std::optional<uint256> from_snapshot_blockhash)
: m_mempool(mempool),
m_params(::Params()),
m_blockman(blockman),
+ m_chainman(chainman),
m_from_snapshot_blockhash(from_snapshot_blockhash) {}
void CChainState::InitCoinsDB(
@@ -2090,8 +2095,42 @@ static void AppendWarning(bilingual_str& res, const bilingual_str& warn)
res += warn;
}
+static void UpdateTipLog(
+ const CCoinsViewCache& coins_tip,
+ const CBlockIndex* tip,
+ const CChainParams& params,
+ const std::string& func_name,
+ const std::string& prefix,
+ const std::string& warning_messages) EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
+{
+
+ AssertLockHeld(::cs_main);
+ LogPrintf("%s%s: new best=%s height=%d version=0x%08x log2_work=%f tx=%lu date='%s' progress=%f cache=%.1fMiB(%utxo)%s\n",
+ prefix, func_name,
+ tip->GetBlockHash().ToString(), tip->nHeight, tip->nVersion,
+ log(tip->nChainWork.getdouble()) / log(2.0), (unsigned long)tip->nChainTx,
+ FormatISO8601DateTime(tip->GetBlockTime()),
+ GuessVerificationProgress(params.TxData(), tip),
+ coins_tip.DynamicMemoryUsage() * (1.0 / (1 << 20)),
+ coins_tip.GetCacheSize(),
+ !warning_messages.empty() ? strprintf(" warning='%s'", warning_messages) : "");
+}
+
void CChainState::UpdateTip(const CBlockIndex* pindexNew)
{
+ const auto& coins_tip = this->CoinsTip();
+
+ // The remainder of the function isn't relevant if we are not acting on
+ // the active chainstate, so return if need be.
+ if (this != &m_chainman.ActiveChainstate()) {
+ // Only log every so often so that we don't bury log messages at the tip.
+ constexpr int BACKGROUND_LOG_INTERVAL = 2000;
+ if (pindexNew->nHeight % BACKGROUND_LOG_INTERVAL == 0) {
+ UpdateTipLog(coins_tip, pindexNew, m_params, __func__, "[background validation] ", "");
+ }
+ return;
+ }
+
// New best block
if (m_mempool) {
m_mempool->AddTransactionsUpdated(1);
@@ -2119,12 +2158,7 @@ void CChainState::UpdateTip(const CBlockIndex* pindexNew)
}
}
}
- LogPrintf("%s: new best=%s height=%d version=0x%08x log2_work=%f tx=%lu date='%s' progress=%f cache=%.1fMiB(%utxo)%s\n", __func__,
- pindexNew->GetBlockHash().ToString(), pindexNew->nHeight, pindexNew->nVersion,
- log(pindexNew->nChainWork.getdouble())/log(2.0), (unsigned long)pindexNew->nChainTx,
- FormatISO8601DateTime(pindexNew->GetBlockTime()),
- GuessVerificationProgress(m_params.TxData(), pindexNew), this->CoinsTip().DynamicMemoryUsage() * (1.0 / (1<<20)), this->CoinsTip().GetCacheSize(),
- !warning_messages.empty() ? strprintf(" warning='%s'", warning_messages.original) : "");
+ UpdateTipLog(coins_tip, pindexNew, m_params, __func__, "", warning_messages.original);
}
/** Disconnect m_chain's tip.
@@ -3621,7 +3655,9 @@ bool BlockManager::LoadBlockIndex(
pindex->nStatus |= BLOCK_FAILED_CHILD;
setDirtyBlockIndex.insert(pindex);
}
- if (pindex->IsValid(BLOCK_VALID_TRANSACTIONS) && (pindex->HaveTxsDownloaded() || pindex->pprev == nullptr)) {
+ if (pindex->IsAssumedValid() ||
+ (pindex->IsValid(BLOCK_VALID_TRANSACTIONS) &&
+ (pindex->HaveTxsDownloaded() || pindex->pprev == nullptr))) {
block_index_candidates.insert(pindex);
}
if (pindex->nStatus & BLOCK_FAILED_MASK && (!pindexBestInvalid || pindex->nChainWork > pindexBestInvalid->nChainWork))
@@ -4200,12 +4236,33 @@ void CChainState::CheckBlockIndex()
while (pindex != nullptr) {
nNodes++;
if (pindexFirstInvalid == nullptr && pindex->nStatus & BLOCK_FAILED_VALID) pindexFirstInvalid = pindex;
- if (pindexFirstMissing == nullptr && !(pindex->nStatus & BLOCK_HAVE_DATA)) pindexFirstMissing = pindex;
+ // Assumed-valid index entries will not have data since we haven't downloaded the
+ // full block yet.
+ if (pindexFirstMissing == nullptr && !(pindex->nStatus & BLOCK_HAVE_DATA) && !pindex->IsAssumedValid()) {
+ pindexFirstMissing = pindex;
+ }
if (pindexFirstNeverProcessed == nullptr && pindex->nTx == 0) pindexFirstNeverProcessed = pindex;
if (pindex->pprev != nullptr && pindexFirstNotTreeValid == nullptr && (pindex->nStatus & BLOCK_VALID_MASK) < BLOCK_VALID_TREE) pindexFirstNotTreeValid = pindex;
- if (pindex->pprev != nullptr && pindexFirstNotTransactionsValid == nullptr && (pindex->nStatus & BLOCK_VALID_MASK) < BLOCK_VALID_TRANSACTIONS) pindexFirstNotTransactionsValid = pindex;
- if (pindex->pprev != nullptr && pindexFirstNotChainValid == nullptr && (pindex->nStatus & BLOCK_VALID_MASK) < BLOCK_VALID_CHAIN) pindexFirstNotChainValid = pindex;
- if (pindex->pprev != nullptr && pindexFirstNotScriptsValid == nullptr && (pindex->nStatus & BLOCK_VALID_MASK) < BLOCK_VALID_SCRIPTS) pindexFirstNotScriptsValid = pindex;
+
+ if (pindex->pprev != nullptr && !pindex->IsAssumedValid()) {
+ // Skip validity flag checks for BLOCK_ASSUMED_VALID index entries, since these
+ // *_VALID_MASK flags will not be present for index entries we are temporarily assuming
+ // valid.
+ if (pindexFirstNotTransactionsValid == nullptr &&
+ (pindex->nStatus & BLOCK_VALID_MASK) < BLOCK_VALID_TRANSACTIONS) {
+ pindexFirstNotTransactionsValid = pindex;
+ }
+
+ if (pindexFirstNotChainValid == nullptr &&
+ (pindex->nStatus & BLOCK_VALID_MASK) < BLOCK_VALID_CHAIN) {
+ pindexFirstNotChainValid = pindex;
+ }
+
+ if (pindexFirstNotScriptsValid == nullptr &&
+ (pindex->nStatus & BLOCK_VALID_MASK) < BLOCK_VALID_SCRIPTS) {
+ pindexFirstNotScriptsValid = pindex;
+ }
+ }
// Begin: actual consistency checks.
if (pindex->pprev == nullptr) {
@@ -4216,7 +4273,9 @@ void CChainState::CheckBlockIndex()
if (!pindex->HaveTxsDownloaded()) assert(pindex->nSequenceId <= 0); // nSequenceId can't be set positive for blocks that aren't linked (negative is used for preciousblock)
// VALID_TRANSACTIONS is equivalent to nTx > 0 for all nodes (whether or not pruning has occurred).
// HAVE_DATA is only equivalent to nTx > 0 (or VALID_TRANSACTIONS) if no pruning has occurred.
- if (!fHavePruned) {
+ // Unless these indexes are assumed valid and pending block download on a
+ // background chainstate.
+ if (!fHavePruned && !pindex->IsAssumedValid()) {
// If we've never pruned, then HAVE_DATA should be equivalent to nTx > 0
assert(!(pindex->nStatus & BLOCK_HAVE_DATA) == (pindex->nTx == 0));
assert(pindexFirstMissing == pindexFirstNeverProcessed);
@@ -4225,7 +4284,16 @@ void CChainState::CheckBlockIndex()
if (pindex->nStatus & BLOCK_HAVE_DATA) assert(pindex->nTx > 0);
}
if (pindex->nStatus & BLOCK_HAVE_UNDO) assert(pindex->nStatus & BLOCK_HAVE_DATA);
- assert(((pindex->nStatus & BLOCK_VALID_MASK) >= BLOCK_VALID_TRANSACTIONS) == (pindex->nTx > 0)); // This is pruning-independent.
+ if (pindex->IsAssumedValid()) {
+ // Assumed-valid blocks should have some nTx value.
+ assert(pindex->nTx > 0);
+ // Assumed-valid blocks should connect to the main chain.
+ assert((pindex->nStatus & BLOCK_VALID_MASK) >= BLOCK_VALID_TREE);
+ } else {
+ // Otherwise there should only be an nTx value if we have
+ // actually seen a block's transactions.
+ assert(((pindex->nStatus & BLOCK_VALID_MASK) >= BLOCK_VALID_TRANSACTIONS) == (pindex->nTx > 0)); // This is pruning-independent.
+ }
// All parents having had data (at some point) is equivalent to all parents being VALID_TRANSACTIONS, which is equivalent to HaveTxsDownloaded().
assert((pindexFirstNeverProcessed == nullptr) == pindex->HaveTxsDownloaded());
assert((pindexFirstNotTransactionsValid == nullptr) == pindex->HaveTxsDownloaded());
@@ -4242,11 +4310,17 @@ void CChainState::CheckBlockIndex()
}
if (!CBlockIndexWorkComparator()(pindex, m_chain.Tip()) && pindexFirstNeverProcessed == nullptr) {
if (pindexFirstInvalid == nullptr) {
+ const bool is_active = this == &m_chainman.ActiveChainstate();
+
// If this block sorts at least as good as the current tip and
// is valid and we have all data for its parents, it must be in
// setBlockIndexCandidates. m_chain.Tip() must also be there
// even if some data has been pruned.
- if (pindexFirstMissing == nullptr || pindex == m_chain.Tip()) {
+ //
+ // Don't perform this check for the background chainstate since
+ // its setBlockIndexCandidates shouldn't have some entries (i.e. those past the
+ // snapshot block) which do exist in the block index for the active chainstate.
+ if (is_active && (pindexFirstMissing == nullptr || pindex == m_chain.Tip())) {
assert(setBlockIndexCandidates.count(pindex));
}
// If some parent is missing, then it could be that this block was in
@@ -4581,7 +4655,7 @@ CChainState& ChainstateManager::InitializeChainstate(
if (to_modify) {
throw std::logic_error("should not be overwriting a chainstate");
}
- to_modify.reset(new CChainState(mempool, m_blockman, snapshot_blockhash));
+ to_modify.reset(new CChainState(mempool, m_blockman, *this, snapshot_blockhash));
// Snapshot chainstates and initial IBD chaintates always become active.
if (is_snapshot || (!is_snapshot && !m_active_chainstate)) {
@@ -4650,8 +4724,9 @@ bool ChainstateManager::ActivateSnapshot(
static_cast<size_t>(current_coinsdb_cache_size * IBD_CACHE_PERC));
}
- auto snapshot_chainstate = WITH_LOCK(::cs_main, return std::make_unique<CChainState>(
- /* mempool */ nullptr, m_blockman, base_blockhash));
+ auto snapshot_chainstate = WITH_LOCK(::cs_main,
+ return std::make_unique<CChainState>(
+ /* mempool */ nullptr, m_blockman, *this, base_blockhash));
{
LOCK(::cs_main);
@@ -4854,11 +4929,25 @@ bool ChainstateManager::PopulateAndValidateSnapshot(
// Fake nChainTx so that GuessVerificationProgress reports accurately
index->nChainTx = index->pprev ? index->pprev->nChainTx + index->nTx : 1;
+ // Mark unvalidated block index entries beneath the snapshot base block as assumed-valid.
+ if (!index->IsValid(BLOCK_VALID_SCRIPTS)) {
+ // This flag will be removed once the block is fully validated by a
+ // background chainstate.
+ index->nStatus |= BLOCK_ASSUMED_VALID;
+ }
+
// Fake BLOCK_OPT_WITNESS so that CChainState::NeedsRedownload()
// won't ask to rewind the entire assumed-valid chain on startup.
if (index->pprev && DeploymentActiveAt(*index, ::Params().GetConsensus(), Consensus::DEPLOYMENT_SEGWIT)) {
index->nStatus |= BLOCK_OPT_WITNESS;
}
+
+ setDirtyBlockIndex.insert(index);
+ // Changes to the block index will be flushed to disk after this call
+ // returns in `ActivateSnapshot()`, when `MaybeRebalanceCaches()` is
+ // called, since we've added a snapshot chainstate and therefore will
+ // have to downsize the IBD chainstate, which will result in a call to
+ // `FlushStateToDisk(ALWAYS)`.
}
assert(index);
@@ -4883,22 +4972,6 @@ bool ChainstateManager::IsSnapshotActive() const
return m_snapshot_chainstate && m_active_chainstate == m_snapshot_chainstate.get();
}
-CChainState& ChainstateManager::ValidatedChainstate() const
-{
- LOCK(::cs_main);
- if (m_snapshot_chainstate && IsSnapshotValidated()) {
- return *m_snapshot_chainstate.get();
- }
- assert(m_ibd_chainstate);
- return *m_ibd_chainstate.get();
-}
-
-bool ChainstateManager::IsBackgroundIBD(CChainState* chainstate) const
-{
- LOCK(::cs_main);
- return (m_snapshot_chainstate && chainstate == m_ibd_chainstate.get());
-}
-
void ChainstateManager::Unload()
{
for (CChainState* chainstate : this->GetAll()) {
diff --git a/src/validation.h b/src/validation.h
index a5aa6da39d..b2282828ce 100644
--- a/src/validation.h
+++ b/src/validation.h
@@ -100,6 +100,7 @@ extern RecursiveMutex cs_main;
typedef std::unordered_map<uint256, CBlockIndex*, BlockHasher> BlockMap;
extern Mutex g_best_block_mutex;
extern std::condition_variable g_best_block_cv;
+/** Used to notify getblocktemplate RPC of new tips. */
extern uint256 g_best_block;
/** Whether there are dedicated script-checking threads running.
* False indicates all script checking is done on the main threadMessageHandler thread.
@@ -584,9 +585,15 @@ public:
//! CChainState instances.
BlockManager& m_blockman;
+ //! The chainstate manager that owns this chainstate. The reference is
+ //! necessary so that this instance can check whether it is the active
+ //! chainstate within deeply nested method calls.
+ ChainstateManager& m_chainman;
+
explicit CChainState(
CTxMemPool* mempool,
BlockManager& blockman,
+ ChainstateManager& chainman,
std::optional<uint256> from_snapshot_blockhash = std::nullopt);
/**
@@ -623,9 +630,10 @@ public:
const std::optional<uint256> m_from_snapshot_blockhash;
/**
- * The set of all CBlockIndex entries with BLOCK_VALID_TRANSACTIONS (for itself and all ancestors) and
- * as good as our current tip or better. Entries may be failed, though, and pruning nodes may be
- * missing the data for the block.
+ * The set of all CBlockIndex entries with either BLOCK_VALID_TRANSACTIONS (for
+ * itself and all ancestors) *or* BLOCK_ASSUMED_VALID (if using background
+ * chainstates) and as good as our current tip or better. Entries may be failed,
+ * though, and pruning nodes may be missing the data for the block.
*/
std::set<CBlockIndex*, CBlockIndexWorkComparator> setBlockIndexCandidates;
@@ -837,12 +845,6 @@ private:
* *Background IBD chainstate*: an IBD chainstate for which the
* IBD process is happening in the background while use of the
* active (snapshot) chainstate allows the rest of the system to function.
- *
- * *Validated chainstate*: the most-work chainstate which has been validated
- * locally via initial block download. This will be the snapshot chainstate
- * if a snapshot was loaded and all blocks up to the snapshot starting point
- * have been downloaded and validated (via background validation), otherwise
- * it will be the IBD chainstate.
*/
class ChainstateManager
{
@@ -961,19 +963,6 @@ public:
//! Is there a snapshot in use and has it been fully validated?
bool IsSnapshotValidated() const { return m_snapshot_validated; }
- //! @returns true if this chainstate is being used to validate an active
- //! snapshot in the background.
- bool IsBackgroundIBD(CChainState* chainstate) const;
-
- //! Return the most-work chainstate that has been fully validated.
- //!
- //! During background validation of a snapshot, this is the IBD chain. After
- //! background validation has completed, this is the snapshot chain.
- CChainState& ValidatedChainstate() const;
-
- CChain& ValidatedChain() const { return ValidatedChainstate().m_chain; }
- CBlockIndex* ValidatedTip() const { return ValidatedChain().Tip(); }
-
/**
* Process an incoming block. This only returns after the best known valid
* block is made active. Note that it does not, however, guarantee that the