This commit is contained in:
Reproducibility Matters 2025-01-08 18:07:06 +00:00 committed by GitHub
commit 8199e6c7de
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 70 additions and 89 deletions

View file

@ -64,10 +64,10 @@ int main(int argc, char* argv[])
// SETUP: Context // SETUP: Context
kernel::Context kernel_context{}; kernel::Context kernel_context{};
// We can't use a goto here, but we can use an assert since none of the if (!kernel::SanityChecks(kernel_context)) {
// things instantiated so far requires running the epilogue to be torn down std::cerr << "Failed sanity check.";
// properly return 1;
assert(kernel::SanityChecks(kernel_context)); }
ValidationSignals validation_signals{std::make_unique<util::ImmediateTaskRunner>()}; ValidationSignals validation_signals{std::make_unique<util::ImmediateTaskRunner>()};
@ -131,12 +131,12 @@ int main(int argc, char* argv[])
auto [status, error] = node::LoadChainstate(chainman, cache_sizes, options); auto [status, error] = node::LoadChainstate(chainman, cache_sizes, options);
if (status != node::ChainstateLoadStatus::SUCCESS) { if (status != node::ChainstateLoadStatus::SUCCESS) {
std::cerr << "Failed to load Chain state from your datadir." << std::endl; std::cerr << "Failed to load Chain state from your datadir." << std::endl;
goto epilogue; return 1;
} else { } else {
std::tie(status, error) = node::VerifyLoadedChainstate(chainman, options); std::tie(status, error) = node::VerifyLoadedChainstate(chainman, options);
if (status != node::ChainstateLoadStatus::SUCCESS) { if (status != node::ChainstateLoadStatus::SUCCESS) {
std::cerr << "Failed to verify loaded Chain state from your datadir." << std::endl; std::cerr << "Failed to verify loaded Chain state from your datadir." << std::endl;
goto epilogue; return 1;
} }
} }
@ -144,7 +144,7 @@ int main(int argc, char* argv[])
BlockValidationState state; BlockValidationState state;
if (!chainstate->ActivateBestChain(state, nullptr)) { if (!chainstate->ActivateBestChain(state, nullptr)) {
std::cerr << "Failed to connect best block (" << state.ToString() << ")" << std::endl; std::cerr << "Failed to connect best block (" << state.ToString() << ")" << std::endl;
goto epilogue; return 1;
} }
} }
@ -255,18 +255,4 @@ int main(int argc, char* argv[])
break; break;
} }
} }
epilogue:
// Without this precise shutdown sequence, there will be a lot of nullptr
// dereferencing and UB.
validation_signals.FlushBackgroundCallbacks();
{
LOCK(cs_main);
for (Chainstate* chainstate : chainman.GetAll()) {
if (chainstate->CanFlushToDisk()) {
chainstate->ForceFlushStateToDisk();
chainstate->ResetCoinsViews();
}
}
}
} }

View file

@ -346,8 +346,13 @@ void Shutdown(NodeContext& node)
} }
} }
// After there are no more peers/RPC left to give us new data which may generate // After there are no more peers/RPC left to give us new data which may
// CValidationInterface callbacks, flush them... // generate CValidationInterface callbacks, flush them. Any future
// callbacks will be dropped. This should absolutely be safe - if missing a
// callback results in an unrecoverable situation, unclean shutdown would
// too. The only reason to do the above flushes is to let the wallet and
// indexes catch up with our current chain to avoid any strange pruning
// edge cases and make next startup faster by avoiding rescan.
if (node.validation_signals) node.validation_signals->FlushBackgroundCallbacks(); if (node.validation_signals) node.validation_signals->FlushBackgroundCallbacks();
// Stop and delete all indexes only after flushing background callbacks. // Stop and delete all indexes only after flushing background callbacks.
@ -357,21 +362,6 @@ void Shutdown(NodeContext& node)
DestroyAllBlockFilterIndexes(); DestroyAllBlockFilterIndexes();
node.indexes.clear(); // all instances are nullptr now node.indexes.clear(); // all instances are nullptr now
// Any future callbacks will be dropped. This should absolutely be safe - if
// missing a callback results in an unrecoverable situation, unclean shutdown
// would too. The only reason to do the above flushes is to let the wallet catch
// up with our current chain to avoid any strange pruning edge cases and make
// next startup faster by avoiding rescan.
if (node.chainman) {
LOCK(cs_main);
for (Chainstate* chainstate : node.chainman->GetAll()) {
if (chainstate->CanFlushToDisk()) {
chainstate->ForceFlushStateToDisk();
chainstate->ResetCoinsViews();
}
}
}
for (const auto& client : node.chain_clients) { for (const auto& client : node.chain_clients) {
client->stop(); client->stop();
} }
@ -387,9 +377,9 @@ void Shutdown(NodeContext& node)
if (node.validation_signals) { if (node.validation_signals) {
node.validation_signals->UnregisterAllValidationInterfaces(); node.validation_signals->UnregisterAllValidationInterfaces();
} }
node.chainman.reset();
node.mempool.reset(); node.mempool.reset();
node.fee_estimator.reset(); node.fee_estimator.reset();
node.chainman.reset();
node.validation_signals.reset(); node.validation_signals.reset();
node.scheduler.reset(); node.scheduler.reset();
node.ecc_context.reset(); node.ecc_context.reset();

View file

@ -341,6 +341,8 @@ FUZZ_TARGET(ephemeral_package_eval, .init = initialize_tx_pool)
node.validation_signals->UnregisterSharedValidationInterface(outpoints_updater); node.validation_signals->UnregisterSharedValidationInterface(outpoints_updater);
chainstate.SetMempool(g_setup->m_node.mempool.get());
WITH_LOCK(::cs_main, tx_pool.check(chainstate.CoinsTip(), chainstate.m_chain.Height() + 1)); WITH_LOCK(::cs_main, tx_pool.check(chainstate.CoinsTip(), chainstate.m_chain.Height() + 1));
} }
@ -536,6 +538,8 @@ FUZZ_TARGET(tx_package_eval, .init = initialize_tx_pool)
node.validation_signals->UnregisterSharedValidationInterface(outpoints_updater); node.validation_signals->UnregisterSharedValidationInterface(outpoints_updater);
chainstate.SetMempool(g_setup->m_node.mempool.get());
WITH_LOCK(::cs_main, tx_pool.check(chainstate.CoinsTip(), chainstate.m_chain.Height() + 1)); WITH_LOCK(::cs_main, tx_pool.check(chainstate.CoinsTip(), chainstate.m_chain.Height() + 1));
} }
} // namespace } // namespace

View file

@ -93,7 +93,7 @@ void SetMempoolConstraints(ArgsManager& args, FuzzedDataProvider& fuzzed_data_pr
ToString(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 999))); ToString(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 999)));
} }
void Finish(FuzzedDataProvider& fuzzed_data_provider, MockedTxPool& tx_pool, Chainstate& chainstate) void Finish(FuzzedDataProvider& fuzzed_data_provider, MockedTxPool& tx_pool, DummyChainState& chainstate)
{ {
WITH_LOCK(::cs_main, tx_pool.check(chainstate.CoinsTip(), chainstate.m_chain.Height() + 1)); WITH_LOCK(::cs_main, tx_pool.check(chainstate.CoinsTip(), chainstate.m_chain.Height() + 1));
{ {
@ -112,6 +112,7 @@ void Finish(FuzzedDataProvider& fuzzed_data_provider, MockedTxPool& tx_pool, Cha
WITH_LOCK(::cs_main, tx_pool.check(chainstate.CoinsTip(), chainstate.m_chain.Height() + 1)); WITH_LOCK(::cs_main, tx_pool.check(chainstate.CoinsTip(), chainstate.m_chain.Height() + 1));
} }
g_setup->m_node.validation_signals->SyncWithValidationInterfaceQueue(); g_setup->m_node.validation_signals->SyncWithValidationInterfaceQueue();
chainstate.SetMempool(g_setup->m_node.mempool.get());
} }
void MockTime(FuzzedDataProvider& fuzzed_data_provider, const Chainstate& chainstate) void MockTime(FuzzedDataProvider& fuzzed_data_provider, const Chainstate& chainstate)

View file

@ -6,21 +6,10 @@
#define BITCOIN_TEST_FUZZ_UTIL_MEMPOOL_H #define BITCOIN_TEST_FUZZ_UTIL_MEMPOOL_H
#include <kernel/mempool_entry.h> #include <kernel/mempool_entry.h>
#include <validation.h>
class CTransaction; class CTransaction;
class CTxMemPool;
class FuzzedDataProvider; class FuzzedDataProvider;
class DummyChainState final : public Chainstate
{
public:
void SetMempool(CTxMemPool* mempool)
{
m_mempool = mempool;
}
};
[[nodiscard]] CTxMemPoolEntry ConsumeTxMemPoolEntry(FuzzedDataProvider& fuzzed_data_provider, const CTransaction& tx) noexcept; [[nodiscard]] CTxMemPoolEntry ConsumeTxMemPoolEntry(FuzzedDataProvider& fuzzed_data_provider, const CTransaction& tx) noexcept;
#endif // BITCOIN_TEST_FUZZ_UTIL_MEMPOOL_H #endif // BITCOIN_TEST_FUZZ_UTIL_MEMPOOL_H

View file

@ -4,22 +4,21 @@
#include <node/mempool_persist.h> #include <node/mempool_persist.h>
#include <node/mempool_args.h>
#include <node/mempool_persist_args.h> #include <node/mempool_persist_args.h>
#include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/fuzz.h> #include <test/fuzz/fuzz.h>
#include <test/fuzz/util.h> #include <test/fuzz/util.h>
#include <test/fuzz/util/mempool.h>
#include <test/util/setup_common.h> #include <test/util/setup_common.h>
#include <test/util/txmempool.h>
#include <txmempool.h> #include <txmempool.h>
#include <util/check.h>
#include <util/time.h> #include <util/time.h>
#include <util/translation.h>
#include <validation.h> #include <validation.h>
#include <cstdint> #include <functional>
#include <vector> #include <memory>
namespace fs {
class path;
}
using node::DumpMempool; using node::DumpMempool;
using node::LoadMempool; using node::LoadMempool;
@ -27,12 +26,12 @@ using node::LoadMempool;
using node::MempoolPath; using node::MempoolPath;
namespace { namespace {
const TestingSetup* g_setup; TestingSetup* g_setup;
} // namespace } // namespace
void initialize_validation_load_mempool() void initialize_validation_load_mempool()
{ {
static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(); static auto testing_setup = MakeNoLogFileContext<TestingSetup>();
g_setup = testing_setup.get(); g_setup = testing_setup.get();
} }
@ -43,12 +42,9 @@ FUZZ_TARGET(validation_load_mempool, .init = initialize_validation_load_mempool)
SetMockTime(ConsumeTime(fuzzed_data_provider)); SetMockTime(ConsumeTime(fuzzed_data_provider));
FuzzedFileProvider fuzzed_file_provider{fuzzed_data_provider}; FuzzedFileProvider fuzzed_file_provider{fuzzed_data_provider};
bilingual_str error; auto& pool{g_setup->ReplaceMempool()};
CTxMemPool pool{MemPoolOptionsForTest(g_setup->m_node), error};
Assert(error.empty());
auto& chainstate{static_cast<DummyChainState&>(g_setup->m_node.chainman->ActiveChainstate())}; auto& chainstate{g_setup->m_node.chainman->ActiveChainstate()};
chainstate.SetMempool(&pool);
auto fuzzed_fopen = [&](const fs::path&, const char*) { auto fuzzed_fopen = [&](const fs::path&, const char*) {
return fuzzed_file_provider.open(); return fuzzed_file_provider.open();

View file

@ -49,14 +49,7 @@ struct MinerTestingSetup : public TestingSetup {
} }
CTxMemPool& MakeMempool() CTxMemPool& MakeMempool()
{ {
// Delete the previous mempool to ensure with valgrind that the old return ReplaceMempool();
// pointer is not accessed, when the new one should be accessed
// instead.
m_node.mempool.reset();
bilingual_str error;
m_node.mempool = std::make_unique<CTxMemPool>(MemPoolOptionsForTest(m_node), error);
Assert(error.empty());
return *m_node.mempool;
} }
std::unique_ptr<Mining> MakeMining() std::unique_ptr<Mining> MakeMining()
{ {

View file

@ -74,6 +74,7 @@ CreateAndActivateUTXOSnapshot(
CBlockIndex *orig_tip = node.chainman->ActiveChainstate().m_chain.Tip(); CBlockIndex *orig_tip = node.chainman->ActiveChainstate().m_chain.Tip();
uint256 gen_hash = node.chainman->ActiveChainstate().m_chain[0]->GetBlockHash(); uint256 gen_hash = node.chainman->ActiveChainstate().m_chain[0]->GetBlockHash();
node.chainman->ResetChainstates(); node.chainman->ResetChainstates();
Assert(node.chainman->GetAll().size() == 0);
node.chainman->InitializeChainstate(node.mempool.get()); node.chainman->InitializeChainstate(node.mempool.get());
Chainstate& chain = node.chainman->ActiveChainstate(); Chainstate& chain = node.chainman->ActiveChainstate();
Assert(chain.LoadGenesisBlock()); Assert(chain.LoadGenesisBlock());

View file

@ -272,9 +272,9 @@ ChainTestingSetup::~ChainTestingSetup()
m_node.addrman.reset(); m_node.addrman.reset();
m_node.netgroupman.reset(); m_node.netgroupman.reset();
m_node.args = nullptr; m_node.args = nullptr;
m_node.chainman.reset();
m_node.mempool.reset(); m_node.mempool.reset();
Assert(!m_node.fee_estimator); // Each test must create a local object, if they wish to use the fee_estimator Assert(!m_node.fee_estimator); // Each test must create a local object, if they wish to use the fee_estimator
m_node.chainman.reset();
m_node.validation_signals.reset(); m_node.validation_signals.reset();
m_node.scheduler.reset(); m_node.scheduler.reset();
} }
@ -304,6 +304,20 @@ void ChainTestingSetup::LoadVerifyActivateChainstate()
} }
} }
CTxMemPool& ChainTestingSetup::ReplaceMempool()
{
// Delete the previous mempool to ensure with valgrind that the old
// pointer is not accessed, when the new one should be accessed
// instead.
m_node.mempool.reset();
bilingual_str error;
m_node.mempool = std::make_unique<CTxMemPool>(MemPoolOptionsForTest(m_node), error);
Assert(error.empty());
auto& dummy_chainstate{static_cast<DummyChainState&>(m_node.chainman->ActiveChainstate())};
dummy_chainstate.SetMempool(m_node.mempool.get());
return *m_node.mempool;
}
TestingSetup::TestingSetup( TestingSetup::TestingSetup(
const ChainType chainType, const ChainType chainType,
TestOpts opts) TestOpts opts)

View file

@ -113,6 +113,8 @@ struct ChainTestingSetup : public BasicTestingSetup {
// Supplies a chainstate, if one is needed // Supplies a chainstate, if one is needed
void LoadVerifyActivateChainstate(); void LoadVerifyActivateChainstate();
CTxMemPool& ReplaceMempool();
}; };
/** Testing setup that configures a complete environment. /** Testing setup that configures a complete environment.

View file

@ -8,6 +8,7 @@
#include <policy/packages.h> #include <policy/packages.h>
#include <txmempool.h> #include <txmempool.h>
#include <util/time.h> #include <util/time.h>
#include <validation.h>
namespace node { namespace node {
struct NodeContext; struct NodeContext;
@ -16,6 +17,15 @@ struct PackageMempoolAcceptResult;
CTxMemPool::Options MemPoolOptionsForTest(const node::NodeContext& node); CTxMemPool::Options MemPoolOptionsForTest(const node::NodeContext& node);
class DummyChainState final : public Chainstate
{
public:
void SetMempool(CTxMemPool* mempool)
{
m_mempool = mempool;
}
};
struct TestMemPoolEntryHelper { struct TestMemPoolEntryHelper {
// Default values // Default values
CAmount nFee{0}; CAmount nFee{0};

View file

@ -369,23 +369,12 @@ struct SnapshotTestSetup : TestChain100Setup {
// @returns a reference to the "restarted" ChainstateManager // @returns a reference to the "restarted" ChainstateManager
ChainstateManager& SimulateNodeRestart() ChainstateManager& SimulateNodeRestart()
{ {
ChainstateManager& chainman = *Assert(m_node.chainman);
BOOST_TEST_MESSAGE("Simulating node restart"); BOOST_TEST_MESSAGE("Simulating node restart");
{ {
for (Chainstate* cs : chainman.GetAll()) {
LOCK(::cs_main);
cs->ForceFlushStateToDisk();
}
// Process all callbacks referring to the old manager before wiping it.
m_node.validation_signals->SyncWithValidationInterfaceQueue();
LOCK(::cs_main);
chainman.ResetChainstates();
BOOST_CHECK_EQUAL(chainman.GetAll().size(), 0);
m_node.notifications = std::make_unique<KernelNotifications>(Assert(m_node.shutdown_request), m_node.exit_status, *Assert(m_node.warnings)); m_node.notifications = std::make_unique<KernelNotifications>(Assert(m_node.shutdown_request), m_node.exit_status, *Assert(m_node.warnings));
const ChainstateManager::Options chainman_opts{ const ChainstateManager::Options chainman_opts{
.chainparams = ::Params(), .chainparams = ::Params(),
.datadir = chainman.m_options.datadir, .datadir = Assert(m_node.chainman)->m_options.datadir,
.notifications = *m_node.notifications, .notifications = *m_node.notifications,
.signals = m_node.validation_signals.get(), .signals = m_node.validation_signals.get(),
}; };
@ -397,7 +386,11 @@ struct SnapshotTestSetup : TestChain100Setup {
// For robustness, ensure the old manager is destroyed before creating a // For robustness, ensure the old manager is destroyed before creating a
// new one. // new one.
m_node.chainman.reset(); m_node.chainman.reset();
// Process all callbacks referring to the old manager before creating a
// new one.
m_node.validation_signals->SyncWithValidationInterfaceQueue();
m_node.chainman = std::make_unique<ChainstateManager>(*Assert(m_node.shutdown_signal), chainman_opts, blockman_opts); m_node.chainman = std::make_unique<ChainstateManager>(*Assert(m_node.shutdown_signal), chainman_opts, blockman_opts);
BOOST_CHECK_EQUAL(m_node.chainman->GetAll().size(), 0);
} }
return *Assert(m_node.chainman); return *Assert(m_node.chainman);
} }

View file

@ -6320,6 +6320,11 @@ ChainstateManager::ChainstateManager(const util::SignalInterrupt& interrupt, Opt
ChainstateManager::~ChainstateManager() ChainstateManager::~ChainstateManager()
{ {
LOCK(::cs_main); LOCK(::cs_main);
for (Chainstate* chainstate : GetAll()) {
if (chainstate->CanFlushToDisk()) {
chainstate->ForceFlushStateToDisk();
}
}
m_versionbitscache.Clear(); m_versionbitscache.Clear();
} }

View file

@ -636,9 +636,6 @@ public:
return Assert(m_coins_views)->m_catcherview; return Assert(m_coins_views)->m_catcherview;
} }
//! Destructs all objects related to accessing the UTXO set.
void ResetCoinsViews() { m_coins_views.reset(); }
//! Does this chainstate have a UTXO set attached? //! Does this chainstate have a UTXO set attached?
bool HasCoinsViews() const { return (bool)m_coins_views; } bool HasCoinsViews() const { return (bool)m_coins_views; }