mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-03-13 11:35:20 +01:00
Merge 07d8ec809e
into a50af6e4c4
This commit is contained in:
commit
6948478e68
47 changed files with 1639 additions and 635 deletions
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
51
src/init.cpp
51
src/init.cpp
|
@ -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))()) {
|
||||
|
|
|
@ -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
66
src/kernel/result.h
Normal 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
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
33
src/util/result.cpp
Normal 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
|
|
@ -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
|
@ -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
|
||||
|
|
|
@ -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)) ||
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Reference in a new issue