Merge 75135c673e into merged_master (Bitcoin PR bitcoin/bitcoin#27861)

This commit is contained in:
Byron Hambly 2025-06-30 16:03:15 +02:00
commit a8a9ae2ae1
32 changed files with 407 additions and 205 deletions

View file

@ -216,6 +216,7 @@ BITCOIN_CORE_H = \
netbase.h \
netgroup.h \
netmessagemaker.h \
node/abort.h \
node/blockmanager_args.h \
node/blockstorage.h \
node/caches.h \
@ -326,6 +327,7 @@ BITCOIN_CORE_H = \
util/readwritefile.h \
util/result.h \
util/serfloat.h \
util/signalinterrupt.h \
util/sock.h \
util/spanparsing.h \
util/string.h \
@ -419,6 +421,7 @@ libbitcoin_node_a_SOURCES = \
net.cpp \
net_processing.cpp \
netgroup.cpp \
node/abort.cpp \
node/blockmanager_args.cpp \
node/blockstorage.cpp \
node/caches.cpp \
@ -778,6 +781,7 @@ libbitcoin_util_a_SOURCES = \
util/moneystr.cpp \
util/rbf.cpp \
util/readwritefile.cpp \
util/signalinterrupt.cpp \
util/thread.cpp \
util/threadinterrupt.cpp \
util/threadnames.cpp \
@ -1016,7 +1020,6 @@ libbitcoinkernel_la_SOURCES = \
logging.cpp \
node/blockstorage.cpp \
node/chainstate.cpp \
node/interface_ui.cpp \
node/utxo_snapshot.cpp \
policy/feerate.cpp \
policy/fees.cpp \
@ -1055,6 +1058,7 @@ libbitcoinkernel_la_SOURCES = \
util/moneystr.cpp \
util/rbf.cpp \
util/serfloat.cpp \
util/signalinterrupt.cpp \
util/strencodings.cpp \
util/string.cpp \
util/syserror.cpp \

View file

@ -97,6 +97,15 @@ int main(int argc, char* argv[])
{
std::cout << "Warning: " << warning.original << std::endl;
}
void flushError(const std::string& debug_message) override
{
std::cerr << "Error flushing block data to disk: " << debug_message << std::endl;
}
void fatalError(const std::string& debug_message, const bilingual_str& user_message) override
{
std::cerr << "Error: " << debug_message << std::endl;
std::cerr << (user_message.empty() ? "A fatal internal error occurred." : user_message.original) << std::endl;
}
};
auto notifications = std::make_unique<KernelNotifications>();
@ -112,8 +121,9 @@ int main(int argc, char* argv[])
const node::BlockManager::Options blockman_opts{
.chainparams = chainman_opts.chainparams,
.blocks_dir = abs_datadir / "blocks",
.notifications = chainman_opts.notifications,
};
ChainstateManager chainman{chainman_opts, blockman_opts};
ChainstateManager chainman{kernel_context.interrupt, chainman_opts, blockman_opts};
node::CacheSizes cache_sizes;
cache_sizes.block_tree_db = 2 << 20;

View file

@ -8,6 +8,7 @@
#include <interfaces/chain.h>
#include <kernel/chain.h>
#include <logging.h>
#include <node/abort.h>
#include <node/blockstorage.h>
#include <node/context.h>
#include <node/database_args.h>
@ -30,9 +31,10 @@ constexpr auto SYNC_LOG_INTERVAL{30s};
constexpr auto SYNC_LOCATOR_WRITE_INTERVAL{30s};
template <typename... Args>
static void FatalError(const char* fmt, const Args&... args)
void BaseIndex::FatalErrorf(const char* fmt, const Args&... args)
{
AbortNode(tfm::format(fmt, args...));
auto message = tfm::format(fmt, args...);
node::AbortNode(m_chain->context()->exit_status, message);
}
CBlockLocator GetLocator(interfaces::Chain& chain, const uint256& block_hash)
@ -197,7 +199,7 @@ void BaseIndex::ThreadSync()
break;
}
if (pindex_next->pprev != pindex && !Rewind(pindex, pindex_next->pprev)) {
FatalError("%s: Failed to rewind index %s to a previous chain tip",
FatalErrorf("%s: Failed to rewind index %s to a previous chain tip",
__func__, GetName());
return;
}
@ -221,14 +223,14 @@ void BaseIndex::ThreadSync()
CBlock block;
interfaces::BlockInfo block_info = kernel::MakeBlockInfo(pindex);
if (!m_chainstate->m_blockman.ReadBlockFromDisk(block, *pindex)) {
FatalError("%s: Failed to read block %s from disk",
FatalErrorf("%s: Failed to read block %s from disk",
__func__, pindex->GetBlockHash().ToString());
return;
} else {
block_info.data = &block;
}
if (!CustomAppend(block_info)) {
FatalError("%s: Failed to write block %s to index database",
FatalErrorf("%s: Failed to write block %s to index database",
__func__, pindex->GetBlockHash().ToString());
return;
}
@ -294,7 +296,7 @@ void BaseIndex::BlockConnected(const std::shared_ptr<const CBlock>& block, const
const CBlockIndex* best_block_index = m_best_block_index.load();
if (!best_block_index) {
if (pindex->nHeight != 0) {
FatalError("%s: First block connected is not the genesis block (height=%d)",
FatalErrorf("%s: First block connected is not the genesis block (height=%d)",
__func__, pindex->nHeight);
return;
}
@ -312,7 +314,7 @@ void BaseIndex::BlockConnected(const std::shared_ptr<const CBlock>& block, const
return;
}
if (best_block_index != pindex->pprev && !Rewind(best_block_index, pindex->pprev)) {
FatalError("%s: Failed to rewind index %s to a previous chain tip",
FatalErrorf("%s: Failed to rewind index %s to a previous chain tip",
__func__, GetName());
return;
}
@ -325,7 +327,7 @@ void BaseIndex::BlockConnected(const std::shared_ptr<const CBlock>& block, const
// processed, and the index object being safe to delete.
SetBestBlockIndex(pindex);
} else {
FatalError("%s: Failed to write block %s to index",
FatalErrorf("%s: Failed to write block %s to index",
__func__, pindex->GetBlockHash().ToString());
return;
}
@ -345,7 +347,7 @@ void BaseIndex::ChainStateFlushed(const CBlockLocator& locator)
}
if (!locator_tip_index) {
FatalError("%s: First block (hash=%s) in locator was not found",
FatalErrorf("%s: First block (hash=%s) in locator was not found",
__func__, locator_tip_hash.ToString());
return;
}

View file

@ -94,6 +94,9 @@ private:
virtual bool AllowPrune() const = 0;
template <typename... Args>
void FatalErrorf(const char* fmt, const Args&... args);
protected:
std::unique_ptr<interfaces::Chain> m_chain;
Chainstate* m_chainstate{nullptr};

View file

@ -868,10 +868,6 @@ bool AppInitBasicSetup(const ArgsManager& args, std::atomic<int>& exit_status)
// Enable heap terminate-on-corruption
HeapSetInformation(nullptr, HeapEnableTerminationOnCorruption, nullptr, 0);
#endif
if (!InitShutdownState(exit_status)) {
return InitError(Untranslated("Initializing wait-for-shutdown state failed."));
}
if (!SetupNetworking()) {
return InitError(Untranslated("Initializing networking failed."));
}
@ -1062,7 +1058,7 @@ bool AppInitParameterInteraction(const ArgsManager& args)
// Also report errors from parsing before daemonization
{
KernelNotifications notifications{};
kernel::Notifications notifications{};
ChainstateManager::Options chainman_opts_dummy{
.chainparams = chainparams,
.datadir = args.GetDataDirNet(),
@ -1077,6 +1073,7 @@ bool AppInitParameterInteraction(const ArgsManager& args)
BlockManager::Options blockman_opts_dummy{
.chainparams = chainman_opts_dummy.chainparams,
.blocks_dir = args.GetBlocksDirPath(),
.notifications = chainman_opts_dummy.notifications,
};
auto blockman_result{ApplyArgsManOptions(args, blockman_opts_dummy)};
if (!blockman_result) {
@ -1551,7 +1548,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
// ********************************************************* Step 7: load block chain
node.notifications = std::make_unique<KernelNotifications>();
node.notifications = std::make_unique<KernelNotifications>(node.exit_status);
fReindex = args.GetBoolArg("-reindex", false);
bool fReindexChainState = args.GetBoolArg("-reindex-chainstate", false);
ChainstateManager::Options chainman_opts{
@ -1567,6 +1564,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
BlockManager::Options blockman_opts{
.chainparams = chainman_opts.chainparams,
.blocks_dir = args.GetBlocksDirPath(),
.notifications = chainman_opts.notifications,
};
Assert(ApplyArgsManOptions(args, blockman_opts)); // no error can happen, already checked in AppInitParameterInteraction
@ -1606,7 +1604,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
for (bool fLoaded = false; !fLoaded && !ShutdownRequested();) {
node.mempool = std::make_unique<CTxMemPool>(mempool_opts);
node.chainman = std::make_unique<ChainstateManager>(chainman_opts, blockman_opts);
node.chainman = std::make_unique<ChainstateManager>(node.kernel->interrupt, chainman_opts, blockman_opts);
ChainstateManager& chainman = *node.chainman;
node::ChainstateLoadOptions options;

View file

@ -5,6 +5,7 @@
#ifndef BITCOIN_KERNEL_BLOCKMANAGER_OPTS_H
#define BITCOIN_KERNEL_BLOCKMANAGER_OPTS_H
#include <kernel/notifications_interface.h>
#include <util/fs.h>
#include <cstdint>
@ -25,6 +26,7 @@ struct BlockManagerOpts {
bool fast_prune{false};
bool stop_after_block_import{DEFAULT_STOPAFTERBLOCKIMPORT};
const fs::path blocks_dir;
Notifications& notifications;
};
} // namespace kernel

View file

@ -14,9 +14,12 @@
namespace kernel {
Context* g_context;
Context::Context()
{
assert(!g_context);
g_context = this;
std::string sha256_algo = SHA256AutoDetect();
LogPrintf("Using the '%s' SHA256 implementation\n", sha256_algo);
RandomInit();
@ -26,6 +29,8 @@ Context::Context()
Context::~Context()
{
ECC_Stop();
assert(g_context);
g_context = nullptr;
}
} // namespace kernel

View file

@ -5,6 +5,8 @@
#ifndef BITCOIN_KERNEL_CONTEXT_H
#define BITCOIN_KERNEL_CONTEXT_H
#include <util/signalinterrupt.h>
#include <memory>
namespace kernel {
@ -16,12 +18,24 @@ namespace kernel {
//! State stored directly in this struct should be simple. More complex state
//! should be stored to std::unique_ptr members pointing to opaque types.
struct Context {
//! Interrupt object that can be used to stop long-running kernel operations.
util::SignalInterrupt interrupt;
//! Declare default constructor and destructor that are not inline, so code
//! instantiating the kernel::Context struct doesn't need to #include class
//! definitions for all the unique_ptr members.
Context();
~Context();
};
//! Global pointer to kernel::Context for legacy code. New code should avoid
//! using this, and require state it needs to be passed to it directly.
//!
//! Having this pointer is useful because it allows state be moved out of global
//! variables into the kernel::Context struct before all global references to
//! that state are removed. This allows the global references to be removed
//! incrementally, instead of all at once.
extern Context* g_context;
} // namespace kernel
#endif // BITCOIN_KERNEL_CONTEXT_H

View file

@ -9,13 +9,13 @@
#include <logging.h>
#include <primitives/transaction.h>
#include <serialize.h>
#include <shutdown.h>
#include <streams.h>
#include <sync.h>
#include <txmempool.h>
#include <uint256.h>
#include <util/fs.h>
#include <util/fs_helpers.h>
#include <util/signalinterrupt.h>
#include <util/time.h>
#include <validation.h>
@ -95,7 +95,7 @@ bool LoadMempool(CTxMemPool& pool, const fs::path& load_path, Chainstate& active
} else {
++expired;
}
if (ShutdownRequested())
if (active_chainstate.m_chainman.m_interrupt)
return false;
}
std::map<uint256, CAmount> mapDeltas;

View file

@ -5,12 +5,13 @@
#ifndef BITCOIN_KERNEL_NOTIFICATIONS_INTERFACE_H
#define BITCOIN_KERNEL_NOTIFICATIONS_INTERFACE_H
#include <util/translation.h>
#include <cstdint>
#include <string>
class CBlockIndex;
enum class SynchronizationState;
struct bilingual_str;
namespace kernel {
@ -27,6 +28,23 @@ public:
virtual void headerTip(SynchronizationState state, int64_t height, int64_t timestamp, bool presync) {}
virtual void progress(const bilingual_str& title, int progress_percent, bool resume_possible) {}
virtual void warning(const bilingual_str& warning) {}
//! The flush error notification is sent to notify the user that an error
//! occurred while flushing block data to disk. Kernel code may ignore flush
//! errors that don't affect the immediate operation it is trying to
//! perform. Applications can choose to handle the flush error notification
//! by logging the error, or notifying the user, or triggering an early
//! shutdown as a precaution against causing more errors.
virtual void flushError(const std::string& debug_message) {}
//! The fatal error notification is sent to notify the user when an error
//! occurs in kernel code that can't be recovered from. After this
//! notification is sent, whatever function triggered the error should also
//! return an error code or raise an exception. Applications can choose to
//! handle the fatal error notification by logging the error, or notifying
//! the user, or triggering an early shutdown as a precaution against
//! causing more errors.
virtual void fatalError(const std::string& debug_message, const bilingual_str& user_message = {}) {}
};
} // namespace kernel

27
src/node/abort.cpp Normal file
View file

@ -0,0 +1,27 @@
// Copyright (c) 2023 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <node/abort.h>
#include <logging.h>
#include <node/interface_ui.h>
#include <shutdown.h>
#include <util/translation.h>
#include <warnings.h>
#include <atomic>
#include <cstdlib>
#include <string>
namespace node {
void AbortNode(std::atomic<int>& exit_status, const std::string& debug_message, const bilingual_str& user_message, bool shutdown)
{
SetMiscWarning(Untranslated(debug_message));
LogPrintf("*** %s\n", debug_message);
InitError(user_message.empty() ? _("A fatal internal error occurred, see debug.log for details") : user_message);
exit_status.store(EXIT_FAILURE);
if (shutdown) StartShutdown();
}
} // namespace node

17
src/node/abort.h Normal file
View file

@ -0,0 +1,17 @@
// Copyright (c) 2023 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_NODE_ABORT_H
#define BITCOIN_NODE_ABORT_H
#include <util/translation.h>
#include <atomic>
#include <string>
namespace node {
void AbortNode(std::atomic<int>& exit_status, const std::string& debug_message, const bilingual_str& user_message = {}, bool shutdown = true);
} // namespace node
#endif // BITCOIN_NODE_ABORT_H

View file

@ -14,12 +14,12 @@
#include <logging.h>
#include <pow.h>
#include <reverse_iterator.h>
#include <shutdown.h>
#include <signet.h>
#include <streams.h>
#include <undo.h>
#include <util/batchpriority.h>
#include <util/fs.h>
#include <util/signalinterrupt.h>
#include <validation.h>
#include <map>
@ -260,7 +260,7 @@ bool BlockManager::LoadBlockIndex(ChainstateManager& chainman)
if (fTrimHeaders) {
trim_below_height = std::numeric_limits<int>::max();
}
if (!m_block_tree_db->LoadBlockIndexGuts(GetConsensus(), [this](const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return this->InsertBlockIndex(hash); }, trim_below_height)) {
if (!m_block_tree_db->LoadBlockIndexGuts(GetConsensus(), [this](const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return this->InsertBlockIndex(hash); }, m_interrupt, trim_below_height)) {
return false;
}
@ -270,7 +270,7 @@ bool BlockManager::LoadBlockIndex(ChainstateManager& chainman)
CBlockIndexHeightOnlyComparator());
for (CBlockIndex* pindex : vSortedByHeight) {
if (ShutdownRequested()) return false;
if (m_interrupt) return false;
pindex->nChainWork = (pindex->pprev ? pindex->pprev->nChainWork : 0) + GetBlockProof(*pindex);
pindex->nTimeMax = (pindex->pprev ? std::max(pindex->pprev->nTimeMax, pindex->nTime) : pindex->nTime);
@ -540,7 +540,7 @@ void BlockManager::FlushUndoFile(int block_file, bool finalize)
{
FlatFilePos undo_pos_old(block_file, m_blockfile_info[block_file].nUndoSize);
if (!UndoFileSeq().Flush(undo_pos_old, finalize)) {
AbortNode("Flushing undo file to disk failed. This is likely the result of an I/O error.");
m_opts.notifications.flushError("Flushing undo file to disk failed. This is likely the result of an I/O error.");
}
}
@ -559,7 +559,7 @@ void BlockManager::FlushBlockFile(bool fFinalize, bool finalize_undo)
FlatFilePos block_pos_old(m_last_blockfile, m_blockfile_info[m_last_blockfile].nSize);
if (!BlockFileSeq().Flush(block_pos_old, fFinalize)) {
AbortNode("Flushing block file to disk failed. This is likely the result of an I/O error.");
m_opts.notifications.flushError("Flushing block file to disk failed. This is likely the result of an I/O error.");
}
// we do not always flush the undo file, as the chain tip may be lagging behind the incoming blocks,
// e.g. during IBD or a sync after a node going offline
@ -671,7 +671,8 @@ bool BlockManager::FindBlockPos(FlatFilePos& pos, unsigned int nAddSize, unsigne
bool out_of_space;
size_t bytes_allocated = BlockFileSeq().Allocate(pos, nAddSize, out_of_space);
if (out_of_space) {
return AbortNode("Disk space is too low!", _("Disk space is too low!"));
m_opts.notifications.fatalError("Disk space is too low!", _("Disk space is too low!"));
return false;
}
if (bytes_allocated != 0 && IsPruneMode()) {
m_check_for_pruning = true;
@ -695,7 +696,7 @@ bool BlockManager::FindUndoPos(BlockValidationState& state, int nFile, FlatFileP
bool out_of_space;
size_t bytes_allocated = UndoFileSeq().Allocate(pos, nAddSize, out_of_space);
if (out_of_space) {
return AbortNode(state, "Disk space is too low!", _("Disk space is too low!"));
return FatalError(m_opts.notifications, state, "Disk space is too low!", _("Disk space is too low!"));
}
if (bytes_allocated != 0 && IsPruneMode()) {
m_check_for_pruning = true;
@ -735,7 +736,7 @@ bool BlockManager::WriteUndoDataForBlock(const CBlockUndo& blockundo, BlockValid
return error("ConnectBlock(): FindUndoPos failed");
}
if (!UndoWriteToDisk(blockundo, _pos, block.pprev->GetBlockHash(), GetParams().MessageStart())) {
return AbortNode(state, "Failed to write undo data");
return FatalError(m_opts.notifications, state, "Failed to write undo data");
}
// rev files are written in block height order, whereas blk files are written as blocks come in (often out of order)
// we want to flush the rev (undo) file once we've written the last block, which is indicated by the last height
@ -855,7 +856,7 @@ FlatFilePos BlockManager::SaveBlockToDisk(const CBlock& block, int nHeight, CCha
}
if (!position_known) {
if (!WriteBlockToDisk(block, blockPos, GetParams().MessageStart())) {
AbortNode("Failed to write block");
m_opts.notifications.fatalError("Failed to write block");
return FlatFilePos();
}
}
@ -903,8 +904,8 @@ void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImportFile
}
LogPrintf("Reindexing block file blk%05u.dat...\n", (unsigned int)nFile);
chainman.ActiveChainstate().LoadExternalBlockFile(file, &pos, &blocks_with_unknown_parent);
if (ShutdownRequested()) {
LogPrintf("Shutdown requested. Exit %s\n", __func__);
if (chainman.m_interrupt) {
LogPrintf("Interrupt requested. Exit %s\n", __func__);
return;
}
nFile++;
@ -922,8 +923,8 @@ void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImportFile
if (file) {
LogPrintf("Importing blocks file %s...\n", fs::PathToString(path));
chainman.ActiveChainstate().LoadExternalBlockFile(file);
if (ShutdownRequested()) {
LogPrintf("Shutdown requested. Exit %s\n", __func__);
if (chainman.m_interrupt) {
LogPrintf("Interrupt requested. Exit %s\n", __func__);
return;
}
} else {
@ -939,7 +940,7 @@ void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImportFile
for (Chainstate* chainstate : WITH_LOCK(::cs_main, return chainman.GetAll())) {
BlockValidationState state;
if (!chainstate->ActivateBestChain(state, nullptr)) {
AbortNode(strprintf("Failed to connect best block (%s)", state.ToString()));
chainman.GetNotifications().fatalError(strprintf("Failed to connect best block (%s)", state.ToString()));
return;
}
}

View file

@ -33,6 +33,9 @@ struct FlatFilePos;
namespace Consensus {
struct Params;
}
namespace util {
class SignalInterrupt;
} // namespace util
namespace node {
@ -163,10 +166,12 @@ private:
public:
using Options = kernel::BlockManagerOpts;
explicit BlockManager(Options opts)
explicit BlockManager(const util::SignalInterrupt& interrupt, Options opts)
: m_prune_mode{opts.prune_target > 0},
m_opts{std::move(opts)} {};
m_opts{std::move(opts)},
m_interrupt{interrupt} {};
const util::SignalInterrupt& m_interrupt;
std::atomic<bool> m_importing{false};
BlockMap m_block_index GUARDED_BY(cs_main);

View file

@ -10,7 +10,12 @@
#include <common/args.h>
#include <common/system.h>
#include <kernel/context.h>
#include <logging.h>
#include <node/abort.h>
#include <node/interface_ui.h>
#include <shutdown.h>
#include <util/check.h>
#include <util/strencodings.h>
#include <util/string.h>
#include <util/translation.h>
@ -72,4 +77,14 @@ void KernelNotifications::warning(const bilingual_str& warning)
DoWarning(warning);
}
void KernelNotifications::flushError(const std::string& debug_message)
{
AbortNode(m_exit_status, debug_message);
}
void KernelNotifications::fatalError(const std::string& debug_message, const bilingual_str& user_message)
{
node::AbortNode(m_exit_status, debug_message, user_message, m_shutdown_on_fatal_error);
}
} // namespace node

View file

@ -7,6 +7,7 @@
#include <kernel/notifications_interface.h>
#include <atomic>
#include <cstdint>
#include <string>
@ -18,6 +19,8 @@ namespace node {
class KernelNotifications : public kernel::Notifications
{
public:
KernelNotifications(std::atomic<int>& exit_status) : m_exit_status{exit_status} {}
void blockTip(SynchronizationState state, CBlockIndex& index) override;
void headerTip(SynchronizationState state, int64_t height, int64_t timestamp, bool presync) override;
@ -25,6 +28,15 @@ public:
void progress(const bilingual_str& title, int progress_percent, bool resume_possible) override;
void warning(const bilingual_str& warning) override;
void flushError(const std::string& debug_message) override;
void fatalError(const std::string& debug_message, const bilingual_str& user_message = {}) override;
//! Useful for tests, can be set to false to avoid shutdown on fatal error.
bool m_shutdown_on_fatal_error{true};
private:
std::atomic<int>& m_exit_status;
};
} // namespace node

View file

@ -87,7 +87,6 @@ void AppTests::appTests()
// Reset global state to avoid interfering with later tests.
ClearGlobalAssetDir();
LogInstance().DisconnectTestLogger();
AbortShutdown();
}
//! Entry point for BitcoinGUI tests.

View file

@ -5,107 +5,40 @@
#include <shutdown.h>
#if defined(HAVE_CONFIG_H)
#include <config/bitcoin-config.h>
#endif
#include <kernel/context.h>
#include <logging.h>
#include <node/interface_ui.h>
#include <util/check.h>
#include <util/tokenpipe.h>
#include <warnings.h>
#include <util/signalinterrupt.h>
#include <assert.h>
#include <atomic>
#ifdef WIN32
#include <condition_variable>
#endif
static std::atomic<int>* g_exit_status{nullptr};
bool AbortNode(const std::string& strMessage, bilingual_str user_message)
{
SetMiscWarning(Untranslated(strMessage));
LogPrintf("*** %s\n", strMessage);
if (user_message.empty()) {
user_message = _("A fatal internal error occurred, see debug.log for details");
}
InitError(user_message);
Assert(g_exit_status)->store(EXIT_FAILURE);
StartShutdown();
return false;
}
static std::atomic<bool> fRequestShutdown(false);
#ifdef WIN32
/** On windows it is possible to simply use a condition variable. */
std::mutex g_shutdown_mutex;
std::condition_variable g_shutdown_cv;
#else
/** On UNIX-like operating systems use the self-pipe trick.
*/
static TokenPipeEnd g_shutdown_r;
static TokenPipeEnd g_shutdown_w;
#endif
bool InitShutdownState(std::atomic<int>& exit_status)
{
g_exit_status = &exit_status;
#ifndef WIN32
std::optional<TokenPipe> pipe = TokenPipe::Make();
if (!pipe) return false;
g_shutdown_r = pipe->TakeReadEnd();
g_shutdown_w = pipe->TakeWriteEnd();
#endif
return true;
}
#include <system_error>
void StartShutdown()
{
#ifdef WIN32
std::unique_lock<std::mutex> lk(g_shutdown_mutex);
fRequestShutdown = true;
g_shutdown_cv.notify_one();
#else
// This must be reentrant and safe for calling in a signal handler, so using a condition variable is not safe.
// Make sure that the token is only written once even if multiple threads call this concurrently or in
// case of a reentrant signal.
if (!fRequestShutdown.exchange(true)) {
// Write an arbitrary byte to the write end of the shutdown pipe.
int res = g_shutdown_w.TokenWrite('x');
if (res != 0) {
LogPrintf("Sending shutdown token failed\n");
assert(0);
}
try {
Assert(kernel::g_context)->interrupt();
} catch (const std::system_error&) {
LogPrintf("Sending shutdown token failed\n");
assert(0);
}
#endif
}
void AbortShutdown()
{
if (fRequestShutdown) {
// Cancel existing shutdown by waiting for it, this will reset condition flags and remove
// the shutdown token from the pipe.
WaitForShutdown();
}
fRequestShutdown = false;
Assert(kernel::g_context)->interrupt.reset();
}
bool ShutdownRequested()
{
return fRequestShutdown;
return bool{Assert(kernel::g_context)->interrupt};
}
void WaitForShutdown()
{
#ifdef WIN32
std::unique_lock<std::mutex> lk(g_shutdown_mutex);
g_shutdown_cv.wait(lk, [] { return fRequestShutdown.load(); });
#else
int res = g_shutdown_r.TokenRead();
if (res != 'x') {
try {
Assert(kernel::g_context)->interrupt.wait();
} catch (const std::system_error&) {
LogPrintf("Reading shutdown token failed\n");
assert(0);
}
#endif
}

View file

@ -6,18 +6,6 @@
#ifndef BITCOIN_SHUTDOWN_H
#define BITCOIN_SHUTDOWN_H
#include <util/translation.h> // For bilingual_str
#include <atomic>
/** Abort with a message */
bool AbortNode(const std::string& strMessage, bilingual_str user_message = bilingual_str{});
/** Initialize shutdown state. This must be called before using either StartShutdown(),
* AbortShutdown() or WaitForShutdown(). Calling ShutdownRequested() is always safe.
*/
bool InitShutdownState(std::atomic<int>& exit_status);
/** Request shutdown of the application. */
void StartShutdown();

View file

@ -5,14 +5,16 @@
#include <chainparams.h>
#include <node/blockstorage.h>
#include <node/context.h>
#include <node/kernel_notifications.h>
#include <util/chaintype.h>
#include <validation.h>
#include <boost/test/unit_test.hpp>
#include <test/util/setup_common.h>
using node::BlockManager;
using node::BLOCK_SERIALIZATION_HEADER_SIZE;
using node::BlockManager;
using node::KernelNotifications;
using node::MAX_BLOCKFILE_SIZE;
// use BasicTestingSetup here for the data directory configuration, setup, and cleanup
@ -21,11 +23,13 @@ BOOST_FIXTURE_TEST_SUITE(blockmanager_tests, BasicTestingSetup)
BOOST_AUTO_TEST_CASE(blockmanager_find_block_pos)
{
const auto params {CreateChainParams(ArgsManager{}, ChainType::MAIN)};
KernelNotifications notifications{m_node.exit_status};
const BlockManager::Options blockman_opts{
.chainparams = *params,
.blocks_dir = m_args.GetBlocksDirPath(),
.notifications = notifications,
};
BlockManager blockman{blockman_opts};
BlockManager blockman{m_node.kernel->interrupt, blockman_opts};
CChain chain {};
// simulate adding a genesis block normally
BOOST_CHECK_EQUAL(blockman.SaveBlockToDisk(params->GenesisBlock(), 0, chain, nullptr).nPos, BLOCK_SERIALIZATION_HEADER_SIZE);

View file

@ -205,7 +205,7 @@ ChainTestingSetup::ChainTestingSetup(const ChainType chainType, const std::vecto
m_cache_sizes = CalculateCacheSizes(m_args);
m_node.notifications = std::make_unique<KernelNotifications>();
m_node.notifications = std::make_unique<KernelNotifications>(m_node.exit_status);
const ChainstateManager::Options chainman_opts{
.chainparams = chainparams,
@ -219,8 +219,9 @@ ChainTestingSetup::ChainTestingSetup(const ChainType chainType, const std::vecto
const BlockManager::Options blockman_opts{
.chainparams = chainman_opts.chainparams,
.blocks_dir = m_args.GetBlocksDirPath(),
.notifications = chainman_opts.notifications,
};
m_node.chainman = std::make_unique<ChainstateManager>(chainman_opts, blockman_opts);
m_node.chainman = std::make_unique<ChainstateManager>(m_node.kernel->interrupt, chainman_opts, blockman_opts);
m_node.chainman->m_blockman.m_block_tree_db = std::make_unique<CBlockTreeDB>(DBParams{
.path = m_args.GetDataDirNet() / "blocks" / "index",
.cache_bytes = static_cast<size_t>(m_cache_sizes.block_tree_db),

View file

@ -380,7 +380,7 @@ struct SnapshotTestSetup : TestChain100Setup {
LOCK(::cs_main);
chainman.ResetChainstates();
BOOST_CHECK_EQUAL(chainman.GetAll().size(), 0);
m_node.notifications = std::make_unique<KernelNotifications>();
m_node.notifications = std::make_unique<KernelNotifications>(m_node.exit_status);
const ChainstateManager::Options chainman_opts{
.chainparams = ::Params(),
.datadir = chainman.m_options.datadir,
@ -392,11 +392,12 @@ struct SnapshotTestSetup : TestChain100Setup {
const BlockManager::Options blockman_opts{
.chainparams = chainman_opts.chainparams,
.blocks_dir = m_args.GetBlocksDirPath(),
.notifications = chainman_opts.notifications,
};
// For robustness, ensure the old manager is destroyed before creating a
// new one.
m_node.chainman.reset();
m_node.chainman = std::make_unique<ChainstateManager>(chainman_opts, blockman_opts);
m_node.chainman = std::make_unique<ChainstateManager>(m_node.kernel->interrupt, chainman_opts, blockman_opts);
}
return *Assert(m_node.chainman);
}
@ -566,7 +567,7 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_snapshot_completion, SnapshotTestSetup
auto db_cache_before_complete = active_cs.m_coinsdb_cache_size_bytes;
SnapshotCompletionResult res;
auto mock_shutdown = [](bilingual_str msg) {};
m_node.notifications->m_shutdown_on_fatal_error = false;
fs::path snapshot_chainstate_dir = *node::FindSnapshotChainstateDir(chainman.m_options.datadir);
BOOST_CHECK(fs::exists(snapshot_chainstate_dir));
@ -576,8 +577,7 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_snapshot_completion, SnapshotTestSetup
const uint256 snapshot_tip_hash = WITH_LOCK(chainman.GetMutex(),
return chainman.ActiveTip()->GetBlockHash());
res = WITH_LOCK(::cs_main,
return chainman.MaybeCompleteSnapshotValidation(mock_shutdown));
res = WITH_LOCK(::cs_main, return chainman.MaybeCompleteSnapshotValidation());
BOOST_CHECK_EQUAL(res, SnapshotCompletionResult::SUCCESS);
WITH_LOCK(::cs_main, BOOST_CHECK(chainman.IsSnapshotValidated()));
@ -593,8 +593,7 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_snapshot_completion, SnapshotTestSetup
BOOST_CHECK_EQUAL(all_chainstates[0], &active_cs);
// Trying completion again should return false.
res = WITH_LOCK(::cs_main,
return chainman.MaybeCompleteSnapshotValidation(mock_shutdown));
res = WITH_LOCK(::cs_main, return chainman.MaybeCompleteSnapshotValidation());
BOOST_CHECK_EQUAL(res, SnapshotCompletionResult::SKIPPED);
// The invalid snapshot path should not have been used.
@ -647,7 +646,7 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_snapshot_completion_hash_mismatch, Sna
Chainstate& validation_chainstate = *std::get<0>(chainstates);
ChainstateManager& chainman = *Assert(m_node.chainman);
SnapshotCompletionResult res;
auto mock_shutdown = [](bilingual_str msg) {};
m_node.notifications->m_shutdown_on_fatal_error = false;
// Test tampering with the IBD UTXO set with an extra coin to ensure it causes
// snapshot completion to fail.
@ -663,8 +662,7 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_snapshot_completion_hash_mismatch, Sna
fs::path snapshot_chainstate_dir = gArgs.GetDataDirNet() / "chainstate_snapshot";
BOOST_CHECK(fs::exists(snapshot_chainstate_dir));
res = WITH_LOCK(::cs_main,
return chainman.MaybeCompleteSnapshotValidation(mock_shutdown));
res = WITH_LOCK(::cs_main, return chainman.MaybeCompleteSnapshotValidation());
BOOST_CHECK_EQUAL(res, SnapshotCompletionResult::HASH_MISMATCH);
auto all_chainstates = chainman.GetAll();

View file

@ -9,8 +9,8 @@
#include <logging.h>
#include <pow.h>
#include <random.h>
#include <shutdown.h>
#include <uint256.h>
#include <util/signalinterrupt.h>
#include <util/translation.h>
#include <util/vector.h>
@ -372,7 +372,7 @@ const CBlockIndex *CBlockTreeDB::RegenerateFullIndex(const CBlockIndex *pindexTr
return pindexNew;
}
bool CBlockTreeDB::LoadBlockIndexGuts(const Consensus::Params& consensusParams, std::function<CBlockIndex*(const uint256&)> insertBlockIndex, int trimBelowHeight)
bool CBlockTreeDB::LoadBlockIndexGuts(const Consensus::Params& consensusParams, std::function<CBlockIndex*(const uint256&)> insertBlockIndex, const util::SignalInterrupt& interrupt, int trimBelowHeight)
{
AssertLockHeld(::cs_main);
std::unique_ptr<CDBIterator> pcursor(NewIterator());
@ -383,7 +383,7 @@ bool CBlockTreeDB::LoadBlockIndexGuts(const Consensus::Params& consensusParams,
// Load m_block_index
while (pcursor->Valid()) {
if (ShutdownRequested()) return false;
if (interrupt) return false;
std::pair<uint8_t, uint256> key;
if (pcursor->GetKey(key) && key.first == DB_BLOCK_INDEX) {
CDiskBlockIndex diskindex;

View file

@ -29,6 +29,9 @@ class uint256;
namespace Consensus {
struct Params;
};
namespace util {
class SignalInterrupt;
} // namespace util
//! -dbcache default (MiB)
static const int64_t nDefaultDbCache = 450;
@ -100,7 +103,7 @@ public:
void ReadReindexing(bool &fReindexing);
bool WriteFlag(const std::string &name, bool fValue);
bool ReadFlag(const std::string &name, bool &fValue);
bool LoadBlockIndexGuts(const Consensus::Params& consensusParams, std::function<CBlockIndex*(const uint256&)> insertBlockIndex, int trimBelowHeight)
bool LoadBlockIndexGuts(const Consensus::Params& consensusParams, std::function<CBlockIndex*(const uint256&)> insertBlockIndex, const util::SignalInterrupt& interrupt, int trimBelowHeight)
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
// ELEMENTS:
const CBlockIndex *RegenerateFullIndex(const CBlockIndex *pindexTrimmed, CBlockIndex *pindexNew) const;

View file

@ -0,0 +1,74 @@
// Copyright (c) 2022 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <util/signalinterrupt.h>
#ifdef WIN32
#include <mutex>
#else
#include <util/tokenpipe.h>
#endif
#include <ios>
#include <optional>
namespace util {
SignalInterrupt::SignalInterrupt() : m_flag{false}
{
#ifndef WIN32
std::optional<TokenPipe> pipe = TokenPipe::Make();
if (!pipe) throw std::ios_base::failure("Could not create TokenPipe");
m_pipe_r = pipe->TakeReadEnd();
m_pipe_w = pipe->TakeWriteEnd();
#endif
}
SignalInterrupt::operator bool() const
{
return m_flag;
}
void SignalInterrupt::reset()
{
// Cancel existing interrupt by waiting for it, this will reset condition flags and remove
// the token from the pipe.
if (*this) wait();
m_flag = false;
}
void SignalInterrupt::operator()()
{
#ifdef WIN32
std::unique_lock<std::mutex> lk(m_mutex);
m_flag = true;
m_cv.notify_one();
#else
// This must be reentrant and safe for calling in a signal handler, so using a condition variable is not safe.
// Make sure that the token is only written once even if multiple threads call this concurrently or in
// case of a reentrant signal.
if (!m_flag.exchange(true)) {
// Write an arbitrary byte to the write end of the pipe.
int res = m_pipe_w.TokenWrite('x');
if (res != 0) {
throw std::ios_base::failure("Could not write interrupt token");
}
}
#endif
}
void SignalInterrupt::wait()
{
#ifdef WIN32
std::unique_lock<std::mutex> lk(m_mutex);
m_cv.wait(lk, [this] { return m_flag.load(); });
#else
int res = m_pipe_r.TokenRead();
if (res != 'x') {
throw std::ios_base::failure("Did not read expected interrupt token");
}
#endif
}
} // namespace util

View file

@ -0,0 +1,52 @@
// Copyright (c) 2023 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_UTIL_SIGNALINTERRUPT_H
#define BITCOIN_UTIL_SIGNALINTERRUPT_H
#ifdef WIN32
#include <condition_variable>
#include <mutex>
#else
#include <util/tokenpipe.h>
#endif
#include <atomic>
#include <cstdlib>
namespace util {
/**
* Helper class that manages an interrupt flag, and allows a thread or
* signal to interrupt another thread.
*
* This class is safe to be used in a signal handler. If sending an interrupt
* from a signal handler is not necessary, the more lightweight \ref
* CThreadInterrupt class can be used instead.
*/
class SignalInterrupt
{
public:
SignalInterrupt();
explicit operator bool() const;
void operator()();
void reset();
void wait();
private:
std::atomic<bool> m_flag;
#ifndef WIN32
// On UNIX-like operating systems use the self-pipe trick.
TokenPipeEnd m_pipe_r;
TokenPipeEnd m_pipe_w;
#else
// On windows use a condition variable, since we don't have any signals there
std::mutex m_mutex;
std::condition_variable m_cv;
#endif
};
} // namespace util
#endif // BITCOIN_UTIL_SIGNALINTERRUPT_H

View file

@ -12,11 +12,17 @@
#include <chrono>
#include <condition_variable>
/*
A helper class for interruptible sleeps. Calling operator() will interrupt
any current sleep, and after that point operator bool() will return true
until reset.
*/
/**
* A helper class for interruptible sleeps. Calling operator() will interrupt
* any current sleep, and after that point operator bool() will return true
* until reset.
*
* This class should not be used in a signal handler. It uses thread
* synchronization primitives that are not safe to use with signals. If sending
* an interrupt from a signal handler is necessary, the \ref SignalInterrupt
* class can be used instead.
*/
class CThreadInterrupt
{
public:

View file

@ -54,6 +54,7 @@
#include <util/hasher.h>
#include <util/moneystr.h>
#include <util/rbf.h>
#include <util/signalinterrupt.h>
#include <util/strencodings.h>
#include <util/time.h>
#include <util/trace.h>
@ -1957,9 +1958,9 @@ bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,
return true;
}
bool AbortNode(BlockValidationState& state, const std::string& strMessage, const bilingual_str& userMessage)
bool FatalError(Notifications& notifications, BlockValidationState& state, const std::string& strMessage, const bilingual_str& userMessage)
{
AbortNode(strMessage, userMessage);
notifications.fatalError(strMessage, userMessage);
return state.Error(strMessage);
}
@ -2277,7 +2278,7 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state,
// We don't write down blocks to disk if they may have been
// corrupted, so this should be impossible unless we're having hardware
// problems.
return AbortNode(state, "Corrupt block found indicating potential hardware failure; shutting down");
return FatalError(m_chainman.GetNotifications(), state, "Corrupt block found indicating potential hardware failure; shutting down");
}
return error("%s: Consensus::CheckBlock: %s", __func__, state.ToString());
}
@ -2736,7 +2737,7 @@ bool Chainstate::FlushStateToDisk(
if (fDoFullFlush || fPeriodicWrite) {
// Ensure we can write block index
if (!CheckDiskSpace(m_blockman.m_opts.blocks_dir)) {
return AbortNode(state, "Disk space is too low!", _("Disk space is too low!"));
return FatalError(m_chainman.GetNotifications(), state, "Disk space is too low!", _("Disk space is too low!"));
}
{
LOG_TIME_MILLIS_WITH_CATEGORY("write block and undo data to disk", BCLog::BENCH);
@ -2756,7 +2757,7 @@ bool Chainstate::FlushStateToDisk(
}
}
if (!m_blockman.WriteBlockIndexDB()) {
return AbortNode(state, "Failed to write to block index database");
return FatalError(m_chainman.GetNotifications(), state, "Failed to write to block index database");
}
// This should be done inside WriteBatchSync, but CBlockIndex is const there
@ -2814,11 +2815,11 @@ bool Chainstate::FlushStateToDisk(
// an overestimation, as most will delete an existing entry or
// overwrite one. Still, use a conservative safety factor of 2.
if (!CheckDiskSpace(m_chainman.m_options.datadir, 48 * 2 * 2 * CoinsTip().GetCacheSize())) {
return AbortNode(state, "Disk space is too low!", _("Disk space is too low!"));
return FatalError(m_chainman.GetNotifications(), state, "Disk space is too low!", _("Disk space is too low!"));
}
// Flush the chainstate (which may refer to block index entries).
if (!CoinsTip().Flush())
return AbortNode(state, "Failed to write to coin database");
return FatalError(m_chainman.GetNotifications(), state, "Failed to write to coin database");
m_last_flush = nNow;
full_flush_completed = true;
TRACE5(utxocache, flush,
@ -2834,7 +2835,7 @@ bool Chainstate::FlushStateToDisk(
GetMainSignals().ChainStateFlushed(m_chain.GetLocator());
}
} catch (const std::runtime_error& e) {
return AbortNode(state, std::string("System error while flushing: ") + e.what());
return FatalError(m_chainman.GetNotifications(), state, std::string("System error while flushing: ") + e.what());
}
return true;
}
@ -3096,7 +3097,7 @@ bool Chainstate::ConnectTip(BlockValidationState& state, CBlockIndex* pindexNew,
if (!pblock) {
std::shared_ptr<CBlock> pblockNew = std::make_shared<CBlock>();
if (!m_blockman.ReadBlockFromDisk(*pblockNew, *pindexNew)) {
return AbortNode(state, "Failed to read block");
return FatalError(m_chainman.GetNotifications(), state, "Failed to read block");
}
pthisBlock = pblockNew;
} else {
@ -3294,7 +3295,7 @@ bool Chainstate::ActivateBestChainStep(BlockValidationState& state, CBlockIndex*
// If we're unable to disconnect a block during normal operation,
// then that is a failure of our local system -- we should abort
// rather than stay on a less work chain.
AbortNode(state, "Failed to disconnect block; see debug.log for details");
FatalError(m_chainman.GetNotifications(), state, "Failed to disconnect block; see debug.log for details");
return false;
}
fBlocksDisconnected = true;
@ -3521,11 +3522,11 @@ bool Chainstate::ActivateBestChain(BlockValidationState& state, std::shared_ptr<
break;
}
// We check shutdown only after giving ActivateBestChainStep a chance to run once so that we
// never shutdown before connecting the genesis block during LoadChainTip(). Previously this
// caused an assert() failure during shutdown in such cases as the UTXO DB flushing checks
// We check interrupt only after giving ActivateBestChainStep a chance to run once so that we
// never interrupt before connecting the genesis block during LoadChainTip(). Previously this
// caused an assert() failure during interrupt in such cases as the UTXO DB flushing checks
// that the best block hash is non-null.
if (ShutdownRequested()) break;
if (m_chainman.m_interrupt) break;
} while (pindexNewTip != pindexMostWork);
CheckBlockIndex();
@ -3615,7 +3616,7 @@ bool Chainstate::InvalidateBlock(BlockValidationState& state, CBlockIndex* pinde
// Disconnect (descendants of) pindex, and mark them invalid.
while (true) {
if (ShutdownRequested()) break;
if (m_chainman.m_interrupt) break;
// Make sure the queue of validation callbacks doesn't grow unboundedly.
LimitValidationInterfaceQueue();
@ -4442,7 +4443,7 @@ bool Chainstate::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, BlockV
}
ReceivedBlockTransactions(block, pindex, blockPos);
} catch (const std::runtime_error& e) {
return AbortNode(state, std::string("System error: ") + e.what());
return FatalError(m_chainman.GetNotifications(), state, std::string("System error: ") + e.what());
}
FlushStateToDisk(state, FlushStateMode::NONE);
@ -4552,7 +4553,7 @@ void Chainstate::LoadMempool(const fs::path& load_path, FopenFn mockable_fopen_f
{
if (!m_mempool) return;
::LoadMempool(*m_mempool, load_path, *this, mockable_fopen_function);
m_mempool->SetLoadTried(!ShutdownRequested());
m_mempool->SetLoadTried(!m_chainman.m_interrupt);
}
bool Chainstate::LoadChainTip()
@ -4685,7 +4686,7 @@ VerifyDBResult CVerifyDB::VerifyDB(
skipped_l3_checks = true;
}
}
if (ShutdownRequested()) return VerifyDBResult::INTERRUPTED;
if (chainstate.m_chainman.m_interrupt) return VerifyDBResult::INTERRUPTED;
}
if (pindexFailure) {
LogPrintf("Verification error: coin database inconsistencies found (last %i blocks, %i good transactions before that)\n", chainstate.m_chain.Height() - pindexFailure->nHeight + 1, nGoodTransactions);
@ -4718,7 +4719,7 @@ VerifyDBResult CVerifyDB::VerifyDB(
LogPrintf("Verification error: found unconnectable block at %d, hash=%s (%s)\n", pindex->nHeight, pindex->GetBlockHash().ToString(), state.ToString());
return VerifyDBResult::CORRUPTED_BLOCK_DB;
}
if (ShutdownRequested()) return VerifyDBResult::INTERRUPTED;
if (chainstate.m_chainman.m_interrupt) return VerifyDBResult::INTERRUPTED;
}
}
@ -4886,7 +4887,7 @@ bool ChainstateManager::LoadBlockIndex()
}
for (CBlockIndex* pindex : vSortedByHeight) {
if (ShutdownRequested()) return false;
if (m_interrupt) return false;
if (pindex->IsAssumedValid() ||
(pindex->IsValid(BLOCK_VALID_TRANSACTIONS) &&
(pindex->HaveTxsDownloaded() || pindex->pprev == nullptr))) {
@ -4992,7 +4993,7 @@ void Chainstate::LoadExternalBlockFile(
// such as a block fails to deserialize.
uint64_t nRewind = blkdat.GetPos();
while (!blkdat.eof()) {
if (ShutdownRequested()) return;
if (m_chainman.m_interrupt) return;
blkdat.SetPos(nRewind);
nRewind++; // start one byte further next time, in case of failure
@ -5133,7 +5134,7 @@ void Chainstate::LoadExternalBlockFile(
}
}
} catch (const std::runtime_error& e) {
AbortNode(std::string("System error: ") + e.what());
m_chainman.GetNotifications().fatalError(std::string("System error: ") + e.what());
}
LogPrintf("Loaded %i blocks from external file in %dms\n", nLoaded, Ticks<std::chrono::milliseconds>(SteadyClock::now() - start));
}
@ -5585,7 +5586,7 @@ bool ChainstateManager::ActivateSnapshot(
snapshot_chainstate.reset();
bool removed = DeleteCoinsDBFromDisk(*snapshot_datadir, /*is_snapshot=*/true);
if (!removed) {
AbortNode(strprintf("Failed to remove snapshot chainstate dir (%s). "
GetNotifications().fatalError(strprintf("Failed to remove snapshot chainstate dir (%s). "
"Manually remove it before restarting.\n", fs::PathToString(*snapshot_datadir)));
}
}
@ -5625,13 +5626,13 @@ struct StopHashingException : public std::exception
{
const char* what() const throw() override
{
return "ComputeUTXOStats interrupted by shutdown.";
return "ComputeUTXOStats interrupted.";
}
};
static void SnapshotUTXOHashBreakpoint()
static void SnapshotUTXOHashBreakpoint(const util::SignalInterrupt& interrupt)
{
if (ShutdownRequested()) throw StopHashingException();
if (interrupt) throw StopHashingException();
}
bool ChainstateManager::PopulateAndValidateSnapshot(
@ -5708,7 +5709,7 @@ bool ChainstateManager::PopulateAndValidateSnapshot(
// If our average Coin size is roughly 41 bytes, checking every 120,000 coins
// means <5MB of memory imprecision.
if (coins_processed % 120000 == 0) {
if (ShutdownRequested()) {
if (m_interrupt) {
return false;
}
@ -5765,7 +5766,7 @@ bool ChainstateManager::PopulateAndValidateSnapshot(
try {
maybe_stats = ComputeUTXOStats(
CoinStatsHashType::HASH_SERIALIZED, snapshot_coinsdb, m_blockman, SnapshotUTXOHashBreakpoint);
CoinStatsHashType::HASH_SERIALIZED, snapshot_coinsdb, m_blockman, [&interrupt = m_interrupt] { SnapshotUTXOHashBreakpoint(interrupt); });
} catch (StopHashingException const&) {
return false;
}
@ -5850,8 +5851,7 @@ bool ChainstateManager::PopulateAndValidateSnapshot(
// through IsUsable() checks, or
//
// (ii) giving each chainstate its own lock instead of using cs_main for everything.
SnapshotCompletionResult ChainstateManager::MaybeCompleteSnapshotValidation(
std::function<void(bilingual_str)> shutdown_fnc)
SnapshotCompletionResult ChainstateManager::MaybeCompleteSnapshotValidation()
{
AssertLockHeld(cs_main);
if (m_ibd_chainstate.get() == &this->ActiveChainstate() ||
@ -5903,7 +5903,7 @@ SnapshotCompletionResult ChainstateManager::MaybeCompleteSnapshotValidation(
user_error = strprintf(Untranslated("%s\n%s"), user_error, util::ErrorString(rename_result));
}
shutdown_fnc(user_error);
GetNotifications().fatalError(user_error.original, user_error);
};
if (index_new.GetBlockHash() != snapshot_blockhash) {
@ -5943,7 +5943,7 @@ SnapshotCompletionResult ChainstateManager::MaybeCompleteSnapshotValidation(
CoinStatsHashType::HASH_SERIALIZED,
&ibd_coins_db,
m_blockman,
SnapshotUTXOHashBreakpoint);
[&interrupt = m_interrupt] { SnapshotUTXOHashBreakpoint(interrupt); });
} catch (StopHashingException const&) {
return SnapshotCompletionResult::STATS_FAILED;
}
@ -6052,9 +6052,10 @@ static ChainstateManager::Options&& Flatten(ChainstateManager::Options&& opts)
return std::move(opts);
}
ChainstateManager::ChainstateManager(Options options, node::BlockManager::Options blockman_options)
: m_options{Flatten(std::move(options))},
m_blockman{std::move(blockman_options)} {}
ChainstateManager::ChainstateManager(const util::SignalInterrupt& interrupt, Options options, node::BlockManager::Options blockman_options)
: m_interrupt{interrupt},
m_options{Flatten(std::move(options))},
m_blockman{interrupt, std::move(blockman_options)} {}
ChainstateManager::~ChainstateManager()
{
@ -6199,13 +6200,13 @@ bool ChainstateManager::ValidatedSnapshotCleanup()
fs::path tmp_old{ibd_chainstate_path + "_todelete"};
auto rename_failed_abort = [](
auto rename_failed_abort = [this](
fs::path p_old,
fs::path p_new,
const fs::filesystem_error& err) {
LogPrintf("%s: error renaming file (%s): %s\n",
__func__, fs::PathToString(p_old), err.what());
AbortNode(strprintf(
GetNotifications().fatalError(strprintf(
"Rename of '%s' -> '%s' failed. "
"Cannot clean up the background chainstate leveldb directory.",
fs::PathToString(p_old), fs::PathToString(p_new)));
@ -6230,7 +6231,7 @@ bool ChainstateManager::ValidatedSnapshotCleanup()
}
if (!DeleteCoinsDBFromDisk(tmp_old, /*is_snapshot=*/false)) {
// No need to AbortNode because once the unneeded bg chainstate data is
// No need to FatalError because once the unneeded bg chainstate data is
// moved, it will not interfere with subsequent initialization.
LogPrintf("Deletion of %s failed. Please remove it manually, as the " /* Continued */
"directory is now unnecessary.\n",

View file

@ -65,6 +65,9 @@ class SnapshotMetadata;
namespace Consensus {
struct Params;
} // namespace Consensus
namespace util {
class SignalInterrupt;
} // namespace util
/** Maximum number of dedicated script-checking threads allowed */
static const int MAX_SCRIPTCHECK_THREADS = 15;
@ -113,7 +116,7 @@ void StopScriptCheckWorkerThreads();
CAmount GetBlockSubsidy(int nHeight, const Consensus::Params& consensusParams);
bool AbortNode(BlockValidationState& state, const std::string& strMessage, const bilingual_str& userMessage = bilingual_str{});
bool FatalError(kernel::Notifications& notifications, BlockValidationState& state, const std::string& strMessage, const bilingual_str& userMessage = {});
/** Guess verification progress (as a fraction between 0.0=genesis and 1.0=current tip). */
double GuessVerificationProgress(const CBlockIndex* pindex, int64_t blockInterval);
@ -969,7 +972,7 @@ private:
public:
using Options = kernel::ChainstateManagerOpts;
explicit ChainstateManager(Options options, node::BlockManager::Options blockman_options);
explicit ChainstateManager(const util::SignalInterrupt& interrupt, Options options, node::BlockManager::Options blockman_options);
const CChainParams& GetParams() const { return m_options.chainparams; }
const Consensus::Params& GetConsensus() const { return m_options.chainparams.GetConsensus(); }
@ -992,6 +995,7 @@ public:
*/
RecursiveMutex& GetMutex() const LOCK_RETURNED(::cs_main) { return ::cs_main; }
const util::SignalInterrupt& m_interrupt;
const Options m_options;
std::thread m_load_block;
//! A single BlockManager instance is shared across each constructed
@ -1062,10 +1066,7 @@ public:
//! If the coins match (expected), then mark the validation chainstate for
//! deletion and continue using the snapshot chainstate as active.
//! Otherwise, revert to using the ibd chainstate and shutdown.
SnapshotCompletionResult MaybeCompleteSnapshotValidation(
std::function<void(bilingual_str)> shutdown_fnc =
[](bilingual_str msg) { AbortNode(msg.original, msg); })
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
SnapshotCompletionResult MaybeCompleteSnapshotValidation() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
//! The most-work chain.
Chainstate& ActiveChainstate() const;

View file

@ -9,14 +9,17 @@
#include <issuance.h>
#include <key_io.h>
#include <mainchainrpc.h>
#include <node/context.h>
#include <node/kernel_notifications.h>
#include <rpc/rawtransaction_util.h>
#include <rpc/server.h>
#include <rpc/server_util.h>
#include <rpc/util.h>
#include <script/generic.hpp>
#include <script/pegins.h>
#include <secp256k1.h>
#include <timedata.h>
#include <util/signalinterrupt.h>
#include <wallet/coincontrol.h>
#include <wallet/fees.h>
#include <wallet/rpc/util.h>
@ -199,11 +202,8 @@ RPCHelpMan getpeginaddress()
}
throw JSONRPCError(RPC_INTERNAL_ERROR, message);
}
CTxDestination mainchain_dest(WitnessV0ScriptHash(calculate_contract(fedpegscripts.front().second, dest_script)));
// P2SH-wrapped is the only valid choice for non-dynafed chains but still an
// option for dynafed-enabled ones as well
node::KernelNotifications notifications{};
ChainstateManager::Options opts{
node::KernelNotifications notifications{pwallet->chain().context()->exit_status};
ChainstateManager::Options chain_opts{
.chainparams = chainparams,
.datadir = gArgs.GetDataDirNet(),
.adjusted_time_callback = GetAdjustedTime,
@ -211,7 +211,15 @@ RPCHelpMan getpeginaddress()
.assumed_valid_block = consensus.defaultAssumeValid,
.notifications = notifications,
};
if (!DeploymentActiveAfter(pwallet->chain().getTip(), ChainstateManager(opts, {chainparams}), Consensus::DEPLOYMENT_DYNA_FED) ||
node::BlockManager::Options block_opts{
.chainparams = chain_opts.chainparams,
.blocks_dir = gArgs.GetBlocksDirPath(),
.notifications = chain_opts.notifications,
};
CTxDestination mainchain_dest(WitnessV0ScriptHash(calculate_contract(fedpegscripts.front().second, dest_script)));
// P2SH-wrapped is the only valid choice for non-dynafed chains but still an
// option for dynafed-enabled ones as well
if (!DeploymentActiveAfter(pwallet->chain().getTip(), ChainstateManager(util::SignalInterrupt(), chain_opts, block_opts), Consensus::DEPLOYMENT_DYNA_FED) ||
fedpegscripts.front().first.IsPayToScriptHash()) {
mainchain_dest = ScriptHash(GetScriptForDestination(mainchain_dest));
}

View file

@ -16,7 +16,7 @@ import re
import sys
FUNCTION_NAMES_AND_NUMBER_OF_LEADING_ARGUMENTS = [
'FatalError,0',
'FatalErrorf,0',
'fprintf,1',
'tfm::format,1', # Assuming tfm::::format(std::ostream&, ...
'LogConnectFailure,1',

View file

@ -14,7 +14,8 @@ import sys
FALSE_POSITIVES = [
("src/dbwrapper.cpp", "vsnprintf(p, limit - p, format, backup_ap)"),
("src/index/base.cpp", "FatalError(const char* fmt, const Args&... args)"),
("src/index/base.cpp", "FatalErrorf(const char* fmt, const Args&... args)"),
("src/index/base.h", "FatalErrorf(const char* fmt, const Args&... args)"),
("src/netbase.cpp", "LogConnectFailure(bool manual_connection, const char* fmt, const Args&... args)"),
("src/clientversion.cpp", "strprintf(_(COPYRIGHT_HOLDERS).translated, COPYRIGHT_HOLDERS_SUBSTITUTION)"),
("src/test/translation_tests.cpp", "strprintf(format, arg)"),