This commit is contained in:
Ryan Ofsky 2025-03-13 02:05:34 +01:00 committed by GitHub
commit 6948478e68
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
47 changed files with 1639 additions and 635 deletions

View file

@ -46,8 +46,9 @@ static void AssembleBlock(benchmark::Bench& bench)
LOCK(::cs_main);
for (const auto& txr : txs) {
const MempoolAcceptResult res = test_setup->m_node.chainman->ProcessTransaction(txr);
auto [res, process_result]{test_setup->m_node.chainman->ProcessTransaction(txr)};
assert(res.m_result_type == MempoolAcceptResult::ResultType::VALID);
assert(process_result);
}
}

View file

@ -67,7 +67,7 @@ static void LoadExternalBlockFile(benchmark::Bench& bench)
// "rb" is "binary, O_RDONLY", positioned to the start of the file.
// The file will be closed by LoadExternalBlockFile().
AutoFile file{fsbridge::fopen(blkfile, "rb")};
testing_setup->m_node.chainman->LoadExternalBlockFile(file, &pos, &blocks_with_unknown_parent);
Assert(testing_setup->m_node.chainman->LoadExternalBlockFile(file, &pos, &blocks_with_unknown_parent));
});
fs::remove(blkfile);
}

View file

@ -34,7 +34,8 @@ static void SaveBlockBench(benchmark::Bench& bench)
const CBlock block{CreateTestBlock()};
bench.run([&] {
const auto pos{blockman.WriteBlock(block, 413'567)};
assert(!pos.IsNull());
assert(pos);
assert(!pos->IsNull());
});
}
@ -43,9 +44,10 @@ static void ReadBlockBench(benchmark::Bench& bench)
const auto testing_setup{MakeNoLogFileContext<const TestingSetup>(ChainType::MAIN)};
auto& blockman{testing_setup->m_node.chainman->m_blockman};
const auto pos{blockman.WriteBlock(CreateTestBlock(), 413'567)};
assert(pos);
CBlock block;
bench.run([&] {
const auto success{blockman.ReadBlock(block, pos)};
const auto success{blockman.ReadBlock(block, *pos)};
assert(success);
});
}
@ -55,10 +57,11 @@ static void ReadRawBlockBench(benchmark::Bench& bench)
const auto testing_setup{MakeNoLogFileContext<const TestingSetup>(ChainType::MAIN)};
auto& blockman{testing_setup->m_node.chainman->m_blockman};
const auto pos{blockman.WriteBlock(CreateTestBlock(), 413'567)};
assert(pos);
std::vector<uint8_t> block_data;
blockman.ReadRawBlock(block_data, pos); // warmup
blockman.ReadRawBlock(block_data, *pos); // warmup
bench.run([&] {
const auto success{blockman.ReadRawBlock(block_data, pos)};
const auto success{blockman.ReadRawBlock(block_data, *pos)};
assert(success);
});
}

View file

@ -129,13 +129,13 @@ int main(int argc, char* argv[])
ChainstateManager chainman{interrupt, chainman_opts, blockman_opts};
node::ChainstateLoadOptions options;
auto [status, error] = node::LoadChainstate(chainman, cache_sizes, options);
if (status != node::ChainstateLoadStatus::SUCCESS) {
auto load_result{node::LoadChainstate(chainman, cache_sizes, options)};
if (!load_result) {
std::cerr << "Failed to load Chain state from your datadir." << std::endl;
goto epilogue;
} else {
std::tie(status, error) = node::VerifyLoadedChainstate(chainman, options);
if (status != node::ChainstateLoadStatus::SUCCESS) {
auto verify_result{node::VerifyLoadedChainstate(chainman, options)};
if (!verify_result) {
std::cerr << "Failed to verify loaded Chain state from your datadir." << std::endl;
goto epilogue;
}
@ -143,7 +143,8 @@ int main(int argc, char* argv[])
for (Chainstate* chainstate : WITH_LOCK(::cs_main, return chainman.GetAll())) {
BlockValidationState state;
if (!chainstate->ActivateBestChain(state, nullptr)) {
auto activate_result{chainstate->ActivateBestChain(state, nullptr)};
if (!activate_result) {
std::cerr << "Failed to connect best block (" << state.ToString() << ")" << std::endl;
goto epilogue;
}
@ -212,7 +213,9 @@ int main(int argc, char* argv[])
bool new_block;
auto sc = std::make_shared<submitblock_StateCatcher>(block.GetHash());
validation_signals.RegisterSharedValidationInterface(sc);
bool accepted = chainman.ProcessNewBlock(blockptr, /*force_processing=*/true, /*min_pow_checked=*/true, /*new_block=*/&new_block);
kernel::FlushResult<void, kernel::AbortFailure> process_result;
bool accepted = chainman.ProcessNewBlock(blockptr, /*force_processing=*/true, /*min_pow_checked=*/true, /*new_block=*/&new_block, process_result);
if (!process_result) std::cerr << util::ErrorString(process_result).original << std::endl;
validation_signals.UnregisterSharedValidationInterface(sc);
if (!new_block && accepted) {
std::cerr << "duplicate" << std::endl;
@ -265,7 +268,8 @@ epilogue:
LOCK(cs_main);
for (Chainstate* chainstate : chainman.GetAll()) {
if (chainstate->CanFlushToDisk()) {
chainstate->ForceFlushStateToDisk();
auto flush_result{chainstate->ForceFlushStateToDisk()};
if (!flush_result) std::cerr << util::ErrorString(flush_result).original << std::endl;
chainstate->ResetCoinsViews();
}
}

View file

@ -120,12 +120,12 @@
using common::AmountErrMsg;
using common::InvalidPortErrMsg;
using common::ResolveErrMsg;
using kernel::FlushResult;
using kernel::InterruptResult;
using node::ApplyArgsManOptions;
using node::BlockManager;
using node::CalculateCacheSizes;
using node::ChainstateLoadResult;
using node::ChainstateLoadStatus;
using node::ChainstateLoadError;
using node::DEFAULT_PERSIST_MEMPOOL;
using node::DEFAULT_PRINT_MODIFIED_FEE;
using node::DEFAULT_STOPATHEIGHT;
@ -342,7 +342,7 @@ void Shutdown(NodeContext& node)
LOCK(cs_main);
for (Chainstate* chainstate : node.chainman->GetAll()) {
if (chainstate->CanFlushToDisk()) {
chainstate->ForceFlushStateToDisk();
(void)chainstate->ForceFlushStateToDisk();
}
}
}
@ -368,7 +368,7 @@ void Shutdown(NodeContext& node)
LOCK(cs_main);
for (Chainstate* chainstate : node.chainman->GetAll()) {
if (chainstate->CanFlushToDisk()) {
chainstate->ForceFlushStateToDisk();
(void)chainstate->ForceFlushStateToDisk();
chainstate->ResetCoinsViews();
}
}
@ -1221,7 +1221,7 @@ bool CheckHostPortOptions(const ArgsManager& args) {
// A GUI user may opt to retry once with do_reindex set if there is a failure during chainstate initialization.
// The function therefore has to support re-entry.
static ChainstateLoadResult InitAndLoadChainstate(
FlushResult<kernel::InterruptResult, ChainstateLoadError> InitAndLoadChainstate(
NodeContext& node,
bool do_reindex,
const bool do_reindex_chainstate,
@ -1237,7 +1237,7 @@ static ChainstateLoadResult InitAndLoadChainstate(
bilingual_str mempool_error;
node.mempool = std::make_unique<CTxMemPool>(mempool_opts, mempool_error);
if (!mempool_error.empty()) {
return {ChainstateLoadStatus::FAILURE_FATAL, mempool_error};
return {util::Error{mempool_error}, ChainstateLoadError::FAILURE_FATAL};
}
LogPrintf("* Using %.1f MiB for in-memory UTXO set (plus up to %.1f MiB of unused mempool space)\n", cache_sizes.coins * (1.0 / 1024 / 1024), mempool_opts.max_size_bytes * (1.0 / 1024 / 1024));
ChainstateManager::Options chainman_opts{
@ -1267,12 +1267,12 @@ static ChainstateLoadResult InitAndLoadChainstate(
node.chainman = std::make_unique<ChainstateManager>(*Assert(node.shutdown_signal), chainman_opts, blockman_opts);
} catch (dbwrapper_error& e) {
LogError("%s", e.what());
return {ChainstateLoadStatus::FAILURE, _("Error opening block database")};
return {util::Error{_("Error opening block database")}, ChainstateLoadError::FAILURE};
} catch (std::exception& e) {
return {ChainstateLoadStatus::FAILURE_FATAL, Untranslated(strprintf("Failed to initialize ChainstateManager: %s", e.what()))};
return {util::Error{Untranslated(strprintf("Failed to initialize ChainstateManager: %s", e.what()))}, ChainstateLoadError::FAILURE_FATAL};
}
ChainstateManager& chainman = *node.chainman;
if (chainman.m_interrupt) return {ChainstateLoadStatus::INTERRUPTED, {}};
if (chainman.m_interrupt) return kernel::Interrupted{};
// This is defined and set here instead of inline in validation.h to avoid a hard
// dependency between validation and index/base, since the latter is not in
@ -1307,27 +1307,27 @@ static ChainstateLoadResult InitAndLoadChainstate(
"", CClientUIInterface::MSG_ERROR);
};
uiInterface.InitMessage(_("Loading block index…"));
auto catch_exceptions = [](auto&& f) -> ChainstateLoadResult {
auto catch_exceptions = [](auto&& f) -> FlushResult<InterruptResult, node::ChainstateLoadError> {
try {
return f();
} catch (const std::exception& e) {
LogError("%s\n", e.what());
return std::make_tuple(node::ChainstateLoadStatus::FAILURE, _("Error loading databases"));
return {util::Error{_("Error loading databases")}, node::ChainstateLoadError::FAILURE};
}
};
auto [status, error] = catch_exceptions([&] { return LoadChainstate(chainman, cache_sizes, options); });
if (status == node::ChainstateLoadStatus::SUCCESS) {
auto result = catch_exceptions([&] { return LoadChainstate(chainman, cache_sizes, options); });
if (result && !IsInterrupted(*result)) {
uiInterface.InitMessage(_("Verifying blocks…"));
if (chainman.m_blockman.m_have_pruned && options.check_blocks > MIN_BLOCKS_TO_KEEP) {
LogWarning("pruned datadir may not have more than %d blocks; only checking available blocks\n",
MIN_BLOCKS_TO_KEEP);
}
std::tie(status, error) = catch_exceptions([&] { return VerifyLoadedChainstate(chainman, options); });
if (status == node::ChainstateLoadStatus::SUCCESS) {
result.Update(catch_exceptions([&] { return VerifyLoadedChainstate(chainman, options); }));
if (result && !IsInterrupted(*result)) {
LogInfo("Block index and chainstate loaded");
}
}
return {status, error};
return result;
};
bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
@ -1685,14 +1685,15 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
const bool do_reindex_chainstate{args.GetBoolArg("-reindex-chainstate", false)};
// Chainstate initialization and loading may be retried once with reindexing by GUI users
auto [status, error] = InitAndLoadChainstate(
auto result = InitAndLoadChainstate(
node,
do_reindex,
do_reindex_chainstate,
kernel_cache_sizes,
args);
if (status == ChainstateLoadStatus::FAILURE && !do_reindex && !ShutdownRequested(node)) {
if (!result && result.GetFailure() == ChainstateLoadError::FAILURE && !do_reindex && !ShutdownRequested(node)) {
// suggest a reindex
const auto& error = util::ErrorString(result);
bool do_retry = uiInterface.ThreadSafeQuestion(
error + Untranslated(".\n\n") + _("Do you want to rebuild the databases now?"),
error.original + ".\nPlease restart with -reindex or -reindex-chainstate to recover.",
@ -1704,15 +1705,15 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
if (!Assert(node.shutdown_signal)->reset()) {
LogError("Internal error: failed to reset shutdown signal.\n");
}
std::tie(status, error) = InitAndLoadChainstate(
result.Update(InitAndLoadChainstate(
node,
do_reindex,
do_reindex_chainstate,
kernel_cache_sizes,
args);
args));
}
if (status != ChainstateLoadStatus::SUCCESS && status != ChainstateLoadStatus::INTERRUPTED) {
return InitError(error);
if (!result) {
return InitError(util::ErrorString(result));
}
// As LoadBlockIndex can take several minutes, it's possible the user
@ -1767,7 +1768,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
LOCK(cs_main);
for (Chainstate* chainstate : chainman.GetAll()) {
uiInterface.InitMessage(_("Pruning blockstore…"));
chainstate->PruneAndFlush();
(void)chainstate->PruneAndFlush();
}
}
} else {
@ -1833,7 +1834,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
node.background_init_thread = std::thread(&util::TraceThread, "initload", [=, &chainman, &args, &node] {
ScheduleBatchPriority();
// Import blocks and ActivateBestChain()
ImportBlocks(chainman, vImportFiles);
(void)ImportBlocks(chainman, vImportFiles);
if (args.GetBoolArg("-stopafterblockimport", DEFAULT_STOPAFTERBLOCKIMPORT)) {
LogPrintf("Stopping after block import\n");
if (!(Assert(node.shutdown_request))()) {

View file

@ -68,6 +68,7 @@ add_library(bitcoinkernel
../util/hasher.cpp
../util/moneystr.cpp
../util/rbf.cpp
../util/result.cpp
../util/serfloat.cpp
../util/signalinterrupt.cpp
../util/strencodings.cpp

66
src/kernel/result.h Normal file
View file

@ -0,0 +1,66 @@
// Copyright (c) 2024 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_KERNEL_RESULT_H
#define BITCOIN_KERNEL_RESULT_H
#include <util/result.h>
//! @file Kernel result types for use with util::Result.
//!
//! Currently, many kernel functions trigger flushes and node shutdowns at low
//! levels deep within the code. This can make it hard to reason about when a
//! particular kernel function call might trigger a flush or shutdown.
//!
//! Ideally, low level functions would not trigger flushes and shutdowns at all,
//! but would just return information so higher level code can determine when to
//! flush and shut down. However, implementing this would require significant
//! restructuring of code and probably change external behavior. In the
//! meantime, FlushStatus and AbortFailure result types below can be used to
//! provide a consistent indication to callers of when shutdowns and flushes are
//! triggered, and which functions can and cannot trigger them.
namespace kernel {
//! Result type for functions that might trigger flushes, indicating whether
//! flushes were skipped, performed successfully, or failed.
enum class FlushStatus { SKIPPED, SUCCESS, FAILURE };
//! Result type for functions that might trigger fatal errors and need to abort
//! the node, indicating whether a fatal error did occur.
struct AbortFailure {
bool fatal{false};
};
template <typename SuccessType = void, typename FailureType = void>
using FlushResult = util::Result<SuccessType, FailureType, FlushStatus>;
} // namespace kernel
namespace util {
template<>
struct ResultTraits<kernel::FlushStatus>
{
static void Update(kernel::FlushStatus& dst, kernel::FlushStatus& src)
{
using kernel::FlushStatus;
if (dst == FlushStatus::FAILURE || src == FlushStatus::FAILURE) {
dst = FlushStatus::FAILURE;
} else if (dst == FlushStatus::SUCCESS || src == FlushStatus::SUCCESS) {
dst = FlushStatus::SUCCESS;
} else {
dst = FlushStatus::SKIPPED;
}
}
};
template<>
struct ResultTraits<kernel::AbortFailure>
{
static void Update(kernel::AbortFailure& dst, kernel::AbortFailure& src)
{
dst.fatal |= src.fatal;
}
};
} // namespace util
#endif // BITCOIN_KERNEL_RESULT_H

View file

@ -56,6 +56,8 @@
#include <utility>
using namespace util::hex_literals;
using kernel::AbortFailure;
using kernel::FlushResult;
TRACEPOINT_SEMAPHORE(net, inbound_message);
TRACEPOINT_SEMAPHORE(net, misbehaving_connection);
@ -3077,7 +3079,7 @@ bool PeerManagerImpl::ProcessOrphanTx(Peer& peer)
CTransactionRef porphanTx = nullptr;
while (CTransactionRef porphanTx = m_txdownloadman.GetTxToReconsider(peer.m_id)) {
const MempoolAcceptResult result = m_chainman.ProcessTransaction(porphanTx);
auto [result, flush_result]{m_chainman.ProcessTransaction(porphanTx)};
const TxValidationState& state = result.m_state;
const Txid& orphanHash = porphanTx->GetHash();
const Wtxid& orphan_wtxid = porphanTx->GetWitnessHash();
@ -3271,7 +3273,8 @@ void PeerManagerImpl::ProcessGetCFCheckPt(CNode& node, Peer& peer, DataStream& v
void PeerManagerImpl::ProcessBlock(CNode& node, const std::shared_ptr<const CBlock>& block, bool force_processing, bool min_pow_checked)
{
bool new_block{false};
m_chainman.ProcessNewBlock(block, force_processing, min_pow_checked, &new_block);
FlushResult<void, AbortFailure> process_result; // Ignore flush and fatal error information, only care whether block is accepted.
(void)m_chainman.ProcessNewBlock(block, force_processing, min_pow_checked, &new_block, process_result);
if (new_block) {
node.m_last_block_time = GetTime<std::chrono::seconds>();
// In case this block came from a different peer than we requested
@ -4249,7 +4252,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
}
if (package_to_validate) {
const auto package_result{ProcessNewPackage(m_chainman.ActiveChainstate(), m_mempool, package_to_validate->m_txns, /*test_accept=*/false, /*client_maxfeerate=*/std::nullopt)};
auto [package_result, process_result]{ProcessNewPackage(m_chainman.ActiveChainstate(), m_mempool, package_to_validate->m_txns, /*test_accept=*/false, /*client_maxfeerate=*/std::nullopt)};
LogDebug(BCLog::TXPACKAGES, "package evaluation for %s: %s\n", package_to_validate->ToString(),
package_result.m_state.IsValid() ? "package accepted" : "package rejected");
ProcessPackageResult(package_to_validate.value(), package_result);
@ -4260,7 +4263,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
// ReceivedTx should not be telling us to validate the tx and a package.
Assume(!package_to_validate.has_value());
const MempoolAcceptResult result = m_chainman.ProcessTransaction(ptx);
auto [result, flush_result]{m_chainman.ProcessTransaction(ptx)};
const TxValidationState& state = result.m_state;
if (result.m_result_type == MempoolAcceptResult::ResultType::VALID) {
@ -4269,7 +4272,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
}
if (state.IsInvalid()) {
if (auto package_to_validate{ProcessInvalidTx(pfrom.GetId(), ptx, state, /*first_time_failure=*/true)}) {
const auto package_result{ProcessNewPackage(m_chainman.ActiveChainstate(), m_mempool, package_to_validate->m_txns, /*test_accept=*/false, /*client_maxfeerate=*/std::nullopt)};
auto [package_result, process_result]{ProcessNewPackage(m_chainman.ActiveChainstate(), m_mempool, package_to_validate->m_txns, /*test_accept=*/false, /*client_maxfeerate=*/std::nullopt)};
LogDebug(BCLog::TXPACKAGES, "package evaluation for %s: %s\n", package_to_validate->ToString(),
package_result.m_state.IsValid() ? "package accepted" : "package rejected");
ProcessPackageResult(package_to_validate.value(), package_result);

View file

@ -41,6 +41,12 @@
#include <ranges>
#include <unordered_map>
using kernel::AbortFailure;
using kernel::FlushResult;
using kernel::FlushStatus;
using kernel::Interrupted;
using kernel::InterruptResult;
namespace kernel {
static constexpr uint8_t DB_BLOCK_FILES{'f'};
static constexpr uint8_t DB_BLOCK_INDEX{'b'};
@ -395,18 +401,19 @@ CBlockIndex* BlockManager::InsertBlockIndex(const uint256& hash)
return pindex;
}
bool BlockManager::LoadBlockIndex(const std::optional<uint256>& snapshot_blockhash)
util::Result<InterruptResult, AbortFailure> BlockManager::LoadBlockIndexData(const std::optional<uint256>& snapshot_blockhash)
{
if (!m_block_tree_db->LoadBlockIndexGuts(
GetConsensus(), [this](const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return this->InsertBlockIndex(hash); }, m_interrupt)) {
return false;
return util::Error{};
}
if (snapshot_blockhash) {
const std::optional<AssumeutxoData> maybe_au_data = GetParams().AssumeutxoForBlockhash(*snapshot_blockhash);
if (!maybe_au_data) {
m_opts.notifications.fatalError(strprintf(_("Assumeutxo data not found for the given blockhash '%s'."), snapshot_blockhash->ToString()));
return false;
auto error{strprintf(_("Assumeutxo data not found for the given blockhash '%s'."), snapshot_blockhash->ToString())};
m_opts.notifications.fatalError(error);
return {util::Error{std::move(error)}, AbortFailure{.fatal = true}};
}
const AssumeutxoData& au_data = *Assert(maybe_au_data);
m_snapshot_height = au_data.height;
@ -433,10 +440,11 @@ bool BlockManager::LoadBlockIndex(const std::optional<uint256>& snapshot_blockha
CBlockIndex* previous_index{nullptr};
for (CBlockIndex* pindex : vSortedByHeight) {
if (m_interrupt) return false;
if (m_interrupt) return Interrupted{};
if (previous_index && pindex->nHeight > previous_index->nHeight + 1) {
LogError("%s: block index is non-contiguous, index of height %d missing\n", __func__, previous_index->nHeight + 1);
return false;
auto error{Untranslated(strprintf("block index is non-contiguous, index of height %d missing", previous_index->nHeight + 1))};
LogError("%s: %s\n", __func__, error.original);
return util::Error{std::move(error)};
}
previous_index = pindex;
pindex->nChainWork = (pindex->pprev ? pindex->pprev->nChainWork : 0) + GetBlockProof(*pindex);
@ -471,7 +479,7 @@ bool BlockManager::LoadBlockIndex(const std::optional<uint256>& snapshot_blockha
}
}
return true;
return {};
}
bool BlockManager::WriteBlockIndexDB()
@ -496,10 +504,11 @@ bool BlockManager::WriteBlockIndexDB()
return true;
}
bool BlockManager::LoadBlockIndexDB(const std::optional<uint256>& snapshot_blockhash)
util::Result<InterruptResult, AbortFailure> BlockManager::LoadBlockIndexDB(const std::optional<uint256>& snapshot_blockhash)
{
if (!LoadBlockIndex(snapshot_blockhash)) {
return false;
auto result{LoadBlockIndexData(snapshot_blockhash)};
if (!result || IsInterrupted(*result)) {
return result;
}
int max_blockfile_num{0};
@ -531,7 +540,8 @@ bool BlockManager::LoadBlockIndexDB(const std::optional<uint256>& snapshot_block
for (std::set<int>::iterator it = setBlkDataFiles.begin(); it != setBlkDataFiles.end(); it++) {
FlatFilePos pos(*it, 0);
if (OpenBlockFile(pos, true).IsNull()) {
return false;
result.Update(util::Error{});
return result;
}
}
@ -555,7 +565,7 @@ bool BlockManager::LoadBlockIndexDB(const std::optional<uint256>& snapshot_block
m_block_tree_db->ReadReindexing(fReindexing);
if (fReindexing) m_blockfiles_indexed = false;
return true;
return result;
}
void BlockManager::ScanAndUnlinkAlreadyPrunedFiles()
@ -702,19 +712,24 @@ bool BlockManager::ReadBlockUndo(CBlockUndo& blockundo, const CBlockIndex& index
return true;
}
bool BlockManager::FlushUndoFile(int block_file, bool finalize)
FlushResult<> BlockManager::FlushUndoFile(int block_file, bool finalize)
{
FlushResult<> result;
FlatFilePos undo_pos_old(block_file, m_blockfile_info[block_file].nUndoSize);
if (!m_undo_file_seq.Flush(undo_pos_old, finalize)) {
m_opts.notifications.flushError(_("Flushing undo file to disk failed. This is likely the result of an I/O error."));
return false;
auto error{_("Flushing undo file to disk failed. This is likely the result of an I/O error.")};
m_opts.notifications.flushError(error);
result.Update(util::Error{std::move(error)});
result.Update(util::Info{FlushStatus::FAILURE});
} else {
result.Update(util::Info{FlushStatus::SUCCESS});
}
return true;
return result;
}
bool BlockManager::FlushBlockFile(int blockfile_num, bool fFinalize, bool finalize_undo)
FlushResult<> BlockManager::FlushBlockFile(int blockfile_num, bool fFinalize, bool finalize_undo)
{
bool success = true;
FlushResult<> result;
LOCK(cs_LastBlockFile);
if (m_blockfile_info.size() < 1) {
@ -722,23 +737,27 @@ bool BlockManager::FlushBlockFile(int blockfile_num, bool fFinalize, bool finali
// chainstate init, when we call ChainstateManager::MaybeRebalanceCaches() (which
// then calls FlushStateToDisk()), resulting in a call to this function before we
// have populated `m_blockfile_info` via LoadBlockIndexDB().
return true;
return result;
}
assert(static_cast<int>(m_blockfile_info.size()) > blockfile_num);
FlatFilePos block_pos_old(blockfile_num, m_blockfile_info[blockfile_num].nSize);
if (!m_block_file_seq.Flush(block_pos_old, fFinalize)) {
m_opts.notifications.flushError(_("Flushing block file to disk failed. This is likely the result of an I/O error."));
success = false;
auto error{_("Flushing block file to disk failed. This is likely the result of an I/O error.")};
m_opts.notifications.flushError(error);
result.Update(util::Error{std::move(error)});
result.Update(util::Info{FlushStatus::FAILURE});
} else {
result.Update(util::Info{FlushStatus::SUCCESS});
}
// 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
if (!fFinalize || finalize_undo) {
if (!FlushUndoFile(blockfile_num, finalize_undo)) {
success = false;
if (!(FlushUndoFile(blockfile_num, finalize_undo) >> result)) {
result.Update(util::Error{});
}
}
return success;
return result;
}
BlockfileType BlockManager::BlockfileTypeForHeight(int height)
@ -749,7 +768,7 @@ BlockfileType BlockManager::BlockfileTypeForHeight(int height)
return (height >= *m_snapshot_height) ? BlockfileType::ASSUMED : BlockfileType::NORMAL;
}
bool BlockManager::FlushChainstateBlockFile(int tip_height)
FlushResult<> BlockManager::FlushChainstateBlockFile(int tip_height)
{
LOCK(cs_LastBlockFile);
auto& cursor = m_blockfile_cursors[BlockfileTypeForHeight(tip_height)];
@ -760,7 +779,7 @@ bool BlockManager::FlushChainstateBlockFile(int tip_height)
return FlushBlockFile(cursor->file_num, /*fFinalize=*/false, /*finalize_undo=*/false);
}
// No need to log warnings in this case.
return true;
return {};
}
uint64_t BlockManager::CalculateCurrentUsage()
@ -803,8 +822,9 @@ fs::path BlockManager::GetBlockPosFilename(const FlatFilePos& pos) const
return m_block_file_seq.FileName(pos);
}
FlatFilePos BlockManager::FindNextBlockPos(unsigned int nAddSize, unsigned int nHeight, uint64_t nTime)
FlushResult<FlatFilePos, AbortFailure> BlockManager::FindNextBlockPos(unsigned int nAddSize, unsigned int nHeight, uint64_t nTime)
{
FlushResult<FlatFilePos, AbortFailure> result;
LOCK(cs_LastBlockFile);
const BlockfileType chain_type = BlockfileTypeForHeight(nHeight);
@ -867,10 +887,12 @@ FlatFilePos BlockManager::FindNextBlockPos(unsigned int nAddSize, unsigned int n
// data may be inconsistent after a crash if the flush is called during
// a reindex. A flush error might also leave some of the data files
// untrimmed.
if (!FlushBlockFile(last_blockfile, /*fFinalize=*/true, finalize_undo)) {
LogPrintLevel(BCLog::BLOCKSTORAGE, BCLog::Level::Warning,
if (!(FlushBlockFile(last_blockfile, /*fFinalize=*/true, finalize_undo) >> result)) {
auto warning{Untranslated(strprintf(
"Failed to flush previous block file %05i (finalize=1, finalize_undo=%i) before opening new block file %05i\n",
last_blockfile, finalize_undo, nFile);
last_blockfile, finalize_undo, nFile))};
LogPrintLevel(BCLog::BLOCKSTORAGE, BCLog::Level::Warning, "%s\n", warning.original);
result.AddWarning(std::move(warning));
}
// No undo data yet in the new file, so reset our undo-height tracking.
m_blockfile_cursors[chain_type] = BlockfileCursor{nFile};
@ -882,15 +904,18 @@ FlatFilePos BlockManager::FindNextBlockPos(unsigned int nAddSize, unsigned int n
bool out_of_space;
size_t bytes_allocated = m_block_file_seq.Allocate(pos, nAddSize, out_of_space);
if (out_of_space) {
m_opts.notifications.fatalError(_("Disk space is too low!"));
return {};
auto error{_("Disk space is too low!")};
m_opts.notifications.fatalError(error);
result.Update({util::Error{std::move(error)}, AbortFailure{.fatal = true}});
return result;
}
if (bytes_allocated != 0 && IsPruneMode()) {
m_check_for_pruning = true;
}
m_dirty_fileinfo.insert(nFile);
return pos;
result.Update(pos);
return result;
}
void BlockManager::UpdateBlockInfo(const CBlock& block, unsigned int nHeight, const FlatFilePos& pos)
@ -915,7 +940,7 @@ void BlockManager::UpdateBlockInfo(const CBlock& block, unsigned int nHeight, co
m_dirty_fileinfo.insert(nFile);
}
bool BlockManager::FindUndoPos(BlockValidationState& state, int nFile, FlatFilePos& pos, unsigned int nAddSize)
util::Result<void, AbortFailure> BlockManager::FindUndoPos(BlockValidationState& state, int nFile, FlatFilePos& pos, unsigned int nAddSize)
{
pos.nFile = nFile;
@ -928,17 +953,20 @@ bool BlockManager::FindUndoPos(BlockValidationState& state, int nFile, FlatFileP
bool out_of_space;
size_t bytes_allocated = m_undo_file_seq.Allocate(pos, nAddSize, out_of_space);
if (out_of_space) {
return FatalError(m_opts.notifications, state, _("Disk space is too low!"));
auto error{_("Disk space is too low!")};
FatalError(m_opts.notifications, state, error);
return {util::Error{std::move(error)}, AbortFailure{.fatal = true}};
}
if (bytes_allocated != 0 && IsPruneMode()) {
m_check_for_pruning = true;
}
return true;
return {};
}
bool BlockManager::WriteBlockUndo(const CBlockUndo& blockundo, BlockValidationState& state, CBlockIndex& block)
FlushResult<void, AbortFailure> BlockManager::WriteBlockUndo(const CBlockUndo& blockundo, BlockValidationState& state, CBlockIndex& block)
{
FlushResult<void, AbortFailure> result;
AssertLockHeld(::cs_main);
const BlockfileType type = BlockfileTypeForHeight(block.nHeight);
auto& cursor = *Assert(WITH_LOCK(cs_LastBlockFile, return m_blockfile_cursors[type]));
@ -947,15 +975,20 @@ bool BlockManager::WriteBlockUndo(const CBlockUndo& blockundo, BlockValidationSt
if (block.GetUndoPos().IsNull()) {
FlatFilePos pos;
const unsigned int blockundo_size{static_cast<unsigned int>(GetSerializeSize(blockundo))};
if (!FindUndoPos(state, block.nFile, pos, blockundo_size + UNDO_DATA_DISK_OVERHEAD)) {
LogError("FindUndoPos failed");
return false;
if (!(FindUndoPos(state, block.nFile, pos, blockundo_size + UNDO_DATA_DISK_OVERHEAD) >> result)) {
auto error{Untranslated("FindUndoPos failed")};
LogError("%s", error.original);
result.Update(util::Error{std::move(error)});
return result;
}
// Open history file to append
AutoFile fileout{OpenUndoFile(pos)};
if (fileout.IsNull()) {
LogError("OpenUndoFile failed");
return FatalError(m_opts.notifications, state, _("Failed to write undo data."));
auto error{_("Failed to write undo data.")};
FatalError(m_opts.notifications, state, error);
result.Update({util::Error{std::move(error)}, AbortFailure{.fatal = true}});
return result;
}
// Write index header
@ -981,8 +1014,10 @@ bool BlockManager::WriteBlockUndo(const CBlockUndo& blockundo, BlockValidationSt
// the caller would assume the undo data not to be written, when in
// fact it is. Note though, that a failed flush might leave the data
// file untrimmed.
if (!FlushUndoFile(pos.nFile, true)) {
LogPrintLevel(BCLog::BLOCKSTORAGE, BCLog::Level::Warning, "Failed to flush undo file %05i\n", pos.nFile);
if (!(FlushUndoFile(pos.nFile, true) >> result)) {
auto warning{Untranslated(strprintf("Failed to flush undo file %05i\n", pos.nFile))};
LogPrintLevel(BCLog::BLOCKSTORAGE, BCLog::Level::Warning, "%s\n", warning.original);
result.AddWarning(std::move(warning));
}
} else if (pos.nFile == cursor.file_num && block.nHeight > cursor.undo_height) {
cursor.undo_height = block.nHeight;
@ -993,7 +1028,7 @@ bool BlockManager::WriteBlockUndo(const CBlockUndo& blockundo, BlockValidationSt
m_dirty_blockindex.insert(&block);
}
return true;
return result;
}
bool BlockManager::ReadBlock(CBlock& block, const FlatFilePos& pos) const
@ -1089,27 +1124,31 @@ bool BlockManager::ReadRawBlock(std::vector<uint8_t>& block, const FlatFilePos&
return true;
}
FlatFilePos BlockManager::WriteBlock(const CBlock& block, int nHeight)
FlushResult<FlatFilePos, AbortFailure> BlockManager::WriteBlock(const CBlock& block, int nHeight)
{
const unsigned int block_size{static_cast<unsigned int>(GetSerializeSize(TX_WITH_WITNESS(block)))};
FlatFilePos pos{FindNextBlockPos(block_size + BLOCK_SERIALIZATION_HEADER_SIZE, nHeight, block.GetBlockTime())};
if (pos.IsNull()) {
LogError("FindNextBlockPos failed");
return FlatFilePos();
auto result{FindNextBlockPos(block_size + BLOCK_SERIALIZATION_HEADER_SIZE, nHeight, block.GetBlockTime())};
if (!result || result->IsNull()) {
auto error{Untranslated("FindNextBlockPos failed")};
LogError("%s", error.original);
result.Update(util::Error{std::move(error)});
return result;
}
AutoFile fileout{OpenBlockFile(pos)};
AutoFile fileout{OpenBlockFile(*result)};
if (fileout.IsNull()) {
LogError("OpenBlockFile failed");
m_opts.notifications.fatalError(_("Failed to write block."));
return FlatFilePos();
auto error{_("Failed to write block.")};
m_opts.notifications.fatalError(error);
result.Update({util::Error{std::move(error)}, AbortFailure{.fatal = true}});
return result;
}
// Write index header
fileout << GetParams().MessageStart() << block_size;
// Write block
pos.nPos += BLOCK_SERIALIZATION_HEADER_SIZE;
result->nPos += BLOCK_SERIALIZATION_HEADER_SIZE;
fileout << TX_WITH_WITNESS(block);
return pos;
return result;
}
static auto InitBlocksdirXorKey(const BlockManager::Options& opts)
@ -1201,8 +1240,9 @@ public:
}
};
void ImportBlocks(ChainstateManager& chainman, std::span<const fs::path> import_paths)
FlushResult<InterruptResult, AbortFailure> ImportBlocks(ChainstateManager& chainman, std::span<const fs::path> import_paths)
{
FlushResult<InterruptResult, AbortFailure> result;
ImportingNow imp{chainman.m_blockman.m_importing};
// -reindex
@ -1221,10 +1261,12 @@ void ImportBlocks(ChainstateManager& chainman, std::span<const fs::path> import_
break; // This error is logged in OpenBlockFile
}
LogPrintf("Reindexing block file blk%05u.dat...\n", (unsigned int)nFile);
chainman.LoadExternalBlockFile(file, &pos, &blocks_with_unknown_parent);
// Ignore failure value, do not treat flush error as failure.
chainman.LoadExternalBlockFile(file, &pos, &blocks_with_unknown_parent) >> result;
if (chainman.m_interrupt) {
LogPrintf("Interrupt requested. Exit %s\n", __func__);
return;
result.Update(Interrupted{});
return result;
}
nFile++;
}
@ -1232,7 +1274,8 @@ void ImportBlocks(ChainstateManager& chainman, std::span<const fs::path> import_
chainman.m_blockman.m_blockfiles_indexed = true;
LogPrintf("Reindexing finished\n");
// To avoid ending up in a situation without genesis block, re-try initializing (no-op if reindexing worked):
chainman.ActiveChainstate().LoadGenesisBlock();
// Ignore failure value, do not treat flush error as failure.
chainman.ActiveChainstate().LoadGenesisBlock() >> result;
}
// -loadblock=
@ -1240,10 +1283,12 @@ void ImportBlocks(ChainstateManager& chainman, std::span<const fs::path> import_
AutoFile file{fsbridge::fopen(path, "rb")};
if (!file.IsNull()) {
LogPrintf("Importing blocks file %s...\n", fs::PathToString(path));
chainman.LoadExternalBlockFile(file);
// Ignore failure value, do not treat flush error as failure.
chainman.LoadExternalBlockFile(file) >> result;
if (chainman.m_interrupt) {
LogPrintf("Interrupt requested. Exit %s\n", __func__);
return;
result.Update(Interrupted{});
return result;
}
} else {
LogPrintf("Warning: Could not open blocks file %s\n", fs::PathToString(path));
@ -1257,12 +1302,15 @@ void ImportBlocks(ChainstateManager& chainman, std::span<const fs::path> import_
// the relevant pointers before the ABC call.
for (Chainstate* chainstate : WITH_LOCK(::cs_main, return chainman.GetAll())) {
BlockValidationState state;
if (!chainstate->ActivateBestChain(state, nullptr)) {
chainman.GetNotifications().fatalError(strprintf(_("Failed to connect best block (%s)."), state.ToString()));
return;
if (!(chainstate->ActivateBestChain(state, nullptr) >> result)) {
auto error{strprintf(_("Failed to connect best block (%s)."), state.ToString())};
chainman.GetNotifications().fatalError(error);
result.Update({util::Error{std::move(error)}, AbortFailure{.fatal = true}});
return result;
}
}
// End scope of ImportingNow
return result;
}
std::ostream& operator<<(std::ostream& os, const BlockfileType& type) {

View file

@ -13,6 +13,7 @@
#include <kernel/chainparams.h>
#include <kernel/cs_main.h>
#include <kernel/messagestartchars.h>
#include <kernel/result.h>
#include <primitives/block.h>
#include <streams.h>
#include <sync.h>
@ -148,14 +149,14 @@ private:
* per index entry (nStatus, nChainWork, nTimeMax, etc.) as well as peripheral
* collections like m_dirty_blockindex.
*/
bool LoadBlockIndex(const std::optional<uint256>& snapshot_blockhash)
[[nodiscard]] util::Result<kernel::InterruptResult, kernel::AbortFailure> LoadBlockIndexData(const std::optional<uint256>& snapshot_blockhash)
EXCLUSIVE_LOCKS_REQUIRED(cs_main);
/** Return false if block file or undo file flushing fails. */
[[nodiscard]] bool FlushBlockFile(int blockfile_num, bool fFinalize, bool finalize_undo);
[[nodiscard]] kernel::FlushResult<> FlushBlockFile(int blockfile_num, bool fFinalize, bool finalize_undo);
/** Return false if undo file flushing fails. */
[[nodiscard]] bool FlushUndoFile(int block_file, bool finalize = false);
[[nodiscard]] kernel::FlushResult<> FlushUndoFile(int block_file, bool finalize = false);
/**
* Helper function performing various preparations before a block can be saved to disk:
@ -166,9 +167,9 @@ private:
* The nAddSize argument passed to this function should include not just the size of the serialized CBlock, but also the size of
* separator fields (BLOCK_SERIALIZATION_HEADER_SIZE).
*/
[[nodiscard]] FlatFilePos FindNextBlockPos(unsigned int nAddSize, unsigned int nHeight, uint64_t nTime);
[[nodiscard]] bool FlushChainstateBlockFile(int tip_height);
bool FindUndoPos(BlockValidationState& state, int nFile, FlatFilePos& pos, unsigned int nAddSize);
[[nodiscard]] kernel::FlushResult<FlatFilePos, kernel::AbortFailure> FindNextBlockPos(unsigned int nAddSize, unsigned int nHeight, uint64_t nTime);
[[nodiscard]] kernel::FlushResult<> FlushChainstateBlockFile(int tip_height);
[[nodiscard]] util::Result<void, kernel::AbortFailure> FindUndoPos(BlockValidationState& state, int nFile, FlatFilePos& pos, unsigned int nAddSize);
AutoFile OpenUndoFile(const FlatFilePos& pos, bool fReadOnly = false) const;
@ -301,7 +302,7 @@ public:
std::unique_ptr<BlockTreeDB> m_block_tree_db GUARDED_BY(::cs_main);
bool WriteBlockIndexDB() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
bool LoadBlockIndexDB(const std::optional<uint256>& snapshot_blockhash)
[[nodiscard]] util::Result<kernel::InterruptResult, kernel::AbortFailure> LoadBlockIndexDB(const std::optional<uint256>& snapshot_blockhash)
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
/**
@ -324,7 +325,7 @@ public:
/** Get block file info entry for one block file */
CBlockFileInfo* GetBlockFileInfo(size_t n);
bool WriteBlockUndo(const CBlockUndo& blockundo, BlockValidationState& state, CBlockIndex& block)
[[nodiscard]] kernel::FlushResult<void, kernel::AbortFailure> WriteBlockUndo(const CBlockUndo& blockundo, BlockValidationState& state, CBlockIndex& block)
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
/** Store block on disk and update block file statistics.
@ -335,7 +336,7 @@ public:
* @returns in case of success, the position to which the block was written to
* in case of an error, an empty FlatFilePos
*/
FlatFilePos WriteBlock(const CBlock& block, int nHeight);
[[nodiscard]] kernel::FlushResult<FlatFilePos, kernel::AbortFailure> WriteBlock(const CBlock& block, int nHeight);
/** Update blockfile info while processing a block during reindex. The block must be available on disk.
*
@ -424,7 +425,7 @@ public:
};
// Calls ActivateBestChain() even if no blocks are imported.
void ImportBlocks(ChainstateManager& chainman, std::span<const fs::path> import_paths);
[[nodiscard]] kernel::FlushResult<kernel::InterruptResult, kernel::AbortFailure> ImportBlocks(ChainstateManager& chainman, std::span<const fs::path> import_paths);
} // namespace node
#endif // BITCOIN_NODE_BLOCKSTORAGE_H

View file

@ -19,6 +19,7 @@
#include <util/fs.h>
#include <util/signalinterrupt.h>
#include <util/time.h>
#include <util/overloaded.h>
#include <util/translation.h>
#include <validation.h>
@ -26,36 +27,48 @@
#include <cassert>
#include <vector>
using kernel::AbortFailure;
using kernel::CacheSizes;
using kernel::FlushResult;
using kernel::Interrupted;
using kernel::InterruptResult;
namespace node {
// Complete initialization of chainstates after the initial call has been made
// to ChainstateManager::InitializeChainstate().
static ChainstateLoadResult CompleteChainstateInitialization(
static FlushResult<InterruptResult, ChainstateLoadError> CompleteChainstateInitialization(
ChainstateManager& chainman,
const ChainstateLoadOptions& options) EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
{
if (chainman.m_interrupt) return {ChainstateLoadStatus::INTERRUPTED, {}};
if (chainman.m_interrupt) return Interrupted{};
FlushResult<InterruptResult, ChainstateLoadError> result;
// LoadBlockIndex will load m_have_pruned if we've ever removed a
// block file from disk.
// Note that it also sets m_blockfiles_indexed based on the disk flag!
if (!chainman.LoadBlockIndex()) {
if (chainman.m_interrupt) return {ChainstateLoadStatus::INTERRUPTED, {}};
return {ChainstateLoadStatus::FAILURE, _("Error loading block database")};
auto load_result{chainman.LoadBlockIndex() >> result};
if (!load_result) {
result.Update({util::Error{_("Error loading block database")}, ChainstateLoadError::FAILURE});
return result;
} else if (IsInterrupted(*load_result) || chainman.m_interrupt) {
result.Update(Interrupted{});
return result;
}
if (!chainman.BlockIndex().empty() &&
!chainman.m_blockman.LookupBlockIndex(chainman.GetConsensus().hashGenesisBlock)) {
// If the loaded chain has a wrong genesis, bail out immediately
// (we're likely using a testnet datadir, or the other way around).
return {ChainstateLoadStatus::FAILURE_INCOMPATIBLE_DB, _("Incorrect or no genesis block found. Wrong datadir for network?")};
result.Update({util::Error{_("Incorrect or no genesis block found. Wrong datadir for network?")}, ChainstateLoadError::FAILURE_INCOMPATIBLE_DB});
return result;
}
// Check for changed -prune state. What we are concerned about is a user who has pruned blocks
// in the past, but is now trying to run unpruned.
if (chainman.m_blockman.m_have_pruned && !options.prune) {
return {ChainstateLoadStatus::FAILURE, _("You need to rebuild the database using -reindex to go back to unpruned mode. This will redownload the entire blockchain")};
result.Update({util::Error{_("You need to rebuild the database using -reindex to go back to unpruned mode. This will redownload the entire blockchain")}, ChainstateLoadError::FAILURE});
return result;
}
// At this point blocktree args are consistent with what's on disk.
@ -63,7 +76,8 @@ static ChainstateLoadResult CompleteChainstateInitialization(
// (otherwise we use the one already on disk).
// This is called again in ImportBlocks after the reindex completes.
if (chainman.m_blockman.m_blockfiles_indexed && !chainman.ActiveChainstate().LoadGenesisBlock()) {
return {ChainstateLoadStatus::FAILURE, _("Error initializing block database")};
result.Update({util::Error{_("Error initializing block database")}, ChainstateLoadError::FAILURE});
return result;
}
auto is_coinsview_empty = [&](Chainstate* chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
@ -93,7 +107,7 @@ static ChainstateLoadResult CompleteChainstateInitialization(
/*should_wipe=*/options.wipe_chainstate_db);
} catch (dbwrapper_error& err) {
LogError("%s\n", err.what());
return {ChainstateLoadStatus::FAILURE, _("Error opening coins database")};
return {util::Error{_("Error opening coins database")}, ChainstateLoadError::FAILURE};
}
if (options.coins_error_cb) {
@ -103,14 +117,17 @@ static ChainstateLoadResult CompleteChainstateInitialization(
// Refuse to load unsupported database format.
// This is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate
if (chainstate->CoinsDB().NeedsUpgrade()) {
return {ChainstateLoadStatus::FAILURE_INCOMPATIBLE_DB, _("Unsupported chainstate database format found. "
"Please restart with -reindex-chainstate. This will "
"rebuild the chainstate database.")};
result.Update({util::Error{_("Unsupported chainstate database format found. "
"Please restart with -reindex-chainstate. This will "
"rebuild the chainstate database.")},
ChainstateLoadError::FAILURE_INCOMPATIBLE_DB});
return result;
}
// ReplayBlocks is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate
if (!chainstate->ReplayBlocks()) {
return {ChainstateLoadStatus::FAILURE, _("Unable to replay blocks. You will need to rebuild the database using -reindex-chainstate.")};
result.Update({util::Error{_("Unable to replay blocks. You will need to rebuild the database using -reindex-chainstate.")}, ChainstateLoadError::FAILURE});
return result;
}
// The on-disk coinsdb is now in a good state, create the cache
@ -120,7 +137,8 @@ static ChainstateLoadResult CompleteChainstateInitialization(
if (!is_coinsview_empty(chainstate)) {
// LoadChainTip initializes the chain based on CoinsTip()'s best block
if (!chainstate->LoadChainTip()) {
return {ChainstateLoadStatus::FAILURE, _("Error initializing block database")};
result.Update({util::Error{_("Error initializing block database")}, ChainstateLoadError::FAILURE});
return result;
}
assert(chainstate->m_chain.Tip() != nullptr);
}
@ -129,21 +147,24 @@ static ChainstateLoadResult CompleteChainstateInitialization(
auto chainstates{chainman.GetAll()};
if (std::any_of(chainstates.begin(), chainstates.end(),
[](const Chainstate* cs) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return cs->NeedsRedownload(); })) {
return {ChainstateLoadStatus::FAILURE, strprintf(_("Witness data for blocks after height %d requires validation. Please restart with -reindex."),
chainman.GetConsensus().SegwitHeight)};
result.Update({util::Error{strprintf(_("Witness data for blocks after height %d requires validation. Please restart with -reindex."),
chainman.GetConsensus().SegwitHeight)}, ChainstateLoadError::FAILURE});
return result;
};
// Now that chainstates are loaded and we're able to flush to
// disk, rebalance the coins caches to desired levels based
// on the condition of each chainstate.
chainman.MaybeRebalanceCaches();
// Ignore failure value, do not treat flush error as failure.
chainman.MaybeRebalanceCaches() >> result;
return {ChainstateLoadStatus::SUCCESS, {}};
return result;
}
ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSizes& cache_sizes,
const ChainstateLoadOptions& options)
FlushResult<InterruptResult, ChainstateLoadError> LoadChainstate(ChainstateManager& chainman, const CacheSizes& cache_sizes,
const ChainstateLoadOptions& options)
{
FlushResult<InterruptResult, ChainstateLoadError> result;
if (!chainman.AssumedValidBlock().IsNull()) {
LogPrintf("Assuming ancestors of block %s have valid signatures.\n", chainman.AssumedValidBlock().GetHex());
} else {
@ -173,13 +194,14 @@ ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSize
if (has_snapshot && options.wipe_chainstate_db) {
LogPrintf("[snapshot] deleting snapshot chainstate due to reindexing\n");
if (!chainman.DeleteSnapshotChainstate()) {
return {ChainstateLoadStatus::FAILURE_FATAL, Untranslated("Couldn't remove snapshot chainstate.")};
result.Update({util::Error{Untranslated("Couldn't remove snapshot chainstate.")}, ChainstateLoadError::FAILURE_FATAL});
return result;
}
}
auto [init_status, init_error] = CompleteChainstateInitialization(chainman, options);
if (init_status != ChainstateLoadStatus::SUCCESS) {
return {init_status, init_error};
result.Update(CompleteChainstateInitialization(chainman, options));
if (!result || IsInterrupted(*result)) {
return result;
}
// If a snapshot chainstate was fully validated by a background chainstate during
@ -190,14 +212,18 @@ ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSize
// snapshot is actually validated? Because this entails unusual
// filesystem operations to move leveldb data directories around, and that seems
// too risky to do in the middle of normal runtime.
auto snapshot_completion = chainman.MaybeCompleteSnapshotValidation();
FlushResult<void, AbortFailure> snapshot_result;
auto snapshot_completion = chainman.MaybeCompleteSnapshotValidation(snapshot_result);
// Ignore failure value, do not treat flush error as failure.
snapshot_result >> result;
if (snapshot_completion == SnapshotCompletionResult::SKIPPED) {
// do nothing; expected case
} else if (snapshot_completion == SnapshotCompletionResult::SUCCESS) {
LogPrintf("[snapshot] cleaning up unneeded background chainstate, then reinitializing\n");
if (!chainman.ValidatedSnapshotCleanup()) {
return {ChainstateLoadStatus::FAILURE_FATAL, Untranslated("Background chainstate cleanup failed unexpectedly.")};
result.Update({util::Error{Untranslated("Background chainstate cleanup failed unexpectedly.")}, ChainstateLoadError::FAILURE_FATAL});
return result;
}
// Because ValidatedSnapshotCleanup() has torn down chainstates with
@ -213,20 +239,22 @@ ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSize
// for the fully validated chainstate.
chainman.ActiveChainstate().ClearBlockIndexCandidates();
auto [init_status, init_error] = CompleteChainstateInitialization(chainman, options);
if (init_status != ChainstateLoadStatus::SUCCESS) {
return {init_status, init_error};
auto result{CompleteChainstateInitialization(chainman, options)};
if (!result || IsInterrupted(*result)) {
return result;
}
} else {
return {ChainstateLoadStatus::FAILURE_FATAL, _(
result.Update({util::Error{_(
"UTXO snapshot failed to validate. "
"Restart to resume normal initial block download, or try loading a different snapshot.")};
"Restart to resume normal initial block download, or try loading a different snapshot.")},
ChainstateLoadError::FAILURE_FATAL});
return result;
}
return {ChainstateLoadStatus::SUCCESS, {}};
return result;
}
ChainstateLoadResult VerifyLoadedChainstate(ChainstateManager& chainman, const ChainstateLoadOptions& options)
FlushResult<InterruptResult, ChainstateLoadError> VerifyLoadedChainstate(ChainstateManager& chainman, const ChainstateLoadOptions& options)
{
auto is_coinsview_empty = [&](Chainstate* chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
return options.wipe_chainstate_db || chainstate->CoinsTip().GetBestBlock().IsNull();
@ -234,36 +262,40 @@ ChainstateLoadResult VerifyLoadedChainstate(ChainstateManager& chainman, const C
LOCK(cs_main);
FlushResult<InterruptResult, ChainstateLoadError> result;
for (Chainstate* chainstate : chainman.GetAll()) {
if (!is_coinsview_empty(chainstate)) {
const CBlockIndex* tip = chainstate->m_chain.Tip();
if (tip && tip->nTime > GetTime() + MAX_FUTURE_BLOCK_TIME) {
return {ChainstateLoadStatus::FAILURE, _("The block database contains a block which appears to be from the future. "
"This may be due to your computer's date and time being set incorrectly. "
"Only rebuild the block database if you are sure that your computer's date and time are correct")};
result.Update({util::Error{_("The block database contains a block which appears to be from the future. "
"This may be due to your computer's date and time being set incorrectly. "
"Only rebuild the block database if you are sure that your computer's date and time are correct")},
ChainstateLoadError::FAILURE});
return result;
}
VerifyDBResult result = CVerifyDB(chainman.GetNotifications()).VerifyDB(
auto verify_result{CVerifyDB(chainman.GetNotifications()).VerifyDB(
*chainstate, chainman.GetConsensus(), chainstate->CoinsDB(),
options.check_level,
options.check_blocks);
switch (result) {
case VerifyDBResult::SUCCESS:
case VerifyDBResult::SKIPPED_MISSING_BLOCKS:
break;
case VerifyDBResult::INTERRUPTED:
return {ChainstateLoadStatus::INTERRUPTED, _("Block verification was interrupted")};
case VerifyDBResult::CORRUPTED_BLOCK_DB:
return {ChainstateLoadStatus::FAILURE, _("Corrupted block database detected")};
case VerifyDBResult::SKIPPED_L3_CHECKS:
if (options.require_full_verification) {
return {ChainstateLoadStatus::FAILURE_INSUFFICIENT_DBCACHE, _("Insufficient dbcache for block verification")};
}
break;
} // no default case, so the compiler can warn about missing cases
options.check_blocks) >> result};
if (verify_result) {
std::visit(util::Overloaded{
[&](VerifySuccess) {},
[&](SkippedMissingBlocks) {},
[&](SkippedL3Checks) {
if (options.require_full_verification) {
result.Update({util::Error{_("Insufficient dbcache for block verification")}, ChainstateLoadError::FAILURE_INSUFFICIENT_DBCACHE});
}
},
[&](kernel::Interrupted) {
result.Update(Interrupted{});
}}, *verify_result);
} else {
result.Update({util::Error{_("Corrupted block database detected")}, ChainstateLoadError::FAILURE});
}
}
}
return {ChainstateLoadStatus::SUCCESS, {}};
return result;
}
} // namespace node

View file

@ -5,6 +5,8 @@
#ifndef BITCOIN_NODE_CHAINSTATE_H
#define BITCOIN_NODE_CHAINSTATE_H
#include <kernel/notifications_interface.h>
#include <util/result.h>
#include <util/translation.h>
#include <validation.h>
@ -41,34 +43,16 @@ struct ChainstateLoadOptions {
//! case, and treat other cases as errors. More complex applications may want to
//! try reindexing in the generic failure case, and pass an interrupt callback
//! and exit cleanly in the interrupted case.
enum class ChainstateLoadStatus {
SUCCESS,
enum class ChainstateLoadError {
FAILURE, //!< Generic failure which reindexing may fix
FAILURE_FATAL, //!< Fatal error which should not prompt to reindex
FAILURE_INCOMPATIBLE_DB,
FAILURE_INSUFFICIENT_DBCACHE,
INTERRUPTED,
};
//! Chainstate load status code and optional error string.
using ChainstateLoadResult = std::tuple<ChainstateLoadStatus, bilingual_str>;
/** This sequence can have 4 types of outcomes:
*
* 1. Success
* 2. Shutdown requested
* - nothing failed but a shutdown was triggered in the middle of the
* sequence
* 3. Soft failure
* - a failure that might be recovered from with a reindex
* 4. Hard failure
* - a failure that definitively cannot be recovered from with a reindex
*
* LoadChainstate returns a (status code, error string) tuple.
*/
ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const kernel::CacheSizes& cache_sizes,
const ChainstateLoadOptions& options);
ChainstateLoadResult VerifyLoadedChainstate(ChainstateManager& chainman, const ChainstateLoadOptions& options);
kernel::FlushResult<kernel::InterruptResult, ChainstateLoadError> LoadChainstate(ChainstateManager& chainman, const kernel::CacheSizes& cache_sizes,
const ChainstateLoadOptions& options);
kernel::FlushResult<kernel::InterruptResult, ChainstateLoadError> VerifyLoadedChainstate(ChainstateManager& chainman, const ChainstateLoadOptions& options);
} // namespace node
#endif // BITCOIN_NODE_CHAINSTATE_H

View file

@ -81,6 +81,8 @@ using interfaces::MakeSignalHandler;
using interfaces::Mining;
using interfaces::Node;
using interfaces::WalletLoader;
using kernel::AbortFailure;
using kernel::FlushResult;
using node::BlockAssembler;
using node::BlockWaitOptions;
using util::Join;
@ -944,7 +946,8 @@ public:
block.hashMerkleRoot = BlockMerkleRoot(block);
auto block_ptr = std::make_shared<const CBlock>(block);
return chainman().ProcessNewBlock(block_ptr, /*force_processing=*/true, /*min_pow_checked=*/true, /*new_block=*/nullptr);
FlushResult<void, AbortFailure> process_result; // Ignore flush and fatal error information, only care whether block is accepted.
return chainman().ProcessNewBlock(block_ptr, /*force_processing=*/true, /*min_pow_checked=*/true, /*new_block=*/nullptr, process_result);
}
std::unique_ptr<BlockTemplate> waitNext(BlockWaitOptions options) override

View file

@ -32,20 +32,28 @@
#include <vector>
using fsbridge::FopenFn;
using kernel::FlushResult;
using kernel::Interrupted;
using kernel::InterruptResult;
namespace node {
static const uint64_t MEMPOOL_DUMP_VERSION_NO_XOR_KEY{1};
static const uint64_t MEMPOOL_DUMP_VERSION{2};
bool LoadMempool(CTxMemPool& pool, const fs::path& load_path, Chainstate& active_chainstate, ImportMempoolOptions&& opts)
FlushResult<InterruptResult> LoadMempool(CTxMemPool& pool, const fs::path& load_path, Chainstate& active_chainstate, ImportMempoolOptions&& opts)
{
if (load_path.empty()) return false;
FlushResult<InterruptResult> result;
if (load_path.empty()) {
result.Update(util::Error{});
return result;
}
AutoFile file{opts.mockable_fopen_function(load_path, "rb")};
if (file.IsNull()) {
LogInfo("Failed to open mempool file. Continuing anyway.\n");
return false;
result.Update(util::Error{});
return result;
}
int64_t count = 0;
@ -64,7 +72,8 @@ bool LoadMempool(CTxMemPool& pool, const fs::path& load_path, Chainstate& active
} else if (version == MEMPOOL_DUMP_VERSION) {
file >> xor_key;
} else {
return false;
result.Update(util::Error{});
return result;
}
file.SetXor(xor_key);
uint64_t total_txns_to_load;
@ -98,7 +107,9 @@ bool LoadMempool(CTxMemPool& pool, const fs::path& load_path, Chainstate& active
}
if (nTime > TicksSinceEpoch<std::chrono::seconds>(now - pool.m_opts.expiry)) {
LOCK(cs_main);
const auto& accepted = AcceptToMemoryPool(active_chainstate, tx, nTime, /*bypass_limits=*/false, /*test_accept=*/false);
auto [accepted, flush_result] = AcceptToMemoryPool(active_chainstate, tx, nTime, /*bypass_limits=*/false, /*test_accept=*/false);
// Ignore failure value, do not treat flush error as failure.
flush_result >> result;
if (accepted.m_result_type == MempoolAcceptResult::ResultType::VALID) {
++count;
} else {
@ -115,8 +126,10 @@ bool LoadMempool(CTxMemPool& pool, const fs::path& load_path, Chainstate& active
} else {
++expired;
}
if (active_chainstate.m_chainman.m_interrupt)
return false;
if (active_chainstate.m_chainman.m_interrupt) {
result.Update(Interrupted{});
return result;
}
}
std::map<uint256, CAmount> mapDeltas;
file >> mapDeltas;
@ -139,11 +152,12 @@ bool LoadMempool(CTxMemPool& pool, const fs::path& load_path, Chainstate& active
}
} catch (const std::exception& e) {
LogInfo("Failed to deserialize mempool data on file: %s. Continuing anyway.\n", e.what());
return false;
result.Update(util::Error{});
return result;
}
LogInfo("Imported mempool transactions from file: %i succeeded, %i failed, %i expired, %i already there, %i waiting for initial broadcast\n", count, failed, expired, already_there, unbroadcast);
return true;
return result;
}
bool DumpMempool(const CTxMemPool& pool, const fs::path& dump_path, FopenFn mockable_fopen_function, bool skip_file_commit)

View file

@ -5,6 +5,8 @@
#ifndef BITCOIN_NODE_MEMPOOL_PERSIST_H
#define BITCOIN_NODE_MEMPOOL_PERSIST_H
#include <node/blockstorage.h>
#include <kernel/result.h>
#include <util/fs.h>
class Chainstate;
@ -24,7 +26,7 @@ struct ImportMempoolOptions {
bool apply_unbroadcast_set{true};
};
/** Import the file and attempt to add its contents to the mempool. */
bool LoadMempool(CTxMemPool& pool, const fs::path& load_path,
kernel::FlushResult<kernel::InterruptResult> LoadMempool(CTxMemPool& pool, const fs::path& load_path,
Chainstate& active_chainstate,
ImportMempoolOptions&& opts);

View file

@ -72,7 +72,7 @@ TransactionError BroadcastTransaction(NodeContext& node, const CTransactionRef t
if (max_tx_fee > 0) {
// First, call ATMP with test_accept and check the fee. If ATMP
// fails here, return error immediately.
const MempoolAcceptResult result = node.chainman->ProcessTransaction(tx, /*test_accept=*/ true);
auto [result, flush_result]{node.chainman->ProcessTransaction(tx, /*test_accept=*/ true)};
if (result.m_result_type != MempoolAcceptResult::ResultType::VALID) {
return HandleATMPError(result.m_state, err_string);
} else if (result.m_base_fees.value() > max_tx_fee) {
@ -80,7 +80,7 @@ TransactionError BroadcastTransaction(NodeContext& node, const CTransactionRef t
}
}
// Try to submit the transaction to the mempool.
const MempoolAcceptResult result = node.chainman->ProcessTransaction(tx, /*test_accept=*/ false);
auto [result, flush_result]{node.chainman->ProcessTransaction(tx, /*test_accept=*/ false)};
if (result.m_result_type != MempoolAcceptResult::ResultType::VALID) {
return HandleATMPError(result.m_state, err_string);
}

View file

@ -887,7 +887,7 @@ static RPCHelpMan pruneblockchain()
height = chainHeight - MIN_BLOCKS_TO_KEEP;
}
PruneBlockFilesManual(active_chainstate, height);
(void)PruneBlockFilesManual(active_chainstate, height);
return GetPruneHeight(chainman.m_blockman, active_chain).value_or(-1);
},
};
@ -1000,7 +1000,7 @@ static RPCHelpMan gettxoutsetinfo()
NodeContext& node = EnsureAnyNodeContext(request.context);
ChainstateManager& chainman = EnsureChainman(node);
Chainstate& active_chainstate = chainman.ActiveChainstate();
active_chainstate.ForceFlushStateToDisk();
(void)active_chainstate.ForceFlushStateToDisk();
CCoinsView* coins_view;
BlockManager* blockman;
@ -1194,8 +1194,9 @@ static RPCHelpMan verifychain()
LOCK(cs_main);
Chainstate& active_chainstate = chainman.ActiveChainstate();
return CVerifyDB(chainman.GetNotifications()).VerifyDB(
active_chainstate, chainman.GetParams().GetConsensus(), active_chainstate.CoinsTip(), check_level, check_depth) == VerifyDBResult::SUCCESS;
auto verify_result{CVerifyDB(chainman.GetNotifications()).VerifyDB(
active_chainstate, chainman.GetParams().GetConsensus(), active_chainstate.CoinsTip(), check_level, check_depth)};
return verify_result && std::holds_alternative<VerifySuccess>(*verify_result);
},
};
}
@ -1597,7 +1598,7 @@ static RPCHelpMan preciousblock()
}
BlockValidationState state;
chainman.ActiveChainstate().PreciousBlock(state, pblockindex);
(void)chainman.ActiveChainstate().PreciousBlock(state, pblockindex);
if (!state.IsValid()) {
throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString());
@ -1618,10 +1619,10 @@ void InvalidateBlock(ChainstateManager& chainman, const uint256 block_hash) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
}
}
chainman.ActiveChainstate().InvalidateBlock(state, pblockindex);
(void)chainman.ActiveChainstate().InvalidateBlock(state, pblockindex);
if (state.IsValid()) {
chainman.ActiveChainstate().ActivateBestChain(state);
(void)chainman.ActiveChainstate().ActivateBestChain(state);
}
if (!state.IsValid()) {
@ -1666,7 +1667,7 @@ void ReconsiderBlock(ChainstateManager& chainman, uint256 block_hash) {
}
BlockValidationState state;
chainman.ActiveChainstate().ActivateBestChain(state);
(void)chainman.ActiveChainstate().ActivateBestChain(state);
if (!state.IsValid()) {
throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString());
@ -2319,7 +2320,7 @@ static RPCHelpMan scantxoutset()
ChainstateManager& chainman = EnsureChainman(node);
LOCK(cs_main);
Chainstate& active_chainstate = chainman.ActiveChainstate();
active_chainstate.ForceFlushStateToDisk();
(void)active_chainstate.ForceFlushStateToDisk();
pcursor = CHECK_NONFATAL(active_chainstate.CoinsDB().Cursor());
tip = CHECK_NONFATAL(active_chainstate.m_chain.Tip());
}
@ -3123,7 +3124,7 @@ PrepareUTXOSnapshot(
//
AssertLockHeld(::cs_main);
chainstate.ForceFlushStateToDisk();
(void)chainstate.ForceFlushStateToDisk();
maybe_stats = GetUTXOStats(&chainstate.CoinsDB(), chainstate.m_blockman, CoinStatsHashType::HASH_SERIALIZED, interruption_point);
if (!maybe_stats) {

View file

@ -188,9 +188,12 @@ static RPCHelpMan testmempoolaccept()
Chainstate& chainstate = chainman.ActiveChainstate();
const PackageMempoolAcceptResult package_result = [&] {
LOCK(::cs_main);
if (txns.size() > 1) return ProcessNewPackage(chainstate, mempool, txns, /*test_accept=*/true, /*client_maxfeerate=*/{});
return PackageMempoolAcceptResult(txns[0]->GetWitnessHash(),
chainman.ProcessTransaction(txns[0], /*test_accept=*/true));
if (txns.size() > 1) {
auto [mempool_accept, flush_result]{ProcessNewPackage(chainstate, mempool, txns, /*test_accept=*/true, /*client_maxfeerate=*/{})};
return mempool_accept;
}
auto [mempool_accept, flush_result]{chainman.ProcessTransaction(txns[0], /*test_accept=*/true)};
return PackageMempoolAcceptResult(txns[0]->GetWitnessHash(), mempool_accept);
}();
UniValue rpc_result(UniValue::VARR);
@ -1019,7 +1022,7 @@ static RPCHelpMan submitpackage()
NodeContext& node = EnsureAnyNodeContext(request.context);
CTxMemPool& mempool = EnsureMemPool(node);
Chainstate& chainstate = EnsureChainman(node).ActiveChainstate();
const auto package_result = WITH_LOCK(::cs_main, return ProcessNewPackage(chainstate, mempool, txns, /*test_accept=*/ false, client_maxfeerate));
auto [package_result, flush_result]{WITH_LOCK(::cs_main, return ProcessNewPackage(chainstate, mempool, txns, /*test_accept=*/ false, client_maxfeerate))};
std::string package_msg = "success";

View file

@ -48,6 +48,8 @@
using interfaces::BlockTemplate;
using interfaces::Mining;
using kernel::AbortFailure;
using kernel::FlushResult;
using node::BlockAssembler;
using node::GetMinimumTime;
using node::NodeContext;
@ -152,7 +154,8 @@ static bool GenerateBlock(ChainstateManager& chainman, CBlock&& block, uint64_t&
if (!process_new_block) return true;
if (!chainman.ProcessNewBlock(block_out, /*force_processing=*/true, /*min_pow_checked=*/true, nullptr)) {
FlushResult<void, AbortFailure> process_result; // Ignore flush and fatal error information, only care whether block is accepted.
if (!chainman.ProcessNewBlock(block_out, /*force_processing=*/true, /*min_pow_checked=*/true, nullptr, process_result)) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "ProcessNewBlock, block not accepted");
}
@ -1067,7 +1070,8 @@ static RPCHelpMan submitblock()
bool new_block;
auto sc = std::make_shared<submitblock_StateCatcher>(block.GetHash());
CHECK_NONFATAL(chainman.m_options.signals)->RegisterSharedValidationInterface(sc);
bool accepted = chainman.ProcessNewBlock(blockptr, /*force_processing=*/true, /*min_pow_checked=*/true, /*new_block=*/&new_block);
FlushResult<void, AbortFailure> process_result;
bool accepted = chainman.ProcessNewBlock(blockptr, /*force_processing=*/true, /*min_pow_checked=*/true, /*new_block=*/&new_block, process_result);
CHECK_NONFATAL(chainman.m_options.signals)->UnregisterSharedValidationInterface(sc);
if (!new_block && accepted) {
return "duplicate";

View file

@ -18,6 +18,8 @@
#include <boost/test/unit_test.hpp>
using kernel::AbortFailure;
using kernel::FlushResult;
using node::BlockAssembler;
using node::BlockManager;
using node::CBlockTemplate;
@ -176,7 +178,9 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup)
uint256 chainA_last_header = last_header;
for (size_t i = 0; i < 2; i++) {
const auto& block = chainA[i];
BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(block, true, true, nullptr));
FlushResult<void, AbortFailure> process_result;
BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(block, true, true, nullptr, process_result));
BOOST_CHECK(process_result);
}
for (size_t i = 0; i < 2; i++) {
const auto& block = chainA[i];
@ -194,7 +198,9 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup)
uint256 chainB_last_header = last_header;
for (size_t i = 0; i < 3; i++) {
const auto& block = chainB[i];
BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(block, true, true, nullptr));
FlushResult<void, AbortFailure> process_result;
BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(block, true, true, nullptr, process_result));
BOOST_CHECK(process_result);
}
for (size_t i = 0; i < 3; i++) {
const auto& block = chainB[i];
@ -225,7 +231,9 @@ BOOST_FIXTURE_TEST_CASE(blockfilter_index_initial_sync, BuildChainTestingSetup)
// Reorg back to chain A.
for (size_t i = 2; i < 4; i++) {
const auto& block = chainA[i];
BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(block, true, true, nullptr));
FlushResult<void, AbortFailure> process_result;
BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(block, true, true, nullptr, process_result));
BOOST_CHECK(process_result);
}
// Check that chain A and B blocks can be retrieved.

View file

@ -40,7 +40,7 @@ BOOST_AUTO_TEST_CASE(blockmanager_find_block_pos)
};
BlockManager blockman{*Assert(m_node.shutdown_signal), blockman_opts};
// simulate adding a genesis block normally
BOOST_CHECK_EQUAL(blockman.WriteBlock(params->GenesisBlock(), 0).nPos, BLOCK_SERIALIZATION_HEADER_SIZE);
BOOST_CHECK_EQUAL(Assert(blockman.WriteBlock(params->GenesisBlock(), 0))->nPos, BLOCK_SERIALIZATION_HEADER_SIZE);
// simulate what happens during reindex
// simulate a well-formed genesis block being found at offset 8 in the blk00000.dat file
// the block is found at offset 8 because there is an 8 byte serialization header
@ -53,7 +53,7 @@ BOOST_AUTO_TEST_CASE(blockmanager_find_block_pos)
// this is a check to make sure that https://github.com/bitcoin/bitcoin/issues/21379 does not recur
// 8 bytes (for serialization header) + 285 (for serialized genesis block) = 293
// add another 8 bytes for the second block's serialization header and we get 293 + 8 = 301
FlatFilePos actual{blockman.WriteBlock(params->GenesisBlock(), 1)};
FlatFilePos actual{*Assert(blockman.WriteBlock(params->GenesisBlock(), 1))};
BOOST_CHECK_EQUAL(actual.nPos, BLOCK_SERIALIZATION_HEADER_SIZE + ::GetSerializeSize(TX_WITH_WITNESS(params->GenesisBlock())) + BLOCK_SERIALIZATION_HEADER_SIZE);
}
@ -166,10 +166,10 @@ BOOST_AUTO_TEST_CASE(blockmanager_flush_block_file)
BOOST_CHECK_EQUAL(blockman.CalculateCurrentUsage(), 0);
// Write the first block to a new location.
FlatFilePos pos1{blockman.WriteBlock(block1, /*nHeight=*/1)};
FlatFilePos pos1{*Assert(blockman.WriteBlock(block1, /*nHeight=*/1))};
// Write second block
FlatFilePos pos2{blockman.WriteBlock(block2, /*nHeight=*/2)};
FlatFilePos pos2{*Assert(blockman.WriteBlock(block2, /*nHeight=*/2))};
// Two blocks in the file
BOOST_CHECK_EQUAL(blockman.CalculateCurrentUsage(), (TEST_BLOCK_SIZE + BLOCK_SERIALIZATION_HEADER_SIZE) * 2);

View file

@ -13,6 +13,9 @@
#include <boost/test/unit_test.hpp>
using kernel::AbortFailure;
using kernel::FlushResult;
BOOST_AUTO_TEST_SUITE(coinstatsindex_tests)
BOOST_FIXTURE_TEST_CASE(coinstatsindex_initial_sync, TestChain100Setup)
@ -98,7 +101,9 @@ BOOST_FIXTURE_TEST_CASE(coinstatsindex_unclean_shutdown, TestChain100Setup)
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));
FlushResult<void, AbortFailure> accept_result;
BOOST_CHECK(m_node.chainman->AcceptBlock(new_block, accept_result, state, &new_block_index, true, nullptr, nullptr, true));
BOOST_CHECK(accept_result);
CCoinsViewCache view(&chainstate.CoinsTip());
BOOST_CHECK(chainstate.ConnectBlock(block, state, new_block_index, view));
}

View file

@ -38,9 +38,9 @@ FUZZ_TARGET(load_external_block_file, .init = initialize_load_external_block_fil
// Corresponds to the -reindex case (track orphan blocks across files).
FlatFilePos flat_file_pos;
std::multimap<uint256, FlatFilePos> blocks_with_unknown_parent;
g_setup->m_node.chainman->LoadExternalBlockFile(fuzzed_block_file, &flat_file_pos, &blocks_with_unknown_parent);
Assert(g_setup->m_node.chainman->LoadExternalBlockFile(fuzzed_block_file, &flat_file_pos, &blocks_with_unknown_parent));
} else {
// Corresponds to the -loadblock= case (orphan blocks aren't tracked across files).
g_setup->m_node.chainman->LoadExternalBlockFile(fuzzed_block_file);
Assert(g_setup->m_node.chainman->LoadExternalBlockFile(fuzzed_block_file));
}
}

View file

@ -320,8 +320,8 @@ FUZZ_TARGET(ephemeral_package_eval, .init = initialize_tx_pool)
auto single_submit = txs.size() == 1;
const auto result_package = WITH_LOCK(::cs_main,
return ProcessNewPackage(chainstate, tx_pool, txs, /*test_accept=*/single_submit, /*client_maxfeerate=*/{}));
auto [result_package, process_result]{WITH_LOCK(::cs_main,
return ProcessNewPackage(chainstate, tx_pool, txs, /*test_accept=*/single_submit, /*client_maxfeerate=*/{}))};
const auto res = WITH_LOCK(::cs_main, return AcceptToMemoryPool(chainstate, txs.back(), GetTime(),
/*bypass_limits=*/fuzzed_data_provider.ConsumeBool(), /*test_accept=*/!single_submit));
@ -495,13 +495,15 @@ FUZZ_TARGET(tx_package_eval, .init = initialize_tx_pool)
client_maxfeerate = CFeeRate(fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(-1, 50 * COIN), 100);
}
const auto result_package = WITH_LOCK(::cs_main,
return ProcessNewPackage(chainstate, tx_pool, txs, /*test_accept=*/single_submit, client_maxfeerate));
auto [result_package, process_result]{WITH_LOCK(::cs_main,
return ProcessNewPackage(chainstate, tx_pool, txs, /*test_accept=*/single_submit, client_maxfeerate))};
Assert(process_result);
// Always set bypass_limits to false because it is not supported in ProcessNewPackage and
// can be a source of divergence.
const auto res = WITH_LOCK(::cs_main, return AcceptToMemoryPool(chainstate, txs.back(), GetTime(),
/*bypass_limits=*/false, /*test_accept=*/!single_submit));
auto [res, flush_result]{WITH_LOCK(::cs_main, return AcceptToMemoryPool(chainstate, txs.back(), GetTime(),
/*bypass_limits=*/false, /*test_accept=*/!single_submit))};
Assert(flush_result);
const bool passed = res.m_result_type == MempoolAcceptResult::ResultType::VALID;
node.validation_signals->SyncWithValidationInterfaceQueue();

View file

@ -299,8 +299,9 @@ FUZZ_TARGET(tx_pool_standard, .init = initialize_tx_pool)
// Make sure ProcessNewPackage on one transaction works.
// The result is not guaranteed to be the same as what is returned by ATMP.
const auto result_package = WITH_LOCK(::cs_main,
return ProcessNewPackage(chainstate, tx_pool, {tx}, true, /*client_maxfeerate=*/{}));
auto [result_package, process_result]{WITH_LOCK(::cs_main,
return ProcessNewPackage(chainstate, tx_pool, {tx}, true, /*client_maxfeerate=*/{}))};
Assert(process_result);
// If something went wrong due to a package-specific policy, it might not return a
// validation result for the transaction.
if (result_package.m_state.GetResult() != PackageValidationResult::PCKG_POLICY) {
@ -310,7 +311,8 @@ FUZZ_TARGET(tx_pool_standard, .init = initialize_tx_pool)
it->second.m_result_type == MempoolAcceptResult::ResultType::INVALID);
}
const auto res = WITH_LOCK(::cs_main, return AcceptToMemoryPool(chainstate, tx, GetTime(), bypass_limits, /*test_accept=*/false));
auto [res, flush_result]{WITH_LOCK(::cs_main, return AcceptToMemoryPool(chainstate, tx, GetTime(), bypass_limits, /*test_accept=*/false))};
Assert(flush_result);
const bool accepted = res.m_result_type == MempoolAcceptResult::ResultType::VALID;
node.validation_signals->SyncWithValidationInterfaceQueue();
node.validation_signals->UnregisterSharedValidationInterface(txr);
@ -413,7 +415,8 @@ FUZZ_TARGET(tx_pool, .init = initialize_tx_pool)
const auto tx = MakeTransactionRef(mut_tx);
const bool bypass_limits = fuzzed_data_provider.ConsumeBool();
const auto res = WITH_LOCK(::cs_main, return AcceptToMemoryPool(chainstate, tx, GetTime(), bypass_limits, /*test_accept=*/false));
auto [res, flush_result]{WITH_LOCK(::cs_main, return AcceptToMemoryPool(chainstate, tx, GetTime(), bypass_limits, /*test_accept=*/false))};
Assert(flush_result);
const bool accepted = res.m_result_type == MempoolAcceptResult::ResultType::VALID;
if (accepted) {
txids.push_back(tx->GetHash());

View file

@ -89,7 +89,7 @@ FUZZ_TARGET(utxo_total_supply)
};
const auto UpdateUtxoStats = [&]() {
LOCK(chainman.GetMutex());
chainman.ActiveChainstate().ForceFlushStateToDisk();
Assert(chainman.ActiveChainstate().ForceFlushStateToDisk());
utxo_stats = std::move(
*Assert(kernel::ComputeUTXOStats(kernel::CoinStatsHashType::NONE, &chainman.ActiveChainstate().CoinsDB(), chainman.m_blockman, {})));
// Check that miner can't print more money than they are allowed to

View file

@ -102,7 +102,7 @@ BOOST_AUTO_TEST_CASE(findCommonAncestor)
auto* orig_tip = active.Tip();
for (int i = 0; i < 10; ++i) {
BlockValidationState state;
m_node.chainman->ActiveChainstate().InvalidateBlock(state, active.Tip());
BOOST_CHECK(m_node.chainman->ActiveChainstate().InvalidateBlock(state, active.Tip()));
}
BOOST_CHECK_EQUAL(active.Height(), orig_tip->nHeight - 10);
coinbaseKey.MakeNewKey(true);

View file

@ -33,6 +33,8 @@
using namespace util::hex_literals;
using interfaces::BlockTemplate;
using interfaces::Mining;
using kernel::AbortFailure;
using kernel::FlushResult;
using node::BlockAssembler;
namespace miner_tests {
@ -709,7 +711,9 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
// via the Mining interface. The former is used by net_processing as well
// as the submitblock RPC.
if (current_height % 2 == 0) {
BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(shared_pblock, /*force_processing=*/true, /*min_pow_checked=*/true, nullptr));
FlushResult<void, AbortFailure> process_result;
BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(shared_pblock, /*force_processing=*/true, /*min_pow_checked=*/true, nullptr, process_result));
BOOST_CHECK(process_result);
} else {
BOOST_REQUIRE(block_template->submitSolution(block.nVersion, block.nTime, block.nNonce, MakeTransactionRef(txCoinbase)));
}

View file

@ -11,6 +11,9 @@
#include <boost/test/unit_test.hpp>
using kernel::AbortFailure;
using kernel::FlushResult;
BOOST_FIXTURE_TEST_SUITE(peerman_tests, RegTestingSetup)
/** Window, in blocks, for connecting to NODE_NETWORK_LIMITED peers */
@ -24,7 +27,9 @@ static void mineBlock(const node::NodeContext& node, std::chrono::seconds block_
while (!CheckProofOfWork(block.GetHash(), block.nBits, node.chainman->GetConsensus())) ++block.nNonce;
block.fChecked = true; // little speedup
SetMockTime(curr_time); // process block at current time
Assert(node.chainman->ProcessNewBlock(std::make_shared<const CBlock>(block), /*force_processing=*/true, /*min_pow_checked=*/true, nullptr));
FlushResult<void, AbortFailure> process_result;
Assert(node.chainman->ProcessNewBlock(std::make_shared<const CBlock>(block), /*force_processing=*/true, /*min_pow_checked=*/true, nullptr, process_result));
Assert(process_result);
node.validation_signals->SyncWithValidationInterfaceQueue(); // drain events queue
}

View file

@ -2,9 +2,17 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <tinyformat.h>
#include <util/check.h>
#include <util/result.h>
#include <util/translation.h>
#include <algorithm>
#include <boost/test/unit_test.hpp>
#include <memory>
#include <ostream>
#include <string>
#include <utility>
inline bool operator==(const bilingual_str& a, const bilingual_str& b)
{
@ -33,6 +41,34 @@ std::ostream& operator<<(std::ostream& os, const NoCopy& o)
return os << "NoCopy(" << *o.m_n << ")";
}
struct NoCopyNoMove {
NoCopyNoMove(int n) : m_n{n} {}
NoCopyNoMove(const NoCopyNoMove&) = delete;
NoCopyNoMove(NoCopyNoMove&&) = delete;
int m_n;
};
bool operator==(const NoCopyNoMove& a, const NoCopyNoMove& b)
{
return a.m_n == b.m_n;
}
std::ostream& operator<<(std::ostream& os, const NoCopyNoMove& o)
{
os << "NoCopyNoMove(" << o.m_n << ")";
return os;
}
util::Result<void> VoidSuccessFn()
{
return {};
}
util::Result<void> VoidFailFn()
{
return util::Error{Untranslated("void fail.")};
}
util::Result<int> IntFn(int i, bool success)
{
if (success) return i;
@ -45,21 +81,91 @@ util::Result<bilingual_str> StrFn(bilingual_str s, bool success)
return util::Error{Untranslated(strprintf("str %s error.", s.original))};
}
util::Result<NoCopy> NoCopyFn(int i, bool success)
util::Result<NoCopy, NoCopy> NoCopyFn(int i, bool success)
{
if (success) return {i};
return util::Error{Untranslated(strprintf("nocopy %i error.", i))};
return {util::Error{Untranslated(strprintf("nocopy %i error.", i))}, i};
}
template <typename T>
void ExpectResult(const util::Result<T>& result, bool success, const bilingual_str& str)
util::Result<NoCopyNoMove, NoCopyNoMove> NoCopyNoMoveFn(int i, bool success)
{
if (success) return {i};
return {util::Error{Untranslated(strprintf("nocopynomove %i error.", i))}, i};
}
enum FnError { ERR1, ERR2 };
util::Result<int, FnError> IntFailFn(int i, bool success)
{
if (success) return {util::Warning{Untranslated(strprintf("int %i warn.", i))}, i};
return {util::Error{Untranslated(strprintf("int %i error.", i))}, i % 2 ? ERR1 : ERR2};
}
util::Result<std::string, FnError> StrFailFn(int i, bool success)
{
util::Result<std::string, FnError> result;
if (auto int_result{IntFailFn(i, success) >> result}) {
result.Update(strprintf("%i", *int_result));
} else {
result.Update({util::Error{Untranslated("str error")}, int_result.GetFailure()});
}
return result;
}
util::Result<NoCopyNoMove, FnError> EnumFailFn(FnError ret)
{
return {util::Error{Untranslated("enum fail.")}, ret};
}
util::Result<void> WarnFn()
{
return {util::Warning{Untranslated("warn.")}};
}
util::Result<int> MultiWarnFn(int ret)
{
util::Result<int> result;
for (int i = 0; i < ret; ++i) {
result.AddWarning(Untranslated(strprintf("warn %i.", i)));
}
result.Update(ret);
return result;
}
util::Result<void, int> ChainedFailFn(FnError arg, int ret)
{
util::Result<void, int> result{util::Error{Untranslated("chained fail.")}, ret};
EnumFailFn(arg) >> result;
WarnFn() >> result;
return result;
}
util::Result<int, FnError> AccumulateFn(bool success)
{
util::Result<int, FnError> result;
util::Result<int> x = MultiWarnFn(1) >> result;
BOOST_REQUIRE(x);
util::Result<int> y = MultiWarnFn(2) >> result;
BOOST_REQUIRE(y);
result.Update(IntFailFn(*x + *y, success));
return result;
}
util::Result<int, int> TruthyFalsyFn(int i, bool success)
{
if (success) return i;
return {util::Error{Untranslated(strprintf("failure value %i.", i))}, i};
}
template <typename T, typename F>
void ExpectResult(const util::Result<T, F>& result, bool success, const bilingual_str& str)
{
BOOST_CHECK_EQUAL(bool(result), success);
BOOST_CHECK_EQUAL(util::ErrorString(result), str);
}
template <typename T, typename... Args>
void ExpectSuccess(const util::Result<T>& result, const bilingual_str& str, Args&&... args)
template <typename T, typename F, typename... Args>
void ExpectSuccess(const util::Result<T, F>& result, const bilingual_str& str, Args&&... args)
{
ExpectResult(result, true, str);
BOOST_CHECK_EQUAL(result.has_value(), true);
@ -68,20 +174,74 @@ void ExpectSuccess(const util::Result<T>& result, const bilingual_str& str, Args
BOOST_CHECK_EQUAL(&result.value(), &*result);
}
template <typename T, typename... Args>
void ExpectFail(const util::Result<T>& result, const bilingual_str& str)
template <typename T, typename F, typename... Args>
void ExpectFail(const util::Result<T, F>& result, bilingual_str str, Args&&... args)
{
ExpectResult(result, false, str);
F expect_failure{std::forward<Args>(args)...};
BOOST_CHECK_EQUAL(result.GetFailure(), expect_failure);
}
BOOST_AUTO_TEST_CASE(check_sizes)
{
static_assert(sizeof(util::Result<int>) == sizeof(void*)*2);
static_assert(sizeof(util::Result<void>) == sizeof(void*));
}
BOOST_AUTO_TEST_CASE(check_returned)
{
ExpectResult(VoidSuccessFn(), true, {});
ExpectResult(VoidFailFn(), false, Untranslated("void fail."));
ExpectSuccess(IntFn(5, true), {}, 5);
ExpectFail(IntFn(5, false), Untranslated("int 5 error."));
ExpectResult(IntFn(5, false), false, Untranslated("int 5 error."));
ExpectSuccess(NoCopyFn(5, true), {}, 5);
ExpectFail(NoCopyFn(5, false), Untranslated("nocopy 5 error."));
ExpectFail(NoCopyFn(5, false), Untranslated("nocopy 5 error."), 5);
ExpectSuccess(NoCopyNoMoveFn(5, true), {}, 5);
ExpectFail(NoCopyNoMoveFn(5, false), Untranslated("nocopynomove 5 error."), 5);
ExpectSuccess(StrFn(Untranslated("S"), true), {}, Untranslated("S"));
ExpectFail(StrFn(Untranslated("S"), false), Untranslated("str S error."));
ExpectResult(StrFn(Untranslated("S"), false), false, Untranslated("str S error."));
ExpectSuccess(StrFailFn(1, true), Untranslated("int 1 warn."), "1");
ExpectFail(StrFailFn(2, false), Untranslated("int 2 error. str error"), ERR2);
ExpectFail(EnumFailFn(ERR2), Untranslated("enum fail."), ERR2);
ExpectFail(ChainedFailFn(ERR1, 5), Untranslated("chained fail. enum fail. warn."), 5);
ExpectSuccess(MultiWarnFn(3), Untranslated("warn 0. warn 1. warn 2."), 3);
ExpectSuccess(AccumulateFn(true), Untranslated("warn 0. warn 0. warn 1. int 3 warn."), 3);
ExpectFail(AccumulateFn(false), Untranslated("int 3 error. warn 0. warn 0. warn 1."), ERR1);
ExpectSuccess(TruthyFalsyFn(0, true), {}, 0);
ExpectFail(TruthyFalsyFn(0, false), Untranslated("failure value 0."), 0);
ExpectSuccess(TruthyFalsyFn(1, true), {}, 1);
ExpectFail(TruthyFalsyFn(1, false), Untranslated("failure value 1."), 1);
}
BOOST_AUTO_TEST_CASE(check_update)
{
// Test using Update method to change a result value from success -> failure,
// and failure->success.
util::Result<int, FnError> result;
ExpectSuccess(result, {}, 0);
result.Update({util::Error{Untranslated("error")}, ERR1});
ExpectFail(result, Untranslated("error"), ERR1);
result.Update(2);
ExpectSuccess(result, Untranslated("error"), 2);
// Test the same thing but with non-copyable success and failure types.
util::Result<NoCopy, NoCopy> result2{0};
ExpectSuccess(result2, {}, 0);
result2.Update({util::Error{Untranslated("error")}, 3});
ExpectFail(result2, Untranslated("error"), 3);
result2.Update(4);
ExpectSuccess(result2, Untranslated("error"), 4);
}
BOOST_AUTO_TEST_CASE(check_dereference_operators)
{
util::Result<std::pair<int, std::string>> mutable_result;
const auto& const_result{mutable_result};
mutable_result.value() = {1, "23"};
BOOST_CHECK_EQUAL(mutable_result->first, 1);
BOOST_CHECK_EQUAL(const_result->second, "23");
(*mutable_result).first = 5;
BOOST_CHECK_EQUAL((*const_result).first, 5);
}
BOOST_AUTO_TEST_CASE(check_value_or)
@ -94,4 +254,28 @@ BOOST_AUTO_TEST_CASE(check_value_or)
BOOST_CHECK_EQUAL(StrFn(Untranslated("A"), false).value_or(Untranslated("B")), Untranslated("B"));
}
BOOST_AUTO_TEST_CASE(check_message_accessors)
{
util::Result<void> result{util::Error{Untranslated("Error.")}, util::Warning{Untranslated("Warning.")}};
BOOST_CHECK_EQUAL(Assert(result.GetMessages())->errors.size(), 1);
BOOST_CHECK_EQUAL(Assert(result.GetMessages())->errors[0], Untranslated("Error."));
BOOST_CHECK_EQUAL(Assert(result.GetMessages())->warnings.size(), 1);
BOOST_CHECK_EQUAL(Assert(result.GetMessages())->warnings[0], Untranslated("Warning."));
BOOST_CHECK_EQUAL(util::ErrorString(result), Untranslated("Error. Warning."));
}
struct Derived : NoCopyNoMove {
using NoCopyNoMove::NoCopyNoMove;
};
util::Result<std::unique_ptr<NoCopyNoMove>> DerivedToBaseFn(util::Result<std::unique_ptr<Derived>> derived)
{
return derived;
}
BOOST_AUTO_TEST_CASE(derived_to_base)
{
BOOST_CHECK_EQUAL(*Assert(*Assert(DerivedToBaseFn(std::make_unique<Derived>(5)))), 5);
}
BOOST_AUTO_TEST_SUITE_END()

View file

@ -221,7 +221,7 @@ BOOST_FIXTURE_TEST_CASE(handle_missing_inputs, TestChain100Setup)
if (parent_recent_rej_recon) txdownload_impl.RecentRejectsReconsiderableFilter().insert(single_parent->GetHash().ToUint256());
if (parent_recent_conf) txdownload_impl.RecentConfirmedTransactionsFilter().insert(single_parent->GetHash().ToUint256());
if (parent_in_mempool) {
const auto mempool_result = WITH_LOCK(::cs_main, return m_node.chainman->ProcessTransaction(single_parent));
auto [mempool_result, flush_result]{WITH_LOCK(::cs_main, return m_node.chainman->ProcessTransaction(single_parent))};
BOOST_CHECK(mempool_result.m_result_type == MempoolAcceptResult::ResultType::VALID);
coinbase_idx += 1;
assert(coinbase_idx < m_coinbase_txns.size());

View file

@ -222,7 +222,8 @@ BOOST_AUTO_TEST_CASE(package_validation_tests)
/*output_amount=*/CAmount(48 * COIN), /*submit=*/false);
CTransactionRef tx_child = MakeTransactionRef(mtx_child);
Package package_parent_child{tx_parent, tx_child};
const auto result_parent_child = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package_parent_child, /*test_accept=*/true, /*client_maxfeerate=*/{});
auto [result_parent_child, process_result]{ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package_parent_child, /*test_accept=*/true, /*client_maxfeerate=*/{})};
BOOST_CHECK(process_result);
if (auto err_parent_child{CheckPackageMempoolAcceptResult(package_parent_child, result_parent_child, /*expect_valid=*/true, nullptr)}) {
BOOST_ERROR(err_parent_child.value());
} else {
@ -241,7 +242,8 @@ BOOST_AUTO_TEST_CASE(package_validation_tests)
CTransactionRef giant_ptx = create_placeholder_tx(999, 999);
BOOST_CHECK(GetVirtualTransactionSize(*giant_ptx) > DEFAULT_ANCESTOR_SIZE_LIMIT_KVB * 1000);
Package package_single_giant{giant_ptx};
auto result_single_large = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package_single_giant, /*test_accept=*/true, /*client_maxfeerate=*/{});
auto [result_single_large, process_result_single]{ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package_single_giant, /*test_accept=*/true, /*client_maxfeerate=*/{})};
BOOST_CHECK(process_result_single);
if (auto err_single_large{CheckPackageMempoolAcceptResult(package_single_giant, result_single_large, /*expect_valid=*/false, nullptr)}) {
BOOST_ERROR(err_single_large.value());
} else {
@ -369,8 +371,9 @@ BOOST_AUTO_TEST_CASE(package_submission_tests)
/*output_amount=*/CAmount(49 * COIN), /*submit=*/false);
package_unrelated.emplace_back(MakeTransactionRef(mtx));
}
auto result_unrelated_submit = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
package_unrelated, /*test_accept=*/false, /*client_maxfeerate=*/{});
auto [result_unrelated_submit, process_result]{ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
package_unrelated, /*test_accept=*/false, /*client_maxfeerate=*/{})};
BOOST_CHECK(process_result);
// We don't expect m_tx_results for each transaction when basic sanity checks haven't passed.
BOOST_CHECK(result_unrelated_submit.m_state.IsInvalid());
BOOST_CHECK_EQUAL(result_unrelated_submit.m_state.GetResult(), PackageValidationResult::PCKG_POLICY);
@ -409,8 +412,9 @@ BOOST_AUTO_TEST_CASE(package_submission_tests)
// 3 Generations is not allowed.
{
auto result_3gen_submit = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
package_3gen, /*test_accept=*/false, /*client_maxfeerate=*/{});
auto [result_3gen_submit, process_result]{ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
package_3gen, /*test_accept=*/false, /*client_maxfeerate=*/{})};
BOOST_CHECK(process_result);
BOOST_CHECK(result_3gen_submit.m_state.IsInvalid());
BOOST_CHECK_EQUAL(result_3gen_submit.m_state.GetResult(), PackageValidationResult::PCKG_POLICY);
BOOST_CHECK_EQUAL(result_3gen_submit.m_state.GetRejectReason(), "package-not-child-with-parents");
@ -426,8 +430,9 @@ BOOST_AUTO_TEST_CASE(package_submission_tests)
mtx_parent_invalid.vin[0].scriptWitness = bad_witness;
CTransactionRef tx_parent_invalid = MakeTransactionRef(mtx_parent_invalid);
Package package_invalid_parent{tx_parent_invalid, tx_child};
auto result_quit_early = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
package_invalid_parent, /*test_accept=*/ false, /*client_maxfeerate=*/{});
auto [result_quit_early, process_result]{ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
package_invalid_parent, /*test_accept=*/ false, /*client_maxfeerate=*/{})};
BOOST_CHECK(process_result);
if (auto err_parent_invalid{CheckPackageMempoolAcceptResult(package_invalid_parent, result_quit_early, /*expect_valid=*/false, m_node.mempool.get())}) {
BOOST_ERROR(err_parent_invalid.value());
} else {
@ -447,8 +452,9 @@ BOOST_AUTO_TEST_CASE(package_submission_tests)
package_missing_parent.push_back(tx_parent);
package_missing_parent.push_back(MakeTransactionRef(mtx_child));
{
const auto result_missing_parent = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
package_missing_parent, /*test_accept=*/false, /*client_maxfeerate=*/{});
auto [result_missing_parent, process_result]{ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
package_missing_parent, /*test_accept=*/false, /*client_maxfeerate=*/{})};
BOOST_CHECK(process_result);
BOOST_CHECK(result_missing_parent.m_state.IsInvalid());
BOOST_CHECK_EQUAL(result_missing_parent.m_state.GetResult(), PackageValidationResult::PCKG_POLICY);
BOOST_CHECK_EQUAL(result_missing_parent.m_state.GetRejectReason(), "package-not-child-with-unconfirmed-parents");
@ -457,8 +463,9 @@ BOOST_AUTO_TEST_CASE(package_submission_tests)
// Submit package with parent + child.
{
const auto submit_parent_child = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
package_parent_child, /*test_accept=*/false, /*client_maxfeerate=*/{});
auto [submit_parent_child, process_result]{ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
package_parent_child, /*test_accept=*/false, /*client_maxfeerate=*/{})};
BOOST_CHECK(process_result);
expected_pool_size += 2;
BOOST_CHECK_MESSAGE(submit_parent_child.m_state.IsValid(),
"Package validation unexpectedly failed: " << submit_parent_child.m_state.GetRejectReason());
@ -479,8 +486,9 @@ BOOST_AUTO_TEST_CASE(package_submission_tests)
// Already-in-mempool transactions should be detected and de-duplicated.
{
const auto submit_deduped = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
package_parent_child, /*test_accept=*/false, /*client_maxfeerate=*/{});
auto [submit_deduped, process_result]{ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
package_parent_child, /*test_accept=*/false, /*client_maxfeerate=*/{})};
BOOST_CHECK(process_result);
if (auto err_deduped{CheckPackageMempoolAcceptResult(package_parent_child, submit_deduped, /*expect_valid=*/true, m_node.mempool.get())}) {
BOOST_ERROR(err_deduped.value());
} else {
@ -513,11 +521,12 @@ BOOST_AUTO_TEST_CASE(package_single_tx)
/*output_amount=*/CAmount(49 * COIN), /*submit=*/false);
CTransactionRef tx_single = MakeTransactionRef(mtx_single);
Package package_tx_single{tx_single};
const auto result_single_tx = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
package_tx_single, /*test_accept=*/false, /*client_maxfeerate=*/{});
auto [result_single_tx, process_result_single_tx]{ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
package_tx_single, /*test_accept=*/false, /*client_maxfeerate=*/{})};
expected_pool_size += 1;
BOOST_CHECK_MESSAGE(result_single_tx.m_state.IsValid(),
"Package validation unexpectedly failed: " << result_single_tx.m_state.ToString());
BOOST_CHECK(process_result_single_tx);
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
// Parent and Child. Both submitted by themselves through the ProcessNewPackage interface.
@ -529,7 +538,8 @@ BOOST_AUTO_TEST_CASE(package_single_tx)
/*output_amount=*/CAmount(50 * COIN) - high_fee, /*submit=*/false);
CTransactionRef tx_parent = MakeTransactionRef(mtx_parent);
Package package_just_parent{tx_parent};
const auto result_just_parent = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package_just_parent, /*test_accept=*/false, /*client_maxfeerate=*/{});
auto [result_just_parent, process_result_just_parent]{ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package_just_parent, /*test_accept=*/false, /*client_maxfeerate=*/{})};
BOOST_CHECK(process_result_just_parent);
if (auto err_parent_child{CheckPackageMempoolAcceptResult(package_just_parent, result_just_parent, /*expect_valid=*/true, nullptr)}) {
BOOST_ERROR(err_parent_child.value());
} else {
@ -550,7 +560,8 @@ BOOST_AUTO_TEST_CASE(package_single_tx)
/*output_amount=*/CAmount(50 * COIN) - 2 * high_fee, /*submit=*/false);
CTransactionRef tx_child = MakeTransactionRef(mtx_child);
Package package_just_child{tx_child};
const auto result_just_child = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package_just_child, /*test_accept=*/false, /*client_maxfeerate=*/{});
auto [result_just_child, process_result_just_child]{ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package_just_child, /*test_accept=*/false, /*client_maxfeerate=*/{})};
BOOST_CHECK(process_result_just_child);
if (auto err_parent_child{CheckPackageMempoolAcceptResult(package_just_child, result_just_child, /*expect_valid=*/true, nullptr)}) {
BOOST_ERROR(err_parent_child.value());
} else {
@ -570,9 +581,9 @@ BOOST_AUTO_TEST_CASE(package_single_tx)
/*output_amount=*/CAmount(49 * COIN - 1), /*submit=*/false);
CTransactionRef tx_single_low_fee = MakeTransactionRef(mtx_single_low_fee);
Package package_tx_single_low_fee{tx_single_low_fee};
const auto result_single_tx_low_fee = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
package_tx_single_low_fee, /*test_accept=*/false, /*client_maxfeerate=*/{});
auto [result_single_tx_low_fee, process_result_single_tx_low_fee]{ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
package_tx_single_low_fee, /*test_accept=*/false, /*client_maxfeerate=*/{})};
BOOST_CHECK(process_result_single_tx_low_fee);
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
BOOST_CHECK(!result_single_tx_low_fee.m_state.IsValid());
@ -643,16 +654,18 @@ BOOST_AUTO_TEST_CASE(package_witness_swap_tests)
// same-txid-different-witness.
{
Package package_parent_child1{ptx_parent, ptx_child1};
const auto submit_witness1 = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
package_parent_child1, /*test_accept=*/false, /*client_maxfeerate=*/{});
auto [submit_witness1, process_result1]{ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
package_parent_child1, /*test_accept=*/false, /*client_maxfeerate=*/{})};
BOOST_CHECK(process_result1);
if (auto err_witness1{CheckPackageMempoolAcceptResult(package_parent_child1, submit_witness1, /*expect_valid=*/true, m_node.mempool.get())}) {
BOOST_ERROR(err_witness1.value());
}
// Child2 would have been validated individually.
Package package_parent_child2{ptx_parent, ptx_child2};
const auto submit_witness2 = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
package_parent_child2, /*test_accept=*/false, /*client_maxfeerate=*/{});
auto [submit_witness2, process_result2]{ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
package_parent_child2, /*test_accept=*/false, /*client_maxfeerate=*/{})};
BOOST_CHECK(process_result2);
if (auto err_witness2{CheckPackageMempoolAcceptResult(package_parent_child2, submit_witness2, /*expect_valid=*/true, m_node.mempool.get())}) {
BOOST_ERROR(err_witness2.value());
} else {
@ -665,8 +678,9 @@ BOOST_AUTO_TEST_CASE(package_witness_swap_tests)
// Deduplication should work when wtxid != txid. Submit package with the already-in-mempool
// transactions again, which should not fail.
const auto submit_segwit_dedup = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
package_parent_child1, /*test_accept=*/false, /*client_maxfeerate=*/{});
auto [submit_segwit_dedup, process_result]{ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
package_parent_child1, /*test_accept=*/false, /*client_maxfeerate=*/{})};
BOOST_CHECK(process_result);
if (auto err_segwit_dedup{CheckPackageMempoolAcceptResult(package_parent_child1, submit_segwit_dedup, /*expect_valid=*/true, m_node.mempool.get())}) {
BOOST_ERROR(err_segwit_dedup.value());
} else {
@ -696,8 +710,9 @@ BOOST_AUTO_TEST_CASE(package_witness_swap_tests)
// We already submitted child1 above.
{
Package package_child2_grandchild{ptx_child2, ptx_grandchild};
const auto submit_spend_ignored = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
package_child2_grandchild, /*test_accept=*/false, /*client_maxfeerate=*/{});
auto [submit_spend_ignored, process_result]{ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
package_child2_grandchild, /*test_accept=*/false, /*client_maxfeerate=*/{})};
BOOST_CHECK(process_result);
if (auto err_spend_ignored{CheckPackageMempoolAcceptResult(package_child2_grandchild, submit_spend_ignored, /*expect_valid=*/true, m_node.mempool.get())}) {
BOOST_ERROR(err_spend_ignored.value());
} else {
@ -760,8 +775,9 @@ BOOST_AUTO_TEST_CASE(package_witness_swap_tests)
CTransactionRef ptx_parent2_v1 = MakeTransactionRef(mtx_parent2_v1);
CTransactionRef ptx_parent2_v2 = MakeTransactionRef(mtx_parent2_v2);
// Put parent2_v1 in the package, submit parent2_v2 to the mempool.
const MempoolAcceptResult parent2_v2_result = m_node.chainman->ProcessTransaction(ptx_parent2_v2);
auto [parent2_v2_result, process_result]{m_node.chainman->ProcessTransaction(ptx_parent2_v2)};
BOOST_CHECK(parent2_v2_result.m_result_type == MempoolAcceptResult::ResultType::VALID);
BOOST_CHECK(process_result);
package_mixed.push_back(ptx_parent2_v1);
// parent3 will be a new transaction. Put a low feerate to make it invalid on its own.
@ -795,7 +811,8 @@ BOOST_AUTO_TEST_CASE(package_witness_swap_tests)
// parent3 should be accepted
// child should be accepted
{
const auto mixed_result = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package_mixed, false, /*client_maxfeerate=*/{});
auto [mixed_result, process_result]{ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package_mixed, false, /*client_maxfeerate=*/{})};
BOOST_CHECK(process_result);
if (auto err_mixed{CheckPackageMempoolAcceptResult(package_mixed, mixed_result, /*expect_valid=*/true, m_node.mempool.get())}) {
BOOST_ERROR(err_mixed.value());
} else {
@ -858,8 +875,9 @@ BOOST_AUTO_TEST_CASE(package_cpfp_tests)
m_node.mempool->PrioritiseTransaction(tx_parent->GetHash(), child_value - coinbase_value);
{
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
const auto submit_cpfp_deprio = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
package_cpfp, /*test_accept=*/ false, /*client_maxfeerate=*/{});
auto [submit_cpfp_deprio, process_result]{ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
package_cpfp, /*test_accept=*/ false, /*client_maxfeerate=*/{})};
BOOST_CHECK(process_result);
if (auto err_cpfp_deprio{CheckPackageMempoolAcceptResult(package_cpfp, submit_cpfp_deprio, /*expect_valid=*/false, m_node.mempool.get())}) {
BOOST_ERROR(err_cpfp_deprio.value());
} else {
@ -880,8 +898,9 @@ BOOST_AUTO_TEST_CASE(package_cpfp_tests)
// child pays enough for the package feerate to meet the threshold.
{
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
const auto submit_cpfp = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
package_cpfp, /*test_accept=*/ false, /*client_maxfeerate=*/{});
auto [submit_cpfp, process_result]{ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
package_cpfp, /*test_accept=*/ false, /*client_maxfeerate=*/{})};
BOOST_CHECK(process_result);
if (auto err_cpfp{CheckPackageMempoolAcceptResult(package_cpfp, submit_cpfp, /*expect_valid=*/true, m_node.mempool.get())}) {
BOOST_ERROR(err_cpfp.value());
} else {
@ -932,8 +951,9 @@ BOOST_AUTO_TEST_CASE(package_cpfp_tests)
// Cheap package should fail for being too low fee.
{
const auto submit_package_too_low = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
package_still_too_low, /*test_accept=*/false, /*client_maxfeerate=*/{});
auto [submit_package_too_low, process_result]{ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
package_still_too_low, /*test_accept=*/false, /*client_maxfeerate=*/{})};
BOOST_CHECK(process_result);
if (auto err_package_too_low{CheckPackageMempoolAcceptResult(package_still_too_low, submit_package_too_low, /*expect_valid=*/false, m_node.mempool.get())}) {
BOOST_ERROR(err_package_too_low.value());
} else {
@ -958,8 +978,9 @@ BOOST_AUTO_TEST_CASE(package_cpfp_tests)
m_node.mempool->PrioritiseTransaction(tx_child_cheap->GetHash(), 1 * COIN);
// Now that the child's fees have "increased" by 1 BTC, the cheap package should succeed.
{
const auto submit_prioritised_package = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
package_still_too_low, /*test_accept=*/false, /*client_maxfeerate=*/{});
auto [submit_prioritised_package, process_result]{ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
package_still_too_low, /*test_accept=*/false, /*client_maxfeerate=*/{})};
BOOST_CHECK(process_result);
if (auto err_prioritised{CheckPackageMempoolAcceptResult(package_still_too_low, submit_prioritised_package, /*expect_valid=*/true, m_node.mempool.get())}) {
BOOST_ERROR(err_prioritised.value());
} else {
@ -1006,8 +1027,9 @@ BOOST_AUTO_TEST_CASE(package_cpfp_tests)
// Parent pays 1 BTC and child pays none. The parent should be accepted without the child.
{
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
const auto submit_rich_parent = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
package_rich_parent, /*test_accept=*/false, /*client_maxfeerate=*/{});
auto [submit_rich_parent, process_result]{ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
package_rich_parent, /*test_accept=*/false, /*client_maxfeerate=*/{})};
BOOST_CHECK(process_result);
if (auto err_rich_parent{CheckPackageMempoolAcceptResult(package_rich_parent, submit_rich_parent, /*expect_valid=*/false, m_node.mempool.get())}) {
BOOST_ERROR(err_rich_parent.value());
} else {
@ -1064,7 +1086,8 @@ BOOST_AUTO_TEST_CASE(package_rbf_tests)
package2.push_back(tx_child_2);
LOCK(m_node.mempool->cs);
const auto submit1 = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package1, /*test_accept=*/false, std::nullopt);
auto [submit1, process_result1]{ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package1, /*test_accept=*/false, std::nullopt)};
BOOST_CHECK(process_result1);
if (auto err_1{CheckPackageMempoolAcceptResult(package1, submit1, /*expect_valid=*/true, m_node.mempool.get())}) {
BOOST_ERROR(err_1.value());
}
@ -1077,7 +1100,8 @@ BOOST_AUTO_TEST_CASE(package_rbf_tests)
expected_pool_size += 2;
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
const auto submit2 = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package2, /*test_accept=*/false, std::nullopt);
auto [submit2, process_result2]{ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package2, /*test_accept=*/false, std::nullopt)};
BOOST_CHECK(process_result2);
if (auto err_2{CheckPackageMempoolAcceptResult(package2, submit2, /*expect_valid=*/true, m_node.mempool.get())}) {
BOOST_ERROR(err_2.value());
}
@ -1126,7 +1150,8 @@ BOOST_AUTO_TEST_CASE(package_rbf_tests)
// 1 parent paying 199sat, 1 child paying 1300sat.
Package package3{tx_parent_3, tx_child_3};
const auto submit1 = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package1, false, std::nullopt);
auto [submit1, process_result1]{ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package1, false, std::nullopt)};
BOOST_CHECK(process_result1);
if (auto err_1{CheckPackageMempoolAcceptResult(package1, submit1, /*expect_valid=*/true, m_node.mempool.get())}) {
BOOST_ERROR(err_1.value());
}
@ -1139,7 +1164,8 @@ BOOST_AUTO_TEST_CASE(package_rbf_tests)
// This replacement is actually not package rbf; the parent carries enough fees
// to replace the entire package on its own.
const auto submit2 = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package2, false, std::nullopt);
auto [submit2, process_result2]{ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package2, false, std::nullopt)};
BOOST_CHECK(process_result2);
if (auto err_2{CheckPackageMempoolAcceptResult(package2, submit2, /*expect_valid=*/true, m_node.mempool.get())}) {
BOOST_ERROR(err_2.value());
}
@ -1150,7 +1176,8 @@ BOOST_AUTO_TEST_CASE(package_rbf_tests)
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
// Package RBF, in which the replacement transaction's child sponsors the fees to meet RBF feerate rules
const auto submit3 = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package3, false, std::nullopt);
auto [submit3, process_result3]{ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package3, false, std::nullopt)};
BOOST_CHECK(process_result3);
if (auto err_3{CheckPackageMempoolAcceptResult(package3, submit3, /*expect_valid=*/true, m_node.mempool.get())}) {
BOOST_ERROR(err_3.value());
}
@ -1174,12 +1201,14 @@ BOOST_AUTO_TEST_CASE(package_rbf_tests)
// Finally, check that we can prioritise tx_child_1 to get package1 into the mempool.
// It should not be possible to resubmit package1 and get it in without prioritisation.
const auto submit4 = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package1, false, std::nullopt);
auto [submit4, process4]{ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package1, false, std::nullopt)};
BOOST_CHECK(process4);
if (auto err_4{CheckPackageMempoolAcceptResult(package1, submit4, /*expect_valid=*/false, m_node.mempool.get())}) {
BOOST_ERROR(err_4.value());
}
m_node.mempool->PrioritiseTransaction(tx_child_1->GetHash(), 1363);
const auto submit5 = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package1, false, std::nullopt);
auto [submit5, process5]{ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package1, false, std::nullopt)};
BOOST_CHECK(process5);
if (auto err_5{CheckPackageMempoolAcceptResult(package1, submit5, /*expect_valid=*/true, m_node.mempool.get())}) {
BOOST_ERROR(err_5.value());
}

View file

@ -40,7 +40,8 @@ BOOST_FIXTURE_TEST_CASE(tx_mempool_reject_coinbase, TestChain100Setup)
LOCK(cs_main);
unsigned int initialPoolSize = m_node.mempool->size();
const MempoolAcceptResult result = m_node.chainman->ProcessTransaction(MakeTransactionRef(coinbaseTx));
auto [result, flush_result]{m_node.chainman->ProcessTransaction(MakeTransactionRef(coinbaseTx))};
BOOST_CHECK(flush_result);
BOOST_CHECK(result.m_result_type == MempoolAcceptResult::ResultType::INVALID);

View file

@ -39,7 +39,8 @@ BOOST_FIXTURE_TEST_CASE(tx_mempool_block_doublespend, Dersig100Setup)
const auto ToMemPool = [this](const CMutableTransaction& tx) {
LOCK(cs_main);
const MempoolAcceptResult result = m_node.chainman->ProcessTransaction(MakeTransactionRef(tx));
auto [result, process_result]{m_node.chainman->ProcessTransaction(MakeTransactionRef(tx))};
BOOST_CHECK(process_result);
return result.m_result_type == MempoolAcceptResult::ResultType::VALID;
};

View file

@ -83,7 +83,7 @@ CreateAndActivateUTXOSnapshot(
chain.CoinsTip().SetBestBlock(gen_hash);
chain.setBlockIndexCandidates.insert(node.chainman->m_blockman.LookupBlockIndex(gen_hash));
chain.LoadChainTip();
node.chainman->MaybeRebalanceCaches();
Assert(node.chainman->MaybeRebalanceCaches());
// Reset the HAVE_DATA flags below the snapshot height, simulating
// never-having-downloaded them in the first place.
@ -105,7 +105,8 @@ CreateAndActivateUTXOSnapshot(
}
}
BlockValidationState state;
if (!node.chainman->ActiveChainstate().ActivateBestChain(state)) {
auto activate_result{node.chainman->ActiveChainstate().ActivateBestChain(state)};
if (!activate_result) {
throw std::runtime_error(strprintf("ActivateBestChain failed. (%s)", state.ToString()));
}
Assert(

View file

@ -17,6 +17,8 @@
#include <validationinterface.h>
#include <versionbits.h>
using kernel::AbortFailure;
using kernel::FlushResult;
using node::BlockAssembler;
using node::NodeContext;
@ -97,7 +99,9 @@ COutPoint MineBlock(const NodeContext& node, std::shared_ptr<CBlock>& block)
bool new_block;
BlockValidationStateCatcher bvsc{block->GetHash()};
node.validation_signals->RegisterValidationInterface(&bvsc);
const bool processed{chainman.ProcessNewBlock(block, true, true, &new_block)};
FlushResult<void, AbortFailure> process_result;
const bool processed{chainman.ProcessNewBlock(block, true, true, &new_block, process_result)};
assert(process_result);
const bool duplicate{!new_block && processed};
assert(!duplicate);
node.validation_signals->UnregisterValidationInterface(&bvsc);

View file

@ -62,6 +62,8 @@
#include <stdexcept>
using namespace util::hex_literals;
using kernel::AbortFailure;
using kernel::FlushResult;
using node::ApplyArgsManOptions;
using node::BlockAssembler;
using node::BlockManager;
@ -290,14 +292,15 @@ void ChainTestingSetup::LoadVerifyActivateChainstate()
options.check_blocks = m_args.GetIntArg("-checkblocks", DEFAULT_CHECKBLOCKS);
options.check_level = m_args.GetIntArg("-checklevel", DEFAULT_CHECKLEVEL);
options.require_full_verification = m_args.IsArgSet("-checkblocks") || m_args.IsArgSet("-checklevel");
auto [status, error] = LoadChainstate(chainman, m_kernel_cache_sizes, options);
assert(status == node::ChainstateLoadStatus::SUCCESS);
auto load_result{LoadChainstate(chainman, m_kernel_cache_sizes, options)};
Assert(load_result);
std::tie(status, error) = VerifyLoadedChainstate(chainman, options);
assert(status == node::ChainstateLoadStatus::SUCCESS);
auto verify_result{VerifyLoadedChainstate(chainman, options)};
Assert(verify_result);
BlockValidationState state;
if (!chainman.ActiveChainstate().ActivateBestChain(state)) {
auto activate_result{chainman.ActiveChainstate().ActivateBestChain(state)};
if (!activate_result) {
throw std::runtime_error(strprintf("ActivateBestChain failed. (%s)", state.ToString()));
}
}
@ -401,7 +404,9 @@ CBlock TestChain100Setup::CreateAndProcessBlock(
CBlock block = this->CreateBlock(txns, scriptPubKey, *chainstate);
std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(block);
Assert(m_node.chainman)->ProcessNewBlock(shared_pblock, true, true, nullptr);
FlushResult<void, AbortFailure> process_result;
(void)Assert(m_node.chainman)->ProcessNewBlock(shared_pblock, true, true, nullptr, process_result);
Assert(process_result);
return block;
}
@ -481,8 +486,9 @@ CMutableTransaction TestChain100Setup::CreateValidMempoolTransaction(const std::
// If submit=true, add transaction to the mempool.
if (submit) {
LOCK(cs_main);
const MempoolAcceptResult result = m_node.chainman->ProcessTransaction(MakeTransactionRef(mempool_txn));
auto [result, process_result]{m_node.chainman->ProcessTransaction(MakeTransactionRef(mempool_txn))};
assert(result.m_result_type == MempoolAcceptResult::ResultType::VALID);
Assert(process_result);
}
return mempool_txn;
}

View file

@ -19,6 +19,8 @@
#include <thread>
using kernel::AbortFailure;
using kernel::FlushResult;
using node::BlockAssembler;
namespace validation_block_tests {
@ -158,9 +160,11 @@ BOOST_AUTO_TEST_CASE(processnewblock_signals_ordering)
BuildChain(Params().GenesisBlock().GetHash(), 100, 15, 10, 500, blocks);
}
FlushResult<void, AbortFailure> process_result;
bool ignored;
// Connect the genesis block and drain any outstanding events
BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlock(std::make_shared<CBlock>(Params().GenesisBlock()), true, true, &ignored));
BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlock(std::make_shared<CBlock>(Params().GenesisBlock()), true, true, &ignored, process_result));
BOOST_CHECK(process_result);
m_node.validation_signals->SyncWithValidationInterfaceQueue();
// subscribe to events (this subscriber will validate event ordering)
@ -183,14 +187,18 @@ BOOST_AUTO_TEST_CASE(processnewblock_signals_ordering)
FastRandomContext insecure;
for (int i = 0; i < 1000; i++) {
const auto& block = blocks[insecure.randrange(blocks.size() - 1)];
Assert(m_node.chainman)->ProcessNewBlock(block, true, true, &ignored);
FlushResult<void, AbortFailure> process_result;
(void)Assert(m_node.chainman)->ProcessNewBlock(block, true, true, &ignored, process_result);
Assert(process_result);
}
// to make sure that eventually we process the full chain - do it here
for (const auto& block : blocks) {
if (block->vtx.size() == 1) {
bool processed = Assert(m_node.chainman)->ProcessNewBlock(block, true, true, &ignored);
FlushResult<void, AbortFailure> process_result;
bool processed = Assert(m_node.chainman)->ProcessNewBlock(block, true, true, &ignored, process_result);
assert(processed);
Assert(process_result);
}
}
});
@ -228,7 +236,10 @@ BOOST_AUTO_TEST_CASE(mempool_locks_reorg)
{
bool ignored;
auto ProcessBlock = [&](std::shared_ptr<const CBlock> block) -> bool {
return Assert(m_node.chainman)->ProcessNewBlock(block, /*force_processing=*/true, /*min_pow_checked=*/true, /*new_block=*/&ignored);
FlushResult<void, AbortFailure> process_result;
bool ret{Assert(m_node.chainman)->ProcessNewBlock(block, /*force_processing=*/true, /*min_pow_checked=*/true, /*new_block=*/&ignored, process_result)};
BOOST_CHECK(process_result);
return ret;
};
// Process all mined blocks
@ -279,8 +290,9 @@ BOOST_AUTO_TEST_CASE(mempool_locks_reorg)
{
LOCK(cs_main);
for (const auto& tx : txs) {
const MempoolAcceptResult result = m_node.chainman->ProcessTransaction(tx);
auto [result, process_result]{m_node.chainman->ProcessTransaction(tx)};
BOOST_REQUIRE(result.m_result_type == MempoolAcceptResult::ResultType::VALID);
BOOST_CHECK(process_result);
}
}

View file

@ -20,6 +20,9 @@
#include <boost/test/unit_test.hpp>
using kernel::AbortFailure;
using kernel::FlushResult;
BOOST_FIXTURE_TEST_SUITE(validation_chainstate_tests, ChainTestingSetup)
//! Test resizing coins-related Chainstate caches during runtime.
@ -45,18 +48,18 @@ BOOST_AUTO_TEST_CASE(validation_chainstate_resize_caches)
BOOST_CHECK(c1.CoinsTip().HaveCoinInCache(outpoint));
c1.ResizeCoinsCaches(
BOOST_CHECK(c1.ResizeCoinsCaches(
1 << 24, // upsizing the coinsview cache
1 << 22 // downsizing the coinsdb cache
);
));
// View should still have the coin cached, since we haven't destructed the cache on upsize.
BOOST_CHECK(c1.CoinsTip().HaveCoinInCache(outpoint));
c1.ResizeCoinsCaches(
BOOST_CHECK(c1.ResizeCoinsCaches(
1 << 22, // downsizing the coinsview cache
1 << 23 // upsizing the coinsdb cache
);
));
// The view cache should be empty since we had to destruct to downsize.
BOOST_CHECK(!c1.CoinsTip().HaveCoinInCache(outpoint));
@ -130,13 +133,15 @@ BOOST_FIXTURE_TEST_CASE(chainstate_update_tip, TestChain100Setup)
LOCK(::cs_main);
bool checked = CheckBlock(*pblockone, state, chainparams.GetConsensus());
BOOST_CHECK(checked);
FlushResult<void, AbortFailure> accept_result;
bool accepted = chainman.AcceptBlock(
pblockone, state, &pindex, true, nullptr, &newblock, true);
pblockone, accept_result, state, &pindex, true, nullptr, &newblock, true);
BOOST_CHECK(accepted);
BOOST_CHECK(accept_result);
}
// UpdateTip is called here
bool block_added = background_cs.ActivateBestChain(state, pblockone);
auto block_added{background_cs.ActivateBestChain(state, pblockone)};
// Ensure tip is as expected
BOOST_CHECK_EQUAL(background_cs.m_chain.Tip()->GetBlockHash(), pblockone->GetHash());

View file

@ -28,6 +28,8 @@
#include <boost/test/unit_test.hpp>
using kernel::AbortFailure;
using kernel::FlushResult;
using node::BlockManager;
using node::KernelNotifications;
using node::SnapshotMetadata;
@ -126,7 +128,7 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_rebalance_caches, TestChain100Setup)
{
LOCK(::cs_main);
c1.InitCoinsCache(1 << 23);
manager.MaybeRebalanceCaches();
BOOST_CHECK(manager.MaybeRebalanceCaches());
}
BOOST_CHECK_EQUAL(c1.m_coinstip_cache_size_bytes, max_cache);
@ -152,7 +154,7 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_rebalance_caches, TestChain100Setup)
{
LOCK(::cs_main);
c2.InitCoinsCache(1 << 23);
manager.MaybeRebalanceCaches();
BOOST_CHECK(manager.MaybeRebalanceCaches());
}
BOOST_CHECK_CLOSE(double(c1.m_coinstip_cache_size_bytes), max_cache * 0.05, 1);
@ -375,7 +377,7 @@ struct SnapshotTestSetup : TestChain100Setup {
{
for (Chainstate* cs : chainman.GetAll()) {
LOCK(::cs_main);
cs->ForceFlushStateToDisk();
BOOST_CHECK(cs->ForceFlushStateToDisk());
}
// Process all callbacks referring to the old manager before wiping it.
m_node.validation_signals->SyncWithValidationInterfaceQueue();
@ -456,7 +458,7 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_loadblockindex, TestChain100Setup)
BOOST_CHECK(cs->setBlockIndexCandidates.empty());
}
WITH_LOCK(::cs_main, chainman.LoadBlockIndex());
BOOST_CHECK(WITH_LOCK(::cs_main, return chainman.LoadBlockIndex()));
};
// Ensure that without any assumed-valid BlockIndex entries, only the current tip is
@ -647,8 +649,10 @@ 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());
FlushResult<void, AbortFailure> process_result;
res = WITH_LOCK(::cs_main, return chainman.MaybeCompleteSnapshotValidation(process_result));
BOOST_CHECK_EQUAL(res, SnapshotCompletionResult::SUCCESS);
BOOST_CHECK(process_result);
WITH_LOCK(::cs_main, BOOST_CHECK(chainman.IsSnapshotValidated()));
BOOST_CHECK(chainman.IsSnapshotActive());
@ -663,8 +667,9 @@ 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());
res = WITH_LOCK(::cs_main, return chainman.MaybeCompleteSnapshotValidation(process_result));
BOOST_CHECK_EQUAL(res, SnapshotCompletionResult::SKIPPED);
BOOST_CHECK(process_result);
// The invalid snapshot path should not have been used.
fs::path snapshot_invalid_dir = gArgs.GetDataDirNet() / "chainstate_snapshot_INVALID";
@ -734,8 +739,10 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_snapshot_completion_hash_mismatch, Sna
{
ASSERT_DEBUG_LOG("failed to validate the -assumeutxo snapshot state");
res = WITH_LOCK(::cs_main, return chainman.MaybeCompleteSnapshotValidation());
FlushResult<void, AbortFailure> process_result;
res = WITH_LOCK(::cs_main, return chainman.MaybeCompleteSnapshotValidation(process_result));
BOOST_CHECK_EQUAL(res, SnapshotCompletionResult::HASH_MISMATCH);
BOOST_CHECK(!process_result);
}
auto all_chainstates = chainman.GetAll();

View file

@ -17,6 +17,7 @@ add_library(bitcoin_util STATIC EXCLUDE_FROM_ALL
moneystr.cpp
rbf.cpp
readwritefile.cpp
result.cpp
serfloat.cpp
signalinterrupt.cpp
sock.cpp

33
src/util/result.cpp Normal file
View file

@ -0,0 +1,33 @@
// Copyright (c) 2024 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or https://www.opensource.org/licenses/mit-license.php.
#include <util/result.h>
#include <algorithm>
#include <initializer_list>
#include <iterator>
#include <util/translation.h>
namespace util {
namespace detail {
bilingual_str JoinMessages(const Messages& messages)
{
bilingual_str result;
for (const auto& messages : {messages.errors, messages.warnings}) {
for (const auto& message : messages) {
if (!result.empty()) result += Untranslated(" ");
result += message;
}
}
return result;
}
} // namespace detail
void ResultTraits<Messages>::Update(Messages& dst, Messages& src) {
dst.errors.insert(dst.errors.end(), std::make_move_iterator(src.errors.begin()), std::make_move_iterator(src.errors.end()));
dst.warnings.insert(dst.warnings.end(), std::make_move_iterator(src.warnings.begin()), std::make_move_iterator(src.warnings.end()));
src.errors.clear();
src.warnings.clear();
}
} // namespace util

View file

@ -8,91 +8,439 @@
#include <attributes.h>
#include <util/translation.h>
#include <variant>
#include <cassert>
#include <memory>
#include <new>
#include <optional>
#include <type_traits>
#include <utility>
#include <vector>
namespace util {
//! Default MessagesType, simple list of errors and warnings.
struct Messages {
std::vector<bilingual_str> errors{};
std::vector<bilingual_str> warnings{};
};
//! The Result<SuccessType, FailureType, InfoType, MessagesType> class provides
//! an efficient way for functions to return structured result information, as
//! well as descriptive error and warning messages.
//!
//! Logically, a result object is equivalent to:
//!
//! tuple<variant<SuccessType, FailureType>, InfoType, MessagesType>
//!
//! But the physical representation is more efficient because it avoids
//! allocating memory for FailureType and MessagesType fields unless there is an
//! error.
//!
//! Result<SuccessType> objects support the same operators as
//! std::optional<SuccessType>, such as !result, *result, result->member, to
//! make SuccessType values easy to access. They also provide
//! result.GetFailure(), result.GetInfo(), result.GetMessages() methods to
//! access other parts of the result. A simple usage example is:
//!
//! util::Result<int> AddNumbers(int a, int b)
//! {
//! if (b == 0) return util::Error{_("Not adding 0, that's dumb.")};
//! return a + b;
//! }
//!
//! void TryAddNumbers(int a, int b)
//! {
//! if (auto result = AddNumbers(a, b)) {
//! LogPrintf("%i + %i = %i\n", a, b, *result);
//! } else {
//! LogPrintf("Error: %s\n", util::ErrorString(result).translated);
//! }
//! }
//!
//! The `Result` class is intended to be used for high-level functions that need
//! to report error messages to end users. Low-level functions that don't need
//! error-reporting and only need error-handling should avoid `Result` and
//! instead use standard classes like `std::optional`, `std::variant`,
//! `std::expected`, and `std::tuple`, or custom structs and enum types to
//! return function results.
//!
//! Usage examples can be found in \example ../test/result_tests.cpp.
template <typename SuccessType = void, typename FailureType = void, typename InfoType = void, typename MessagesType = Messages>
class Result;
//! Wrapper object to pass an error string to the Result constructor.
struct Error {
bilingual_str message;
};
//! Wrapper object to pass a warning string to the Result constructor.
struct Warning {
bilingual_str message;
};
//! The util::Result class provides a standard way for functions to return
//! either error messages or result values.
//! Wrapper object to pass an InfoType object to the result constructor.
template <typename T>
struct Info {
Info(T&& obj) : m_obj(obj) {}
T& m_obj;
};
//! Type trait that can be specialized to control the way SuccessType /
//! FailureType / InfoType / MessagesType values are combined. Default behavior
//! is for new values to overwrite existing ones, but this can be specialized
//! for custom behavior when Result::Update() method is called or << operator is
//! used. For example, this is specialized for Messages struct below to append
//! error and warning messages instead of overwriting them. It can also be used,
//! for example, to merge FailureType values when a function can return multiple
//! failures.
template<typename T>
struct ResultTraits {
template<typename O>
static void Update(O& dst, T& src)
{
dst = std::move(src);
}
};
//! Type trait that can be specialized to let the Result class work with custom
//! MessagesType types holding error and warning messages.
template<typename MessagesType>
struct MessagesTraits;
//! ResultTraits specialization for Messages struct.
template<>
struct ResultTraits<Messages> {
static void Update(Messages& dst, Messages& src);
};
//! MessagesTraits specialization for Messages struct.
template<>
struct MessagesTraits<Messages> {
static void AddError(Messages& messages, bilingual_str error)
{
messages.errors.emplace_back(std::move(error));
}
static void AddWarning(Messages& messages, bilingual_str warning)
{
messages.warnings.emplace_back(std::move(warning));
}
static bool HasMessages(const Messages& messages)
{
return messages.errors.size() || messages.warnings.size();
}
};
namespace detail {
//! Helper function to join messages in space separated string.
bilingual_str JoinMessages(const Messages& messages);
//! Substitute for std::monostate that doesn't depend on std::variant.
struct Monostate{};
//! Implemention note: Result class inherits from an InfoHolder class holding an
//! IntoType value, a FailDataHolder class holding a unique_ptr to FailureType
//! and MessagesTypes values, and a SuccessHolder class holding a SuccessType
//! value in an anonymous union.
//!
//! It is intended for high-level functions that need to report error strings to
//! end users. Lower-level functions that don't need this error-reporting and
//! only need error-handling should avoid util::Result and instead use standard
//! classes like std::optional, std::variant, and std::tuple, or custom structs
//! and enum types to return function results.
//!
//! Usage examples can be found in \example ../test/result_tests.cpp, but in
//! general code returning `util::Result<T>` values is very similar to code
//! returning `std::optional<T>` values. Existing functions returning
//! `std::optional<T>` can be updated to return `util::Result<T>` and return
//! error strings usually just replacing `return std::nullopt;` with `return
//! util::Error{error_string};`.
template <class M>
class Result
//! To take advantage of the Empty Base Optimization, inheritance is linear with
//! FailDataHolder inheriting from InfoHolder, SuccessHolder inheriting from
//! FailDataHolder, and Holder classes specializing for void so no space is used
//! when void types are specified.
//! @{
//! Container for InfoType, providing public GetInfo() method.
template <typename InfoType>
class InfoHolder
{
private:
using T = std::conditional_t<std::is_same_v<M, void>, std::monostate, M>;
protected:
InfoType m_info{};
public:
// Public accessors.
const InfoType& GetInfo() const LIFETIMEBOUND { return m_info; }
InfoType& GetInfo() LIFETIMEBOUND { return m_info; }
};
std::variant<bilingual_str, T> m_variant;
//! Specialization of InfoHolder when InfoType is void.
template <>
class InfoHolder<void> {};
//! Disallow copy constructor, require Result to be moved for efficiency.
Result(const Result&) = delete;
//! Container for FailureType and MessagesType, providing public operator
//! bool(), GetFailure(), GetMessages(), and EnsureMessages() methods.
template <typename FailureType, typename InfoType, typename MessagesType>
class FailDataHolder : public InfoHolder<InfoType>
{
protected:
struct FailData {
std::optional<std::conditional_t<std::is_same_v<FailureType, void>, Monostate, FailureType>> failure{};
MessagesType messages{};
};
std::unique_ptr<FailData> m_fail_data;
//! Disallow operator= to avoid confusion in the future when the Result
//! class gains support for richer error reporting, and callers should have
//! ability to set a new result value without clearing existing error
//! messages.
Result& operator=(const Result&) = delete;
Result& operator=(Result&&) = delete;
template <typename FT>
friend bilingual_str ErrorString(const Result<FT>& result);
// Private accessor, create FailData if it doesn't exist.
FailData& EnsureFailData() LIFETIMEBOUND
{
if (!m_fail_data) m_fail_data = std::make_unique<FailData>();
return *m_fail_data;
}
public:
Result() : m_variant{std::in_place_index_t<1>{}, std::monostate{}} {} // constructor for void
Result(T obj) : m_variant{std::in_place_index_t<1>{}, std::move(obj)} {}
Result(Error error) : m_variant{std::in_place_index_t<0>{}, std::move(error.message)} {}
Result(Result&&) = default;
~Result() = default;
// Public accessors.
explicit operator bool() const { return !m_fail_data || !m_fail_data->failure; }
const auto& GetFailure() const LIFETIMEBOUND { assert(!*this); return *m_fail_data->failure; }
auto& GetFailure() LIFETIMEBOUND { assert(!*this); return *m_fail_data->failure; }
const auto* GetMessages() const LIFETIMEBOUND { return m_fail_data ? &m_fail_data->messages : nullptr; }
auto* GetMessages() LIFETIMEBOUND { return m_fail_data ? &m_fail_data->messages : nullptr; }
auto& EnsureMessages() LIFETIMEBOUND { return EnsureFailData().messages; }
};
//! std::optional methods, so functions returning optional<T> can change to
//! return Result<T> with minimal changes to existing code, and vice versa.
bool has_value() const noexcept { return m_variant.index() == 1; }
const T& value() const LIFETIMEBOUND
{
assert(has_value());
return std::get<1>(m_variant);
}
T& value() LIFETIMEBOUND
{
assert(has_value());
return std::get<1>(m_variant);
}
//! Container for SuccessType, providing public accessor methods similar to
//! std::optional methods to access the success value.
template <typename SuccessType, typename FailureType, typename InfoType, typename MessagesType>
class SuccessHolder : public FailDataHolder<FailureType, InfoType, MessagesType>
{
protected:
//! Success value embedded in an anonymous union so it doesn't need to be
//! constructed if the result is holding a failure value,
union { SuccessType m_success; };
//! Empty constructor that needs to be declared because the class contains a union.
SuccessHolder() {}
~SuccessHolder() { if (*this) m_success.~SuccessType(); }
public:
// Public accessors.
bool has_value() const { return bool{*this}; }
const SuccessType& value() const LIFETIMEBOUND { assert(has_value()); return m_success; }
SuccessType& value() LIFETIMEBOUND { assert(has_value()); return m_success; }
template <class U>
T value_or(U&& default_value) const&
SuccessType value_or(U&& default_value) const&
{
return has_value() ? value() : std::forward<U>(default_value);
}
template <class U>
T value_or(U&& default_value) &&
SuccessType value_or(U&& default_value) &&
{
return has_value() ? std::move(value()) : std::forward<U>(default_value);
}
explicit operator bool() const noexcept { return has_value(); }
const T* operator->() const LIFETIMEBOUND { return &value(); }
const T& operator*() const LIFETIMEBOUND { return value(); }
T* operator->() LIFETIMEBOUND { return &value(); }
T& operator*() LIFETIMEBOUND { return value(); }
const SuccessType* operator->() const LIFETIMEBOUND { return &value(); }
const SuccessType& operator*() const LIFETIMEBOUND { return value(); }
SuccessType* operator->() LIFETIMEBOUND { return &value(); }
SuccessType& operator*() LIFETIMEBOUND { return value(); }
};
template <typename T>
bilingual_str ErrorString(const Result<T>& result)
//! Specialization of SuccessHolder when SuccessType is void.
template <typename FailureType, typename InfoType, typename MessagesType>
class SuccessHolder<void, FailureType, InfoType, MessagesType> : public FailDataHolder<FailureType, InfoType, MessagesType>
{
return result ? bilingual_str{} : std::get<0>(result.m_variant);
};
//! @}
} // namespace detail
// Result type class, documented at the top of this file.
template <typename SuccessType_, typename FailureType_, typename InfoType_, typename MessagesType_>
class Result : public detail::SuccessHolder<SuccessType_, FailureType_, InfoType_, MessagesType_>
{
public:
using SuccessType = SuccessType_;
using FailureType = FailureType_;
using InfoType = InfoType_;
using MessagesType = MessagesType_;
static constexpr bool is_result{true};
//! Construct a Result object setting a success or failure value and
//! optional warning and error messages. Initial util::Error and
//! util::Warning arguments are processed first to add warning and error
//! messages. Then, any remaining arguments are passed to the SuccessType
//! constructor and used to construct a success value in the success case.
//! In the failure case, if any util::Error arguments were passed, any
//! remaining arguments are passed to the FailureType constructor and used
//! to construct a failure value.
template <typename... Args>
Result(Args&&... args)
{
Construct</*Failure=*/false>(*this, std::forward<Args>(args)...);
}
//! Move-construct a Result object from another Result object, moving the
//! success or failure value and any error or warning messages.
template <typename O>
requires (std::decay_t<O>::is_result)
Result(O&& other)
{
Move</*Constructed=*/false>(*this, other);
}
//! Update this result by moving from another result object. Existing
//! success, failure, info, and messages values are updated (using
//! ResultTraits::Update specializations), so errors and warning messages
//! get appended instead of overwriting existing ones.
Result& Update(Result&& other) LIFETIMEBOUND
{
Move</*Constructed=*/true>(*this, other);
return *this;
}
//! Disallow operator= and require explicit Result::Update() calls to avoid
//! accidentally clearing error and warning messages when combining results.
Result& operator=(Result&&) = delete;
void AddError(bilingual_str error)
{
if (!error.empty()) MessagesTraits<MessagesType>::AddError(this->EnsureFailData().messages, std::move(error));
}
void AddWarning(bilingual_str warning)
{
if (!warning.empty()) MessagesTraits<MessagesType>::AddWarning(this->EnsureFailData().messages, std::move(warning));
}
protected:
template <typename, typename, typename, typename>
friend class Result;
//! Helper function to construct a new success or failure value using the
//! arguments provided.
template <bool Failure, typename Result, typename... Args>
static void Construct(Result& result, Args&&... args)
{
if constexpr (Failure) {
static_assert(sizeof...(args) > 0 || !std::is_scalar_v<FailureType>,
"Refusing to default-construct failure value with int, float, enum, or pointer type, please specify an explicit failure value.");
result.EnsureFailData().failure.emplace(std::forward<Args>(args)...);
} else if constexpr (std::is_same_v<typename Result::SuccessType, void>) {
static_assert(sizeof...(args) == 0, "Error: Arguments cannot be passed to a Result<void> constructor.");
} else {
new (&result.m_success) typename Result::SuccessType{std::forward<Args>(args)...};
}
}
//! Construct() overload peeling off a util::Error constructor argument.
template <bool Failure, typename Result, typename... Args>
static void Construct(Result& result, util::Error error, Args&&... args)
{
result.AddError(std::move(error.message));
Construct</*Failure=*/true>(result, std::forward<Args>(args)...);
}
//! Construct() overload peeling off a util::Warning constructor argument.
template <bool Failure, typename Result, typename... Args>
static void Construct(Result& result, util::Warning warning, Args&&... args)
{
result.AddWarning(std::move(warning.message));
Construct<Failure>(result, std::forward<Args>(args)...);
}
//! Construct() overload peeling off a util::Info constructor argument.
template <bool Failure, typename Result, typename T, typename... Args>
static void Construct(Result& result, util::Info<T> info, Args&&... args)
{
ResultTraits<InfoType>::Update(result.GetInfo(), info.m_obj);
Construct<Failure>(result, std::forward<Args>(args)...);
}
//! Move success, failure, info, and messages from source Result object to
//! destination object. Existing values are updated (using
//! ResultTraits::Update specializations), so destination errors and warning
//! messages get appended to instead of overwritten. The source and
//! destination results are not required to have the same types, and
//! assigning void source types to non-void destinations type is allowed,
//! since no source information is lost. But assigning non-void source types
//! to void destination types is not allowed, since this would discard
//! source information.
template <bool DstConstructed, typename DstResult, typename SrcResult>
static void Move(DstResult& dst, SrcResult& src)
{
// Use operator>> to move info and messages values first, then move
// success or failure value below.
src >> dst;
// If DstConstructed is true, it means dst has either a success value or
// a failure value set, which needs to be updated or replaced. If
// DstConstructed is false, then dst is being constructed now and has no
// values set.
if constexpr (DstConstructed) {
if (dst && src) {
// dst and src both hold success values, so update dst from src and return
if constexpr (!std::is_same_v<typename SrcResult::SuccessType, void>) {
ResultTraits<typename SrcResult::SuccessType>::Update(*dst, *src);
}
return;
} else if (!dst && !src) {
// dst and src both hold failure values, so update dst from src and return
if constexpr (!std::is_same_v<typename SrcResult::FailureType, void>) {
ResultTraits<typename SrcResult::FailureType>::Update(dst.GetFailure(), src.GetFailure());
}
return;
} else if (dst) {
// dst has a success value, so destroy it before replacing it with src failure value
if constexpr (!std::is_same_v<typename DstResult::SuccessType, void>) {
using DstSuccess = typename DstResult::SuccessType;
dst.m_success.~DstSuccess();
}
} else {
// dst has a failure value, so reset it before replacing it with src success value
dst.m_fail_data->failure.reset();
}
}
// At this point dst has no success or failure value set. Can assert
// there is no failure value.
assert(dst);
if (src) {
// src has a success value, so move it to dst. If the src success
// type is void and the dst success type is non-void, just
// initialize the dst success value by default initialization.
if constexpr (!std::is_same_v<typename SrcResult::SuccessType, void>) {
new (&dst.m_success) typename DstResult::SuccessType{std::move(src.m_success)};
} else if constexpr (!std::is_same_v<typename DstResult::SuccessType, void>) {
new (&dst.m_success) typename DstResult::SuccessType{};
}
assert(dst);
} else {
// src has a failure value, so move it to dst. If the src failure
// type is void, just initialize the dst failure value by default
// initialization.
if constexpr (!std::is_same_v<typename SrcResult::FailureType, void>) {
dst.EnsureFailData().failure.emplace(std::move(src.GetFailure()));
} else {
dst.EnsureFailData().failure.emplace();
}
assert(!dst);
}
}
};
//! Move information from a source Result object to a destination object. It
//! only moves InfoType and MessagesType values without affecting SuccessType or
//! FailureType values of either Result object.
//!
//! This is useful for combining error and warning messages from multiple result
//! objects into a single object, e.g.:
//!
//! util::Result<void> result;
//! auto r1 = DoSomething() >> result;
//! auto r2 = DoSomethingElse() >> result;
//! ...
//! return result;
//!
template <typename SrcResult, typename DstResult>
requires (std::decay_t<SrcResult>::is_result)
decltype(auto) operator>>(SrcResult&& src LIFETIMEBOUND, DstResult&& dst)
{
using SrcType = std::decay_t<SrcResult>;
if constexpr (!std::is_same_v<typename SrcType::InfoType, void>) {
ResultTraits<typename SrcType::InfoType>::Update(dst.GetInfo(), src.GetInfo());
}
if (src.GetMessages() && MessagesTraits<typename SrcType::MessagesType>::HasMessages(*src.GetMessages())) {
ResultTraits<typename SrcType::MessagesType>::Update(dst.EnsureMessages(), *src.GetMessages());
}
return static_cast<SrcType&&>(src);
}
//! Join error and warning messages in a space separated string. This is
//! intended for simple applications where there's probably only one error or
//! warning message to report, but multiple messages should not be lost if they
//! are present. More complicated applications should use Result::GetMessages()
//! method directly.
template <typename Result>
bilingual_str ErrorString(const Result& result)
{
const auto* messages{result.GetMessages()};
return messages ? detail::JoinMessages(*messages) : bilingual_str{};
}
} // namespace util

File diff suppressed because it is too large Load diff

View file

@ -17,6 +17,7 @@
#include <kernel/chainparams.h>
#include <kernel/chainstatemanager_opts.h>
#include <kernel/cs_main.h> // IWYU pragma: export
#include <kernel/result.h>
#include <node/blockstorage.h>
#include <policy/feerate.h>
#include <policy/packages.h>
@ -96,7 +97,7 @@ CAmount GetBlockSubsidy(int nHeight, const Consensus::Params& consensusParams);
bool FatalError(kernel::Notifications& notifications, BlockValidationState& state, const bilingual_str& message);
/** Prune block files up to a given height */
void PruneBlockFilesManual(Chainstate& active_chainstate, int nManualPruneHeight);
[[nodiscard]] kernel::FlushResult<void, kernel::AbortFailure> PruneBlockFilesManual(Chainstate& active_chainstate, int nManualPruneHeight);
/**
* Validation result for a transaction evaluated by MemPoolAccept (single or package).
@ -263,7 +264,7 @@ struct PackageMempoolAcceptResult
*
* @returns a MempoolAcceptResult indicating whether the transaction was accepted/rejected with reason.
*/
MempoolAcceptResult AcceptToMemoryPool(Chainstate& active_chainstate, const CTransactionRef& tx,
[[nodiscard]] std::tuple<MempoolAcceptResult, kernel::FlushResult<void, kernel::AbortFailure>> AcceptToMemoryPool(Chainstate& active_chainstate, const CTransactionRef& tx,
int64_t accept_time, bool bypass_limits, bool test_accept)
EXCLUSIVE_LOCKS_REQUIRED(cs_main);
@ -277,7 +278,7 @@ MempoolAcceptResult AcceptToMemoryPool(Chainstate& active_chainstate, const CTra
* If a transaction fails, validation will exit early and some results may be missing. It is also
* possible for the package to be partially submitted.
*/
PackageMempoolAcceptResult ProcessNewPackage(Chainstate& active_chainstate, CTxMemPool& pool,
[[nodiscard]] std::tuple<PackageMempoolAcceptResult, kernel::FlushResult<void, kernel::AbortFailure>> ProcessNewPackage(Chainstate& active_chainstate, CTxMemPool& pool,
const Package& txns, bool test_accept, const std::optional<CFeeRate>& client_maxfeerate)
EXCLUSIVE_LOCKS_REQUIRED(cs_main);
@ -384,7 +385,7 @@ public:
bool CheckBlock(const CBlock& block, BlockValidationState& state, const Consensus::Params& consensusParams, bool fCheckPOW = true, bool fCheckMerkleRoot = true);
/** Check a block is completely valid from start to finish (only works on top of our current best block) */
bool TestBlockValidity(BlockValidationState& state,
kernel::FlushResult<> TestBlockValidity(BlockValidationState& state,
const CChainParams& chainparams,
Chainstate& chainstate,
const CBlock& block,
@ -401,13 +402,10 @@ bool IsBlockMutated(const CBlock& block, bool check_witness_root);
/** Return the sum of the claimed work on a given set of headers. No verification of PoW is done. */
arith_uint256 CalculateClaimedHeadersWork(std::span<const CBlockHeader> headers);
enum class VerifyDBResult {
SUCCESS,
CORRUPTED_BLOCK_DB,
INTERRUPTED,
SKIPPED_L3_CHECKS,
SKIPPED_MISSING_BLOCKS,
};
struct VerifySuccess{};
struct SkippedL3Checks{};
struct SkippedMissingBlocks{};
using VerifyDBResult = std::variant<VerifySuccess, kernel::Interrupted, SkippedL3Checks, SkippedMissingBlocks>;
/** RAII wrapper for VerifyDB: Verify consistency of the block and coin databases */
class CVerifyDB
@ -418,7 +416,7 @@ private:
public:
explicit CVerifyDB(kernel::Notifications& notifications);
~CVerifyDB();
[[nodiscard]] VerifyDBResult VerifyDB(
[[nodiscard]] kernel::FlushResult<VerifyDBResult> VerifyDB(
Chainstate& chainstate,
const Consensus::Params& consensus_params,
CCoinsView& coinsview,
@ -650,7 +648,7 @@ public:
//! Resize the CoinsViews caches dynamically and flush state to disk.
//! @returns true unless an error occurred during the flush.
bool ResizeCoinsCaches(size_t coinstip_size, size_t coinsdb_size)
[[nodiscard]] kernel::FlushResult<void, kernel::AbortFailure> ResizeCoinsCaches(size_t coinstip_size, size_t coinsdb_size)
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
/**
@ -664,17 +662,17 @@ public:
*
* @returns true unless a system error occurred
*/
bool FlushStateToDisk(
[[nodiscard]] kernel::FlushResult<void, kernel::AbortFailure> FlushStateToDisk(
BlockValidationState& state,
FlushStateMode mode,
int nManualPruneHeight = 0);
//! Unconditionally flush all changes to disk.
void ForceFlushStateToDisk();
[[nodiscard]] kernel::FlushResult<> ForceFlushStateToDisk();
//! Prune blockfiles from the disk if necessary and then flush chainstate changes
//! if we pruned.
void PruneAndFlush();
[[nodiscard]] kernel::FlushResult<> PruneAndFlush();
/**
* Find the best known block, and make it the tip of the block chain. The
@ -697,7 +695,7 @@ public:
*
* @returns true unless a system error occurred
*/
bool ActivateBestChain(
[[nodiscard]] kernel::FlushResult<> ActivateBestChain(
BlockValidationState& state,
std::shared_ptr<const CBlock> pblock = nullptr)
EXCLUSIVE_LOCKS_REQUIRED(!m_chainstate_mutex)
@ -706,23 +704,23 @@ public:
// Block (dis)connection on a given view:
DisconnectResult DisconnectBlock(const CBlock& block, const CBlockIndex* pindex, CCoinsViewCache& view)
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
bool ConnectBlock(const CBlock& block, BlockValidationState& state, CBlockIndex* pindex,
[[nodiscard]] kernel::FlushResult<void, kernel::AbortFailure> ConnectBlock(const CBlock& block, BlockValidationState& state, CBlockIndex* pindex,
CCoinsViewCache& view, bool fJustCheck = false) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
// Apply the effects of a block disconnection on the UTXO set.
bool DisconnectTip(BlockValidationState& state, DisconnectedBlockTransactions* disconnectpool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_mempool->cs);
[[nodiscard]] kernel::FlushResult<> DisconnectTip(BlockValidationState& state, DisconnectedBlockTransactions* disconnectpool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_mempool->cs);
// Manual block validity manipulation:
/** Mark a block as precious and reorganize.
*
* May not be called in a validationinterface callback.
*/
bool PreciousBlock(BlockValidationState& state, CBlockIndex* pindex)
[[nodiscard]] kernel::FlushResult<> PreciousBlock(BlockValidationState& state, CBlockIndex* pindex)
EXCLUSIVE_LOCKS_REQUIRED(!m_chainstate_mutex)
LOCKS_EXCLUDED(::cs_main);
/** Mark a block as invalid. */
bool InvalidateBlock(BlockValidationState& state, CBlockIndex* pindex)
[[nodiscard]] kernel::FlushResult<> InvalidateBlock(BlockValidationState& state, CBlockIndex* pindex)
EXCLUSIVE_LOCKS_REQUIRED(!m_chainstate_mutex)
LOCKS_EXCLUDED(::cs_main);
@ -738,7 +736,7 @@ public:
/** Whether the chain state needs to be redownloaded due to lack of witness data */
[[nodiscard]] bool NeedsRedownload() const EXCLUSIVE_LOCKS_REQUIRED(cs_main);
/** Ensures we have a genesis block in the block tree, possibly writing one to disk. */
bool LoadGenesisBlock();
[[nodiscard]] kernel::FlushResult<> LoadGenesisBlock();
void TryAddBlockIndexCandidate(CBlockIndex* pindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
@ -770,8 +768,8 @@ public:
}
private:
bool ActivateBestChainStep(BlockValidationState& state, CBlockIndex* pindexMostWork, const std::shared_ptr<const CBlock>& pblock, bool& fInvalidFound, ConnectTrace& connectTrace) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_mempool->cs);
bool ConnectTip(BlockValidationState& state, CBlockIndex* pindexNew, const std::shared_ptr<const CBlock>& pblock, ConnectTrace& connectTrace, DisconnectedBlockTransactions& disconnectpool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_mempool->cs);
[[nodiscard]] kernel::FlushResult<void, kernel::AbortFailure> ActivateBestChainStep(BlockValidationState& state, CBlockIndex* pindexMostWork, const std::shared_ptr<const CBlock>& pblock, bool& fInvalidFound, ConnectTrace& connectTrace) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_mempool->cs);
[[nodiscard]] kernel::FlushResult<void, kernel::AbortFailure> ConnectTip(BlockValidationState& state, CBlockIndex* pindexNew, const std::shared_ptr<const CBlock>& pblock, ConnectTrace& connectTrace, DisconnectedBlockTransactions& disconnectpool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_mempool->cs);
void InvalidBlockFound(CBlockIndex* pindex, const BlockValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
CBlockIndex* FindMostWorkChain() EXCLUSIVE_LOCKS_REQUIRED(cs_main);
@ -794,7 +792,7 @@ private:
* Passing fAddToMempool=false will skip trying to add the transactions back,
* and instead just erase from the mempool as needed.
*/
void MaybeUpdateMempoolForReorg(
[[nodiscard]] kernel::FlushResult<> MaybeUpdateMempoolForReorg(
DisconnectedBlockTransactions& disconnectpool,
bool fAddToMempool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_mempool->cs);
@ -1091,7 +1089,7 @@ public:
//! - "Fast forward" the tip of the new chainstate to the base of the snapshot.
//! - Move the new chainstate to `m_snapshot_chainstate` and make it our
//! ChainstateActive().
[[nodiscard]] util::Result<CBlockIndex*> ActivateSnapshot(
[[nodiscard]] kernel::FlushResult<CBlockIndex*, kernel::AbortFailure> ActivateSnapshot(
AutoFile& coins_file, const node::SnapshotMetadata& metadata, bool in_memory);
//! Once the background validation chainstate has reached the height which
@ -1101,7 +1099,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() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
[[nodiscard]] SnapshotCompletionResult MaybeCompleteSnapshotValidation(kernel::FlushResult<void, kernel::AbortFailure>& result) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
//! Returns nullptr if no snapshot has been loaded.
const CBlockIndex* GetSnapshotBaseBlock() const EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
@ -1177,7 +1175,7 @@ public:
* unknown parent, key is parent block hash
* (only used for reindex)
* */
void LoadExternalBlockFile(
[[nodiscard]] kernel::FlushResult<kernel::InterruptResult, kernel::AbortFailure> LoadExternalBlockFile(
AutoFile& file_in,
FlatFilePos* dbp = nullptr,
std::multimap<uint256, FlatFilePos>* blocks_with_unknown_parent = nullptr);
@ -1204,9 +1202,10 @@ public:
* block header is already present in block
* index then this parameter has no effect)
* @param[out] new_block A boolean which is set to indicate if the block was first received via this call
* @param[out] result Errors from saving or flushing the block.
* @returns If the block was processed, independently of block validity
*/
bool ProcessNewBlock(const std::shared_ptr<const CBlock>& block, bool force_processing, bool min_pow_checked, bool* new_block) LOCKS_EXCLUDED(cs_main);
[[nodiscard]] bool ProcessNewBlock(const std::shared_ptr<const CBlock>& block, bool force_processing, bool min_pow_checked, bool* new_block, kernel::FlushResult<void, kernel::AbortFailure>& result) LOCKS_EXCLUDED(cs_main);
/**
* Process incoming block headers.
@ -1232,6 +1231,7 @@ public:
* @param[in] min_pow_checked True if proof-of-work anti-DoS checks have
* been done by caller for headers chain
*
* @param[out] result Errors from saving or flushing the block.
* @param[out] state The state of the block validation.
* @param[out] ppindex Optional return parameter to get the
* CBlockIndex pointer for this block.
@ -1240,7 +1240,7 @@ public:
*
* @returns False if the block or header is invalid, or if saving to disk fails (likely a fatal error); true otherwise.
*/
bool AcceptBlock(const std::shared_ptr<const CBlock>& pblock, BlockValidationState& state, CBlockIndex** ppindex, bool fRequested, const FlatFilePos* dbp, bool* fNewBlock, bool min_pow_checked) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
[[nodiscard]] bool AcceptBlock(const std::shared_ptr<const CBlock>& pblock, kernel::FlushResult<void, kernel::AbortFailure>& result, BlockValidationState& state, CBlockIndex** ppindex, bool fRequested, const FlatFilePos* dbp, bool* fNewBlock, bool min_pow_checked) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
void ReceivedBlockTransactions(const CBlock& block, CBlockIndex* pindexNew, const FlatFilePos& pos) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
@ -1250,15 +1250,15 @@ public:
* @param[in] tx The transaction to submit for mempool acceptance.
* @param[in] test_accept When true, run validation checks but don't submit to mempool.
*/
[[nodiscard]] MempoolAcceptResult ProcessTransaction(const CTransactionRef& tx, bool test_accept=false)
[[nodiscard]] std::tuple<MempoolAcceptResult, kernel::FlushResult<void, kernel::AbortFailure>> ProcessTransaction(const CTransactionRef& tx, bool test_accept=false)
EXCLUSIVE_LOCKS_REQUIRED(cs_main);
//! Load the block tree and coins database from disk, initializing state if we're running with -reindex
bool LoadBlockIndex() EXCLUSIVE_LOCKS_REQUIRED(cs_main);
[[nodiscard]] util::Result<kernel::InterruptResult, kernel::AbortFailure> LoadBlockIndex() EXCLUSIVE_LOCKS_REQUIRED(cs_main);
//! Check to see if caches are out of balance and if so, call
//! ResizeCoinsCaches() as needed.
void MaybeRebalanceCaches() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
[[nodiscard]] kernel::FlushResult<> MaybeRebalanceCaches() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
/** Update uncommitted block structures (currently: only the witness reserved value). This is safe for submitted blocks. */
void UpdateUncommittedBlockStructures(CBlock& block, const CBlockIndex* pindexPrev) const;
@ -1295,7 +1295,7 @@ public:
//! directories are moved or deleted.
//!
//! @sa node/chainstate:LoadChainstate()
bool ValidatedSnapshotCleanup() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
[[nodiscard]] util::Result<void, kernel::AbortFailure> ValidatedSnapshotCleanup() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
//! @returns the chainstate that indexes should consult when ensuring that an
//! index is synced with a chain where we can expect block index entries to have

View file

@ -2405,15 +2405,13 @@ DBErrors CWallet::LoadWallet()
util::Result<void> CWallet::RemoveTxs(std::vector<uint256>& txs_to_remove)
{
AssertLockHeld(cs_wallet);
util::Result<void> result;
bilingual_str str_err; // future: make RunWithinTxn return a util::Result
bool was_txn_committed = RunWithinTxn(GetDatabase(), /*process_desc=*/"remove transactions", [&](WalletBatch& batch) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) {
util::Result<void> result{RemoveTxs(batch, txs_to_remove)};
if (!result) str_err = util::ErrorString(result);
return result.has_value();
return bool{result.Update(RemoveTxs(batch, txs_to_remove))};
});
if (!str_err.empty()) return util::Error{str_err};
if (!was_txn_committed) return util::Error{_("Error starting/committing db txn for wallet transactions removal process")};
return {}; // all good
if (result && !was_txn_committed) result.Update(util::Error{_("Error starting/committing db txn for wallet transactions removal process")});
return result;
}
util::Result<void> CWallet::RemoveTxs(WalletBatch& batch, std::vector<uint256>& txs_to_remove)
@ -2972,7 +2970,7 @@ static util::Result<fs::path> GetWalletPath(const std::string& name)
// 2. Path to an existing directory.
// 3. Path to a symlink to a directory.
// 4. For backwards compatibility, the name of a data file in -walletdir.
const fs::path wallet_path = fsbridge::AbsPathJoin(GetWalletDir(), fs::PathFromString(name));
fs::path wallet_path = fsbridge::AbsPathJoin(GetWalletDir(), fs::PathFromString(name));
fs::file_type path_type = fs::symlink_status(wallet_path).type();
if (!(path_type == fs::file_type::not_found || path_type == fs::file_type::directory ||
(path_type == fs::file_type::symlink && fs::is_directory(wallet_path)) ||

View file

@ -9,6 +9,7 @@ to a hash that has been compiled into bitcoind.
The assumeutxo value generated and used here is committed to in
`CRegTestParams::m_assumeutxo_data` in `src/kernel/chainparams.cpp`.
"""
import os
from shutil import rmtree
from dataclasses import dataclass
@ -171,9 +172,10 @@ class AssumeutxoTest(BitcoinTestFramework):
with self.nodes[0].assert_debug_log([log_msg]):
self.nodes[0].assert_start_raises_init_error(expected_msg=error_msg)
expected_error_msg = "Error: A fatal internal error occurred, see debug.log for details: Assumeutxo data not found for the given blockhash '7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a'."
error_details = "Assumeutxo data not found for the given blockhash"
expected_error(log_msg=error_details, error_msg=expected_error_msg)
assumeutxo_error = "Assumeutxo data not found for the given blockhash '7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a'."
blockindex_error = "Error loading block database"
expected_error_msg = f"Error: A fatal internal error occurred, see debug.log for details: {assumeutxo_error}{os.linesep}Error: {assumeutxo_error} {blockindex_error}"
expected_error(log_msg=assumeutxo_error, error_msg=expected_error_msg)
# resurrect node again
rmtree(chainstate_snapshot_path)