This commit is contained in:
Ryan Ofsky 2024-04-29 04:34:21 +02:00 committed by GitHub
commit 2810da35c7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 696 additions and 181 deletions

View File

@ -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 \

View File

@ -190,7 +190,7 @@ void ReadFromStream(AddrMan& addr, DataStream& ssPeers)
DeserializeDB(ssPeers, addr, false);
}
util::Result<std::unique_ptr<AddrMan>> LoadAddrman(const NetGroupManager& netgroupman, const ArgsManager& args)
util::ResultPtr<std::unique_ptr<AddrMan>> LoadAddrman(const NetGroupManager& netgroupman, const ArgsManager& args)
{
auto check_addrman = std::clamp<int32_t>(args.GetIntArg("-checkaddrman", DEFAULT_ADDRMAN_CONSISTENCY_CHECKS), 0, 1000000);
bool deterministic = HasTestOption(args, "addrman"); // use a deterministic addrman only for tests

View File

@ -49,7 +49,7 @@ public:
};
/** Returns an error string on failure */
util::Result<std::unique_ptr<AddrMan>> LoadAddrman(const NetGroupManager& netgroupman, const ArgsManager& args);
util::ResultPtr<std::unique_ptr<AddrMan>> LoadAddrman(const NetGroupManager& netgroupman, const ArgsManager& args);
/**
* Dump the anchor IP address database (anchors.dat)

View File

@ -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 result = node::LoadChainstate(chainman, cache_sizes, options);
if (!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) {
result = node::VerifyLoadedChainstate(chainman, options);
if (!result) {
std::cerr << "Failed to verify loaded Chain state from your datadir." << std::endl;
goto epilogue;
}

View File

@ -994,7 +994,7 @@ bool AppInitParameterInteraction(const ArgsManager& args)
// ********************************************************* Step 3: parameter-to-internal-flags
auto result = init::SetLoggingCategories(args);
if (!result) return InitError(util::ErrorString(result));
result = init::SetLoggingLevel(args);
result.Set(init::SetLoggingLevel(args));
if (!result) return InitError(util::ErrorString(result));
nConnectTimeout = args.GetIntArg("-timeout", DEFAULT_CONNECT_TIMEOUT);
@ -1261,7 +1261,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
uiInterface.InitMessage(_("Loading P2P addresses…").translated);
auto addrman{LoadAddrman(*node.netgroupman, args)};
if (!addrman) return InitError(util::ErrorString(addrman));
node.addrman = std::move(*addrman);
node.addrman = std::move(addrman.value());
}
assert(!node.banman);
@ -1575,33 +1575,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<void, 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) {
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 = catch_exceptions([&]{ return VerifyLoadedChainstate(chainman, options);});
if (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(

View File

@ -323,16 +323,16 @@ class WalletLoader : public ChainClient
{
public:
//! Create new wallet.
virtual util::Result<std::unique_ptr<Wallet>> createWallet(const std::string& name, const SecureString& passphrase, uint64_t wallet_creation_flags, std::vector<bilingual_str>& warnings) = 0;
virtual util::ResultPtr<std::unique_ptr<Wallet>> createWallet(const std::string& name, const SecureString& passphrase, uint64_t wallet_creation_flags, std::vector<bilingual_str>& warnings) = 0;
//! Load existing wallet.
virtual util::Result<std::unique_ptr<Wallet>> loadWallet(const std::string& name, std::vector<bilingual_str>& warnings) = 0;
virtual util::ResultPtr<std::unique_ptr<Wallet>> loadWallet(const std::string& name, std::vector<bilingual_str>& warnings) = 0;
//! Return default wallet directory.
virtual std::string getWalletDir() = 0;
//! Restore backup wallet
virtual util::Result<std::unique_ptr<Wallet>> restoreWallet(const fs::path& backup_file, const std::string& wallet_name, std::vector<bilingual_str>& warnings) = 0;
virtual util::ResultPtr<std::unique_ptr<Wallet>> restoreWallet(const fs::path& backup_file, const std::string& wallet_name, std::vector<bilingual_str>& warnings) = 0;
//! Migrate a wallet
virtual util::Result<WalletMigrationResult> migrateWallet(const std::string& name, const SecureString& passphrase) = 0;

View File

@ -32,7 +32,7 @@
namespace node {
// Complete initialization of chainstates after the initial call has been made
// to ChainstateManager::InitializeChainstate().
static ChainstateLoadResult CompleteChainstateInitialization(
static util::Result<void, ChainstateLoadError> CompleteChainstateInitialization(
ChainstateManager& chainman,
const CacheSizes& cache_sizes,
const ChainstateLoadOptions& options) EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
@ -56,28 +56,28 @@ static ChainstateLoadResult CompleteChainstateInitialization(
}
}
if (chainman.m_interrupt) return {ChainstateLoadStatus::INTERRUPTED, {}};
if (chainman.m_interrupt) return {util::Error{}, ChainstateLoadError::INTERRUPTED};
// 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) return {util::Error{}, ChainstateLoadError::INTERRUPTED};
return {util::Error{_("Error loading block database")}, ChainstateLoadError::FAILURE};
}
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?")};
return {util::Error{_("Incorrect or no genesis block found. Wrong datadir for network?")}, ChainstateLoadError::FAILURE_INCOMPATIBLE_DB};
}
// 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")};
return {util::Error{_("You need to rebuild the database using -reindex to go back to unpruned mode. This will redownload the entire blockchain")}, ChainstateLoadError::FAILURE};
}
// At this point blocktree args are consistent with what's on disk.
@ -85,7 +85,7 @@ 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")};
return {util::Error{_("Error initializing block database")}, ChainstateLoadError::FAILURE};
}
auto is_coinsview_empty = [&](Chainstate* chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
@ -119,14 +119,15 @@ 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.")};
return {util::Error{ _("Unsupported chainstate database format found. "
"Please restart with -reindex-chainstate. This will "
"rebuild the chainstate database.")},
ChainstateLoadError::FAILURE_INCOMPATIBLE_DB};
}
// 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.")};
return {util::Error{_("Unable to replay blocks. You will need to rebuild the database using -reindex-chainstate.")}, ChainstateLoadError::FAILURE};
}
// The on-disk coinsdb is now in a good state, create the cache
@ -136,7 +137,7 @@ 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")};
return {util::Error{_("Error initializing block database")}, ChainstateLoadError::FAILURE};
}
assert(chainstate->m_chain.Tip() != nullptr);
}
@ -146,8 +147,8 @@ 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)};
return {util::Error{strprintf(_("Witness data for blocks after height %d requires validation. Please restart with -reindex."),
chainman.GetConsensus().SegwitHeight)}, ChainstateLoadError::FAILURE};
};
}
@ -156,11 +157,11 @@ static ChainstateLoadResult CompleteChainstateInitialization(
// on the condition of each chainstate.
chainman.MaybeRebalanceCaches();
return {ChainstateLoadStatus::SUCCESS, {}};
return {};
}
ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSizes& cache_sizes,
const ChainstateLoadOptions& options)
util::Result<void, ChainstateLoadError> LoadChainstate(ChainstateManager& chainman, const CacheSizes& cache_sizes,
const ChainstateLoadOptions& options)
{
if (!chainman.AssumedValidBlock().IsNull()) {
LogPrintf("Assuming ancestors of block %s have valid signatures.\n", chainman.AssumedValidBlock().GetHex());
@ -191,13 +192,13 @@ 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.")};
return {util::Error{Untranslated("Couldn't remove snapshot chainstate.")}, ChainstateLoadError::FAILURE_FATAL};
}
}
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) {
return result;
}
// If a snapshot chainstate was fully validated by a background chainstate during
@ -215,7 +216,7 @@ 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.")};
return {util::Error{Untranslated("Background chainstate cleanup failed unexpectedly.")}, ChainstateLoadError::FAILURE_FATAL};
}
// Because ValidatedSnapshotCleanup() has torn down chainstates with
@ -231,20 +232,20 @@ 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) {
return result;
}
} else {
return {ChainstateLoadStatus::FAILURE, _(
"UTXO snapshot failed to validate. "
return util::Error{
_("UTXO snapshot failed to validate. "
"Restart to resume normal initial block download, or try loading a different snapshot.")};
}
return {ChainstateLoadStatus::SUCCESS, {}};
return {};
}
ChainstateLoadResult VerifyLoadedChainstate(ChainstateManager& chainman, const ChainstateLoadOptions& options)
util::Result<void, 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();
@ -256,9 +257,10 @@ ChainstateLoadResult VerifyLoadedChainstate(ChainstateManager& chainman, const C
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")};
return {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};
}
VerifyDBResult result = CVerifyDB(chainman.GetNotifications()).VerifyDB(
@ -270,18 +272,18 @@ ChainstateLoadResult VerifyLoadedChainstate(ChainstateManager& chainman, const C
case VerifyDBResult::SKIPPED_MISSING_BLOCKS:
break;
case VerifyDBResult::INTERRUPTED:
return {ChainstateLoadStatus::INTERRUPTED, _("Block verification was interrupted")};
return {util::Error{_("Block verification was interrupted")}, ChainstateLoadError::INTERRUPTED};
case VerifyDBResult::CORRUPTED_BLOCK_DB:
return {ChainstateLoadStatus::FAILURE, _("Corrupted block database detected")};
return {util::Error{_("Corrupted block database detected")}, ChainstateLoadError::FAILURE};
case VerifyDBResult::SKIPPED_L3_CHECKS:
if (options.require_full_verification) {
return {ChainstateLoadStatus::FAILURE_INSUFFICIENT_DBCACHE, _("Insufficient dbcache for block verification")};
return {util::Error{_("Insufficient dbcache for block verification")}, ChainstateLoadError::FAILURE_INSUFFICIENT_DBCACHE};
}
break;
} // no default case, so the compiler can warn about missing cases
}
}
return {ChainstateLoadStatus::SUCCESS, {}};
return {};
}
} // namespace node

View File

@ -5,6 +5,7 @@
#ifndef BITCOIN_NODE_CHAINSTATE_H
#define BITCOIN_NODE_CHAINSTATE_H
#include <util/result.h>
#include <util/translation.h>
#include <validation.h>
@ -35,12 +36,11 @@ struct ChainstateLoadOptions {
std::function<void()> coins_error_cb;
};
//! Chainstate load status. Simple applications can just check for the success
//! 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,
//! Chainstate load errors. Simple applications can just treat all errors as
//! failures. More complex applications may want to try reindexing in the
//! generic error case, and pass an interrupt callback and exit cleanly in the
//! interrupted case.
enum class ChainstateLoadError {
FAILURE, //!< Generic failure which reindexing may fix
FAILURE_FATAL, //!< Fatal error which should not prompt to reindex
FAILURE_INCOMPATIBLE_DB,
@ -48,25 +48,9 @@ enum class ChainstateLoadStatus {
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<void, ChainstateLoadError> LoadChainstate(ChainstateManager& chainman, const CacheSizes& cache_sizes,
const ChainstateLoadOptions& options);
util::Result<void, ChainstateLoadError> VerifyLoadedChainstate(ChainstateManager& chainman, const ChainstateLoadOptions& options);
} // namespace node
#endif // BITCOIN_NODE_CHAINSTATE_H

View File

@ -377,7 +377,7 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con
editStatus = WALLET_UNLOCK_FAILURE;
return QString();
}
op_dest = walletModel->wallet().getNewDestination(address_type, strLabel);
op_dest.Set(walletModel->wallet().getNewDestination(address_type, strLabel));
if (!op_dest) {
editStatus = KEY_GENERATION_FAILURE;
return QString();

View File

@ -266,7 +266,7 @@ void CreateWalletActivity::createWallet()
auto wallet{node().walletLoader().createWallet(name, m_passphrase, flags, m_warning_message)};
if (wallet) {
m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(*wallet));
m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(wallet.value()));
} else {
m_error_message = util::ErrorString(wallet);
}
@ -355,7 +355,7 @@ void OpenWalletActivity::open(const std::string& path)
auto wallet{node().walletLoader().loadWallet(path, m_warning_message)};
if (wallet) {
m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(*wallet));
m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(wallet.value()));
} else {
m_error_message = util::ErrorString(wallet);
}
@ -408,7 +408,7 @@ void RestoreWalletActivity::restore(const fs::path& backup_file, const std::stri
auto wallet{node().walletLoader().restoreWallet(backup_file, wallet_name, m_warning_message)};
if (wallet) {
m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(*wallet));
m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(wallet.value()));
} else {
m_error_message = util::ErrorString(wallet);
}

View File

@ -260,7 +260,7 @@ 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());
ancestors_calculated.Set(pool.CalculateMemPoolAncestors(entry.Fee(200000LL).Time(NodeSeconds{4s}).FromTx(tx10), CTxMemPool::Limits::NoLimits()));
BOOST_REQUIRE(ancestors_calculated);
BOOST_CHECK(*ancestors_calculated == setAncestors);

View File

@ -2,9 +2,17 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <tinyformat.h>
#include <util/check.h>
#include <util/result.h>
#include <util/translation.h>
#include <algorithm>
#include <boost/test/unit_test.hpp>
#include <memory>
#include <ostream>
#include <string>
#include <utility>
inline bool operator==(const bilingual_str& a, const bilingual_str& b)
{
@ -33,6 +41,34 @@ std::ostream& operator<<(std::ostream& os, const NoCopy& o)
return os << "NoCopy(" << *o.m_n << ")";
}
struct NoCopyNoMove {
NoCopyNoMove(int n) : m_n{n} {}
NoCopyNoMove(const NoCopyNoMove&) = delete;
NoCopyNoMove(NoCopyNoMove&&) = delete;
int m_n;
};
bool operator==(const NoCopyNoMove& a, const NoCopyNoMove& b)
{
return a.m_n == b.m_n;
}
std::ostream& operator<<(std::ostream& os, const NoCopyNoMove& o)
{
os << "NoCopyNoMove(" << o.m_n << ")";
return os;
}
util::Result<void> VoidSuccessFn()
{
return {};
}
util::Result<void> VoidFailFn()
{
return util::Error{Untranslated("void fail.")};
}
util::Result<int> IntFn(int i, bool success)
{
if (success) return i;
@ -45,21 +81,83 @@ 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)
{
auto result = IntFailFn(i, success);
if (!success) return {util::MoveMessages(result), util::Error{Untranslated("str error")}, result.GetFailure()};
return {util::MoveMessages(result), strprintf("%i", *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<void> result;
for (int i = 0; i < ret; ++i) {
result.AddWarning(strprintf(Untranslated("warn %i."), i));
}
return {util::MoveMessages(result), ret};
}
util::Result<void, int> ChainedFailFn(FnError arg, int ret)
{
return {util::Error{Untranslated("chained fail.")}, util::MoveMessages(EnumFailFn(arg)), util::MoveMessages(WarnFn()), ret};
}
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.Set(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 +165,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_set)
{
// Test using Set method to change a result value from success -> failure,
// and failure->success.
util::Result<int, FnError> result;
ExpectSuccess(result, {}, 0);
result.Set({util::Error{Untranslated("error")}, ERR1});
ExpectFail(result, Untranslated("error"), ERR1);
result.Set(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.Set({util::Error{Untranslated("error")}, 3});
ExpectFail(result2, Untranslated("error"), 3);
result2.Set(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 +244,63 @@ 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(result.GetErrors().size(), 1);
BOOST_CHECK_EQUAL(result.GetErrors()[0], Untranslated("Error."));
BOOST_CHECK_EQUAL(result.GetWarnings().size(), 1);
BOOST_CHECK_EQUAL(result.GetWarnings()[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)
{
// Check derived to base conversions work for util::Result
BOOST_CHECK_EQUAL(*Assert(*Assert(DerivedToBaseFn(std::make_unique<Derived>(5)))), 5);
// Check conversions work between util::Result and util::ResultPtr
util::ResultPtr<std::unique_ptr<Derived>> derived{std::make_unique<Derived>(5)};
util::ResultPtr<std::unique_ptr<NoCopyNoMove>> base{DerivedToBaseFn(std::move(derived))};
BOOST_CHECK_EQUAL(*Assert(base), 5);
}
//! For testing ResultPtr, return pointer to a pair of ints, or return pointer to null, or return an error message.
util::ResultPtr<std::unique_ptr<std::pair<int, int>>> PtrFn(std::optional<std::pair<int, int>> i, bool success)
{
if (success) return i ? std::make_unique<std::pair<int, int>>(*i) : nullptr;
return util::Error{strprintf(Untranslated("PtrFn(%s) error."), i ? strprintf("%i, %i", i->first, i->second) : "nullopt")};
}
BOOST_AUTO_TEST_CASE(check_ptr)
{
auto result_pair = PtrFn(std::pair{1, 2}, true);
ExpectResult(result_pair, true, {});
BOOST_CHECK(result_pair);
BOOST_CHECK_EQUAL(result_pair->first, 1);
BOOST_CHECK_EQUAL(result_pair->second, 2);
BOOST_CHECK(*result_pair == std::pair(1,2));
auto result_null = PtrFn(std::nullopt, true);
ExpectResult(result_null, true, {});
BOOST_CHECK(!result_null);
auto result_error_pair = PtrFn(std::pair{1, 2}, false);
ExpectResult(result_error_pair, false, Untranslated("PtrFn(1, 2) error."));
BOOST_CHECK(!result_error_pair);
auto result_error_null = PtrFn(std::nullopt, false);
ExpectResult(result_error_null, false, Untranslated("PtrFn(nullopt) error."));
BOOST_CHECK(!result_error_null);
}
BOOST_AUTO_TEST_SUITE_END()

View File

@ -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 result = LoadChainstate(chainman, m_cache_sizes, options);
assert(result);
std::tie(status, error) = VerifyLoadedChainstate(chainman, options);
assert(status == node::ChainstateLoadStatus::SUCCESS);
result = VerifyLoadedChainstate(chainman, options);
assert(result);
BlockValidationState state;
if (!chainman.ActiveChainstate().ActivateBestChain(state)) {

32
src/util/result.cpp Normal file
View File

@ -0,0 +1,32 @@
// Copyright (c) 2022 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 std::vector<bilingual_str>& errors, const std::vector<bilingual_str>& warnings)
{
bilingual_str result;
for (const auto& messages : {errors, warnings}) {
for (const auto& message : messages) {
if (!result.empty()) result += Untranslated(" ");
result += message;
}
}
return result;
}
void MoveMessages(std::vector<bilingual_str>& src, std::vector<bilingual_str>& dest)
{
dest.insert(dest.end(), std::make_move_iterator(src.begin()), std::make_move_iterator(src.end()));
src.clear();
}
} // namespace detail
} // namespace util

View File

@ -8,58 +8,258 @@
#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 {
//! The `Result` class provides a standard way for functions to return error and
//! warning strings in addition to optional result values.
//!
//! The easiest way to understand `Result<T>` is to think of it as a substitute
//! for `std::optional<T>`, that just contains error and warning strings in
//! addition to the optional return value. For example:
//!
//! 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, but in
//! general code using `Result<T>` return values is similar to code using
//! `std::optional<T>` return values. Existing functions returning
//! `std::optional<T>` can be updated to return `Result<T>` usually just
//! replacing `return std::nullopt;` with `return util::Error{error_string};`.
//!
//! An optional failure type `F` can be passed as a `Result<T, F>` template
//! argument. The default type `F = void` is sufficient for most code that just
//! needs an error description without more complicated error handling. But code
//! that does need more fine-grained failure information can set custom error
//! values of type `F` and retrieve them with the `GetFailure()` method.
template <typename T, typename F = void>
class Result;
//! Wrapper types to pass error and warning strings to Result constructors.
struct Error {
bilingual_str message;
};
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
//! Wrapper type to move error and warning strings from an existing Result object to a new Result constructor.
template <typename Result>
struct MoveMessages {
MoveMessages(Result&& result) : m_result(result) {}
Result& m_result;
};
//! Template deduction guide for MoveMessages class.
template <class Result>
MoveMessages(Result&& result) -> MoveMessages<Result>;
namespace detail {
//! Empty string list
const std::vector<bilingual_str> EMPTY_LIST{};
//! Helper function to join messages in space separated string.
bilingual_str JoinMessages(const std::vector<bilingual_str>& errors, const std::vector<bilingual_str>& warnings);
//! Helper function to move messages from one vector to another.
void MoveMessages(std::vector<bilingual_str>& src, std::vector<bilingual_str>& dest);
//! Substitute for std::monostate that doesn't depend on std::variant.
struct MonoState{};
//! Error information only allocated if there are errors or warnings.
template <typename F>
struct ErrorInfo {
std::optional<std::conditional_t<std::is_same_v<F, void>, MonoState, F>> failure{};
std::vector<bilingual_str> errors{};
std::vector<bilingual_str> warnings{};
};
//! Result base class which is inherited by Result<T, F>.
//! T is the type of the success return value, or void if there is none.
//! F is the type of the failure return value, or void if there is none.
template <typename T, typename F>
class ResultBase;
//! Result base specialization for empty (T=void) value type. Holds error
//! information and provides accessor methods.
template <typename F>
class ResultBase<void, F>
{
private:
using T = std::conditional_t<std::is_same_v<M, void>, std::monostate, M>;
protected:
std::unique_ptr<ErrorInfo<F>> m_info;
std::variant<bilingual_str, T> m_variant;
ErrorInfo<F>& Info() LIFETIMEBOUND
{
if (!m_info) m_info = std::make_unique<ErrorInfo<F>>();
return *m_info;
}
template <typename FT>
friend bilingual_str ErrorString(const Result<FT>& result);
//! Value setter methods which do nothing because this ResultBase class is
//! specialized for type T=void, so there is no result value for it to hold.
//! The other ResultBase specialization below for non-void T overrides these
//! methods to manage the result value it contains.
void ConstructValue() {}
template <typename O>
void MoveValue(O&& other) {}
void DestroyValue() {}
//! Helper function to construct a new Result success value or failure value
//! using the arguments provided.
template <bool Failure, typename Result, typename... Args>
static void ConstructResult(Result& result, Args&&... args)
{
if constexpr (Failure) {
result.Info().failure.emplace(std::forward<Args>(args)...);
} else {
result.ConstructValue(std::forward<Args>(args)...);
}
}
//! ConstructResult() overload peeling off a util::Error constructor argument.
template <bool Failure, typename Result, typename... Args>
static void ConstructResult(Result& result, util::Error error, Args&&... args)
{
result.AddError(std::move(error.message));
ConstructResult</*Failure=*/true>(result, std::forward<Args>(args)...);
}
//! ConstructResult() overload peeling off a util::Warning constructor argument.
template <bool Failure, typename Result, typename... Args>
static void ConstructResult(Result& result, util::Warning warning, Args&&... args)
{
result.AddWarning(std::move(warning.message));
ConstructResult<Failure>(result, std::forward<Args>(args)...);
}
//! ConstructResult() overload peeling off a util::MoveMessages constructor argument.
template <bool Failure, typename Result, typename R, typename... Args>
static void ConstructResult(Result& result, util::MoveMessages<R> messages, Args&&... args)
{
result.MoveMessages(std::move(messages.m_result));
ConstructResult<Failure>(result, std::forward<Args>(args)...);
}
//! Helper function to move success or failure values and any error and
//! warning messages from one result object to another. The two result
//! objects must have compatible success and failure types.
template <bool Constructed, typename DstResult, typename SrcResult>
static void MoveResult(DstResult& dst, SrcResult&& src)
{
if constexpr (Constructed) {
if (dst) {
dst.DestroyValue();
} else {
dst.m_info->failure.reset();
}
}
dst.MoveMessages(src);
if (src) {
dst.MoveValue(std::move(src));
} else {
dst.Info().failure = std::move(src.m_info->failure);
}
}
//! Disallow potentially dangerous assignment operators which might erase
//! error and warning messages. The Result::Set() method can be used instead
//! of operator= to assign result values while keeping any existing errors
//! and warnings.
template <typename Result>
ResultBase& operator=(Result&&) = delete;
template <typename, typename>
friend class ResultBase;
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)} {}
//! Success check.
explicit operator bool() const { return !m_info || !m_info->failure; }
//! Error retrieval.
const auto& GetFailure() const LIFETIMEBOUND { assert(!*this); return *m_info->failure; }
const std::vector<bilingual_str>& GetErrors() const LIFETIMEBOUND { return m_info ? m_info->errors : EMPTY_LIST; }
const std::vector<bilingual_str>& GetWarnings() const LIFETIMEBOUND { return m_info ? m_info->warnings : EMPTY_LIST; }
//! Add error and warning messages.
void AddError(bilingual_str error)
{
if (!error.empty()) this->Info().errors.emplace_back(std::move(error));
}
void AddWarning(bilingual_str warning)
{
if (!warning.empty()) this->Info().warnings.emplace_back(std::move(warning));
}
//! Move error and warning messages from another result to this one.
template <typename Result>
void MoveMessages(Result&& other)
{
if (other.m_info) {
// Check that errors and warnings are empty before calling
// MoveMessages to avoid allocating memory in this->Info() in the
// typical case when there are no errors or warnings.
if (!other.m_info->errors.empty()) detail::MoveMessages(other.m_info->errors, this->Info().errors);
if (!other.m_info->warnings.empty()) detail::MoveMessages(other.m_info->warnings, this->Info().warnings);
}
}
static constexpr bool is_result{true};
};
//! Result base class for T value type. Holds value and provides accessor methods.
template <typename T, typename F>
class ResultBase : public ResultBase<void, F>
{
protected:
//! Result success value. Uses anonymous union so success value is never
//! constructed in failure case.
union { T m_value; };
template <typename... Args>
void ConstructValue(Args&&... args) { new (&m_value) T{std::forward<Args>(args)...}; }
template <typename O>
void MoveValue(O&& other) { new (&m_value) T{std::move(other.m_value)}; }
void DestroyValue() { m_value.~T(); }
//! Empty constructor that needs to be declared because the class contains a union.
ResultBase() {}
~ResultBase() { if (*this) DestroyValue(); }
template <typename, typename>
friend class ResultBase;
public:
//! 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);
}
bool has_value() const { return bool{*this}; }
const T& value() const LIFETIMEBOUND { assert(has_value()); return m_value; }
T& value() LIFETIMEBOUND { assert(has_value()); return m_value; }
template <class U>
T value_or(U&& default_value) const&
{
@ -70,18 +270,110 @@ public:
{
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(); }
};
} // namespace detail
template <typename T>
bilingual_str ErrorString(const Result<T>& result)
template <typename T, typename F>
class Result : public detail::ResultBase<T, F>
{
return result ? bilingual_str{} : std::get<0>(result.m_variant);
protected:
using Base = detail::ResultBase<T, F>;
public:
//! Construct a Result object setting a success or failure value and
//! optional warning and error messages. Initial util::Error, util::Warning,
//! and util::MoveMessages arguments are processed first to add warning and
//! error messages. Then, any remaining arguments are passed to the type `T`
//! constructor and used to construct a success value in the success case.
//! In the failure case, any remaining arguments are passed to the type `F`
//! constructor and used to construct a failure value.
template <typename... Args>
Result(Args&&... args)
{
Base::template ConstructResult</*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)
{
Base::template MoveResult</*Constructed=*/false>(*this, std::move(other));
}
//! Move success or failure values from another Result object to this
//! object. Also move any error and warning messages from the other Result
//! object to this one. If this Result object has an existing success or
//! failure value, it is cleared and replaced by the other value. If this
//! Result object has any error or warning messages, they are kept and
//! appended to.
Result& Set(Result&& other) LIFETIMEBOUND
{
Base::template MoveResult</*Constructed=*/true>(*this, std::move(other));
return *this;
}
};
//! Operator moving warning and error messages from a source result object
//! to a destination result object. Only moves message strings, does not
//! change success or failure 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 S, typename D>
requires (std::decay_t<S>::is_result)
S&& operator>>(S&& src LIFETIMEBOUND, D&& dst)
{
dst.MoveMessages(src);
return std::move(src);
}
//! Wrapper around util::Result that is less awkward to use with pointer types.
//!
//! It overloads pointer and bool operators so it isn't necessary to dereference
//! the result object twice to access the result value, so it possible to call
//! methods with `result->Method()` rather than `(*result)->Method()` and check
//! whether the pointer is null with `if (result)` rather than `if (result &&
//! *result)`.
//!
//! The `ResultPtr` class just adds syntax sugar to `Result` class. It is still
//! possible to access the result pointer directly using `ResultPtr` `value()`
//! and `has_value()` methods.
template <typename T, typename F = void>
class ResultPtr : public Result<T, F>
{
public:
// Inherit Result constructors (excluding copy and move constructors).
using Result<T, F>::Result;
// Inherit Result copy and move constructors.
template<typename O>
ResultPtr(O&& other) : Result<T, F>{std::forward<O>(other)} {}
explicit operator bool() const noexcept { return this->has_value() && this->value(); }
auto* operator->() const { assert(this->value()); return &*this->value(); }
auto& operator*() const { assert(this->value()); return *this->value(); }
};
//! 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 GetErrors() and
//! GetWarning() methods directly.
template <typename T, typename F>
bilingual_str ErrorString(const Result<T, F>& result) { return detail::JoinMessages(result.GetErrors(), result.GetWarnings()); }
} // namespace util
#endif // BITCOIN_UTIL_RESULT_H

View File

@ -970,7 +970,7 @@ 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);
ancestors.Set(m_pool.CalculateMemPoolAncestors(*entry, cpfp_carve_out_limits));
if (!ancestors) return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "too-long-mempool-chain", error_message);
}

View File

@ -597,7 +597,7 @@ public:
void schedulerMockForward(std::chrono::seconds delta) override { Assert(m_context.scheduler)->MockForward(delta); }
//! WalletLoader methods
util::Result<std::unique_ptr<Wallet>> createWallet(const std::string& name, const SecureString& passphrase, uint64_t wallet_creation_flags, std::vector<bilingual_str>& warnings) override
util::ResultPtr<std::unique_ptr<Wallet>> createWallet(const std::string& name, const SecureString& passphrase, uint64_t wallet_creation_flags, std::vector<bilingual_str>& warnings) override
{
DatabaseOptions options;
DatabaseStatus status;
@ -606,37 +606,25 @@ public:
options.create_flags = wallet_creation_flags;
options.create_passphrase = passphrase;
bilingual_str error;
std::unique_ptr<Wallet> wallet{MakeWallet(m_context, CreateWallet(m_context, name, /*load_on_start=*/true, options, status, error, warnings))};
if (wallet) {
return wallet;
} else {
return util::Error{error};
}
util::ResultPtr<std::unique_ptr<Wallet>> wallet{MakeWallet(m_context, CreateWallet(m_context, name, /*load_on_start=*/true, options, status, error, warnings))};
return wallet ? std::move(wallet) : util::Error{error};
}
util::Result<std::unique_ptr<Wallet>> loadWallet(const std::string& name, std::vector<bilingual_str>& warnings) override
util::ResultPtr<std::unique_ptr<Wallet>> loadWallet(const std::string& name, std::vector<bilingual_str>& warnings) override
{
DatabaseOptions options;
DatabaseStatus status;
ReadDatabaseArgs(*m_context.args, options);
options.require_existing = true;
bilingual_str error;
std::unique_ptr<Wallet> wallet{MakeWallet(m_context, LoadWallet(m_context, name, /*load_on_start=*/true, options, status, error, warnings))};
if (wallet) {
return wallet;
} else {
return util::Error{error};
}
util::ResultPtr<std::unique_ptr<Wallet>> wallet{MakeWallet(m_context, LoadWallet(m_context, name, /*load_on_start=*/true, options, status, error, warnings))};
return wallet ? std::move(wallet) : util::Error{error};
}
util::Result<std::unique_ptr<Wallet>> restoreWallet(const fs::path& backup_file, const std::string& wallet_name, std::vector<bilingual_str>& warnings) override
util::ResultPtr<std::unique_ptr<Wallet>> restoreWallet(const fs::path& backup_file, const std::string& wallet_name, std::vector<bilingual_str>& warnings) override
{
DatabaseStatus status;
bilingual_str error;
std::unique_ptr<Wallet> wallet{MakeWallet(m_context, RestoreWallet(m_context, backup_file, wallet_name, /*load_on_start=*/true, status, error, warnings))};
if (wallet) {
return wallet;
} else {
return util::Error{error};
}
util::ResultPtr<std::unique_ptr<Wallet>> wallet{MakeWallet(m_context, RestoreWallet(m_context, backup_file, wallet_name, /*load_on_start=*/true, status, error, warnings))};
return wallet ? std::move(wallet) : util::Error{error};
}
util::Result<WalletMigrationResult> migrateWallet(const std::string& name, const SecureString& passphrase) override
{

View File

@ -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,7 +866,7 @@ 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"
}
@ -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;
}
}
static bool IsCurrentForAntiFeeSniping(interfaces::Chain& chain, const uint256& block_hash)

View File

@ -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;

View File

@ -108,9 +108,9 @@ struct FuzzedWallet {
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, "");
op_dest.Set(wallet->GetNewDestination(type, ""));
} else {
op_dest = wallet->GetNewChangeDestination(type);
op_dest.Set(wallet->GetNewChangeDestination(type));
}
return *Assert(op_dest);
}

View File

@ -102,7 +102,7 @@ BOOST_FIXTURE_TEST_CASE(wallet_duplicated_preset_inputs_test, TestChain100Setup)
// Second case, don't use 'subtract_fee_from_outputs'.
recipients[0].fSubtractFeeFromAmount = false;
res_tx = CreateTransaction(*wallet, recipients, /*change_pos=*/std::nullopt, coin_control);
res_tx.Set(CreateTransaction(*wallet, recipients, /*change_pos=*/std::nullopt, coin_control));
BOOST_CHECK(!res_tx.has_value());
}

View File

@ -17,6 +17,7 @@ lief
mor
nd
nin
ot
requestor
ser
siz