Skip to content

Commit 2235172

Browse files
committed
send: refactor CreateTransaction flow to return a BResult<CTransactionRef>
1 parent 198fcca commit 2235172

File tree

9 files changed

+81
-93
lines changed

9 files changed

+81
-93
lines changed

src/interfaces/wallet.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <script/standard.h> // For CTxDestination
1313
#include <support/allocators/secure.h> // For SecureString
1414
#include <util/message.h>
15+
#include <util/result.h>
1516
#include <util/ui_change_type.h>
1617

1718
#include <cstdint>
@@ -138,12 +139,11 @@ class Wallet
138139
virtual void listLockedCoins(std::vector<COutPoint>& outputs) = 0;
139140

140141
//! Create transaction.
141-
virtual CTransactionRef createTransaction(const std::vector<wallet::CRecipient>& recipients,
142+
virtual BResult<CTransactionRef> createTransaction(const std::vector<wallet::CRecipient>& recipients,
142143
const wallet::CCoinControl& coin_control,
143144
bool sign,
144145
int& change_pos,
145-
CAmount& fee,
146-
bilingual_str& fail_reason) = 0;
146+
CAmount& fee) = 0;
147147

148148
//! Commit transaction.
149149
virtual void commitTransaction(CTransactionRef tx,

src/qt/walletmodel.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -204,10 +204,10 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact
204204
{
205205
CAmount nFeeRequired = 0;
206206
int nChangePosRet = -1;
207-
bilingual_str error;
208207

209208
auto& newTx = transaction.getWtx();
210-
newTx = m_wallet->createTransaction(vecSend, coinControl, !wallet().privateKeysDisabled() /* sign */, nChangePosRet, nFeeRequired, error);
209+
const auto& res = m_wallet->createTransaction(vecSend, coinControl, !wallet().privateKeysDisabled() /* sign */, nChangePosRet, nFeeRequired);
210+
newTx = res ? res.GetObj() : 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(error.translated),
221+
Q_EMIT message(tr("Send Coins"), QString::fromStdString(res.GetError().translated),
222222
CClientUIInterface::MSG_ERROR);
223223
return TransactionCreationFailed;
224224
}

src/wallet/feebumper.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -219,18 +219,18 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo
219219
new_coin_control.m_min_depth = 1;
220220

221221
constexpr int RANDOM_CHANGE_POSITION = -1;
222-
bilingual_str fail_reason;
223-
std::optional<CreatedTransactionResult> txr = CreateTransaction(wallet, recipients, RANDOM_CHANGE_POSITION, fail_reason, new_coin_control, false);
224-
if (!txr) {
225-
errors.push_back(Untranslated("Unable to create transaction.") + Untranslated(" ") + fail_reason);
222+
auto res = CreateTransaction(wallet, recipients, RANDOM_CHANGE_POSITION, new_coin_control, false);
223+
if (!res) {
224+
errors.push_back(Untranslated("Unable to create transaction.") + Untranslated(" ") + res.GetError());
226225
return Result::WALLET_ERROR;
227226
}
228227

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

232232
// Write back transaction
233-
mtx = CMutableTransaction(*txr->tx);
233+
mtx = CMutableTransaction(*txr.tx);
234234

235235
return Result::OK;
236236
}

src/wallet/interfaces.cpp

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -250,21 +250,21 @@ class WalletImpl : public Wallet
250250
LOCK(m_wallet->cs_wallet);
251251
return m_wallet->ListLockedCoins(outputs);
252252
}
253-
CTransactionRef createTransaction(const std::vector<CRecipient>& recipients,
253+
BResult<CTransactionRef> createTransaction(const std::vector<CRecipient>& recipients,
254254
const CCoinControl& coin_control,
255255
bool sign,
256256
int& change_pos,
257-
CAmount& fee,
258-
bilingual_str& fail_reason) override
257+
CAmount& fee) override
259258
{
260259
LOCK(m_wallet->cs_wallet);
261-
std::optional<CreatedTransactionResult> txr = CreateTransaction(*m_wallet, recipients, change_pos,
262-
fail_reason, coin_control, sign);
263-
if (!txr) return {};
264-
fee = txr->fee;
265-
change_pos = txr->change_pos;
260+
const auto& res = CreateTransaction(*m_wallet, recipients, change_pos,
261+
coin_control, sign);
262+
if (!res) return res.GetError();
263+
const auto& txr = res.GetObj();
264+
fee = txr.fee;
265+
change_pos = txr.change_pos;
266266

267-
return txr->tx;
267+
return txr.tx;
268268
}
269269
void commitTransaction(CTransactionRef tx,
270270
WalletValueMap value_map,

src/wallet/rpc/spend.cpp

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -156,17 +156,16 @@ UniValue SendMoney(CWallet& wallet, const CCoinControl &coin_control, std::vecto
156156

157157
// Send
158158
constexpr int RANDOM_CHANGE_POSITION = -1;
159-
bilingual_str error;
160-
std::optional<CreatedTransactionResult> txr = CreateTransaction(wallet, recipients, RANDOM_CHANGE_POSITION, error, coin_control, true);
161-
if (!txr) {
162-
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, error.original);
159+
auto res = CreateTransaction(wallet, recipients, RANDOM_CHANGE_POSITION, coin_control, true);
160+
if (!res) {
161+
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, res.GetError().original);
163162
}
164-
CTransactionRef tx = txr->tx;
163+
const CTransactionRef& tx = res.GetObj().tx;
165164
wallet.CommitTransaction(tx, std::move(map_value), {} /* orderForm */);
166165
if (verbose) {
167166
UniValue entry(UniValue::VOBJ);
168167
entry.pushKV("txid", tx->GetHash().GetHex());
169-
entry.pushKV("fee_reason", StringForFeeReason(txr->fee_calc.reason));
168+
entry.pushKV("fee_reason", StringForFeeReason(res.GetObj().fee_calc.reason));
170169
return entry;
171170
}
172171
return tx->GetHash().GetHex();

src/wallet/spend.cpp

Lines changed: 43 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -656,18 +656,16 @@ static void DiscourageFeeSniping(CMutableTransaction& tx, FastRandomContext& rng
656656
}
657657
}
658658

659-
static std::optional<CreatedTransactionResult> CreateTransactionInternal(
659+
static BResult<CreatedTransactionResult> CreateTransactionInternal(
660660
CWallet& wallet,
661661
const std::vector<CRecipient>& vecSend,
662662
int change_pos,
663-
bilingual_str& error,
664663
const CCoinControl& coin_control,
665664
bool sign) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
666665
{
667666
AssertLockHeld(wallet.cs_wallet);
668667

669668
// out variables, to be packed into returned result structure
670-
CTransactionRef tx;
671669
CAmount nFeeRet;
672670
int nChangePosInOut = change_pos;
673671

@@ -696,6 +694,7 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal(
696694

697695
// Create change script that will be used if we need change
698696
CScript scriptChange;
697+
bilingual_str error; // possible error str
699698

700699
// coin control: send change to custom address
701700
if (!std::get_if<CNoDestination>(&coin_control.destChange)) {
@@ -743,13 +742,11 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal(
743742
// Do not, ever, assume that it's fine to change the fee rate if the user has explicitly
744743
// provided one
745744
if (coin_control.m_feerate && coin_selection_params.m_effective_feerate > *coin_control.m_feerate) {
746-
error = strprintf(_("Fee rate (%s) is lower than the minimum fee rate setting (%s)"), coin_control.m_feerate->ToString(FeeEstimateMode::SAT_VB), coin_selection_params.m_effective_feerate.ToString(FeeEstimateMode::SAT_VB));
747-
return std::nullopt;
745+
return strprintf(_("Fee rate (%s) is lower than the minimum fee rate setting (%s)"), coin_control.m_feerate->ToString(FeeEstimateMode::SAT_VB), coin_selection_params.m_effective_feerate.ToString(FeeEstimateMode::SAT_VB));
748746
}
749747
if (feeCalc.reason == FeeReason::FALLBACK && !wallet.m_allow_fallback_fee) {
750748
// eventually allow a fallback fee
751-
error = _("Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee.");
752-
return std::nullopt;
749+
return _("Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee.");
753750
}
754751

755752
// Calculate the cost of change
@@ -774,10 +771,8 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal(
774771
coin_selection_params.tx_noinputs_size += ::GetSerializeSize(txout, PROTOCOL_VERSION);
775772
}
776773

777-
if (IsDust(txout, wallet.chain().relayDustFee()))
778-
{
779-
error = _("Transaction amount too small");
780-
return std::nullopt;
774+
if (IsDust(txout, wallet.chain().relayDustFee())) {
775+
return _("Transaction amount too small");
781776
}
782777
txNew.vout.push_back(txout);
783778
}
@@ -798,8 +793,7 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal(
798793
// Choose coins to use
799794
std::optional<SelectionResult> result = SelectCoins(wallet, res_available_coins.coins, /*nTargetValue=*/selection_target, coin_control, coin_selection_params);
800795
if (!result) {
801-
error = _("Insufficient funds");
802-
return std::nullopt;
796+
return _("Insufficient funds");
803797
}
804798
TRACE5(coin_selection, selected_coins, wallet.GetName().c_str(), GetAlgorithmName(result->m_algo).c_str(), result->m_target, result->GetWaste(), result->GetSelectedValue());
805799

@@ -813,10 +807,8 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal(
813807
// Insert change txn at random position:
814808
nChangePosInOut = rng_fast.randrange(txNew.vout.size() + 1);
815809
}
816-
else if ((unsigned int)nChangePosInOut > txNew.vout.size())
817-
{
818-
error = _("Transaction change output index out of range");
819-
return std::nullopt;
810+
else if ((unsigned int)nChangePosInOut > txNew.vout.size()) {
811+
return _("Transaction change output index out of range");
820812
}
821813

822814
assert(nChangePosInOut != -1);
@@ -843,8 +835,7 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal(
843835
TxSize tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), &wallet, &coin_control);
844836
int nBytes = tx_sizes.vsize;
845837
if (nBytes == -1) {
846-
error = _("Missing solving data for estimating transaction size");
847-
return std::nullopt;
838+
return _("Missing solving data for estimating transaction size");
848839
}
849840
nFeeRet = coin_selection_params.m_effective_feerate.GetFee(nBytes);
850841

@@ -904,11 +895,10 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal(
904895
// Error if this output is reduced to be below dust
905896
if (IsDust(txout, wallet.chain().relayDustFee())) {
906897
if (txout.nValue < 0) {
907-
error = _("The transaction amount is too small to pay the fee");
898+
return _("The transaction amount is too small to pay the fee");
908899
} else {
909-
error = _("The transaction amount is too small to send after the fee has been deducted");
900+
return _("The transaction amount is too small to send after the fee has been deducted");
910901
}
911-
return std::nullopt;
912902
}
913903
}
914904
++i;
@@ -918,35 +908,31 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal(
918908

919909
// Give up if change keypool ran out and change is required
920910
if (scriptChange.empty() && nChangePosInOut != -1) {
921-
return std::nullopt;
911+
return error;
922912
}
923913

924914
if (sign && !wallet.SignTransaction(txNew)) {
925-
error = _("Signing transaction failed");
926-
return std::nullopt;
915+
return _("Signing transaction failed");
927916
}
928917

929918
// Return the constructed transaction data.
930-
tx = MakeTransactionRef(std::move(txNew));
919+
CTransactionRef tx = MakeTransactionRef(std::move(txNew));
931920

932921
// Limit size
933922
if ((sign && GetTransactionWeight(*tx) > MAX_STANDARD_TX_WEIGHT) ||
934923
(!sign && tx_sizes.weight > MAX_STANDARD_TX_WEIGHT))
935924
{
936-
error = _("Transaction too large");
937-
return std::nullopt;
925+
return _("Transaction too large");
938926
}
939927

940928
if (nFeeRet > wallet.m_default_max_tx_fee) {
941-
error = TransactionErrorString(TransactionError::MAX_FEE_EXCEEDED);
942-
return std::nullopt;
929+
return TransactionErrorString(TransactionError::MAX_FEE_EXCEEDED);
943930
}
944931

945932
if (gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS)) {
946933
// Lastly, ensure this tx will pass the mempool's chain limits
947934
if (!wallet.chain().checkChainLimits(tx)) {
948-
error = _("Transaction has too long of a mempool chain");
949-
return std::nullopt;
935+
return _("Transaction has too long of a mempool chain");
950936
}
951937
}
952938

@@ -965,48 +951,47 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal(
965951
return CreatedTransactionResult(tx, nFeeRet, nChangePosInOut, feeCalc);
966952
}
967953

968-
std::optional<CreatedTransactionResult> CreateTransaction(
954+
BResult<CreatedTransactionResult> CreateTransaction(
969955
CWallet& wallet,
970956
const std::vector<CRecipient>& vecSend,
971957
int change_pos,
972-
bilingual_str& error,
973958
const CCoinControl& coin_control,
974959
bool sign)
975960
{
976961
if (vecSend.empty()) {
977-
error = _("Transaction must have at least one recipient");
978-
return std::nullopt;
962+
return _("Transaction must have at least one recipient");
979963
}
980964

981965
if (std::any_of(vecSend.cbegin(), vecSend.cend(), [](const auto& recipient){ return recipient.nAmount < 0; })) {
982-
error = _("Transaction amounts must not be negative");
983-
return std::nullopt;
966+
return _("Transaction amounts must not be negative");
984967
}
985968

986969
LOCK(wallet.cs_wallet);
987970

988-
std::optional<CreatedTransactionResult> txr_ungrouped = CreateTransactionInternal(wallet, vecSend, change_pos, error, coin_control, sign);
989-
TRACE4(coin_selection, normal_create_tx_internal, wallet.GetName().c_str(), txr_ungrouped.has_value(),
990-
txr_ungrouped.has_value() ? txr_ungrouped->fee : 0, txr_ungrouped.has_value() ? txr_ungrouped->change_pos : 0);
991-
if (!txr_ungrouped) return std::nullopt;
971+
auto res = CreateTransactionInternal(wallet, vecSend, change_pos, coin_control, sign);
972+
TRACE4(coin_selection, normal_create_tx_internal, wallet.GetName().c_str(), res.HasRes(),
973+
res ? res.GetObj().fee : 0, res ? res.GetObj().change_pos : 0);
974+
if (!res) return res;
975+
const auto& txr_ungrouped = res.GetObj();
992976
// try with avoidpartialspends unless it's enabled already
993-
if (txr_ungrouped->fee > 0 /* 0 means non-functional fee rate estimation */ && wallet.m_max_aps_fee > -1 && !coin_control.m_avoid_partial_spends) {
977+
if (txr_ungrouped.fee > 0 /* 0 means non-functional fee rate estimation */ && wallet.m_max_aps_fee > -1 && !coin_control.m_avoid_partial_spends) {
994978
TRACE1(coin_selection, attempting_aps_create_tx, wallet.GetName().c_str());
995979
CCoinControl tmp_cc = coin_control;
996980
tmp_cc.m_avoid_partial_spends = true;
997-
bilingual_str error2; // fired and forgotten; if an error occurs, we discard the results
998-
std::optional<CreatedTransactionResult> txr_grouped = CreateTransactionInternal(wallet, vecSend, change_pos, error2, tmp_cc, sign);
981+
auto res_tx_grouped = CreateTransactionInternal(wallet, vecSend, change_pos, tmp_cc, sign);
982+
// Helper optional class for now
983+
std::optional<CreatedTransactionResult> txr_grouped{res_tx_grouped.HasRes() ? std::make_optional(res_tx_grouped.GetObj()) : std::nullopt};
999984
// if fee of this alternative one is within the range of the max fee, we use this one
1000-
const bool use_aps{txr_grouped.has_value() ? (txr_grouped->fee <= txr_ungrouped->fee + wallet.m_max_aps_fee) : false};
985+
const bool use_aps{txr_grouped.has_value() ? (txr_grouped->fee <= txr_ungrouped.fee + wallet.m_max_aps_fee) : false};
1001986
TRACE5(coin_selection, aps_create_tx_internal, wallet.GetName().c_str(), use_aps, txr_grouped.has_value(),
1002987
txr_grouped.has_value() ? txr_grouped->fee : 0, txr_grouped.has_value() ? txr_grouped->change_pos : 0);
1003988
if (txr_grouped) {
1004989
wallet.WalletLogPrintf("Fee non-grouped = %lld, grouped = %lld, using %s\n",
1005-
txr_ungrouped->fee, txr_grouped->fee, use_aps ? "grouped" : "non-grouped");
1006-
if (use_aps) return txr_grouped;
990+
txr_ungrouped.fee, txr_grouped->fee, use_aps ? "grouped" : "non-grouped");
991+
if (use_aps) return res_tx_grouped;
1007992
}
1008993
}
1009-
return txr_ungrouped;
994+
return res;
1010995
}
1011996

1012997
bool FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl coinControl)
@@ -1042,11 +1027,15 @@ bool FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& nFeeRet,
10421027
}
10431028
}
10441029

1045-
std::optional<CreatedTransactionResult> txr = CreateTransaction(wallet, vecSend, nChangePosInOut, error, coinControl, false);
1046-
if (!txr) return false;
1047-
CTransactionRef tx_new = txr->tx;
1048-
nFeeRet = txr->fee;
1049-
nChangePosInOut = txr->change_pos;
1030+
auto res = CreateTransaction(wallet, vecSend, nChangePosInOut, coinControl, false);
1031+
if (!res) {
1032+
error = res.GetError();
1033+
return false;
1034+
}
1035+
const auto& txr = res.GetObj();
1036+
CTransactionRef tx_new = txr.tx;
1037+
nFeeRet = txr.fee;
1038+
nChangePosInOut = txr.change_pos;
10501039

10511040
if (nChangePosInOut != -1) {
10521041
tx.vout.insert(tx.vout.begin() + nChangePosInOut, tx_new->vout[nChangePosInOut]);

src/wallet/spend.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#include <consensus/amount.h>
99
#include <policy/fees.h> // for FeeCalculation
10+
#include <util/result.h>
1011
#include <wallet/coinselection.h>
1112
#include <wallet/transaction.h>
1213
#include <wallet/wallet.h>
@@ -120,7 +121,7 @@ struct CreatedTransactionResult
120121
* selected by SelectCoins(); Also create the change output, when needed
121122
* @note passing change_pos as -1 will result in setting a random position
122123
*/
123-
std::optional<CreatedTransactionResult> CreateTransaction(CWallet& wallet, const std::vector<CRecipient>& vecSend, int change_pos, bilingual_str& error, const CCoinControl& coin_control, bool sign = true);
124+
BResult<CreatedTransactionResult> CreateTransaction(CWallet& wallet, const std::vector<CRecipient>& vecSend, int change_pos, const CCoinControl& coin_control, bool sign = true);
124125

125126
/**
126127
* Insert additional inputs into the transaction by

src/wallet/test/spend_tests.cpp

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,18 @@ BOOST_FIXTURE_TEST_CASE(SubtractFee, TestChain100Setup)
2828
auto check_tx = [&wallet](CAmount leftover_input_amount) {
2929
CRecipient recipient{GetScriptForRawPubKey({}), 50 * COIN - leftover_input_amount, true /* subtract fee */};
3030
constexpr int RANDOM_CHANGE_POSITION = -1;
31-
bilingual_str error;
3231
CCoinControl coin_control;
3332
coin_control.m_feerate.emplace(10000);
3433
coin_control.fOverrideFeeRate = true;
3534
// We need to use a change type with high cost of change so that the leftover amount will be dropped to fee instead of added as a change output
3635
coin_control.m_change_type = OutputType::LEGACY;
37-
std::optional<CreatedTransactionResult> txr = CreateTransaction(*wallet, {recipient}, RANDOM_CHANGE_POSITION, error, coin_control);
38-
BOOST_CHECK(txr.has_value());
39-
BOOST_CHECK_EQUAL(txr->tx->vout.size(), 1);
40-
BOOST_CHECK_EQUAL(txr->tx->vout[0].nValue, recipient.nAmount + leftover_input_amount - txr->fee);
41-
BOOST_CHECK_GT(txr->fee, 0);
42-
return txr->fee;
36+
auto res = CreateTransaction(*wallet, {recipient}, RANDOM_CHANGE_POSITION, coin_control);
37+
BOOST_CHECK(res);
38+
const auto& txr = res.GetObj();
39+
BOOST_CHECK_EQUAL(txr.tx->vout.size(), 1);
40+
BOOST_CHECK_EQUAL(txr.tx->vout[0].nValue, recipient.nAmount + leftover_input_amount - txr.fee);
41+
BOOST_CHECK_GT(txr.fee, 0);
42+
return txr.fee;
4343
};
4444

4545
// Send full input amount to recipient, check that only nonzero fee is

0 commit comments

Comments
 (0)