Skip to content

Commit 18ad1b9

Browse files
committed
refactor: pass CRecipient to FundTransaction
Instead turning tx.vout into a vector of `CRecipient`, make `FundTransaction` take a `CRecipient` vector directly. This allows us to remove SFFO logic from the wrapper RPC `FundTransaction` since the `CRecipient` objects have already been created with the correct SFFO values. This also allows us to remove SFFO from both `FundTransaction` function signatures. This sets us up in a future PR to be able to use these RPCs with BIP352 static payment codes.
1 parent 5ad1966 commit 18ad1b9

File tree

4 files changed

+70
-38
lines changed

4 files changed

+70
-38
lines changed

src/wallet/rpc/spend.cpp

+53-25
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,16 @@ std::set<int> InterpretSubtractFeeFromOutputInstructions(const UniValue& sffo_in
7474
}
7575
}
7676
}
77+
if (sffo.isNum()) {
78+
int pos = sffo.getInt<int>();
79+
if (sffo_set.contains(pos))
80+
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter, duplicated position: %d", pos));
81+
if (pos < 0)
82+
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter, negative position: %d", pos));
83+
if (pos >= int(destinations.size()))
84+
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter, position too large: %d", pos));
85+
sffo_set.insert(pos);
86+
}
7787
}
7888
return sffo_set;
7989
}
@@ -480,17 +490,17 @@ static std::vector<RPCArg> FundTxDoc(bool solving_data = true)
480490
return args;
481491
}
482492

483-
CreatedTransactionResult FundTransaction(CWallet& wallet, const CMutableTransaction& tx, const UniValue& options, CCoinControl& coinControl, bool override_min_fee)
493+
CreatedTransactionResult FundTransaction(CWallet& wallet, const CMutableTransaction& tx, const std::vector<CRecipient>& recipients, const UniValue& options, CCoinControl& coinControl, bool override_min_fee)
484494
{
495+
// We want to make sure tx.vout is not used now that we are passing outputs as a vector of recipients.
496+
// This sets us up to remove tx completely in a future PR in favor of passing the inputs directly.
497+
CHECK_NONFATAL(tx.vout.empty());
485498
// Make sure the results are valid at least up to the most recent block
486499
// the user could have gotten from another RPC command prior to now
487500
wallet.BlockUntilSyncedToCurrentChain();
488501

489502
std::optional<unsigned int> change_position;
490503
bool lockUnspents = false;
491-
UniValue subtractFeeFromOutputs;
492-
std::set<int> setSubtractFeeFromOutputs;
493-
494504
if (!options.isNull()) {
495505
if (options.type() == UniValue::VBOOL) {
496506
// backward compatibility bool only fallback
@@ -545,7 +555,7 @@ CreatedTransactionResult FundTransaction(CWallet& wallet, const CMutableTransact
545555

546556
if (options.exists("changePosition") || options.exists("change_position")) {
547557
int pos = (options.exists("change_position") ? options["change_position"] : options["changePosition"]).getInt<int>();
548-
if (pos < 0 || (unsigned int)pos > tx.vout.size()) {
558+
if (pos < 0 || (unsigned int)pos > recipients.size()) {
549559
throw JSONRPCError(RPC_INVALID_PARAMETER, "changePosition out of bounds");
550560
}
551561
change_position = (unsigned int)pos;
@@ -587,9 +597,6 @@ CreatedTransactionResult FundTransaction(CWallet& wallet, const CMutableTransact
587597
coinControl.fOverrideFeeRate = true;
588598
}
589599

590-
if (options.exists("subtractFeeFromOutputs") || options.exists("subtract_fee_from_outputs") )
591-
subtractFeeFromOutputs = (options.exists("subtract_fee_from_outputs") ? options["subtract_fee_from_outputs"] : options["subtractFeeFromOutputs"]).get_array();
592-
593600
if (options.exists("replaceable")) {
594601
coinControl.m_signal_bip125_rbf = options["replaceable"].get_bool();
595602
}
@@ -695,21 +702,10 @@ CreatedTransactionResult FundTransaction(CWallet& wallet, const CMutableTransact
695702
}
696703
}
697704

698-
if (tx.vout.size() == 0)
705+
if (recipients.empty())
699706
throw JSONRPCError(RPC_INVALID_PARAMETER, "TX must have at least one output");
700707

701-
for (unsigned int idx = 0; idx < subtractFeeFromOutputs.size(); idx++) {
702-
int pos = subtractFeeFromOutputs[idx].getInt<int>();
703-
if (setSubtractFeeFromOutputs.count(pos))
704-
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter, duplicated position: %d", pos));
705-
if (pos < 0)
706-
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter, negative position: %d", pos));
707-
if (pos >= int(tx.vout.size()))
708-
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter, position too large: %d", pos));
709-
setSubtractFeeFromOutputs.insert(pos);
710-
}
711-
712-
auto txr = FundTransaction(wallet, tx, change_position, lockUnspents, setSubtractFeeFromOutputs, coinControl);
708+
auto txr = FundTransaction(wallet, tx, recipients, change_position, lockUnspents, coinControl);
713709
if (!txr) {
714710
throw JSONRPCError(RPC_WALLET_ERROR, ErrorString(txr).original);
715711
}
@@ -835,11 +831,25 @@ RPCHelpMan fundrawtransaction()
835831
if (!DecodeHexTx(tx, request.params[0].get_str(), try_no_witness, try_witness)) {
836832
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
837833
}
838-
834+
UniValue options = request.params[1];
835+
std::vector<std::pair<CTxDestination, CAmount>> destinations;
836+
for (const auto& tx_out : tx.vout) {
837+
CTxDestination dest;
838+
ExtractDestination(tx_out.scriptPubKey, dest);
839+
destinations.emplace_back(dest, tx_out.nValue);
840+
}
841+
std::vector<std::string> dummy(destinations.size(), "dummy");
842+
std::vector<CRecipient> recipients = CreateRecipients(
843+
destinations,
844+
InterpretSubtractFeeFromOutputInstructions(options["subtractFeeFromOutputs"], dummy)
845+
);
839846
CCoinControl coin_control;
840847
// Automatically select (additional) coins. Can be overridden by options.add_inputs.
841848
coin_control.m_allow_other_inputs = true;
842-
auto txr = FundTransaction(*pwallet, tx, request.params[1], coin_control, /*override_min_fee=*/true);
849+
// Clear tx.vout since it is not meant to be used now that we are passing outputs directly.
850+
// This sets us up for a future PR to completely remove tx from the function signature in favor of passing inputs directly
851+
tx.vout.clear();
852+
auto txr = FundTransaction(*pwallet, tx, recipients, options, coin_control, /*override_min_fee=*/true);
843853

844854
UniValue result(UniValue::VOBJ);
845855
result.pushKV("hex", EncodeHexTx(*txr.tx));
@@ -1267,13 +1277,22 @@ RPCHelpMan send()
12671277

12681278

12691279
bool rbf{options.exists("replaceable") ? options["replaceable"].get_bool() : pwallet->m_signal_rbf};
1280+
UniValue outputs(UniValue::VOBJ);
1281+
outputs = NormalizeOutputs(request.params[0]);
1282+
std::vector<CRecipient> recipients = CreateRecipients(
1283+
ParseOutputs(outputs),
1284+
InterpretSubtractFeeFromOutputInstructions(options["subtract_fee_from_outputs"], outputs.getKeys())
1285+
);
12701286
CMutableTransaction rawTx = ConstructTransaction(options["inputs"], request.params[0], options["locktime"], rbf);
12711287
CCoinControl coin_control;
12721288
// Automatically select coins, unless at least one is manually selected. Can
12731289
// be overridden by options.add_inputs.
12741290
coin_control.m_allow_other_inputs = rawTx.vin.size() == 0;
12751291
SetOptionsInputWeights(options["inputs"], options);
1276-
auto txr = FundTransaction(*pwallet, rawTx, options, coin_control, /*override_min_fee=*/false);
1292+
// Clear tx.vout since it is not meant to be used now that we are passing outputs directly.
1293+
// This sets us up for a future PR to completely remove tx from the function signature in favor of passing inputs directly
1294+
rawTx.vout.clear();
1295+
auto txr = FundTransaction(*pwallet, rawTx, recipients, options, coin_control, /*override_min_fee=*/false);
12771296

12781297
return FinishTransaction(pwallet, options, CMutableTransaction(*txr.tx));
12791298
}
@@ -1703,12 +1722,21 @@ RPCHelpMan walletcreatefundedpsbt()
17031722
const UniValue &replaceable_arg = options["replaceable"];
17041723
const bool rbf{replaceable_arg.isNull() ? wallet.m_signal_rbf : replaceable_arg.get_bool()};
17051724
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], rbf);
1725+
UniValue outputs(UniValue::VOBJ);
1726+
outputs = NormalizeOutputs(request.params[1]);
1727+
std::vector<CRecipient> recipients = CreateRecipients(
1728+
ParseOutputs(outputs),
1729+
InterpretSubtractFeeFromOutputInstructions(options["subtractFeeFromOutputs"], outputs.getKeys())
1730+
);
17061731
CCoinControl coin_control;
17071732
// Automatically select coins, unless at least one is manually selected. Can
17081733
// be overridden by options.add_inputs.
17091734
coin_control.m_allow_other_inputs = rawTx.vin.size() == 0;
17101735
SetOptionsInputWeights(request.params[0], options);
1711-
auto txr = FundTransaction(wallet, rawTx, options, coin_control, /*override_min_fee=*/true);
1736+
// Clear tx.vout since it is not meant to be used now that we are passing outputs directly.
1737+
// This sets us up for a future PR to completely remove tx from the function signature in favor of passing inputs directly
1738+
rawTx.vout.clear();
1739+
auto txr = FundTransaction(wallet, rawTx, recipients, options, coin_control, /*override_min_fee=*/true);
17121740

17131741
// Make a blank psbt
17141742
PartiallySignedTransaction psbtx(CMutableTransaction(*txr.tx));

src/wallet/spend.cpp

+4-11
Original file line numberDiff line numberDiff line change
@@ -1365,18 +1365,11 @@ util::Result<CreatedTransactionResult> CreateTransaction(
13651365
return res;
13661366
}
13671367

1368-
util::Result<CreatedTransactionResult> FundTransaction(CWallet& wallet, const CMutableTransaction& tx, std::optional<unsigned int> change_pos, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl coinControl)
1368+
util::Result<CreatedTransactionResult> FundTransaction(CWallet& wallet, const CMutableTransaction& tx, const std::vector<CRecipient>& vecSend, std::optional<unsigned int> change_pos, bool lockUnspents, CCoinControl coinControl)
13691369
{
1370-
std::vector<CRecipient> vecSend;
1371-
1372-
// Turn the txout set into a CRecipient vector.
1373-
for (size_t idx = 0; idx < tx.vout.size(); idx++) {
1374-
const CTxOut& txOut = tx.vout[idx];
1375-
CTxDestination dest;
1376-
ExtractDestination(txOut.scriptPubKey, dest);
1377-
CRecipient recipient = {dest, txOut.nValue, setSubtractFeeFromOutputs.count(idx) == 1};
1378-
vecSend.push_back(recipient);
1379-
}
1370+
// We want to make sure tx.vout is not used now that we are passing outputs as a vector of recipients.
1371+
// This sets us up to remove tx completely in a future PR in favor of passing the inputs directly.
1372+
assert(tx.vout.empty());
13801373

13811374
// Set the user desired locktime
13821375
coinControl.m_locktime = tx.nLockTime;

src/wallet/spend.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ util::Result<CreatedTransactionResult> CreateTransaction(CWallet& wallet, const
224224
* Insert additional inputs into the transaction by
225225
* calling CreateTransaction();
226226
*/
227-
util::Result<CreatedTransactionResult> FundTransaction(CWallet& wallet, const CMutableTransaction& tx, std::optional<unsigned int> change_pos, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl);
227+
util::Result<CreatedTransactionResult> FundTransaction(CWallet& wallet, const CMutableTransaction& tx, const std::vector<CRecipient>& recipients, std::optional<unsigned int> change_pos, bool lockUnspents, CCoinControl);
228228
} // namespace wallet
229229

230230
#endif // BITCOIN_WALLET_SPEND_H

src/wallet/test/fuzz/notifications.cpp

+12-1
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,14 @@ struct FuzzedWallet {
132132
}
133133
}
134134
}
135+
std::vector<CRecipient> recipients;
136+
for (size_t idx = 0; idx < tx.vout.size(); idx++) {
137+
const CTxOut& tx_out = tx.vout[idx];
138+
CTxDestination dest;
139+
ExtractDestination(tx_out.scriptPubKey, dest);
140+
CRecipient recipient = {dest, tx_out.nValue, subtract_fee_from_outputs.count(idx) == 1};
141+
recipients.push_back(recipient);
142+
}
135143
CCoinControl coin_control;
136144
coin_control.m_allow_other_inputs = fuzzed_data_provider.ConsumeBool();
137145
CallOneOf(
@@ -158,7 +166,10 @@ struct FuzzedWallet {
158166

159167
int change_position{fuzzed_data_provider.ConsumeIntegralInRange<int>(-1, tx.vout.size() - 1)};
160168
bilingual_str error;
161-
(void)FundTransaction(*wallet, tx, change_position, /*lockUnspents=*/false, subtract_fee_from_outputs, coin_control);
169+
// Clear tx.vout since it is not meant to be used now that we are passing outputs directly.
170+
// This sets us up for a future PR to completely remove tx from the function signature in favor of passing inputs directly
171+
tx.vout.clear();
172+
(void)FundTransaction(*wallet, tx, recipients, change_position, /*lockUnspents=*/false, coin_control);
162173
}
163174
};
164175

0 commit comments

Comments
 (0)