mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-09-14 15:25:51 +02:00
Merge bitcoin/bitcoin#35847: test: move more tests to baseindex_tests and run them for all indexes
34c03075a5test: run generic baseindex tests against every index type (Martin Zumsande)11b3e251c4test: make baseindex flush test chain-length agnostic (Martin Zumsande)8b959f4c6atest: move unclean_shutdown test to baseindex_tests (Martin Zumsande)2232d6afbetest: move index_reorg_crash to baseindex_tests (Martin Zumsande)a3597e2683test: move BuildChain helper into test mining util (Martin Zumsande)954985e6a3test: simplify blockfilter test's BuildChain helper (Martin Zumsande) Pull request description: In #34897, the `baseindex_tests` unit test was introduced, meant for tests that test basic index functionality (e.g. reorg or unclean shutdown behavior) that should work regardless of the particular index type. This PR moves two more of these tests (`index_reorg_crash`, `coinstatsindex_unclean_shutdown`) from test files of specific indexes into that folder. In the second part, tests are executed sequentially for all index types instead of just one particular one, where applicable. Before moving `index_reorg_crash` I extracted the `BuildChain` helper to `util/mining` so that it can be used by multiple tests. While doing that, I simplified the helper a bit. ACKs for top commit: jeanpablojp: tACK34c03075a5sedited: ACK34c03075a5Tree-SHA512: 1d7a43160a9b7ec3c75a8c806967f2031da4855fe449c9c8aac8e44b1940e5ee28fde9473406666e74a682cf135b87f8c0eb9ddba50fce156dce9fc54a7763eb
This commit is contained in:
@@ -2,19 +2,63 @@
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <addresstype.h>
|
||||
#include <blockfilter.h>
|
||||
#include <chain.h>
|
||||
#include <chainparams.h>
|
||||
#include <coins.h>
|
||||
#include <common/args.h>
|
||||
#include <consensus/validation.h>
|
||||
#include <index/base.h>
|
||||
#include <index/blockfilterindex.h>
|
||||
#include <index/coinstatsindex.h>
|
||||
#include <index/txindex.h>
|
||||
#include <index/txospenderindex.h>
|
||||
#include <interfaces/chain.h>
|
||||
#include <kernel/types.h>
|
||||
#include <key.h>
|
||||
#include <node/context.h>
|
||||
#include <primitives/block.h>
|
||||
#include <script/script.h>
|
||||
#include <sync.h>
|
||||
#include <test/util/mining.h>
|
||||
#include <test/util/setup_common.h>
|
||||
#include <test/util/time.h>
|
||||
#include <test/util/validation.h>
|
||||
#include <tinyformat.h>
|
||||
#include <util/byte_units.h>
|
||||
#include <util/check.h>
|
||||
#include <util/fs.h>
|
||||
#include <validation.h>
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
using kernel::ChainstateRole;
|
||||
|
||||
using IndexFactory = std::function<std::unique_ptr<BaseIndex>(node::NodeContext&)>;
|
||||
|
||||
static const std::vector<std::pair<std::string, IndexFactory>> INDEX_FACTORIES{
|
||||
{"coinstatsindex", [](node::NodeContext& node) -> std::unique_ptr<BaseIndex> {
|
||||
return std::make_unique<CoinStatsIndex>(interfaces::MakeChain(node), /*n_cache_size=*/1_MiB); }},
|
||||
{"txindex", [](node::NodeContext& node) -> std::unique_ptr<BaseIndex> {
|
||||
return std::make_unique<TxIndex>(interfaces::MakeChain(node), /*n_cache_size=*/1_MiB); }},
|
||||
{"txospenderindex", [](node::NodeContext& node) -> std::unique_ptr<BaseIndex> {
|
||||
return std::make_unique<TxoSpenderIndex>(interfaces::MakeChain(node), /*n_cache_size=*/1_MiB); }},
|
||||
{"blockfilterindex", [](node::NodeContext& node) -> std::unique_ptr<BaseIndex> {
|
||||
return std::make_unique<BlockFilterIndex>(interfaces::MakeChain(node), BlockFilterType::BASIC, /*n_cache_size=*/1_MiB); }},
|
||||
};
|
||||
|
||||
// Tests of generic BaseIndex functionality that is independent of which
|
||||
// concrete index is being used. CoinStatsIndex is used here merely as a
|
||||
// convenient instantiation of BaseIndex.
|
||||
// concrete index is being used.
|
||||
BOOST_AUTO_TEST_SUITE(baseindex_tests)
|
||||
|
||||
// Test that the index does not commit ahead of the chainstate's last
|
||||
@@ -24,39 +68,161 @@ BOOST_AUTO_TEST_SUITE(baseindex_tests)
|
||||
BOOST_FIXTURE_TEST_CASE(baseindex_no_commit_ahead_of_flush, TestChain100Setup)
|
||||
{
|
||||
Chainstate& chainstate = Assert(m_node.chainman)->ActiveChainstate();
|
||||
auto sync_index = [&](bool do_flush, int expected_sync_height, int expected_commit_height) {
|
||||
CoinStatsIndex index{interfaces::MakeChain(m_node), /*n_cache_size=*/1_MiB};
|
||||
BOOST_REQUIRE(index.Init());
|
||||
index.Sync();
|
||||
if (do_flush) {
|
||||
chainstate.ForceFlushStateToDisk();
|
||||
m_node.chain->context()->validation_signals->SyncWithValidationInterfaceQueue();
|
||||
for (const auto& [index_name, make_index] : INDEX_FACTORIES) {
|
||||
BOOST_TEST_INFO_SCOPE(index_name);
|
||||
const int tip_height{WITH_LOCK(cs_main, return m_node.chainman->ActiveChain().Tip()->nHeight)};
|
||||
auto sync_index = [&](bool do_flush, int expected_sync_height, int expected_commit_height) {
|
||||
auto index{make_index(m_node)};
|
||||
BOOST_REQUIRE(index->Init());
|
||||
index->Sync();
|
||||
if (do_flush) {
|
||||
chainstate.ForceFlushStateToDisk();
|
||||
m_node.chain->context()->validation_signals->SyncWithValidationInterfaceQueue();
|
||||
}
|
||||
BOOST_CHECK_EQUAL(index->GetSummary().best_block_height, expected_sync_height);
|
||||
index->Stop();
|
||||
// Reload index to see which block data was actually committed.
|
||||
BOOST_REQUIRE(index->Init());
|
||||
BOOST_CHECK_EQUAL(index->GetSummary().best_block_height, expected_commit_height);
|
||||
index->Stop();
|
||||
};
|
||||
|
||||
// Part 1: Sync, then "crash" (stop without flushing). Models a node that
|
||||
// started up, had its index catch up, but never flushed before going down.
|
||||
// The end-of-sync Commit() runs at the chain tip but m_last_flushed_block
|
||||
// is null, so it is skipped.
|
||||
sync_index(false, tip_height, 0);
|
||||
|
||||
// Part 2: Restart cleanly. Sync, force a chainstate flush, and drain the
|
||||
// validation queue so the index's ChainStateFlushed callback runs.
|
||||
// Now m_last_flushed_block == tip and the index can commit.
|
||||
sync_index(true, tip_height, tip_height);
|
||||
|
||||
// Part 3: Connect a new block on the chain without flushing
|
||||
// (m_last_flushed_block stays at tip_height). For a real node this would
|
||||
// happen in parallel with Sync(). Here we do it before Sync() to make the
|
||||
// race state deterministic.
|
||||
CreateAndProcessBlock({}, CScript() << OP_TRUE);
|
||||
sync_index(false, tip_height + 1, tip_height);
|
||||
}
|
||||
}
|
||||
|
||||
// Test shutdown between BlockConnected and ChainStateFlushed notifications,
|
||||
// make sure index is not corrupted and is able to reload.
|
||||
BOOST_FIXTURE_TEST_CASE(index_unclean_shutdown, TestChain100Setup)
|
||||
{
|
||||
Chainstate& chainstate = Assert(m_node.chainman)->ActiveChainstate();
|
||||
const CChainParams& params = Params();
|
||||
for (const auto& [index_name, make_index] : INDEX_FACTORIES) {
|
||||
BOOST_TEST_INFO_SCOPE(index_name);
|
||||
{
|
||||
auto index{make_index(m_node)};
|
||||
BOOST_REQUIRE(index->Init());
|
||||
index->Sync();
|
||||
std::shared_ptr<const CBlock> new_block;
|
||||
CBlockIndex* new_block_index = nullptr;
|
||||
{
|
||||
const CScript script_pub_key{CScript() << ToByteVector(coinbaseKey.GetPubKey()) << OP_CHECKSIG};
|
||||
const CBlock block = this->CreateBlock({}, script_pub_key);
|
||||
|
||||
new_block = std::make_shared<CBlock>(block);
|
||||
|
||||
LOCK(cs_main);
|
||||
BlockValidationState state;
|
||||
BOOST_CHECK(CheckBlock(block, state, params.GetConsensus()));
|
||||
BOOST_CHECK(m_node.chainman->AcceptBlock(new_block, state, &new_block_index, true, nullptr, nullptr, true));
|
||||
CCoinsViewCache view(&chainstate.CoinsTip());
|
||||
BOOST_CHECK(chainstate.ConnectBlock(block, state, new_block_index, view));
|
||||
}
|
||||
// Send block connected notification, then stop the index without
|
||||
// sending a chainstate flushed notification. Prior to #24138, this
|
||||
// would cause the index to be corrupted and fail to reload.
|
||||
ValidationInterfaceTest::BlockConnected(ChainstateRole{}, *index, new_block, new_block_index);
|
||||
index->Stop();
|
||||
}
|
||||
|
||||
{
|
||||
auto index{make_index(m_node)};
|
||||
BOOST_REQUIRE(index->Init());
|
||||
// Make sure the index can be loaded.
|
||||
BOOST_REQUIRE(index->StartBackgroundSync());
|
||||
index->Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class IndexReorgCrash : public BaseIndex
|
||||
{
|
||||
private:
|
||||
FakeNodeClock& m_clock;
|
||||
std::unique_ptr<BaseIndex::DB> m_db;
|
||||
std::shared_future<void> m_blocker;
|
||||
int m_blocking_height;
|
||||
|
||||
public:
|
||||
explicit IndexReorgCrash(std::unique_ptr<interfaces::Chain> chain, std::shared_future<void> blocker, int blocking_height, FakeNodeClock& clock)
|
||||
: BaseIndex(std::move(chain), "test index", "testidx"), m_clock(clock), m_blocker(blocker), m_blocking_height(blocking_height)
|
||||
{
|
||||
const fs::path path = gArgs.GetDataDirNet() / "index";
|
||||
fs::create_directories(path);
|
||||
m_db = std::make_unique<BaseIndex::DB>(path / "db", /*n_cache_size=*/0, /*f_memory=*/true, /*f_wipe=*/false);
|
||||
}
|
||||
|
||||
bool AllowPrune() const override { return false; }
|
||||
BaseIndex::DB& GetDB() const override { return *m_db; }
|
||||
|
||||
bool CustomAppend(const interfaces::BlockInfo& block) override
|
||||
{
|
||||
// Simulate a delay so new blocks can get connected during the initial sync
|
||||
if (block.height == m_blocking_height) m_blocker.wait();
|
||||
|
||||
// Move mock time forward so the best index gets updated only when we are not at the blocking height
|
||||
if (block.height == m_blocking_height - 1 || block.height > m_blocking_height) {
|
||||
m_clock += 31s;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(index_reorg_crash, TestChain100Setup)
|
||||
{
|
||||
std::promise<void> promise;
|
||||
std::shared_future<void> blocker(promise.get_future());
|
||||
int blocking_height = WITH_LOCK(cs_main, return m_node.chainman->ActiveChain().Tip()->nHeight);
|
||||
|
||||
IndexReorgCrash index{interfaces::MakeChain(m_node), blocker, blocking_height, m_clock};
|
||||
BOOST_REQUIRE(index.Init());
|
||||
BOOST_REQUIRE(index.StartBackgroundSync());
|
||||
|
||||
auto func_wait_until = [&](int height, std::chrono::milliseconds timeout) {
|
||||
auto deadline = std::chrono::steady_clock::now() + timeout;
|
||||
while (index.GetSummary().best_block_height < height) {
|
||||
if (std::chrono::steady_clock::now() > deadline) {
|
||||
BOOST_FAIL(strprintf("Timeout waiting for index height %d (current: %d)", height, index.GetSummary().best_block_height));
|
||||
return;
|
||||
}
|
||||
std::this_thread::sleep_for(100ms);
|
||||
}
|
||||
BOOST_CHECK_EQUAL(index.GetSummary().best_block_height, expected_sync_height);
|
||||
index.Stop();
|
||||
// Reload index to see which block data was actually committed.
|
||||
BOOST_REQUIRE(index.Init());
|
||||
BOOST_CHECK_EQUAL(index.GetSummary().best_block_height, expected_commit_height);
|
||||
index.Stop();
|
||||
};
|
||||
|
||||
// Part 1: Sync, then "crash" (stop without flushing). Models a node that
|
||||
// started up, had its index catch up, but never flushed before going down.
|
||||
// The end-of-sync Commit() runs at chain tip (height 100) but
|
||||
// m_last_flushed_block is null, so it is skipped.
|
||||
sync_index(false, 100, 0);
|
||||
// Wait until the index is one block before the fork point
|
||||
func_wait_until(blocking_height - 1, /*timeout=*/5s);
|
||||
|
||||
// Part 2: Restart cleanly. Sync, force a chainstate flush, and drain the
|
||||
// validation queue so the index's ChainStateFlushed callback runs.
|
||||
// Now m_last_flushed_block == tip == 100 and the index can commit.
|
||||
sync_index(true, 100, 100);
|
||||
// Create a fork to trigger the reorg
|
||||
std::vector<std::shared_ptr<CBlock>> fork;
|
||||
const CBlockIndex* prev_tip = WITH_LOCK(cs_main, return m_node.chainman->ActiveChain().Tip()->pprev);
|
||||
BOOST_REQUIRE(BuildChain(m_node, prev_tip, GetScriptForDestination(PKHash(GenerateRandomKey().GetPubKey())), 3, fork));
|
||||
|
||||
// Part 3: Connect a new block on the chain without flushing
|
||||
// (m_last_flushed_block stays at 100). For a real node this would happen
|
||||
// in parallel with Sync(). Here we do it before Sync() to make the race
|
||||
// state deterministic.
|
||||
CreateAndProcessBlock({}, CScript() << OP_TRUE);
|
||||
sync_index(false, 101, 100);
|
||||
for (const auto& block : fork) {
|
||||
BOOST_REQUIRE(m_node.chainman->ProcessNewBlock(block, /*force_processing=*/true, /*min_pow_checked=*/true, nullptr));
|
||||
}
|
||||
|
||||
// Unblock the index thread so it can process the reorg
|
||||
promise.set_value();
|
||||
// Wait for the index to reach the new tip
|
||||
func_wait_until(blocking_height + 2, 5s);
|
||||
index.Stop();
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
||||
@@ -5,27 +5,19 @@
|
||||
#include <addresstype.h>
|
||||
#include <blockfilter.h>
|
||||
#include <chain.h>
|
||||
#include <consensus/merkle.h>
|
||||
#include <consensus/validation.h>
|
||||
#include <index/base.h>
|
||||
#include <index/blockfilterindex.h>
|
||||
#include <interfaces/chain.h>
|
||||
#include <interfaces/mining.h>
|
||||
#include <key.h>
|
||||
#include <node/blockstorage.h>
|
||||
#include <pow.h>
|
||||
#include <primitives/block.h>
|
||||
#include <primitives/transaction.h>
|
||||
#include <script/script.h>
|
||||
#include <sync.h>
|
||||
#include <test/util/blockfilter.h>
|
||||
#include <test/util/common.h>
|
||||
#include <test/util/mining.h>
|
||||
#include <test/util/setup_common.h>
|
||||
#include <test/util/time.h>
|
||||
#include <tinyformat.h>
|
||||
#include <uint256.h>
|
||||
#include <util/check.h>
|
||||
#include <util/fs.h>
|
||||
#include <validation.h>
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
@@ -34,11 +26,9 @@
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -46,11 +36,6 @@ using node::BlockManager;
|
||||
|
||||
BOOST_AUTO_TEST_SUITE(blockfilter_index_tests)
|
||||
|
||||
struct BuildChainTestingSetup : public TestChain100Setup {
|
||||
CBlock CreateBlock(const CBlockIndex* prev, const std::vector<CMutableTransaction>& txns, const CScript& scriptPubKey);
|
||||
bool BuildChain(const CBlockIndex* pindex, const CScript& coinbase_script_pub_key, size_t length, std::vector<std::shared_ptr<CBlock>>& chain);
|
||||
};
|
||||
|
||||
static bool CheckFilterLookups(BlockFilterIndex& filter_index, const CBlockIndex* block_index,
|
||||
uint256& last_header, const BlockManager& blockman)
|
||||
{
|
||||
@@ -85,58 +70,7 @@ static bool CheckFilterLookups(BlockFilterIndex& filter_index, const CBlockIndex
|
||||
return true;
|
||||
}
|
||||
|
||||
CBlock BuildChainTestingSetup::CreateBlock(const CBlockIndex* prev,
|
||||
const std::vector<CMutableTransaction>& txns,
|
||||
const CScript& scriptPubKey)
|
||||
{
|
||||
auto mining{interfaces::MakeMining(m_node)};
|
||||
auto block_template{mining->createNewBlock({
|
||||
.coinbase_output_script = scriptPubKey,
|
||||
}, /*cooldown=*/false)};
|
||||
BOOST_REQUIRE(block_template);
|
||||
CBlock block{block_template->getBlock()};
|
||||
block.hashPrevBlock = prev->GetBlockHash();
|
||||
block.nTime = prev->nTime + 1;
|
||||
|
||||
// Replace mempool-selected txns with just coinbase plus passed-in txns:
|
||||
block.vtx.resize(1);
|
||||
for (const CMutableTransaction& tx : txns) {
|
||||
block.vtx.push_back(MakeTransactionRef(tx));
|
||||
}
|
||||
{
|
||||
CMutableTransaction tx_coinbase{*block.vtx.at(0)};
|
||||
tx_coinbase.nLockTime = static_cast<uint32_t>(prev->nHeight);
|
||||
tx_coinbase.vin.at(0).scriptSig = CScript{} << prev->nHeight + 1;
|
||||
block.vtx.at(0) = MakeTransactionRef(std::move(tx_coinbase));
|
||||
block.hashMerkleRoot = BlockMerkleRoot(block);
|
||||
}
|
||||
|
||||
while (!CheckProofOfWork(block.GetHash(), block.nBits, m_node.chainman->GetConsensus())) ++block.nNonce;
|
||||
|
||||
return block;
|
||||
}
|
||||
|
||||
bool BuildChainTestingSetup::BuildChain(const CBlockIndex* pindex,
|
||||
const CScript& coinbase_script_pub_key,
|
||||
size_t length,
|
||||
std::vector<std::shared_ptr<CBlock>>& chain)
|
||||
{
|
||||
std::vector<CMutableTransaction> no_txns;
|
||||
|
||||
chain.resize(length);
|
||||
for (auto& block : chain) {
|
||||
block = std::make_shared<CBlock>(CreateBlock(pindex, no_txns, coinbase_script_pub_key));
|
||||
|
||||
BlockValidationState state;
|
||||
if (!Assert(m_node.chainman)->ProcessNewBlockHeaders({{*block}}, true, state, &pindex)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup)
|
||||
BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, TestChain100Setup)
|
||||
{
|
||||
BlockFilterIndex filter_index(interfaces::MakeChain(m_node), BlockFilterType::BASIC, 1_MiB, true);
|
||||
BOOST_REQUIRE(filter_index.Init());
|
||||
@@ -190,8 +124,8 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup)
|
||||
CScript coinbase_script_pub_key_A = GetScriptForDestination(PKHash(coinbase_key_A.GetPubKey()));
|
||||
CScript coinbase_script_pub_key_B = GetScriptForDestination(PKHash(coinbase_key_B.GetPubKey()));
|
||||
std::vector<std::shared_ptr<CBlock>> chainA, chainB;
|
||||
BOOST_REQUIRE(BuildChain(tip, coinbase_script_pub_key_A, 10, chainA));
|
||||
BOOST_REQUIRE(BuildChain(tip, coinbase_script_pub_key_B, 10, chainB));
|
||||
BOOST_REQUIRE(BuildChain(m_node, tip, coinbase_script_pub_key_A, 10, chainA));
|
||||
BOOST_REQUIRE(BuildChain(m_node, tip, coinbase_script_pub_key_B, 10, chainB));
|
||||
|
||||
// Check that new blocks on chain A get indexed.
|
||||
uint256 chainA_last_header = last_header;
|
||||
@@ -329,78 +263,4 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_init_destroy, BasicTestingSetup)
|
||||
BOOST_CHECK(filter_index == nullptr);
|
||||
}
|
||||
|
||||
class IndexReorgCrash : public BaseIndex
|
||||
{
|
||||
private:
|
||||
FakeNodeClock& m_clock;
|
||||
std::unique_ptr<BaseIndex::DB> m_db;
|
||||
std::shared_future<void> m_blocker;
|
||||
int m_blocking_height;
|
||||
|
||||
public:
|
||||
explicit IndexReorgCrash(std::unique_ptr<interfaces::Chain> chain, std::shared_future<void> blocker, int blocking_height, FakeNodeClock& clock)
|
||||
: BaseIndex(std::move(chain), "test index", "testidx"), m_clock(clock), m_blocker(blocker), m_blocking_height(blocking_height)
|
||||
{
|
||||
const fs::path path = gArgs.GetDataDirNet() / "index";
|
||||
fs::create_directories(path);
|
||||
m_db = std::make_unique<BaseIndex::DB>(path / "db", /*n_cache_size=*/0, /*f_memory=*/true, /*f_wipe=*/false);
|
||||
}
|
||||
|
||||
bool AllowPrune() const override { return false; }
|
||||
BaseIndex::DB& GetDB() const override { return *m_db; }
|
||||
|
||||
bool CustomAppend(const interfaces::BlockInfo& block) override
|
||||
{
|
||||
// Simulate a delay so new blocks can get connected during the initial sync
|
||||
if (block.height == m_blocking_height) m_blocker.wait();
|
||||
|
||||
// Move mock time forward so the best index gets updated only when we are not at the blocking height
|
||||
if (block.height == m_blocking_height - 1 || block.height > m_blocking_height) {
|
||||
m_clock += 31s;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(index_reorg_crash, BuildChainTestingSetup)
|
||||
{
|
||||
std::promise<void> promise;
|
||||
std::shared_future<void> blocker(promise.get_future());
|
||||
int blocking_height = WITH_LOCK(cs_main, return m_node.chainman->ActiveChain().Tip()->nHeight);
|
||||
|
||||
IndexReorgCrash index{interfaces::MakeChain(m_node), blocker, blocking_height, m_clock};
|
||||
BOOST_REQUIRE(index.Init());
|
||||
BOOST_REQUIRE(index.StartBackgroundSync());
|
||||
|
||||
auto func_wait_until = [&](int height, std::chrono::milliseconds timeout) {
|
||||
auto deadline = std::chrono::steady_clock::now() + timeout;
|
||||
while (index.GetSummary().best_block_height < height) {
|
||||
if (std::chrono::steady_clock::now() > deadline) {
|
||||
BOOST_FAIL(strprintf("Timeout waiting for index height %d (current: %d)", height, index.GetSummary().best_block_height));
|
||||
return;
|
||||
}
|
||||
std::this_thread::sleep_for(100ms);
|
||||
}
|
||||
};
|
||||
|
||||
// Wait until the index is one block before the fork point
|
||||
func_wait_until(blocking_height - 1, /*timeout=*/5s);
|
||||
|
||||
// Create a fork to trigger the reorg
|
||||
std::vector<std::shared_ptr<CBlock>> fork;
|
||||
const CBlockIndex* prev_tip = WITH_LOCK(cs_main, return m_node.chainman->ActiveChain().Tip()->pprev);
|
||||
BOOST_REQUIRE(BuildChain(prev_tip, GetScriptForDestination(PKHash(GenerateRandomKey().GetPubKey())), 3, fork));
|
||||
|
||||
for (const auto& block : fork) {
|
||||
BOOST_REQUIRE(m_node.chainman->ProcessNewBlock(block, /*force_processing=*/true, /*min_pow_checked=*/true, nullptr));
|
||||
}
|
||||
|
||||
// Unblock the index thread so it can process the reorg
|
||||
promise.set_value();
|
||||
// Wait for the index to reach the new tip
|
||||
func_wait_until(blocking_height + 2, 5s);
|
||||
index.Stop();
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
||||
@@ -3,20 +3,15 @@
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <chain.h>
|
||||
#include <chainparams.h>
|
||||
#include <coins.h>
|
||||
#include <consensus/validation.h>
|
||||
#include <index/coinstatsindex.h>
|
||||
#include <interfaces/chain.h>
|
||||
#include <kernel/coinstats.h>
|
||||
#include <kernel/types.h>
|
||||
#include <key.h>
|
||||
#include <primitives/block.h>
|
||||
#include <primitives/transaction.h>
|
||||
#include <script/script.h>
|
||||
#include <sync.h>
|
||||
#include <test/util/setup_common.h>
|
||||
#include <test/util/validation.h>
|
||||
#include <util/check.h>
|
||||
#include <validation.h>
|
||||
|
||||
@@ -27,8 +22,6 @@
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
using kernel::ChainstateRole;
|
||||
|
||||
BOOST_AUTO_TEST_SUITE(coinstatsindex_tests)
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(coinstatsindex_initial_sync, TestChain100Setup)
|
||||
@@ -82,45 +75,4 @@ BOOST_FIXTURE_TEST_CASE(coinstatsindex_initial_sync, TestChain100Setup)
|
||||
coin_stats_index.Stop();
|
||||
}
|
||||
|
||||
// Test shutdown between BlockConnected and ChainStateFlushed notifications,
|
||||
// make sure index is not corrupted and is able to reload.
|
||||
BOOST_FIXTURE_TEST_CASE(coinstatsindex_unclean_shutdown, TestChain100Setup)
|
||||
{
|
||||
Chainstate& chainstate = Assert(m_node.chainman)->ActiveChainstate();
|
||||
const CChainParams& params = Params();
|
||||
{
|
||||
CoinStatsIndex index{interfaces::MakeChain(m_node), 1_MiB};
|
||||
BOOST_REQUIRE(index.Init());
|
||||
index.Sync();
|
||||
std::shared_ptr<const CBlock> new_block;
|
||||
CBlockIndex* new_block_index = nullptr;
|
||||
{
|
||||
const CScript script_pub_key{CScript() << ToByteVector(coinbaseKey.GetPubKey()) << OP_CHECKSIG};
|
||||
const CBlock block = this->CreateBlock({}, script_pub_key);
|
||||
|
||||
new_block = std::make_shared<CBlock>(block);
|
||||
|
||||
LOCK(cs_main);
|
||||
BlockValidationState state;
|
||||
BOOST_CHECK(CheckBlock(block, state, params.GetConsensus()));
|
||||
BOOST_CHECK(m_node.chainman->AcceptBlock(new_block, state, &new_block_index, true, nullptr, nullptr, true));
|
||||
CCoinsViewCache view(&chainstate.CoinsTip());
|
||||
BOOST_CHECK(chainstate.ConnectBlock(block, state, new_block_index, view));
|
||||
}
|
||||
// Send block connected notification, then stop the index without
|
||||
// sending a chainstate flushed notification. Prior to #24138, this
|
||||
// would cause the index to be corrupted and fail to reload.
|
||||
ValidationInterfaceTest::BlockConnected(ChainstateRole{}, index, new_block, new_block_index);
|
||||
index.Stop();
|
||||
}
|
||||
|
||||
{
|
||||
CoinStatsIndex index{interfaces::MakeChain(m_node), 1_MiB};
|
||||
BOOST_REQUIRE(index.Init());
|
||||
// Make sure the index can be loaded.
|
||||
BOOST_REQUIRE(index.StartBackgroundSync());
|
||||
index.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
||||
@@ -75,6 +75,47 @@ std::vector<std::shared_ptr<CBlock>> CreateBlockChain(size_t total_height, const
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool BuildChain(const NodeContext& node, const CBlockIndex* pindex,
|
||||
const CScript& coinbase_script_pub_key,
|
||||
size_t length,
|
||||
std::vector<std::shared_ptr<CBlock>>& chain)
|
||||
{
|
||||
auto mining{interfaces::MakeMining(node)};
|
||||
const Consensus::Params& consensus{Assert(node.chainman)->GetConsensus()};
|
||||
|
||||
chain.resize(length);
|
||||
for (auto& chain_block : chain) {
|
||||
auto block_template{mining->createNewBlock({
|
||||
.use_mempool = false,
|
||||
.coinbase_output_script = coinbase_script_pub_key,
|
||||
}, /*cooldown=*/false)};
|
||||
CBlock block{Assert(block_template)->getBlock()};
|
||||
|
||||
// The template is built on the active tip, so repoint it at pindex and
|
||||
// redo the fields that depend on the predecessor.
|
||||
block.hashPrevBlock = pindex->GetBlockHash();
|
||||
block.nTime = pindex->nTime + 1;
|
||||
{
|
||||
CMutableTransaction tx_coinbase{*block.vtx.at(0)};
|
||||
tx_coinbase.nLockTime = static_cast<uint32_t>(pindex->nHeight);
|
||||
tx_coinbase.vin.at(0).scriptSig = CScript{} << pindex->nHeight + 1;
|
||||
block.vtx.at(0) = MakeTransactionRef(std::move(tx_coinbase));
|
||||
block.hashMerkleRoot = BlockMerkleRoot(block);
|
||||
}
|
||||
|
||||
while (!CheckProofOfWork(block.GetHash(), block.nBits, consensus)) ++block.nNonce;
|
||||
|
||||
chain_block = std::make_shared<CBlock>(std::move(block));
|
||||
|
||||
BlockValidationState state;
|
||||
if (!Assert(node.chainman)->ProcessNewBlockHeaders({{*chain_block}}, true, state, &pindex)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
COutPoint MineBlock(const NodeContext& node, const node::BlockCreateOptions& assembler_options)
|
||||
{
|
||||
auto block = PrepareBlock(node, assembler_options);
|
||||
|
||||
@@ -11,8 +11,10 @@
|
||||
#include <vector>
|
||||
|
||||
class CBlock;
|
||||
class CBlockIndex;
|
||||
class CChainParams;
|
||||
class COutPoint;
|
||||
class CScript;
|
||||
namespace node {
|
||||
struct BlockCreateOptions;
|
||||
struct NodeContext;
|
||||
@@ -21,6 +23,13 @@ struct NodeContext;
|
||||
/** Create a blockchain, starting from genesis */
|
||||
std::vector<std::shared_ptr<CBlock>> CreateBlockChain(size_t total_height, const CChainParams& params);
|
||||
|
||||
/**
|
||||
* Build a chain of `length` coinbase-only blocks on top of `pindex` (which need
|
||||
* not be the active tip, allowing forks to be created) and submit their headers.
|
||||
* The blocks themselves are returned in `chain` for the caller to process.
|
||||
*/
|
||||
bool BuildChain(const node::NodeContext& node, const CBlockIndex* pindex, const CScript& coinbase_script_pub_key, size_t length, std::vector<std::shared_ptr<CBlock>>& chain);
|
||||
|
||||
/** Returns the generated coin */
|
||||
COutPoint MineBlock(const node::NodeContext&,
|
||||
const node::BlockCreateOptions& assembler_options);
|
||||
|
||||
Reference in New Issue
Block a user