Skip to content

Commit a23cca5

Browse files
committed
refactor: Replace BResult with util::Result
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 bitcoin#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 in bitcoin#25665. This change makes the following improvements originally implemented in bitcoin#25665: - More explicit API. Drops potentially misleading `BResult` constructor that treats any bilingual string argument as an error. Adds `util::Error` constructor so it is never ambiguous when a result is being assigned an error or non-error value. - Better type compatibility. Supports `util::Result<bilingual_str>` return values to hold translated messages which are not errors. - More standard and consistent API. `util::Result` supports most of the same operators and methods as `std::optional`. `BResult` had a less familiar interface with `HasRes`/`GetObj`/`ReleaseObj` methods. The Result/Res/Obj naming was also not internally consistent. - Better code organization. Puts `src/util/` code in the `util::` namespace so naming reflects code organization and it is obvious where the class is coming from. Drops "B" from name because it is undocumented what it stands for (bilingual?) - Has unit tests.
1 parent 4a4289e commit a23cca5

24 files changed

+247
-125
lines changed

src/Makefile.test.include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ BITCOIN_TESTS =\
119119
test/random_tests.cpp \
120120
test/rbf_tests.cpp \
121121
test/rest_tests.cpp \
122+
test/result_tests.cpp \
122123
test/reverselock_tests.cpp \
123124
test/rpc_tests.cpp \
124125
test/sanity_tests.cpp \

src/bench/wallet_loading.cpp

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,8 @@ static void BenchUnloadWallet(std::shared_ptr<CWallet>&& wallet)
4545

4646
static void AddTx(CWallet& wallet)
4747
{
48-
const auto& dest = wallet.GetNewDestination(OutputType::BECH32, "");
49-
assert(dest.HasRes());
50-
5148
CMutableTransaction mtx;
52-
mtx.vout.push_back({COIN, GetScriptForDestination(dest.GetObj())});
49+
mtx.vout.push_back({COIN, GetScriptForDestination(*Assert(wallet.GetNewDestination(OutputType::BECH32, "")))});
5350
mtx.vin.push_back(CTxIn());
5451

5552
wallet.AddToWallet(MakeTransactionRef(mtx), TxStateInactive{});

src/interfaces/wallet.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ class Wallet
8888
virtual std::string getWalletName() = 0;
8989

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

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

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

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

334334
//! Return available wallets in wallet directory.
335335
virtual std::vector<std::string> listWalletDir() = 0;

src/qt/addresstablemodel.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con
384384
return QString();
385385
}
386386
}
387-
strAddress = EncodeDestination(op_dest.GetObj());
387+
strAddress = EncodeDestination(*op_dest);
388388
}
389389
else
390390
{

src/qt/walletcontroller.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -393,8 +393,8 @@ void RestoreWalletActivity::restore(const fs::path& backup_file, const std::stri
393393
QTimer::singleShot(0, worker(), [this, backup_file, wallet_name] {
394394
auto wallet{node().walletLoader().restoreWallet(backup_file, wallet_name, m_warning_message)};
395395

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

399399
QTimer::singleShot(0, this, &RestoreWalletActivity::finish);
400400
});

src/qt/walletmodel.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact
207207

208208
auto& newTx = transaction.getWtx();
209209
const auto& res = m_wallet->createTransaction(vecSend, coinControl, !wallet().privateKeysDisabled() /* sign */, nChangePosRet, nFeeRequired);
210-
newTx = res ? res.GetObj() : nullptr;
210+
newTx = res ? *res : nullptr;
211211
transaction.setTransactionFee(nFeeRequired);
212212
if (fSubtractFeeFromAmount && newTx)
213213
transaction.reassignAmounts(nChangePosRet);
@@ -218,7 +218,7 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact
218218
{
219219
return SendCoinsReturn(AmountWithFeeExceedsBalance);
220220
}
221-
Q_EMIT message(tr("Send Coins"), QString::fromStdString(res.GetError().translated),
221+
Q_EMIT message(tr("Send Coins"), QString::fromStdString(util::ErrorString(res).translated),
222222
CClientUIInterface::MSG_ERROR);
223223
return TransactionCreationFailed;
224224
}

src/test/result_tests.cpp

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// Copyright (c) 2022 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <util/result.h>
6+
7+
#include <boost/test/unit_test.hpp>
8+
9+
inline bool operator==(const bilingual_str& a, const bilingual_str& b)
10+
{
11+
return a.original == b.original && a.translated == b.translated;
12+
}
13+
14+
inline std::ostream& operator<<(std::ostream& os, const bilingual_str& s)
15+
{
16+
return os << "bilingual_str('" << s.original << "' , '" << s.translated << "')";
17+
}
18+
19+
BOOST_AUTO_TEST_SUITE(result_tests)
20+
21+
struct NoCopy {
22+
NoCopy(int n) : m_n{std::make_unique<int>(n)} {}
23+
std::unique_ptr<int> m_n;
24+
};
25+
26+
bool operator==(const NoCopy& a, const NoCopy& b)
27+
{
28+
return *a.m_n == *b.m_n;
29+
}
30+
31+
std::ostream& operator<<(std::ostream& os, const NoCopy& o)
32+
{
33+
return os << "NoCopy(" << *o.m_n << ")";
34+
}
35+
36+
util::Result<int> IntFn(int i, bool success)
37+
{
38+
if (success) return i;
39+
return util::Error{Untranslated(strprintf("int %i error.", i))};
40+
}
41+
42+
util::Result<bilingual_str> StrFn(bilingual_str s, bool success)
43+
{
44+
if (success) return s;
45+
return util::Error{strprintf(Untranslated("str %s error."), s.original)};
46+
}
47+
48+
util::Result<NoCopy> NoCopyFn(int i, bool success)
49+
{
50+
if (success) return {i};
51+
return util::Error{Untranslated(strprintf("nocopy %i error.", i))};
52+
}
53+
54+
template <typename T>
55+
void ExpectResult(const util::Result<T>& result, bool success, const bilingual_str& str)
56+
{
57+
BOOST_CHECK_EQUAL(bool(result), success);
58+
BOOST_CHECK_EQUAL(util::ErrorString(result), str);
59+
}
60+
61+
template <typename T, typename... Args>
62+
void ExpectSuccess(const util::Result<T>& result, const bilingual_str& str, Args&&... args)
63+
{
64+
ExpectResult(result, true, str);
65+
BOOST_CHECK_EQUAL(result.has_value(), true);
66+
BOOST_CHECK_EQUAL(result.value(), T{std::forward<Args>(args)...});
67+
BOOST_CHECK_EQUAL(&result.value(), &*result);
68+
}
69+
70+
template <typename T, typename... Args>
71+
void ExpectFail(const util::Result<T>& result, const bilingual_str& str)
72+
{
73+
ExpectResult(result, false, str);
74+
}
75+
76+
BOOST_AUTO_TEST_CASE(check_returned)
77+
{
78+
ExpectSuccess(IntFn(5, true), {}, 5);
79+
ExpectFail(IntFn(5, false), Untranslated("int 5 error."));
80+
ExpectSuccess(NoCopyFn(5, true), {}, 5);
81+
ExpectFail(NoCopyFn(5, false), Untranslated("nocopy 5 error."));
82+
ExpectSuccess(StrFn(Untranslated("S"), true), {}, Untranslated("S"));
83+
ExpectFail(StrFn(Untranslated("S"), false), Untranslated("str S error."));
84+
}
85+
86+
BOOST_AUTO_TEST_CASE(check_value_or)
87+
{
88+
BOOST_CHECK_EQUAL(IntFn(10, true).value_or(20), 10);
89+
BOOST_CHECK_EQUAL(IntFn(10, false).value_or(20), 20);
90+
BOOST_CHECK_EQUAL(NoCopyFn(10, true).value_or(20), 10);
91+
BOOST_CHECK_EQUAL(NoCopyFn(10, false).value_or(20), 20);
92+
BOOST_CHECK_EQUAL(StrFn(Untranslated("A"), true).value_or(Untranslated("B")), Untranslated("A"));
93+
BOOST_CHECK_EQUAL(StrFn(Untranslated("A"), false).value_or(Untranslated("B")), Untranslated("B"));
94+
}
95+
96+
BOOST_AUTO_TEST_SUITE_END()

src/test/util/wallet.cpp

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <outputtype.h>
99
#include <script/standard.h>
1010
#ifdef ENABLE_WALLET
11+
#include <util/check.h>
1112
#include <util/translation.h>
1213
#include <wallet/wallet.h>
1314
#endif
@@ -20,10 +21,7 @@ const std::string ADDRESS_BCRT1_UNSPENDABLE = "bcrt1qqqqqqqqqqqqqqqqqqqqqqqqqqqq
2021
std::string getnewaddress(CWallet& w)
2122
{
2223
constexpr auto output_type = OutputType::BECH32;
23-
auto op_dest = w.GetNewDestination(output_type, "");
24-
assert(op_dest.HasRes());
25-
26-
return EncodeDestination(op_dest.GetObj());
24+
return EncodeDestination(*Assert(w.GetNewDestination(output_type, "")));
2725
}
2826

2927
#endif // ENABLE_WALLET

src/util/result.h

Lines changed: 61 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,45 +5,80 @@
55
#ifndef BITCOIN_UTIL_RESULT_H
66
#define BITCOIN_UTIL_RESULT_H
77

8+
#include <attributes.h>
89
#include <util/translation.h>
910

1011
#include <variant>
1112

12-
/*
13-
* 'BResult' is a generic class useful for wrapping a return object
14-
* (in case of success) or propagating the error cause.
15-
*/
16-
template<class T>
17-
class BResult {
13+
namespace util {
14+
15+
struct Error {
16+
bilingual_str message;
17+
};
18+
19+
//! The util::Result class provides a standard way for functions to return
20+
//! either error messages or result values.
21+
//!
22+
//! It is intended for high-level functions that need to report error strings to
23+
//! end users. Lower-level functions that don't need this error-reporting and
24+
//! only need error-handling should avoid util::Result and instead use standard
25+
//! classes like std::optional, std::variant, and std::tuple, or custom structs
26+
//! and enum types to return function results.
27+
//!
28+
//! Usage examples can be found in \example ../test/result_tests.cpp, but in
29+
//! general code returning `util::Result<T>` values is very similar to code
30+
//! returning `std::optional<T>` values. Existing functions returning
31+
//! `std::optional<T>` can be updated to return `util::Result<T>` and return
32+
//! error strings usually just replacing `return std::nullopt;` with `return
33+
//! util::Error{error_string};`.
34+
template <class T>
35+
class Result
36+
{
1837
private:
1938
std::variant<bilingual_str, T> m_variant;
2039

21-
public:
22-
BResult() : m_variant{Untranslated("")} {}
23-
BResult(T obj) : m_variant{std::move(obj)} {}
24-
BResult(bilingual_str error) : m_variant{std::move(error)} {}
40+
template <typename FT>
41+
friend bilingual_str ErrorString(const Result<FT>& result);
2542

26-
/* Whether the function succeeded or not */
27-
bool HasRes() const { return std::holds_alternative<T>(m_variant); }
43+
public:
44+
Result(T obj) : m_variant{std::in_place_index_t<1>{}, std::move(obj)} {}
45+
Result(Error error) : m_variant{std::in_place_index_t<0>{}, std::move(error.message)} {}
2846

29-
/* In case of success, the result object */
30-
const T& GetObj() const {
31-
assert(HasRes());
32-
return std::get<T>(m_variant);
47+
//! std::optional methods, so functions returning optional<T> can change to
48+
//! return Result<T> with minimal changes to existing code, and vice versa.
49+
bool has_value() const noexcept { return m_variant.index() == 1; }
50+
const T& value() const LIFETIMEBOUND
51+
{
52+
assert(has_value());
53+
return std::get<1>(m_variant);
3354
}
34-
T ReleaseObj()
55+
T& value() LIFETIMEBOUND
3556
{
36-
assert(HasRes());
37-
return std::move(std::get<T>(m_variant));
57+
assert(has_value());
58+
return std::get<1>(m_variant);
3859
}
39-
40-
/* In case of failure, the error cause */
41-
const bilingual_str& GetError() const {
42-
assert(!HasRes());
43-
return std::get<bilingual_str>(m_variant);
60+
template <class U>
61+
T value_or(U&& default_value) const&
62+
{
63+
return has_value() ? value() : std::forward<U>(default_value);
4464
}
45-
46-
explicit operator bool() const { return HasRes(); }
65+
template <class U>
66+
T value_or(U&& default_value) &&
67+
{
68+
return has_value() ? std::move(value()) : std::forward<U>(default_value);
69+
}
70+
explicit operator bool() const noexcept { return has_value(); }
71+
const T* operator->() const LIFETIMEBOUND { return &value(); }
72+
const T& operator*() const LIFETIMEBOUND { return value(); }
73+
T* operator->() LIFETIMEBOUND { return &value(); }
74+
T& operator*() LIFETIMEBOUND { return value(); }
4775
};
4876

77+
template <typename T>
78+
bilingual_str ErrorString(const Result<T>& result)
79+
{
80+
return result ? bilingual_str{} : std::get<0>(result.m_variant);
81+
}
82+
} // namespace util
83+
4984
#endif // BITCOIN_UTIL_RESULT_H

src/wallet/feebumper.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,11 +221,11 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo
221221
constexpr int RANDOM_CHANGE_POSITION = -1;
222222
auto res = CreateTransaction(wallet, recipients, RANDOM_CHANGE_POSITION, new_coin_control, false);
223223
if (!res) {
224-
errors.push_back(Untranslated("Unable to create transaction.") + Untranslated(" ") + res.GetError());
224+
errors.push_back(Untranslated("Unable to create transaction.") + Untranslated(" ") + util::ErrorString(res));
225225
return Result::WALLET_ERROR;
226226
}
227227

228-
const auto& txr = res.GetObj();
228+
const auto& txr = *res;
229229
// Write back new fee if successful
230230
new_fee = txr.fee;
231231

src/wallet/interfaces.cpp

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ class WalletImpl : public Wallet
148148
void abortRescan() override { m_wallet->AbortRescan(); }
149149
bool backupWallet(const std::string& filename) override { return m_wallet->BackupWallet(filename); }
150150
std::string getWalletName() override { return m_wallet->GetName(); }
151-
BResult<CTxDestination> getNewDestination(const OutputType type, const std::string label) override
151+
util::Result<CTxDestination> getNewDestination(const OutputType type, const std::string label) override
152152
{
153153
LOCK(m_wallet->cs_wallet);
154154
return m_wallet->GetNewDestination(type, label);
@@ -251,17 +251,17 @@ class WalletImpl : public Wallet
251251
LOCK(m_wallet->cs_wallet);
252252
return m_wallet->ListLockedCoins(outputs);
253253
}
254-
BResult<CTransactionRef> createTransaction(const std::vector<CRecipient>& recipients,
254+
util::Result<CTransactionRef> createTransaction(const std::vector<CRecipient>& recipients,
255255
const CCoinControl& coin_control,
256256
bool sign,
257257
int& change_pos,
258258
CAmount& fee) override
259259
{
260260
LOCK(m_wallet->cs_wallet);
261-
const auto& res = CreateTransaction(*m_wallet, recipients, change_pos,
261+
auto res = CreateTransaction(*m_wallet, recipients, change_pos,
262262
coin_control, sign);
263-
if (!res) return res.GetError();
264-
const auto& txr = res.GetObj();
263+
if (!res) return util::Error{util::ErrorString(res)};
264+
const auto& txr = *res;
265265
fee = txr.fee;
266266
change_pos = txr.change_pos;
267267

@@ -571,12 +571,12 @@ class WalletLoaderImpl : public WalletLoader
571571
options.require_existing = true;
572572
return MakeWallet(m_context, LoadWallet(m_context, name, true /* load_on_start */, options, status, error, warnings));
573573
}
574-
BResult<std::unique_ptr<Wallet>> restoreWallet(const fs::path& backup_file, const std::string& wallet_name, std::vector<bilingual_str>& warnings) override
574+
util::Result<std::unique_ptr<Wallet>> restoreWallet(const fs::path& backup_file, const std::string& wallet_name, std::vector<bilingual_str>& warnings) override
575575
{
576576
DatabaseStatus status;
577577
bilingual_str error;
578-
BResult<std::unique_ptr<Wallet>> wallet{MakeWallet(m_context, RestoreWallet(m_context, backup_file, wallet_name, /*load_on_start=*/true, status, error, warnings))};
579-
if (!wallet) return error;
578+
util::Result<std::unique_ptr<Wallet>> wallet{MakeWallet(m_context, RestoreWallet(m_context, backup_file, wallet_name, /*load_on_start=*/true, status, error, warnings))};
579+
if (!wallet) return util::Error{error};
580580
return wallet;
581581
}
582582
std::string getWalletDir() override

src/wallet/rpc/addresses.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,10 @@ RPCHelpMan getnewaddress()
6060

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

66-
return EncodeDestination(op_dest.GetObj());
66+
return EncodeDestination(*op_dest);
6767
},
6868
};
6969
}
@@ -107,9 +107,9 @@ RPCHelpMan getrawchangeaddress()
107107

108108
auto op_dest = pwallet->GetNewChangeDestination(output_type);
109109
if (!op_dest) {
110-
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, op_dest.GetError().original);
110+
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, util::ErrorString(op_dest).original);
111111
}
112-
return EncodeDestination(op_dest.GetObj());
112+
return EncodeDestination(*op_dest);
113113
},
114114
};
115115
}

0 commit comments

Comments
 (0)