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 Aug 1, 2022
1 parent ce3b756 commit 3262acf
Show file tree
Hide file tree
Showing 24 changed files with 236 additions and 125 deletions.
1 change: 1 addition & 0 deletions src/Makefile.test.include
Expand Up @@ -119,6 +119,7 @@ BITCOIN_TESTS =\
test/random_tests.cpp \
test/rbf_tests.cpp \
test/rest_tests.cpp \
test/result_tests.cpp \
test/reverselock_tests.cpp \
test/rpc_tests.cpp \
test/sanity_tests.cpp \
Expand Down
5 changes: 2 additions & 3 deletions src/bench/wallet_loading.cpp
Expand Up @@ -45,11 +45,10 @@ static void BenchUnloadWallet(std::shared_ptr<CWallet>&& wallet)

static void AddTx(CWallet& wallet)
{
const auto& dest = wallet.GetNewDestination(OutputType::BECH32, "");
assert(dest.HasRes());
const auto dest = *Assert(wallet.GetNewDestination(OutputType::BECH32, ""));

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 = 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
96 changes: 96 additions & 0 deletions src/test/result_tests.cpp
@@ -0,0 +1,96 @@
// 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>

bool operator==(const bilingual_str& a, const bilingual_str& b)
{
return a.original == b.original && a.translated == b.translated;
}

std::ostream& operator<<(std::ostream& os, const bilingual_str& s)
{
return os << "bilingual_str('" << s.original << "' , '" << s.translated << "')";
}

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<bilingual_str> StrFn(bilingual_str s, bool success)
{
if (success) return s;
return {util::Error{strprintf(Untranslated("str %s error."), s.original)}};
}

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), str);
}

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(StrFn(Untranslated("S"), true), {}, Untranslated("S"));
ExpectFail(StrFn(Untranslated("S"), false), Untranslated("str S error."));
ExpectSuccess(NoCopyFn(5, true), {}, 5);
ExpectFail(NoCopyFn(5, false), Untranslated("nocopy 5 error."));
}

BOOST_AUTO_TEST_CASE(check_value_or)
{
BOOST_CHECK_EQUAL(IntFn(10, true).value_or(20), 10);
BOOST_CHECK_EQUAL(IntFn(10, false).value_or(20), 20);
BOOST_CHECK_EQUAL(NoCopyFn(10, true).value_or(20), 10);
BOOST_CHECK_EQUAL(NoCopyFn(10, false).value_or(20), 20);
}

BOOST_AUTO_TEST_SUITE_END()
6 changes: 2 additions & 4 deletions src/test/util/wallet.cpp
Expand Up @@ -8,6 +8,7 @@
#include <outputtype.h>
#include <script/standard.h>
#ifdef ENABLE_WALLET
#include <util/check.h>
#include <util/translation.h>
#include <wallet/wallet.h>
#endif
Expand All @@ -20,10 +21,7 @@ const std::string ADDRESS_BCRT1_UNSPENDABLE = "bcrt1qqqqqqqqqqqqqqqqqqqqqqqqqqqq
std::string getnewaddress(CWallet& w)
{
constexpr auto output_type = OutputType::BECH32;
auto op_dest = w.GetNewDestination(output_type, "");
assert(op_dest.HasRes());

return EncodeDestination(op_dest.GetObj());
return EncodeDestination(*Assert(w.GetNewDestination(output_type, "")));
}

#endif // ENABLE_WALLET
80 changes: 50 additions & 30 deletions src/util/result.h
Expand Up @@ -5,45 +5,65 @@
#ifndef BITCOIN_UTIL_RESULT_H
#define BITCOIN_UTIL_RESULT_H

#include <attributes.h>
#include <util/translation.h>

#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
//! 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 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::in_place_index_t<1>{}, std::move(obj)} {}
Result(Error error) : m_variant{std::in_place_index_t<0>{}, std::move(error.message)} {}

//! 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 LIFETIMEBOUND { assert(*this); return std::get<1>(m_variant); }
T& value() LIFETIMEBOUND { assert(*this); return std::get<1>(m_variant); }
explicit operator bool() const { return has_value(); }
const T* operator->() const { return &value(); }
const T& operator*() const LIFETIMEBOUND { return value(); }
T* operator->() { return &value(); }
T& operator*() LIFETIMEBOUND { return value(); }
template <class U>
constexpr T value_or(U&& default_value) const& { return has_value() ? value() : std::forward<U>(default_value); }
template <class U>
constexpr T value_or(U&& default_value) && { return has_value() ? std::move(**this) : std::forward<U>(default_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 @@ -148,7 +148,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 @@ -251,17 +251,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{util::ErrorString(res)}};
const auto& txr = *res;
fee = txr.fee;
change_pos = txr.change_pos;

Expand Down Expand Up @@ -571,12 +571,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

0 comments on commit 3262acf

Please sign in to comment.