Skip to content

Commit 1ab389b

Browse files
committed
Merge bitcoin#20640: wallet, refactor: return out-params of CreateTransaction() as optional struct
4c5ceb0 wallet: CreateTransaction(): return out-params as (optional) struct (Sebastian Falbesoner) c9fdaa5 wallet: CreateTransactionInternal(): return out-params as (optional) struct (Sebastian Falbesoner) Pull request description: The method `CWallet::CreateTransaction` currently returns several values in the form of out-parameters: * the actual newly created transaction (`CTransactionRef& tx`) * its required fee (`CAmount& nFeeRate`) * the position of the change output (`int& nChangePosInOut`) -- as the name suggests, this is both an in- and out-param By returning these values in an optional structure (which returns no value a.k.a. `std::nullopt` if an error occured), the interfaces is shorter, cleaner (requested change position is now in-param and can be passed by value) and callers don't have to create dummy variables for results that they are not interested in. Note that the names of the replaced out-variables were kept in `CreateTransactionInternal` to keep the diff minimal. Also, the fee calculation data (`FeeCalculation& fee_calc_out`) would be another candidate to put into the structure, but `FeeCalculation` is currently an opaque data type in the wallet interface and I think it should stay that way. As a potential follow-up, I think it would make sense to also do the same refactoring for `CWallet::FundTransaction`, which has a very similar parameter structure. Suggested by laanwj in bitcoin#20588 (comment). ACKs for top commit: achow101: re-ACK 4c5ceb0 Xekyo: ACK 4c5ceb0 w0xlt: crACK bitcoin@4c5ceb0 Tree-SHA512: 27e5348bbf4f698713002d40c834dcda59c711c93207113e14522fc6d9ae7f4d8edf1ef6d214c5dd62bb52943d342878960ca333728828bf39b645a27d55d524
2 parents 0be1dc1 + 4c5ceb0 commit 1ab389b

File tree

7 files changed

+83
-75
lines changed

7 files changed

+83
-75
lines changed

src/wallet/feebumper.cpp

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -218,21 +218,20 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo
218218
// We cannot source new unconfirmed inputs(bip125 rule 2)
219219
new_coin_control.m_min_depth = 1;
220220

221-
CTransactionRef tx_new;
222-
CAmount fee_ret;
223-
int change_pos_in_out = -1; // No requested location for change
221+
constexpr int RANDOM_CHANGE_POSITION = -1;
224222
bilingual_str fail_reason;
225223
FeeCalculation fee_calc_out;
226-
if (!CreateTransaction(wallet, recipients, tx_new, fee_ret, change_pos_in_out, fail_reason, new_coin_control, fee_calc_out, false)) {
224+
std::optional<CreatedTransactionResult> txr = CreateTransaction(wallet, recipients, RANDOM_CHANGE_POSITION, fail_reason, new_coin_control, fee_calc_out, false);
225+
if (!txr) {
227226
errors.push_back(Untranslated("Unable to create transaction.") + Untranslated(" ") + fail_reason);
228227
return Result::WALLET_ERROR;
229228
}
230229

231230
// Write back new fee if successful
232-
new_fee = fee_ret;
231+
new_fee = txr->fee;
233232

234233
// Write back transaction
235-
mtx = CMutableTransaction(*tx_new);
234+
mtx = CMutableTransaction(*txr->tx);
236235

237236
return Result::OK;
238237
}

src/wallet/interfaces.cpp

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -257,13 +257,14 @@ class WalletImpl : public Wallet
257257
bilingual_str& fail_reason) override
258258
{
259259
LOCK(m_wallet->cs_wallet);
260-
CTransactionRef tx;
261260
FeeCalculation fee_calc_out;
262-
if (!CreateTransaction(*m_wallet, recipients, tx, fee, change_pos,
263-
fail_reason, coin_control, fee_calc_out, sign)) {
264-
return {};
265-
}
266-
return tx;
261+
std::optional<CreatedTransactionResult> txr = CreateTransaction(*m_wallet, recipients, change_pos,
262+
fail_reason, coin_control, fee_calc_out, sign);
263+
if (!txr) return {};
264+
fee = txr->fee;
265+
change_pos = txr->change_pos;
266+
267+
return txr->tx;
267268
}
268269
void commitTransaction(CTransactionRef tx,
269270
WalletValueMap value_map,

src/wallet/rpc/spend.cpp

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -155,15 +155,14 @@ UniValue SendMoney(CWallet& wallet, const CCoinControl &coin_control, std::vecto
155155
std::shuffle(recipients.begin(), recipients.end(), FastRandomContext());
156156

157157
// Send
158-
CAmount nFeeRequired = 0;
159-
int nChangePosRet = -1;
158+
constexpr int RANDOM_CHANGE_POSITION = -1;
160159
bilingual_str error;
161-
CTransactionRef tx;
162160
FeeCalculation fee_calc_out;
163-
const bool fCreated = CreateTransaction(wallet, recipients, tx, nFeeRequired, nChangePosRet, error, coin_control, fee_calc_out, true);
164-
if (!fCreated) {
161+
std::optional<CreatedTransactionResult> txr = CreateTransaction(wallet, recipients, RANDOM_CHANGE_POSITION, error, coin_control, fee_calc_out, true);
162+
if (!txr) {
165163
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, error.original);
166164
}
165+
CTransactionRef tx = txr->tx;
167166
wallet.CommitTransaction(tx, std::move(map_value), {} /* orderForm */);
168167
if (verbose) {
169168
UniValue entry(UniValue::VOBJ);

src/wallet/spend.cpp

Lines changed: 42 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -656,19 +656,22 @@ static void DiscourageFeeSniping(CMutableTransaction& tx, FastRandomContext& rng
656656
}
657657
}
658658

659-
static bool CreateTransactionInternal(
659+
static std::optional<CreatedTransactionResult> CreateTransactionInternal(
660660
CWallet& wallet,
661661
const std::vector<CRecipient>& vecSend,
662-
CTransactionRef& tx,
663-
CAmount& nFeeRet,
664-
int& nChangePosInOut,
662+
int change_pos,
665663
bilingual_str& error,
666664
const CCoinControl& coin_control,
667665
FeeCalculation& fee_calc_out,
668666
bool sign) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
669667
{
670668
AssertLockHeld(wallet.cs_wallet);
671669

670+
// out variables, to be packed into returned result structure
671+
CTransactionRef tx;
672+
CAmount nFeeRet;
673+
int nChangePosInOut = change_pos;
674+
672675
FastRandomContext rng_fast;
673676
CMutableTransaction txNew; // The resulting transaction that we make
674677

@@ -742,12 +745,12 @@ static bool CreateTransactionInternal(
742745
// provided one
743746
if (coin_control.m_feerate && coin_selection_params.m_effective_feerate > *coin_control.m_feerate) {
744747
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));
745-
return false;
748+
return std::nullopt;
746749
}
747750
if (feeCalc.reason == FeeReason::FALLBACK && !wallet.m_allow_fallback_fee) {
748751
// eventually allow a fallback fee
749752
error = _("Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee.");
750-
return false;
753+
return std::nullopt;
751754
}
752755

753756
// Calculate the cost of change
@@ -774,7 +777,7 @@ static bool CreateTransactionInternal(
774777
if (IsDust(txout, wallet.chain().relayDustFee()))
775778
{
776779
error = _("Transaction amount too small");
777-
return false;
780+
return std::nullopt;
778781
}
779782
txNew.vout.push_back(txout);
780783
}
@@ -791,7 +794,7 @@ static bool CreateTransactionInternal(
791794
std::optional<SelectionResult> result = SelectCoins(wallet, vAvailableCoins, /*nTargetValue=*/selection_target, coin_control, coin_selection_params);
792795
if (!result) {
793796
error = _("Insufficient funds");
794-
return false;
797+
return std::nullopt;
795798
}
796799
TRACE5(coin_selection, selected_coins, wallet.GetName().c_str(), GetAlgorithmName(result->m_algo).c_str(), result->m_target, result->GetWaste(), result->GetSelectedValue());
797800

@@ -808,7 +811,7 @@ static bool CreateTransactionInternal(
808811
else if ((unsigned int)nChangePosInOut > txNew.vout.size())
809812
{
810813
error = _("Transaction change output index out of range");
811-
return false;
814+
return std::nullopt;
812815
}
813816

814817
assert(nChangePosInOut != -1);
@@ -836,7 +839,7 @@ static bool CreateTransactionInternal(
836839
int nBytes = tx_sizes.vsize;
837840
if (nBytes == -1) {
838841
error = _("Missing solving data for estimating transaction size");
839-
return false;
842+
return std::nullopt;
840843
}
841844
nFeeRet = coin_selection_params.m_effective_feerate.GetFee(nBytes);
842845

@@ -900,7 +903,7 @@ static bool CreateTransactionInternal(
900903
} else {
901904
error = _("The transaction amount is too small to send after the fee has been deducted");
902905
}
903-
return false;
906+
return std::nullopt;
904907
}
905908
}
906909
++i;
@@ -910,12 +913,12 @@ static bool CreateTransactionInternal(
910913

911914
// Give up if change keypool ran out and change is required
912915
if (scriptChange.empty() && nChangePosInOut != -1) {
913-
return false;
916+
return std::nullopt;
914917
}
915918

916919
if (sign && !wallet.SignTransaction(txNew)) {
917920
error = _("Signing transaction failed");
918-
return false;
921+
return std::nullopt;
919922
}
920923

921924
// Return the constructed transaction data.
@@ -926,19 +929,19 @@ static bool CreateTransactionInternal(
926929
(!sign && tx_sizes.weight > MAX_STANDARD_TX_WEIGHT))
927930
{
928931
error = _("Transaction too large");
929-
return false;
932+
return std::nullopt;
930933
}
931934

932935
if (nFeeRet > wallet.m_default_max_tx_fee) {
933936
error = TransactionErrorString(TransactionError::MAX_FEE_EXCEEDED);
934-
return false;
937+
return std::nullopt;
935938
}
936939

937940
if (gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS)) {
938941
// Lastly, ensure this tx will pass the mempool's chain limits
939942
if (!wallet.chain().checkChainLimits(tx)) {
940943
error = _("Transaction has too long of a mempool chain");
941-
return false;
944+
return std::nullopt;
942945
}
943946
}
944947

@@ -955,58 +958,51 @@ static bool CreateTransactionInternal(
955958
feeCalc.est.fail.start, feeCalc.est.fail.end,
956959
(feeCalc.est.fail.totalConfirmed + feeCalc.est.fail.inMempool + feeCalc.est.fail.leftMempool) > 0.0 ? 100 * feeCalc.est.fail.withinTarget / (feeCalc.est.fail.totalConfirmed + feeCalc.est.fail.inMempool + feeCalc.est.fail.leftMempool) : 0.0,
957960
feeCalc.est.fail.withinTarget, feeCalc.est.fail.totalConfirmed, feeCalc.est.fail.inMempool, feeCalc.est.fail.leftMempool);
958-
return true;
961+
return CreatedTransactionResult(tx, nFeeRet, nChangePosInOut);
959962
}
960963

961-
bool CreateTransaction(
964+
std::optional<CreatedTransactionResult> CreateTransaction(
962965
CWallet& wallet,
963966
const std::vector<CRecipient>& vecSend,
964-
CTransactionRef& tx,
965-
CAmount& nFeeRet,
966-
int& nChangePosInOut,
967+
int change_pos,
967968
bilingual_str& error,
968969
const CCoinControl& coin_control,
969970
FeeCalculation& fee_calc_out,
970971
bool sign)
971972
{
972973
if (vecSend.empty()) {
973974
error = _("Transaction must have at least one recipient");
974-
return false;
975+
return std::nullopt;
975976
}
976977

977978
if (std::any_of(vecSend.cbegin(), vecSend.cend(), [](const auto& recipient){ return recipient.nAmount < 0; })) {
978979
error = _("Transaction amounts must not be negative");
979-
return false;
980+
return std::nullopt;
980981
}
981982

982983
LOCK(wallet.cs_wallet);
983984

984-
int nChangePosIn = nChangePosInOut;
985-
Assert(!tx); // tx is an out-param. TODO change the return type from bool to tx (or nullptr)
986-
bool res = CreateTransactionInternal(wallet, vecSend, tx, nFeeRet, nChangePosInOut, error, coin_control, fee_calc_out, sign);
987-
TRACE4(coin_selection, normal_create_tx_internal, wallet.GetName().c_str(), res, nFeeRet, nChangePosInOut);
985+
std::optional<CreatedTransactionResult> txr_ungrouped = CreateTransactionInternal(wallet, vecSend, change_pos, error, coin_control, fee_calc_out, sign);
986+
TRACE4(coin_selection, normal_create_tx_internal, wallet.GetName().c_str(), txr_ungrouped.has_value(),
987+
txr_ungrouped.has_value() ? txr_ungrouped->fee : 0, txr_ungrouped.has_value() ? txr_ungrouped->change_pos : 0);
988+
if (!txr_ungrouped) return std::nullopt;
988989
// try with avoidpartialspends unless it's enabled already
989-
if (res && nFeeRet > 0 /* 0 means non-functional fee rate estimation */ && wallet.m_max_aps_fee > -1 && !coin_control.m_avoid_partial_spends) {
990+
if (txr_ungrouped->fee > 0 /* 0 means non-functional fee rate estimation */ && wallet.m_max_aps_fee > -1 && !coin_control.m_avoid_partial_spends) {
990991
TRACE1(coin_selection, attempting_aps_create_tx, wallet.GetName().c_str());
991992
CCoinControl tmp_cc = coin_control;
992993
tmp_cc.m_avoid_partial_spends = true;
993-
CAmount nFeeRet2;
994-
CTransactionRef tx2;
995-
int nChangePosInOut2 = nChangePosIn;
996994
bilingual_str error2; // fired and forgotten; if an error occurs, we discard the results
997-
if (CreateTransactionInternal(wallet, vecSend, tx2, nFeeRet2, nChangePosInOut2, error2, tmp_cc, fee_calc_out, sign)) {
995+
std::optional<CreatedTransactionResult> txr_grouped = CreateTransactionInternal(wallet, vecSend, change_pos, error2, tmp_cc, fee_calc_out, sign);
996+
if (txr_grouped) {
998997
// if fee of this alternative one is within the range of the max fee, we use this one
999-
const bool use_aps = nFeeRet2 <= nFeeRet + wallet.m_max_aps_fee;
1000-
wallet.WalletLogPrintf("Fee non-grouped = %lld, grouped = %lld, using %s\n", nFeeRet, nFeeRet2, use_aps ? "grouped" : "non-grouped");
1001-
TRACE5(coin_selection, aps_create_tx_internal, wallet.GetName().c_str(), use_aps, res, nFeeRet2, nChangePosInOut2);
1002-
if (use_aps) {
1003-
tx = tx2;
1004-
nFeeRet = nFeeRet2;
1005-
nChangePosInOut = nChangePosInOut2;
1006-
}
998+
const bool use_aps = txr_grouped->fee <= txr_ungrouped->fee + wallet.m_max_aps_fee;
999+
wallet.WalletLogPrintf("Fee non-grouped = %lld, grouped = %lld, using %s\n",
1000+
txr_ungrouped->fee, txr_grouped->fee, use_aps ? "grouped" : "non-grouped");
1001+
TRACE5(coin_selection, aps_create_tx_internal, wallet.GetName().c_str(), use_aps, true, txr_grouped->fee, txr_grouped->change_pos);
1002+
if (use_aps) return txr_grouped;
10071003
}
10081004
}
1009-
return res;
1005+
return txr_ungrouped;
10101006
}
10111007

10121008
bool FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl coinControl)
@@ -1030,11 +1026,12 @@ bool FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& nFeeRet,
10301026
// CreateTransaction call and LockCoin calls (when lockUnspents is true).
10311027
LOCK(wallet.cs_wallet);
10321028

1033-
CTransactionRef tx_new;
10341029
FeeCalculation fee_calc_out;
1035-
if (!CreateTransaction(wallet, vecSend, tx_new, nFeeRet, nChangePosInOut, error, coinControl, fee_calc_out, false)) {
1036-
return false;
1037-
}
1030+
std::optional<CreatedTransactionResult> txr = CreateTransaction(wallet, vecSend, nChangePosInOut, error, coinControl, fee_calc_out, false);
1031+
if (!txr) return false;
1032+
CTransactionRef tx_new = txr->tx;
1033+
nFeeRet = txr->fee;
1034+
nChangePosInOut = txr->change_pos;
10381035

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

src/wallet/spend.h

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
#include <wallet/transaction.h>
1111
#include <wallet/wallet.h>
1212

13+
#include <optional>
14+
1315
namespace wallet {
1416
/** Get the marginal bytes if spending the specified output from this transaction.
1517
* use_max_sig indicates whether to use the maximum sized, 72 byte signature when calculating the
@@ -82,12 +84,22 @@ std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAm
8284
std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, const CCoinControl& coin_control,
8385
const CoinSelectionParams& coin_selection_params) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
8486

87+
struct CreatedTransactionResult
88+
{
89+
CTransactionRef tx;
90+
CAmount fee;
91+
int change_pos;
92+
93+
CreatedTransactionResult(CTransactionRef tx, CAmount fee, int change_pos)
94+
: tx(tx), fee(fee), change_pos(change_pos) {}
95+
};
96+
8597
/**
8698
* Create a new transaction paying the recipients with a set of coins
8799
* selected by SelectCoins(); Also create the change output, when needed
88-
* @note passing nChangePosInOut as -1 will result in setting a random position
100+
* @note passing change_pos as -1 will result in setting a random position
89101
*/
90-
bool CreateTransaction(CWallet& wallet, const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, const CCoinControl& coin_control, FeeCalculation& fee_calc_out, bool sign = true);
102+
std::optional<CreatedTransactionResult> CreateTransaction(CWallet& wallet, const std::vector<CRecipient>& vecSend, int change_pos, bilingual_str& error, const CCoinControl& coin_control, FeeCalculation& fee_calc_out, bool sign = true);
91103

92104
/**
93105
* Insert additional inputs into the transaction by

src/wallet/test/spend_tests.cpp

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,21 +27,20 @@ BOOST_FIXTURE_TEST_CASE(SubtractFee, TestChain100Setup)
2727
// instead of the miner.
2828
auto check_tx = [&wallet](CAmount leftover_input_amount) {
2929
CRecipient recipient{GetScriptForRawPubKey({}), 50 * COIN - leftover_input_amount, true /* subtract fee */};
30-
CTransactionRef tx;
31-
CAmount fee;
32-
int change_pos = -1;
30+
constexpr int RANDOM_CHANGE_POSITION = -1;
3331
bilingual_str error;
3432
CCoinControl coin_control;
3533
coin_control.m_feerate.emplace(10000);
3634
coin_control.fOverrideFeeRate = true;
3735
// 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
3836
coin_control.m_change_type = OutputType::LEGACY;
3937
FeeCalculation fee_calc;
40-
BOOST_CHECK(CreateTransaction(*wallet, {recipient}, tx, fee, change_pos, error, coin_control, fee_calc));
41-
BOOST_CHECK_EQUAL(tx->vout.size(), 1);
42-
BOOST_CHECK_EQUAL(tx->vout[0].nValue, recipient.nAmount + leftover_input_amount - fee);
43-
BOOST_CHECK_GT(fee, 0);
44-
return fee;
38+
std::optional<CreatedTransactionResult> txr = CreateTransaction(*wallet, {recipient}, RANDOM_CHANGE_POSITION, error, coin_control, fee_calc);
39+
BOOST_CHECK(txr.has_value());
40+
BOOST_CHECK_EQUAL(txr->tx->vout.size(), 1);
41+
BOOST_CHECK_EQUAL(txr->tx->vout[0].nValue, recipient.nAmount + leftover_input_amount - txr->fee);
42+
BOOST_CHECK_GT(txr->fee, 0);
43+
return txr->fee;
4544
};
4645

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

src/wallet/test/wallet_tests.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -521,13 +521,14 @@ class ListCoinsTestingSetup : public TestChain100Setup
521521
CWalletTx& AddTx(CRecipient recipient)
522522
{
523523
CTransactionRef tx;
524-
CAmount fee;
525-
int changePos = -1;
526524
bilingual_str error;
527525
CCoinControl dummy;
528526
FeeCalculation fee_calc_out;
529527
{
530-
BOOST_CHECK(CreateTransaction(*wallet, {recipient}, tx, fee, changePos, error, dummy, fee_calc_out));
528+
constexpr int RANDOM_CHANGE_POSITION = -1;
529+
std::optional<CreatedTransactionResult> txr = CreateTransaction(*wallet, {recipient}, RANDOM_CHANGE_POSITION, error, dummy, fee_calc_out);
530+
BOOST_CHECK(txr.has_value());
531+
tx = txr->tx;
531532
}
532533
wallet->CommitTransaction(tx, {}, {});
533534
CMutableTransaction blocktx;

0 commit comments

Comments
 (0)