Skip to content

Commit e54c392

Browse files
committed
Merge bitcoin/bitcoin#28979: wallet, rpc: document and update sendall behavior around unconfirmed inputs
71aae72 test: test sendall does ancestor aware funding (ishaanam) 3675794 wallet, rpc: implement ancestor aware funding for sendall (ishaanam) 544131f rpc, test: test sendall spends unconfirmed change and unconfirmed inputs when specified (ishaanam) Pull request description: This PR: - Adds a functional test that `sendall` spends unconfirmed change - Adds a functional test that `sendall` spends regular unconfirmed inputs when specified by user - Adds ancestor aware funding to `sendall` by using `calculateCombinedBumpFee` and adjusting the effective value accordingly - Adds a functional test for ancestor aware funding in `sendall` ACKs for top commit: S3RK: ACK 71aae72 achow101: ACK 71aae72 furszy: ACK 71aae72 Tree-SHA512: acaeb7c65166ce53123a1d6cb5012197202246acc02ef9f37a28154cc93afdbd77c25e840ab79bdc7e0b88904014a43ab1ddea79d5337dc310ea210634ab61f0
2 parents 701b0cf + 71aae72 commit e54c392

File tree

2 files changed

+77
-2
lines changed

2 files changed

+77
-2
lines changed

src/wallet/rpc/spend.cpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1295,7 +1295,7 @@ RPCHelpMan sendall()
12951295
{
12961296
return RPCHelpMan{"sendall",
12971297
"EXPERIMENTAL warning: this call may be changed in future releases.\n"
1298-
"\nSpend the value of all (or specific) confirmed UTXOs in the wallet to one or more recipients.\n"
1298+
"\nSpend the value of all (or specific) confirmed UTXOs and unconfirmed change in the wallet to one or more recipients.\n"
12991299
"Unconfirmed inbound UTXOs and locked UTXOs will not be spent. Sendall will respect the avoid_reuse wallet flag.\n"
13001300
"If your wallet contains many small inputs, either because it received tiny payments or as a result of accumulating change, consider using `send_max` to exclude inputs that are worth less than the fees needed to spend them.\n",
13011301
{
@@ -1470,10 +1470,18 @@ RPCHelpMan sendall()
14701470
}
14711471
}
14721472

1473+
std::vector<COutPoint> outpoints_spent;
1474+
outpoints_spent.reserve(rawTx.vin.size());
1475+
1476+
for (const CTxIn& tx_in : rawTx.vin) {
1477+
outpoints_spent.push_back(tx_in.prevout);
1478+
}
1479+
14731480
// estimate final size of tx
14741481
const TxSize tx_size{CalculateMaximumSignedTxSize(CTransaction(rawTx), pwallet.get())};
14751482
const CAmount fee_from_size{fee_rate.GetFee(tx_size.vsize)};
1476-
const CAmount effective_value{total_input_value - fee_from_size};
1483+
const std::optional<CAmount> total_bump_fees{pwallet->chain().calculateCombinedBumpFee(outpoints_spent, fee_rate)};
1484+
CAmount effective_value = total_input_value - fee_from_size - total_bump_fees.value_or(0);
14771485

14781486
if (fee_from_size > pwallet->m_default_max_tx_fee) {
14791487
throw JSONRPCError(RPC_WALLET_ERROR, TransactionErrorString(TransactionError::MAX_FEE_EXCEEDED).original);

test/functional/wallet_sendall.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,64 @@ def sendall_with_maxconf(self):
379379
assert_equal(len(self.wallet.listunspent()), 1)
380380
assert_equal(self.wallet.listunspent()[0]['confirmations'], 6)
381381

382+
@cleanup
383+
def sendall_spends_unconfirmed_change(self):
384+
self.log.info("Test that sendall spends unconfirmed change")
385+
self.add_utxos([17])
386+
self.wallet.sendtoaddress(self.remainder_target, 10)
387+
assert_greater_than(self.wallet.getbalances()["mine"]["trusted"], 6)
388+
self.test_sendall_success(sendall_args = [self.remainder_target])
389+
390+
assert_equal(self.wallet.getbalance(), 0)
391+
392+
@cleanup
393+
def sendall_spends_unconfirmed_inputs_if_specified(self):
394+
self.log.info("Test that sendall spends specified unconfirmed inputs")
395+
self.def_wallet.sendtoaddress(self.wallet.getnewaddress(), 17)
396+
self.wallet.syncwithvalidationinterfacequeue()
397+
assert_equal(self.wallet.getbalances()["mine"]["untrusted_pending"], 17)
398+
unspent = self.wallet.listunspent(minconf=0)[0]
399+
400+
self.wallet.sendall(recipients=[self.remainder_target], inputs=[unspent])
401+
assert_equal(self.wallet.getbalance(), 0)
402+
403+
@cleanup
404+
def sendall_does_ancestor_aware_funding(self):
405+
self.log.info("Test that sendall does ancestor aware funding for unconfirmed inputs")
406+
407+
# higher parent feerate
408+
self.def_wallet.sendtoaddress(address=self.wallet.getnewaddress(), amount=17, fee_rate=20)
409+
self.wallet.syncwithvalidationinterfacequeue()
410+
411+
assert_equal(self.wallet.getbalances()["mine"]["untrusted_pending"], 17)
412+
unspent = self.wallet.listunspent(minconf=0)[0]
413+
414+
parent_txid = unspent["txid"]
415+
assert_equal(self.wallet.gettransaction(parent_txid)["confirmations"], 0)
416+
417+
res_1 = self.wallet.sendall(recipients=[self.def_wallet.getnewaddress()], inputs=[unspent], fee_rate=20, add_to_wallet=False, lock_unspents=True)
418+
child_hex = res_1["hex"]
419+
420+
child_tx = self.wallet.decoderawtransaction(child_hex)
421+
higher_parent_feerate_amount = child_tx["vout"][0]["value"]
422+
423+
# lower parent feerate
424+
self.def_wallet.sendtoaddress(address=self.wallet.getnewaddress(), amount=17, fee_rate=10)
425+
self.wallet.syncwithvalidationinterfacequeue()
426+
assert_equal(self.wallet.getbalances()["mine"]["untrusted_pending"], 34)
427+
unspent = self.wallet.listunspent(minconf=0)[0]
428+
429+
parent_txid = unspent["txid"]
430+
assert_equal(self.wallet.gettransaction(parent_txid)["confirmations"], 0)
431+
432+
res_2 = self.wallet.sendall(recipients=[self.def_wallet.getnewaddress()], inputs=[unspent], fee_rate=20, add_to_wallet=False, lock_unspents=True)
433+
child_hex = res_2["hex"]
434+
435+
child_tx = self.wallet.decoderawtransaction(child_hex)
436+
lower_parent_feerate_amount = child_tx["vout"][0]["value"]
437+
438+
assert_greater_than(higher_parent_feerate_amount, lower_parent_feerate_amount)
439+
382440
# This tests needs to be the last one otherwise @cleanup will fail with "Transaction too large" error
383441
def sendall_fails_with_transaction_too_large(self):
384442
self.log.info("Test that sendall fails if resulting transaction is too large")
@@ -460,6 +518,15 @@ def run_test(self):
460518
# Sendall only uses outputs with less than a given number of confirmation when using minconf
461519
self.sendall_with_maxconf()
462520

521+
# Sendall spends unconfirmed change
522+
self.sendall_spends_unconfirmed_change()
523+
524+
# Sendall spends unconfirmed inputs if they are specified
525+
self.sendall_spends_unconfirmed_inputs_if_specified()
526+
527+
# Sendall does ancestor aware funding when spending an unconfirmed UTXO
528+
self.sendall_does_ancestor_aware_funding()
529+
463530
# Sendall fails when many inputs result to too large transaction
464531
self.sendall_fails_with_transaction_too_large()
465532

0 commit comments

Comments
 (0)