Skip to content

Commit

Permalink
refactor: Replace BResult with util::Result
Browse files Browse the repository at this point in the history
Rename BResult class to util::Result and update the class interface to be more
compatible with std::optional and with a full-featured result class implemented
in #25665. Motivation for this change is
to update existing BResult usages now so they don't have to change later when
more features are added to the Result class.
  • Loading branch information
ryanofsky committed Jul 22, 2022
1 parent 6dc3084 commit e71b858
Show file tree
Hide file tree
Showing 23 changed files with 201 additions and 110 deletions.
1 change: 1 addition & 0 deletions src/Makefile.test.include
Expand Up @@ -118,6 +118,7 @@ BITCOIN_TESTS =\
test/raii_event_tests.cpp \
test/random_tests.cpp \
test/rest_tests.cpp \
test/result_tests.cpp \
test/reverselock_tests.cpp \
test/rpc_tests.cpp \
test/sanity_tests.cpp \
Expand Down
4 changes: 2 additions & 2 deletions src/bench/wallet_loading.cpp
Expand Up @@ -46,10 +46,10 @@ static void BenchUnloadWallet(std::shared_ptr<CWallet>&& wallet)
static void AddTx(CWallet& wallet)
{
const auto& dest = wallet.GetNewDestination(OutputType::BECH32, "");
assert(dest.HasRes());
assert(dest);

CMutableTransaction mtx;
mtx.vout.push_back({COIN, GetScriptForDestination(dest.GetObj())});
mtx.vout.push_back({COIN, GetScriptForDestination(*dest)});
mtx.vin.push_back(CTxIn());

wallet.AddToWallet(MakeTransactionRef(mtx), TxStateInactive{});
Expand Down
6 changes: 3 additions & 3 deletions src/interfaces/wallet.h
Expand Up @@ -88,7 +88,7 @@ class Wallet
virtual std::string getWalletName() = 0;

// Get a new address.
virtual BResult<CTxDestination> getNewDestination(const OutputType type, const std::string label) = 0;
virtual util::Result<CTxDestination> getNewDestination(const OutputType type, const std::string label) = 0;

//! Get public key.
virtual bool getPubKey(const CScript& script, const CKeyID& address, CPubKey& pub_key) = 0;
Expand Down Expand Up @@ -139,7 +139,7 @@ class Wallet
virtual void listLockedCoins(std::vector<COutPoint>& outputs) = 0;

//! Create transaction.
virtual BResult<CTransactionRef> createTransaction(const std::vector<wallet::CRecipient>& recipients,
virtual util::Result<CTransactionRef> createTransaction(const std::vector<wallet::CRecipient>& recipients,
const wallet::CCoinControl& coin_control,
bool sign,
int& change_pos,
Expand Down Expand Up @@ -329,7 +329,7 @@ class WalletLoader : public ChainClient
virtual std::string getWalletDir() = 0;

//! Restore backup wallet
virtual BResult<std::unique_ptr<Wallet>> restoreWallet(const fs::path& backup_file, const std::string& wallet_name, std::vector<bilingual_str>& warnings) = 0;
virtual util::Result<std::unique_ptr<Wallet>> restoreWallet(const fs::path& backup_file, const std::string& wallet_name, std::vector<bilingual_str>& warnings) = 0;

//! Return available wallets in wallet directory.
virtual std::vector<std::string> listWalletDir() = 0;
Expand Down
2 changes: 1 addition & 1 deletion src/qt/addresstablemodel.cpp
Expand Up @@ -384,7 +384,7 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con
return QString();
}
}
strAddress = EncodeDestination(op_dest.GetObj());
strAddress = EncodeDestination(*op_dest);
}
else
{
Expand Down
4 changes: 2 additions & 2 deletions src/qt/walletcontroller.cpp
Expand Up @@ -393,8 +393,8 @@ void RestoreWalletActivity::restore(const fs::path& backup_file, const std::stri
QTimer::singleShot(0, worker(), [this, backup_file, wallet_name] {
auto wallet{node().walletLoader().restoreWallet(backup_file, wallet_name, m_warning_message)};

m_error_message = wallet ? bilingual_str{} : wallet.GetError();
if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(wallet.ReleaseObj());
m_error_message = wallet ? bilingual_str{} : util::ErrorString(wallet);
if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(*wallet));

QTimer::singleShot(0, this, &RestoreWalletActivity::finish);
});
Expand Down
4 changes: 2 additions & 2 deletions src/qt/walletmodel.cpp
Expand Up @@ -207,7 +207,7 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact

auto& newTx = transaction.getWtx();
const auto& res = m_wallet->createTransaction(vecSend, coinControl, !wallet().privateKeysDisabled() /* sign */, nChangePosRet, nFeeRequired);
newTx = res ? res.GetObj() : nullptr;
newTx = res ? *res : nullptr;
transaction.setTransactionFee(nFeeRequired);
if (fSubtractFeeFromAmount && newTx)
transaction.reassignAmounts(nChangePosRet);
Expand All @@ -218,7 +218,7 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact
{
return SendCoinsReturn(AmountWithFeeExceedsBalance);
}
Q_EMIT message(tr("Send Coins"), QString::fromStdString(res.GetError().translated),
Q_EMIT message(tr("Send Coins"), QString::fromStdString(util::ErrorString(res).translated),
CClientUIInterface::MSG_ERROR);
return TransactionCreationFailed;
}
Expand Down
71 changes: 71 additions & 0 deletions src/test/result_tests.cpp
@@ -0,0 +1,71 @@
// Copyright (c) 2022 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <util/result.h>

#include <boost/test/unit_test.hpp>

BOOST_AUTO_TEST_SUITE(result_tests)

struct NoCopy
{
NoCopy(int n) : m_n{std::make_unique<int>(n)} {}
std::unique_ptr<int> m_n;
};

bool operator==(const NoCopy& a, const NoCopy& b)
{
return *a.m_n == *b.m_n;
}

std::ostream& operator<<(std::ostream& os, const NoCopy& o)
{
if (o.m_n) os << "NoCopy(" << *o.m_n << ")"; else os << "NoCopy(nullptr)";
return os;
}

util::Result<int> IntFn(int i, bool success)
{
if (success) return i;
return {util::Error{Untranslated(strprintf("int %i error.", i))}};
}

util::Result<NoCopy> NoCopyFn(int i, bool success)
{
if (success) return {i};
return {util::Error{Untranslated(strprintf("nocopy %i error.", i))}};
}

template<typename T>
void ExpectResult(const util::Result<T>& result, bool success, const bilingual_str& str)
{
BOOST_CHECK_EQUAL(bool(result), success);
BOOST_CHECK_EQUAL(ErrorString(result).original, str.original);
BOOST_CHECK_EQUAL(ErrorString(result).translated, str.translated);
}

template<typename T, typename... Args>
void ExpectSuccess(const util::Result<T>& result, const bilingual_str& str, Args&&... args)
{
ExpectResult(result, true, str);
BOOST_CHECK_EQUAL(result.has_value(), true);
BOOST_CHECK_EQUAL(result.value(), T{std::forward<Args>(args)...});
BOOST_CHECK_EQUAL(&result.value(), &*result);
}

template<typename T, typename... Args>
void ExpectFail(const util::Result<T>& result, const bilingual_str& str)
{
ExpectResult(result, false, str);
}

BOOST_AUTO_TEST_CASE(check_returned)
{
ExpectSuccess(IntFn(5, true), {}, 5);
ExpectFail(IntFn(5, false), Untranslated("int 5 error."));
ExpectSuccess(NoCopyFn(5, true), {}, 5);
ExpectFail(NoCopyFn(5, false), Untranslated("nocopy 5 error."));
}

BOOST_AUTO_TEST_SUITE_END()
4 changes: 2 additions & 2 deletions src/test/util/wallet.cpp
Expand Up @@ -21,9 +21,9 @@ std::string getnewaddress(CWallet& w)
{
constexpr auto output_type = OutputType::BECH32;
auto op_dest = w.GetNewDestination(output_type, "");
assert(op_dest.HasRes());
assert(op_dest);

return EncodeDestination(op_dest.GetObj());
return EncodeDestination(*op_dest);
}

#endif // ENABLE_WALLET
79 changes: 49 additions & 30 deletions src/util/result.h
Expand Up @@ -9,41 +9,60 @@

#include <variant>

/*
* 'BResult' is a generic class useful for wrapping a return object
* (in case of success) or propagating the error cause.
*/
namespace util {

struct Error { bilingual_str message; };

//! The util::Result class provides a standard way for functions to return error
//! and in addition to optional 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 T>
class BResult {
class Result {
private:
std::variant<bilingual_str, T> m_variant;

template <typename FT>
friend class Result;
template <typename FT>
friend bilingual_str ErrorString(const Result<FT>& result);

public:
BResult() : m_variant{Untranslated("")} {}
BResult(T obj) : m_variant{std::move(obj)} {}
BResult(bilingual_str error) : m_variant{std::move(error)} {}

/* Whether the function succeeded or not */
bool HasRes() const { return std::holds_alternative<T>(m_variant); }

/* In case of success, the result object */
const T& GetObj() const {
assert(HasRes());
return std::get<T>(m_variant);
}
T ReleaseObj()
{
assert(HasRes());
return std::move(std::get<T>(m_variant));
}

/* In case of failure, the error cause */
const bilingual_str& GetError() const {
assert(!HasRes());
return std::get<bilingual_str>(m_variant);
}

explicit operator bool() const { return HasRes(); }
Result(T obj) : m_variant{std::move(obj)} {}
Result(Error error) : m_variant{std::move(error.message)} {}
template <typename OT>
Result(Error error, Result<OT>&& other) : m_variant{std::move(std::get<0>(other.m_variant))} {}

//! 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 { return m_variant.index() == 1; }
const T& value() const { assert(*this); return std::get<1>(m_variant); }
T& value() { assert(*this); return std::get<1>(m_variant); }
template <typename U>
T value_or(const U& default_value) const { return has_value() ? value() : default_value; }
operator bool() const { return has_value(); }
const T* operator->() const { return &value(); }
const T& operator*() const { return value(); }
T* operator->() { return &value(); }
T& operator*() { return value(); }
};

template <typename T>
bilingual_str ErrorString(const Result<T>& result)
{
return result ? bilingual_str{} : std::get<0>(result.m_variant);
}
} // namespace util

#endif // BITCOIN_UTIL_RESULT_H
4 changes: 2 additions & 2 deletions src/wallet/feebumper.cpp
Expand Up @@ -221,11 +221,11 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo
constexpr int RANDOM_CHANGE_POSITION = -1;
auto res = CreateTransaction(wallet, recipients, RANDOM_CHANGE_POSITION, new_coin_control, false);
if (!res) {
errors.push_back(Untranslated("Unable to create transaction.") + Untranslated(" ") + res.GetError());
errors.push_back(Untranslated("Unable to create transaction.") + Untranslated(" ") + util::ErrorString(res));
return Result::WALLET_ERROR;
}

const auto& txr = res.GetObj();
const auto& txr = *res;
// Write back new fee if successful
new_fee = txr.fee;

Expand Down
16 changes: 8 additions & 8 deletions src/wallet/interfaces.cpp
Expand Up @@ -146,7 +146,7 @@ class WalletImpl : public Wallet
void abortRescan() override { m_wallet->AbortRescan(); }
bool backupWallet(const std::string& filename) override { return m_wallet->BackupWallet(filename); }
std::string getWalletName() override { return m_wallet->GetName(); }
BResult<CTxDestination> getNewDestination(const OutputType type, const std::string label) override
util::Result<CTxDestination> getNewDestination(const OutputType type, const std::string label) override
{
LOCK(m_wallet->cs_wallet);
return m_wallet->GetNewDestination(type, label);
Expand Down Expand Up @@ -249,17 +249,17 @@ class WalletImpl : public Wallet
LOCK(m_wallet->cs_wallet);
return m_wallet->ListLockedCoins(outputs);
}
BResult<CTransactionRef> createTransaction(const std::vector<CRecipient>& recipients,
util::Result<CTransactionRef> createTransaction(const std::vector<CRecipient>& recipients,
const CCoinControl& coin_control,
bool sign,
int& change_pos,
CAmount& fee) override
{
LOCK(m_wallet->cs_wallet);
const auto& res = CreateTransaction(*m_wallet, recipients, change_pos,
auto res = CreateTransaction(*m_wallet, recipients, change_pos,
coin_control, sign);
if (!res) return res.GetError();
const auto& txr = res.GetObj();
if (!res) return {util::Error{}, std::move(res)};
const auto& txr = *res;
fee = txr.fee;
change_pos = txr.change_pos;

Expand Down Expand Up @@ -569,12 +569,12 @@ class WalletLoaderImpl : public WalletLoader
options.require_existing = true;
return MakeWallet(m_context, LoadWallet(m_context, name, true /* load_on_start */, options, status, error, warnings));
}
BResult<std::unique_ptr<Wallet>> restoreWallet(const fs::path& backup_file, const std::string& wallet_name, std::vector<bilingual_str>& warnings) override
util::Result<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;
BResult<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 error;
util::Result<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 {util::Error{error}};
return wallet;
}
std::string getWalletDir() override
Expand Down
8 changes: 4 additions & 4 deletions src/wallet/rpc/addresses.cpp
Expand Up @@ -60,10 +60,10 @@ RPCHelpMan getnewaddress()

auto op_dest = pwallet->GetNewDestination(output_type, label);
if (!op_dest) {
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, op_dest.GetError().original);
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, util::ErrorString(op_dest).original);
}

return EncodeDestination(op_dest.GetObj());
return EncodeDestination(*op_dest);
},
};
}
Expand Down Expand Up @@ -107,9 +107,9 @@ RPCHelpMan getrawchangeaddress()

auto op_dest = pwallet->GetNewChangeDestination(output_type);
if (!op_dest) {
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, op_dest.GetError().original);
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, util::ErrorString(op_dest).original);
}
return EncodeDestination(op_dest.GetObj());
return EncodeDestination(*op_dest);
},
};
}
Expand Down
6 changes: 3 additions & 3 deletions src/wallet/rpc/spend.cpp
Expand Up @@ -158,14 +158,14 @@ UniValue SendMoney(CWallet& wallet, const CCoinControl &coin_control, std::vecto
constexpr int RANDOM_CHANGE_POSITION = -1;
auto res = CreateTransaction(wallet, recipients, RANDOM_CHANGE_POSITION, coin_control, true);
if (!res) {
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, res.GetError().original);
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, util::ErrorString(res).original);
}
const CTransactionRef& tx = res.GetObj().tx;
const CTransactionRef& tx = res->tx;
wallet.CommitTransaction(tx, std::move(map_value), {} /* orderForm */);
if (verbose) {
UniValue entry(UniValue::VOBJ);
entry.pushKV("txid", tx->GetHash().GetHex());
entry.pushKV("fee_reason", StringForFeeReason(res.GetObj().fee_calc.reason));
entry.pushKV("fee_reason", StringForFeeReason(res->fee_calc.reason));
return entry;
}
return tx->GetHash().GetHex();
Expand Down

0 comments on commit e71b858

Please sign in to comment.