mirror of https://github.com/bitcoin/bitcoin
Merge 0c8a1bb144
into a46065e36c
This commit is contained in:
commit
ad75d422af
|
@ -746,6 +746,7 @@ libbitcoin_util_a_SOURCES = \
|
|||
util/moneystr.cpp \
|
||||
util/rbf.cpp \
|
||||
util/readwritefile.cpp \
|
||||
util/result.cpp \
|
||||
util/signalinterrupt.cpp \
|
||||
util/thread.cpp \
|
||||
util/threadinterrupt.cpp \
|
||||
|
@ -984,6 +985,7 @@ libbitcoinkernel_la_SOURCES = \
|
|||
util/hasher.cpp \
|
||||
util/moneystr.cpp \
|
||||
util/rbf.cpp \
|
||||
util/result.cpp \
|
||||
util/serfloat.cpp \
|
||||
util/signalinterrupt.cpp \
|
||||
util/strencodings.cpp \
|
||||
|
|
|
@ -122,13 +122,13 @@ int main(int argc, char* argv[])
|
|||
cache_sizes.coins_db = 2 << 22;
|
||||
cache_sizes.coins = (450 << 20) - (2 << 20) - (2 << 22);
|
||||
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;
|
||||
}
|
||||
|
|
26
src/init.cpp
26
src/init.cpp
|
@ -115,6 +115,7 @@
|
|||
#endif
|
||||
|
||||
using kernel::DumpMempool;
|
||||
using kernel::InterruptResult;
|
||||
using kernel::LoadMempool;
|
||||
using kernel::ValidationCacheSizes;
|
||||
|
||||
|
@ -992,10 +993,8 @@ bool AppInitParameterInteraction(const ArgsManager& args)
|
|||
InitWarning(strprintf(_("Reducing -maxconnections from %d to %d, because of system limitations."), nUserMaxConnections, nMaxConnections));
|
||||
|
||||
// ********************************************************* Step 3: parameter-to-internal-flags
|
||||
auto result = init::SetLoggingCategories(args);
|
||||
if (!result) return InitError(util::ErrorString(result));
|
||||
result = init::SetLoggingLevel(args);
|
||||
if (!result) return InitError(util::ErrorString(result));
|
||||
if (auto result{init::SetLoggingCategories(args)}; !result) return InitError(util::ErrorString(result));
|
||||
if (auto result{init::SetLoggingLevel(args)}; !result) return InitError(util::ErrorString(result));
|
||||
|
||||
nConnectTimeout = args.GetIntArg("-timeout", DEFAULT_CONNECT_TIMEOUT);
|
||||
if (nConnectTimeout <= 0) {
|
||||
|
@ -1575,33 +1574,36 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
|
|||
|
||||
uiInterface.InitMessage(_("Loading block index…").translated);
|
||||
const auto load_block_index_start_time{SteadyClock::now()};
|
||||
auto catch_exceptions = [](auto&& f) {
|
||||
auto catch_exceptions = [](auto&& f) -> util::Result<InterruptResult, node::ChainstateLoadError> {
|
||||
try {
|
||||
return f();
|
||||
} catch (const std::exception& e) {
|
||||
LogPrintf("%s\n", e.what());
|
||||
return std::make_tuple(node::ChainstateLoadStatus::FAILURE, _("Error opening block database"));
|
||||
return {util::Error{_("Error opening block database")}, 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…").translated);
|
||||
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)) {
|
||||
fLoaded = true;
|
||||
LogPrintf(" block index %15dms\n", Ticks<std::chrono::milliseconds>(SteadyClock::now() - load_block_index_start_time));
|
||||
}
|
||||
}
|
||||
|
||||
if (status == node::ChainstateLoadStatus::FAILURE_FATAL || status == node::ChainstateLoadStatus::FAILURE_INCOMPATIBLE_DB || status == node::ChainstateLoadStatus::FAILURE_INSUFFICIENT_DBCACHE) {
|
||||
return InitError(error);
|
||||
if (!result && (result.GetFailure() == node::ChainstateLoadError::FAILURE_FATAL ||
|
||||
result.GetFailure() == node::ChainstateLoadError::FAILURE_INCOMPATIBLE_DB ||
|
||||
result.GetFailure() == node::ChainstateLoadError::FAILURE_INSUFFICIENT_DBCACHE)) {
|
||||
return InitError(util::ErrorString(result));
|
||||
}
|
||||
|
||||
if (!fLoaded && !ShutdownRequested(node)) {
|
||||
bilingual_str error{util::ErrorString(result)};
|
||||
// first suggest a reindex
|
||||
if (!options.reindex) {
|
||||
bool fRet = uiInterface.ThreadSafeQuestion(
|
||||
|
|
|
@ -29,14 +29,18 @@
|
|||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
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 util::Result<InterruptResult, ChainstateLoadError> CompleteChainstateInitialization(
|
||||
ChainstateManager& chainman,
|
||||
const CacheSizes& cache_sizes,
|
||||
const ChainstateLoadOptions& options) EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
|
||||
{
|
||||
util::Result<InterruptResult, ChainstateLoadError> result;
|
||||
auto& pblocktree{chainman.m_blockman.m_block_tree_db};
|
||||
// new BlockTreeDB tries to delete the existing file, which
|
||||
// fails if it's still open from the previous loop. Close it first:
|
||||
|
@ -56,28 +60,37 @@ static ChainstateLoadResult CompleteChainstateInitialization(
|
|||
}
|
||||
}
|
||||
|
||||
if (chainman.m_interrupt) return {ChainstateLoadStatus::INTERRUPTED, {}};
|
||||
if (chainman.m_interrupt) {
|
||||
result.Update(Interrupted{});
|
||||
return result;
|
||||
}
|
||||
|
||||
// LoadBlockIndex will load m_have_pruned if we've ever removed a
|
||||
// block file from disk.
|
||||
// Note that it also sets fReindex global based on the disk flag!
|
||||
// From here on, fReindex and options.reindex values may be different!
|
||||
if (!chainman.LoadBlockIndex()) {
|
||||
if (chainman.m_interrupt) return {ChainstateLoadStatus::INTERRUPTED, {}};
|
||||
return {ChainstateLoadStatus::FAILURE, _("Error loading block database")};
|
||||
if (chainman.m_interrupt) {
|
||||
result.Update(Interrupted{});
|
||||
} else {
|
||||
result.Update({util::Error{_("Error loading block database")}, ChainstateLoadError::FAILURE});
|
||||
}
|
||||
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.
|
||||
|
@ -85,7 +98,8 @@ static ChainstateLoadResult CompleteChainstateInitialization(
|
|||
// (otherwise we use the one already on disk).
|
||||
// This is called again in ImportBlocks after the reindex completes.
|
||||
if (!fReindex && !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) {
|
||||
|
@ -119,14 +133,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
|
||||
|
@ -136,7 +153,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);
|
||||
}
|
||||
|
@ -146,8 +164,9 @@ 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;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -156,12 +175,13 @@ static ChainstateLoadResult CompleteChainstateInitialization(
|
|||
// on the condition of each chainstate.
|
||||
chainman.MaybeRebalanceCaches();
|
||||
|
||||
return {ChainstateLoadStatus::SUCCESS, {}};
|
||||
return result;
|
||||
}
|
||||
|
||||
ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSizes& cache_sizes,
|
||||
const ChainstateLoadOptions& options)
|
||||
util::Result<InterruptResult, ChainstateLoadError> LoadChainstate(ChainstateManager& chainman, const CacheSizes& cache_sizes,
|
||||
const ChainstateLoadOptions& options)
|
||||
{
|
||||
util::Result<InterruptResult, ChainstateLoadError> result;
|
||||
if (!chainman.AssumedValidBlock().IsNull()) {
|
||||
LogPrintf("Assuming ancestors of block %s have valid signatures.\n", chainman.AssumedValidBlock().GetHex());
|
||||
} else {
|
||||
|
@ -191,13 +211,14 @@ ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSize
|
|||
if (has_snapshot && (options.reindex || options.reindex_chainstate)) {
|
||||
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, cache_sizes, options);
|
||||
if (init_status != ChainstateLoadStatus::SUCCESS) {
|
||||
return {init_status, init_error};
|
||||
result.Update(CompleteChainstateInitialization(chainman, cache_sizes, options));
|
||||
if (!result || IsInterrupted(*result)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// If a snapshot chainstate was fully validated by a background chainstate during
|
||||
|
@ -215,7 +236,8 @@ ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSize
|
|||
} 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
|
||||
|
@ -231,20 +253,22 @@ ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSize
|
|||
// for the fully validated chainstate.
|
||||
chainman.ActiveChainstate().ClearBlockIndexCandidates();
|
||||
|
||||
auto [init_status, init_error] = CompleteChainstateInitialization(chainman, cache_sizes, options);
|
||||
if (init_status != ChainstateLoadStatus::SUCCESS) {
|
||||
return {init_status, init_error};
|
||||
auto result{CompleteChainstateInitialization(chainman, cache_sizes, options)};
|
||||
if (!result || IsInterrupted(*result)) {
|
||||
return result;
|
||||
}
|
||||
} else {
|
||||
return {ChainstateLoadStatus::FAILURE, _(
|
||||
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});
|
||||
return result;
|
||||
}
|
||||
|
||||
return {ChainstateLoadStatus::SUCCESS, {}};
|
||||
return result;
|
||||
}
|
||||
|
||||
ChainstateLoadResult VerifyLoadedChainstate(ChainstateManager& chainman, const ChainstateLoadOptions& options)
|
||||
util::Result<InterruptResult, ChainstateLoadError> VerifyLoadedChainstate(ChainstateManager& chainman, const ChainstateLoadOptions& options)
|
||||
{
|
||||
auto is_coinsview_empty = [&](Chainstate* chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
|
||||
return options.reindex || options.reindex_chainstate || chainstate->CoinsTip().GetBestBlock().IsNull();
|
||||
|
@ -252,36 +276,42 @@ ChainstateLoadResult VerifyLoadedChainstate(ChainstateManager& chainman, const C
|
|||
|
||||
LOCK(cs_main);
|
||||
|
||||
util::Result<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(
|
||||
VerifyDBResult verify_result = CVerifyDB(chainman.GetNotifications()).VerifyDB(
|
||||
*chainstate, chainman.GetConsensus(), chainstate->CoinsDB(),
|
||||
options.check_level,
|
||||
options.check_blocks);
|
||||
switch (result) {
|
||||
switch (verify_result) {
|
||||
case VerifyDBResult::SUCCESS:
|
||||
case VerifyDBResult::SKIPPED_MISSING_BLOCKS:
|
||||
break;
|
||||
case VerifyDBResult::INTERRUPTED:
|
||||
return {ChainstateLoadStatus::INTERRUPTED, _("Block verification was interrupted")};
|
||||
result.Update(Interrupted{});
|
||||
return result;
|
||||
case VerifyDBResult::CORRUPTED_BLOCK_DB:
|
||||
return {ChainstateLoadStatus::FAILURE, _("Corrupted block database detected")};
|
||||
result.Update({util::Error{_("Corrupted block database detected")}, ChainstateLoadError::FAILURE});
|
||||
return result;
|
||||
case VerifyDBResult::SKIPPED_L3_CHECKS:
|
||||
if (options.require_full_verification) {
|
||||
return {ChainstateLoadStatus::FAILURE_INSUFFICIENT_DBCACHE, _("Insufficient dbcache for block verification")};
|
||||
result.Update({util::Error{_("Insufficient dbcache for block verification")}, ChainstateLoadError::FAILURE_INSUFFICIENT_DBCACHE});
|
||||
return result;
|
||||
}
|
||||
break;
|
||||
} // no default case, so the compiler can warn about missing cases
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
|
@ -39,34 +41,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 CacheSizes& cache_sizes,
|
||||
const ChainstateLoadOptions& options);
|
||||
ChainstateLoadResult VerifyLoadedChainstate(ChainstateManager& chainman, const ChainstateLoadOptions& options);
|
||||
util::Result<kernel::InterruptResult, ChainstateLoadError> LoadChainstate(ChainstateManager& chainman, const CacheSizes& cache_sizes,
|
||||
const ChainstateLoadOptions& options);
|
||||
util::Result<kernel::InterruptResult, ChainstateLoadError> VerifyLoadedChainstate(ChainstateManager& chainman, const ChainstateLoadOptions& options);
|
||||
} // namespace node
|
||||
|
||||
#endif // BITCOIN_NODE_CHAINSTATE_H
|
||||
|
|
|
@ -369,21 +369,22 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con
|
|||
else if(type == Receive)
|
||||
{
|
||||
// Generate a new address to associate with given label
|
||||
auto op_dest = walletModel->wallet().getNewDestination(address_type, strLabel);
|
||||
if (!op_dest) {
|
||||
if (auto dest{walletModel->wallet().getNewDestination(address_type, strLabel)}) {
|
||||
strAddress = EncodeDestination(*dest);
|
||||
} else {
|
||||
WalletModel::UnlockContext ctx(walletModel->requestUnlock());
|
||||
if (!ctx.isValid()) {
|
||||
// Unlock wallet failed or was cancelled
|
||||
editStatus = WALLET_UNLOCK_FAILURE;
|
||||
return QString();
|
||||
}
|
||||
op_dest = walletModel->wallet().getNewDestination(address_type, strLabel);
|
||||
if (!op_dest) {
|
||||
if (auto dest{walletModel->wallet().getNewDestination(address_type, strLabel)}) {
|
||||
strAddress = EncodeDestination(*dest);
|
||||
} else {
|
||||
editStatus = KEY_GENERATION_FAILURE;
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
strAddress = EncodeDestination(*op_dest);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -202,9 +202,11 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
|
|||
tx7.vout[1].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
|
||||
tx7.vout[1].nValue = 1 * COIN;
|
||||
|
||||
auto ancestors_calculated{pool.CalculateMemPoolAncestors(entry.Fee(2000000LL).FromTx(tx7), CTxMemPool::Limits::NoLimits())};
|
||||
BOOST_REQUIRE(ancestors_calculated.has_value());
|
||||
BOOST_CHECK(*ancestors_calculated == setAncestors);
|
||||
{
|
||||
auto ancestors_calculated{pool.CalculateMemPoolAncestors(entry.Fee(2000000LL).FromTx(tx7), CTxMemPool::Limits::NoLimits())};
|
||||
BOOST_REQUIRE(ancestors_calculated.has_value());
|
||||
BOOST_CHECK(*ancestors_calculated == setAncestors);
|
||||
}
|
||||
|
||||
pool.addUnchecked(entry.FromTx(tx7), setAncestors);
|
||||
BOOST_CHECK_EQUAL(pool.size(), 7U);
|
||||
|
@ -260,9 +262,12 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
|
|||
tx10.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
|
||||
tx10.vout[0].nValue = 10 * COIN;
|
||||
|
||||
ancestors_calculated = pool.CalculateMemPoolAncestors(entry.Fee(200000LL).Time(NodeSeconds{4s}).FromTx(tx10), CTxMemPool::Limits::NoLimits());
|
||||
BOOST_REQUIRE(ancestors_calculated);
|
||||
BOOST_CHECK(*ancestors_calculated == setAncestors);
|
||||
{
|
||||
auto ancestors_calculated{pool.CalculateMemPoolAncestors(entry.Fee(200000LL).Time(NodeSeconds{4s}).FromTx(tx10), CTxMemPool::Limits::NoLimits())};
|
||||
BOOST_REQUIRE(ancestors_calculated);
|
||||
BOOST_CHECK(*ancestors_calculated == setAncestors);
|
||||
}
|
||||
|
||||
|
||||
pool.addUnchecked(entry.FromTx(tx10), setAncestors);
|
||||
|
||||
|
|
|
@ -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{strprintf(Untranslated("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(strprintf(Untranslated("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);
|
||||
|
@ -67,20 +173,73 @@ 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);
|
||||
BOOST_CHECK_EQUAL(result.GetFailure(), F{std::forward<Args>(args)...});
|
||||
}
|
||||
|
||||
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)
|
||||
|
@ -93,4 +252,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()
|
||||
|
|
|
@ -282,11 +282,11 @@ 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_cache_sizes, options);
|
||||
assert(status == node::ChainstateLoadStatus::SUCCESS);
|
||||
auto load_result{LoadChainstate(chainman, m_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)) {
|
||||
|
|
|
@ -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,79 +8,399 @@
|
|||
#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, 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>, 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() and 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 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.
|
||||
//!
|
||||
//! 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
|
||||
//! Type trait that can be specialized to control the way SuccessType /
|
||||
//! FailureType / 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 a FailDataHolder class holding
|
||||
//! a unique_ptr to FailureType and MessagesTypes values, and a SuccessHolder
|
||||
//! class holding a SuccessType value in an anonymous union.
|
||||
//! @{
|
||||
//! Container for FailureType and MessagesType, providing public operator
|
||||
//! bool(), GetFailure(), GetMessages(), and EnsureMessages() methods.
|
||||
template <typename FailureType, typename MessagesType>
|
||||
class FailDataHolder
|
||||
{
|
||||
private:
|
||||
using T = std::conditional_t<std::is_same_v<M, void>, std::monostate, M>;
|
||||
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;
|
||||
|
||||
std::variant<bilingual_str, T> m_variant;
|
||||
|
||||
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)} {}
|
||||
// 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 MessagesType>
|
||||
class SuccessHolder : public FailDataHolder<FailureType, 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 MessagesType>
|
||||
class SuccessHolder<void, FailureType, MessagesType> : public FailDataHolder<FailureType, 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 MessagesType_>
|
||||
class Result : public detail::SuccessHolder<SuccessType_, FailureType_, MessagesType_>
|
||||
{
|
||||
public:
|
||||
using SuccessType = SuccessType_;
|
||||
using FailureType = FailureType_;
|
||||
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, 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.
|
||||
template <typename Result>
|
||||
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>
|
||||
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>) {
|
||||
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)...);
|
||||
}
|
||||
|
||||
//! Move success, failure, 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 messages value 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 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 (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
|
||||
|
||||
|
|
|
@ -946,8 +946,9 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
|||
maybe_rbf_limits.descendant_size_vbytes += conflict->GetSizeWithDescendants();
|
||||
}
|
||||
|
||||
auto ancestors{m_pool.CalculateMemPoolAncestors(*entry, maybe_rbf_limits)};
|
||||
if (!ancestors) {
|
||||
if (auto ancestors{m_pool.CalculateMemPoolAncestors(*entry, maybe_rbf_limits)}) {
|
||||
ws.m_ancestors = std::move(*ancestors);
|
||||
} else {
|
||||
// If CalculateMemPoolAncestors fails second time, we want the original error string.
|
||||
// Contracting/payment channels CPFP carve-out:
|
||||
// If the new transaction is relatively small (up to 40k weight)
|
||||
|
@ -970,11 +971,13 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
|||
if (ws.m_vsize > EXTRA_DESCENDANT_TX_SIZE_LIMIT) {
|
||||
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "too-long-mempool-chain", error_message);
|
||||
}
|
||||
ancestors = m_pool.CalculateMemPoolAncestors(*entry, cpfp_carve_out_limits);
|
||||
if (!ancestors) return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "too-long-mempool-chain", error_message);
|
||||
if (auto ancestors{m_pool.CalculateMemPoolAncestors(*entry, cpfp_carve_out_limits)}) {
|
||||
ws.m_ancestors = std::move(*ancestors);
|
||||
} else {
|
||||
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "too-long-mempool-chain", error_message);
|
||||
}
|
||||
}
|
||||
|
||||
ws.m_ancestors = *ancestors;
|
||||
// Even though just checking direct mempool parents for inheritance would be sufficient, we
|
||||
// check using the full ancestor set here because it's more convenient to use what we have
|
||||
// already calculated.
|
||||
|
|
|
@ -683,11 +683,11 @@ util::Result<SelectionResult> ChooseSelectionResult(interfaces::Chain& chain, co
|
|||
// Vector of results. We will choose the best one based on waste.
|
||||
std::vector<SelectionResult> results;
|
||||
std::vector<util::Result<SelectionResult>> errors;
|
||||
auto append_error = [&] (const util::Result<SelectionResult>& result) {
|
||||
auto append_error = [&] (util::Result<SelectionResult>&& result) {
|
||||
// If any specific error message appears here, then something different from a simple "no selection found" happened.
|
||||
// Let's save it, so it can be retrieved to the user if no other selection algorithm succeeded.
|
||||
if (HasErrorMsg(result)) {
|
||||
errors.emplace_back(result);
|
||||
errors.emplace_back(std::move(result));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -698,7 +698,7 @@ util::Result<SelectionResult> ChooseSelectionResult(interfaces::Chain& chain, co
|
|||
if (!coin_selection_params.m_subtract_fee_outputs) {
|
||||
if (auto bnb_result{SelectCoinsBnB(groups.positive_group, nTargetValue, coin_selection_params.m_cost_of_change, max_inputs_weight)}) {
|
||||
results.push_back(*bnb_result);
|
||||
} else append_error(bnb_result);
|
||||
} else append_error(std::move(bnb_result));
|
||||
}
|
||||
|
||||
// As Knapsack and SRD can create change, also deduce change weight.
|
||||
|
@ -707,25 +707,25 @@ util::Result<SelectionResult> ChooseSelectionResult(interfaces::Chain& chain, co
|
|||
// The knapsack solver has some legacy behavior where it will spend dust outputs. We retain this behavior, so don't filter for positive only here.
|
||||
if (auto knapsack_result{KnapsackSolver(groups.mixed_group, nTargetValue, coin_selection_params.m_min_change_target, coin_selection_params.rng_fast, max_inputs_weight)}) {
|
||||
results.push_back(*knapsack_result);
|
||||
} else append_error(knapsack_result);
|
||||
} else append_error(std::move(knapsack_result));
|
||||
|
||||
if (coin_selection_params.m_effective_feerate > CFeeRate{3 * coin_selection_params.m_long_term_feerate}) { // Minimize input set for feerates of at least 3×LTFRE (default: 30 ṩ/vB+)
|
||||
if (auto cg_result{CoinGrinder(groups.positive_group, nTargetValue, coin_selection_params.m_min_change_target, max_inputs_weight)}) {
|
||||
cg_result->ComputeAndSetWaste(coin_selection_params.min_viable_change, coin_selection_params.m_cost_of_change, coin_selection_params.m_change_fee);
|
||||
results.push_back(*cg_result);
|
||||
} else {
|
||||
append_error(cg_result);
|
||||
append_error(std::move(cg_result));
|
||||
}
|
||||
}
|
||||
|
||||
if (auto srd_result{SelectCoinsSRD(groups.positive_group, nTargetValue, coin_selection_params.m_change_fee, coin_selection_params.rng_fast, max_inputs_weight)}) {
|
||||
results.push_back(*srd_result);
|
||||
} else append_error(srd_result);
|
||||
} else append_error(std::move(srd_result));
|
||||
|
||||
if (results.empty()) {
|
||||
// No solution found, retrieve the first explicit error (if any).
|
||||
// future: add 'severity level' to errors so the worst one can be retrieved instead of the first one.
|
||||
return errors.empty() ? util::Error() : errors.front();
|
||||
return errors.empty() ? util::Error() : std::move(errors.front());
|
||||
}
|
||||
|
||||
// If the chosen input set has unconfirmed inputs, check for synergies from overlapping ancestry
|
||||
|
@ -818,7 +818,7 @@ util::Result<SelectionResult> AutomaticCoinSelection(const CWallet& wallet, Coin
|
|||
// Coin Selection attempts to select inputs from a pool of eligible UTXOs to fund the
|
||||
// transaction at a target feerate. If an attempt fails, more attempts may be made using a more
|
||||
// permissive CoinEligibilityFilter.
|
||||
util::Result<SelectionResult> res = [&] {
|
||||
{
|
||||
// Place coins eligibility filters on a scope increasing order.
|
||||
std::vector<SelectionFilter> ordered_filters{
|
||||
// If possible, fund the transaction with confirmed UTXOs only. Prefer at least six
|
||||
|
@ -866,9 +866,9 @@ util::Result<SelectionResult> AutomaticCoinSelection(const CWallet& wallet, Coin
|
|||
if (CAmount total_amount = available_coins.GetTotalAmount() - total_discarded < value_to_select) {
|
||||
// Special case, too-long-mempool cluster.
|
||||
if (total_amount + total_unconf_long_chain > value_to_select) {
|
||||
return util::Result<SelectionResult>({_("Unconfirmed UTXOs are available, but spending them creates a chain of transactions that will be rejected by the mempool")});
|
||||
return util::Error{_("Unconfirmed UTXOs are available, but spending them creates a chain of transactions that will be rejected by the mempool")};
|
||||
}
|
||||
return util::Result<SelectionResult>(util::Error()); // General "Insufficient Funds"
|
||||
return util::Error{}; // General "Insufficient Funds"
|
||||
}
|
||||
|
||||
// Walk-through the filters until the solution gets found.
|
||||
|
@ -885,19 +885,17 @@ util::Result<SelectionResult> AutomaticCoinSelection(const CWallet& wallet, Coin
|
|||
// If any specific error message appears here, then something particularly wrong might have happened.
|
||||
// Save the error and continue the selection process. So if no solutions gets found, we can return
|
||||
// the detailed error to the upper layers.
|
||||
if (HasErrorMsg(res)) res_detailed_errors.emplace_back(res);
|
||||
if (HasErrorMsg(res)) res_detailed_errors.emplace_back(std::move(res));
|
||||
}
|
||||
}
|
||||
|
||||
// Return right away if we have a detailed error
|
||||
if (!res_detailed_errors.empty()) return res_detailed_errors.front();
|
||||
if (!res_detailed_errors.empty()) return std::move(res_detailed_errors.front());
|
||||
|
||||
|
||||
// General "Insufficient Funds"
|
||||
return util::Result<SelectionResult>(util::Error());
|
||||
}();
|
||||
|
||||
return res;
|
||||
return util::Error{};
|
||||
}
|
||||
}
|
||||
|
||||
static bool IsCurrentForAntiFeeSniping(interfaces::Chain& chain, const uint256& block_hash)
|
||||
|
|
|
@ -291,7 +291,10 @@ FUZZ_TARGET(coinselection)
|
|||
}
|
||||
|
||||
std::vector<COutput> utxos;
|
||||
std::vector<util::Result<SelectionResult>> results{result_srd, result_knapsack, result_bnb};
|
||||
std::vector<util::Result<SelectionResult>> results;
|
||||
results.emplace_back(std::move(result_srd));
|
||||
results.emplace_back(std::move(result_knapsack));
|
||||
results.emplace_back(std::move(result_bnb));
|
||||
CAmount new_total_balance{CreateCoins(fuzzed_data_provider, utxos, coin_params, next_locktime)};
|
||||
if (new_total_balance > 0) {
|
||||
std::set<std::shared_ptr<COutput>> new_utxo_pool;
|
||||
|
|
|
@ -106,13 +106,12 @@ struct FuzzedWallet {
|
|||
CTxDestination GetDestination(FuzzedDataProvider& fuzzed_data_provider)
|
||||
{
|
||||
auto type{fuzzed_data_provider.PickValueInArray(OUTPUT_TYPES)};
|
||||
util::Result<CTxDestination> op_dest{util::Error{}};
|
||||
if (fuzzed_data_provider.ConsumeBool()) {
|
||||
op_dest = wallet->GetNewDestination(type, "");
|
||||
return *Assert(wallet->GetNewDestination(type, ""));
|
||||
} else {
|
||||
op_dest = wallet->GetNewChangeDestination(type);
|
||||
return *Assert(wallet->GetNewChangeDestination(type));
|
||||
}
|
||||
return *Assert(op_dest);
|
||||
assert(false);
|
||||
}
|
||||
CScript GetScriptPubKey(FuzzedDataProvider& fuzzed_data_provider) { return GetScriptForDestination(GetDestination(fuzzed_data_provider)); }
|
||||
void FundTx(FuzzedDataProvider& fuzzed_data_provider, CMutableTransaction tx)
|
||||
|
|
|
@ -97,13 +97,11 @@ BOOST_FIXTURE_TEST_CASE(wallet_duplicated_preset_inputs_test, TestChain100Setup)
|
|||
// so that the recipient's amount is no longer equal to the user's selected target of 299 BTC.
|
||||
|
||||
// First case, use 'subtract_fee_from_outputs=true'
|
||||
util::Result<CreatedTransactionResult> res_tx = CreateTransaction(*wallet, recipients, /*change_pos=*/std::nullopt, coin_control);
|
||||
BOOST_CHECK(!res_tx.has_value());
|
||||
BOOST_CHECK(!CreateTransaction(*wallet, recipients, /*change_pos=*/std::nullopt, coin_control));
|
||||
|
||||
// Second case, don't use 'subtract_fee_from_outputs'.
|
||||
recipients[0].fSubtractFeeFromAmount = false;
|
||||
res_tx = CreateTransaction(*wallet, recipients, /*change_pos=*/std::nullopt, coin_control);
|
||||
BOOST_CHECK(!res_tx.has_value());
|
||||
BOOST_CHECK(!CreateTransaction(*wallet, recipients, /*change_pos=*/std::nullopt, coin_control));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
|
Loading…
Reference in New Issue