refactor: Drop util::Result operator=

`util::Result` objects are aggregates that can hold multiple fields with
different information. Currently Result objects can only hold a success value
of an arbitrary type or a single bilingual_str error message. In followup PR
https://github.com/bitcoin/bitcoin/pull/25722, Result objects may be able to
hold both success and failure values of different types, plus error and warning
messages.

Having a Result::operator= assignment operator that completely erases all
existing Result information before assigning new information is potentially
dangerous in this case. For example, code that looks like it is assigning a
warning value could erase previously-assigned success or failure values.
Conversely, code that looks like it is just assigning a success or failure
value could erase previously assigned error and warning messages.

To prevent potential bugs like this, disable Result::operator= assignment
operator.

It is possible in the future we may want to re-enable operator= in limited
cases (such as when implicit conversions are not used) or add a Replace() or
Reset() method that mimicks default operator= behavior. Followup PR
https://github.com/bitcoin/bitcoin/pull/25722 also adds a Result::Update()
method providing another way to update an existing Result object.

Co-authored-by: stickies-v <stickies-v@protonmail.com>
This commit is contained in:
Ryan Ofsky 2022-09-15 11:15:06 -04:00
parent 2eff198f49
commit 834f65e824
7 changed files with 40 additions and 28 deletions

View file

@ -992,10 +992,8 @@ bool AppInitParameterInteraction(const ArgsManager& args)
InitWarning(strprintf(_("Reducing -maxconnections from %d to %d, because of system limitations."), nUserMaxConnections, nMaxConnections)); InitWarning(strprintf(_("Reducing -maxconnections from %d to %d, because of system limitations."), nUserMaxConnections, nMaxConnections));
// ********************************************************* Step 3: parameter-to-internal-flags // ********************************************************* Step 3: parameter-to-internal-flags
auto result = init::SetLoggingCategories(args); if (auto result{init::SetLoggingCategories(args)}; !result) return InitError(util::ErrorString(result));
if (!result) return InitError(util::ErrorString(result)); if (auto result{init::SetLoggingLevel(args)}; !result) return InitError(util::ErrorString(result));
result = init::SetLoggingLevel(args);
if (!result) return InitError(util::ErrorString(result));
nConnectTimeout = args.GetIntArg("-timeout", DEFAULT_CONNECT_TIMEOUT); nConnectTimeout = args.GetIntArg("-timeout", DEFAULT_CONNECT_TIMEOUT);
if (nConnectTimeout <= 0) { if (nConnectTimeout <= 0) {

View file

@ -369,21 +369,22 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con
else if(type == Receive) else if(type == Receive)
{ {
// Generate a new address to associate with given label // Generate a new address to associate with given label
auto op_dest = walletModel->wallet().getNewDestination(address_type, strLabel); if (auto dest{walletModel->wallet().getNewDestination(address_type, strLabel)}) {
if (!op_dest) { strAddress = EncodeDestination(*dest);
} else {
WalletModel::UnlockContext ctx(walletModel->requestUnlock()); WalletModel::UnlockContext ctx(walletModel->requestUnlock());
if (!ctx.isValid()) { if (!ctx.isValid()) {
// Unlock wallet failed or was cancelled // Unlock wallet failed or was cancelled
editStatus = WALLET_UNLOCK_FAILURE; editStatus = WALLET_UNLOCK_FAILURE;
return QString(); return QString();
} }
op_dest = walletModel->wallet().getNewDestination(address_type, strLabel); if (auto dest_retry{walletModel->wallet().getNewDestination(address_type, strLabel)}) {
if (!op_dest) { strAddress = EncodeDestination(*dest_retry);
} else {
editStatus = KEY_GENERATION_FAILURE; editStatus = KEY_GENERATION_FAILURE;
return QString(); return QString();
} }
} }
strAddress = EncodeDestination(*op_dest);
} }
else else
{ {

View file

@ -202,9 +202,11 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
tx7.vout[1].scriptPubKey = CScript() << OP_11 << OP_EQUAL; tx7.vout[1].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
tx7.vout[1].nValue = 1 * COIN; 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()); auto ancestors_calculated{pool.CalculateMemPoolAncestors(entry.Fee(2000000LL).FromTx(tx7), CTxMemPool::Limits::NoLimits())};
BOOST_CHECK(*ancestors_calculated == setAncestors); BOOST_REQUIRE(ancestors_calculated.has_value());
BOOST_CHECK(*ancestors_calculated == setAncestors);
}
pool.addUnchecked(entry.FromTx(tx7), setAncestors); pool.addUnchecked(entry.FromTx(tx7), setAncestors);
BOOST_CHECK_EQUAL(pool.size(), 7U); BOOST_CHECK_EQUAL(pool.size(), 7U);
@ -260,9 +262,11 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
tx10.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL; tx10.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
tx10.vout[0].nValue = 10 * COIN; 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); auto ancestors_calculated{pool.CalculateMemPoolAncestors(entry.Fee(200000LL).Time(NodeSeconds{4s}).FromTx(tx10), CTxMemPool::Limits::NoLimits())};
BOOST_CHECK(*ancestors_calculated == setAncestors); BOOST_REQUIRE(ancestors_calculated);
BOOST_CHECK(*ancestors_calculated == setAncestors);
}
pool.addUnchecked(entry.FromTx(tx10), setAncestors); pool.addUnchecked(entry.FromTx(tx10), setAncestors);

View file

@ -39,6 +39,13 @@ private:
std::variant<bilingual_str, T> m_variant; std::variant<bilingual_str, T> m_variant;
//! Disallow operator= to avoid confusion in the future when the Result
//! class gains support for richer error reporting, and callers should have
//! ability to set a new result value without clearing existing error
//! messages.
Result& operator=(const Result&) = delete;
Result& operator=(Result&&) = delete;
template <typename FT> template <typename FT>
friend bilingual_str ErrorString(const Result<FT>& result); friend bilingual_str ErrorString(const Result<FT>& result);
@ -46,6 +53,9 @@ public:
Result() : m_variant{std::in_place_index_t<1>{}, std::monostate{}} {} // constructor for void 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(T obj) : m_variant{std::in_place_index_t<1>{}, std::move(obj)} {}
Result(Error error) : m_variant{std::in_place_index_t<0>{}, std::move(error.message)} {} Result(Error error) : m_variant{std::in_place_index_t<0>{}, std::move(error.message)} {}
Result(const Result&) = default;
Result(Result&&) = default;
~Result() = default;
//! std::optional methods, so functions returning optional<T> can change to //! std::optional methods, so functions returning optional<T> can change to
//! return Result<T> with minimal changes to existing code, and vice versa. //! return Result<T> with minimal changes to existing code, and vice versa.

View file

@ -946,8 +946,9 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
maybe_rbf_limits.descendant_size_vbytes += conflict->GetSizeWithDescendants(); maybe_rbf_limits.descendant_size_vbytes += conflict->GetSizeWithDescendants();
} }
auto ancestors{m_pool.CalculateMemPoolAncestors(*entry, maybe_rbf_limits)}; if (auto ancestors{m_pool.CalculateMemPoolAncestors(*entry, maybe_rbf_limits)}) {
if (!ancestors) { ws.m_ancestors = std::move(*ancestors);
} else {
// If CalculateMemPoolAncestors fails second time, we want the original error string. // If CalculateMemPoolAncestors fails second time, we want the original error string.
// Contracting/payment channels CPFP carve-out: // Contracting/payment channels CPFP carve-out:
// If the new transaction is relatively small (up to 40k weight) // 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) { if (ws.m_vsize > EXTRA_DESCENDANT_TX_SIZE_LIMIT) {
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "too-long-mempool-chain", error_message); return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "too-long-mempool-chain", error_message);
} }
ancestors = m_pool.CalculateMemPoolAncestors(*entry, cpfp_carve_out_limits); if (auto ancestors_retry{m_pool.CalculateMemPoolAncestors(*entry, cpfp_carve_out_limits)}) {
if (!ancestors) return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "too-long-mempool-chain", error_message); ws.m_ancestors = std::move(*ancestors_retry);
} 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 // 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 // check using the full ancestor set here because it's more convenient to use what we have
// already calculated. // already calculated.

View file

@ -106,13 +106,11 @@ struct FuzzedWallet {
CTxDestination GetDestination(FuzzedDataProvider& fuzzed_data_provider) CTxDestination GetDestination(FuzzedDataProvider& fuzzed_data_provider)
{ {
auto type{fuzzed_data_provider.PickValueInArray(OUTPUT_TYPES)}; auto type{fuzzed_data_provider.PickValueInArray(OUTPUT_TYPES)};
util::Result<CTxDestination> op_dest{util::Error{}};
if (fuzzed_data_provider.ConsumeBool()) { if (fuzzed_data_provider.ConsumeBool()) {
op_dest = wallet->GetNewDestination(type, ""); return *Assert(wallet->GetNewDestination(type, ""));
} else { } else {
op_dest = wallet->GetNewChangeDestination(type); return *Assert(wallet->GetNewChangeDestination(type));
} }
return *Assert(op_dest);
} }
CScript GetScriptPubKey(FuzzedDataProvider& fuzzed_data_provider) { return GetScriptForDestination(GetDestination(fuzzed_data_provider)); } CScript GetScriptPubKey(FuzzedDataProvider& fuzzed_data_provider) { return GetScriptForDestination(GetDestination(fuzzed_data_provider)); }
void FundTx(FuzzedDataProvider& fuzzed_data_provider, CMutableTransaction tx) void FundTx(FuzzedDataProvider& fuzzed_data_provider, CMutableTransaction tx)

View file

@ -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. // 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' // First case, use 'subtract_fee_from_outputs=true'
util::Result<CreatedTransactionResult> res_tx = CreateTransaction(*wallet, recipients, /*change_pos=*/std::nullopt, coin_control); BOOST_CHECK(!CreateTransaction(*wallet, recipients, /*change_pos=*/std::nullopt, coin_control));
BOOST_CHECK(!res_tx.has_value());
// Second case, don't use 'subtract_fee_from_outputs'. // Second case, don't use 'subtract_fee_from_outputs'.
recipients[0].fSubtractFeeFromAmount = false; recipients[0].fSubtractFeeFromAmount = false;
res_tx = CreateTransaction(*wallet, recipients, /*change_pos=*/std::nullopt, coin_control); BOOST_CHECK(!CreateTransaction(*wallet, recipients, /*change_pos=*/std::nullopt, coin_control));
BOOST_CHECK(!res_tx.has_value());
} }
BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END()