Merge bitcoin/bitcoin#28979: wallet, rpc: document and update `sendall` behavior around unconfirmed inputs

71aae72e1f test: test sendall does ancestor aware funding (ishaanam)
36757941a0 wallet, rpc: implement ancestor aware funding for sendall (ishaanam)
544131f3fb 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 71aae72e1f
  achow101:
    ACK 71aae72e1f
  furszy:
    ACK 71aae72e1f

Tree-SHA512: acaeb7c65166ce53123a1d6cb5012197202246acc02ef9f37a28154cc93afdbd77c25e840ab79bdc7e0b88904014a43ab1ddea79d5337dc310ea210634ab61f0
pull/30227/head
Ava Chow 5 months ago
commit e54c392356
No known key found for this signature in database
GPG Key ID: 17565732E08E5E41

@ -1295,7 +1295,7 @@ RPCHelpMan sendall()
{
return RPCHelpMan{"sendall",
"EXPERIMENTAL warning: this call may be changed in future releases.\n"
"\nSpend the value of all (or specific) confirmed UTXOs in the wallet to one or more recipients.\n"
"\nSpend the value of all (or specific) confirmed UTXOs and unconfirmed change in the wallet to one or more recipients.\n"
"Unconfirmed inbound UTXOs and locked UTXOs will not be spent. Sendall will respect the avoid_reuse wallet flag.\n"
"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",
{
@ -1470,10 +1470,18 @@ RPCHelpMan sendall()
}
}
std::vector<COutPoint> outpoints_spent;
outpoints_spent.reserve(rawTx.vin.size());
for (const CTxIn& tx_in : rawTx.vin) {
outpoints_spent.push_back(tx_in.prevout);
}
// estimate final size of tx
const TxSize tx_size{CalculateMaximumSignedTxSize(CTransaction(rawTx), pwallet.get())};
const CAmount fee_from_size{fee_rate.GetFee(tx_size.vsize)};
const CAmount effective_value{total_input_value - fee_from_size};
const std::optional<CAmount> total_bump_fees{pwallet->chain().calculateCombinedBumpFee(outpoints_spent, fee_rate)};
CAmount effective_value = total_input_value - fee_from_size - total_bump_fees.value_or(0);
if (fee_from_size > pwallet->m_default_max_tx_fee) {
throw JSONRPCError(RPC_WALLET_ERROR, TransactionErrorString(TransactionError::MAX_FEE_EXCEEDED).original);

@ -379,6 +379,64 @@ class SendallTest(BitcoinTestFramework):
assert_equal(len(self.wallet.listunspent()), 1)
assert_equal(self.wallet.listunspent()[0]['confirmations'], 6)
@cleanup
def sendall_spends_unconfirmed_change(self):
self.log.info("Test that sendall spends unconfirmed change")
self.add_utxos([17])
self.wallet.sendtoaddress(self.remainder_target, 10)
assert_greater_than(self.wallet.getbalances()["mine"]["trusted"], 6)
self.test_sendall_success(sendall_args = [self.remainder_target])
assert_equal(self.wallet.getbalance(), 0)
@cleanup
def sendall_spends_unconfirmed_inputs_if_specified(self):
self.log.info("Test that sendall spends specified unconfirmed inputs")
self.def_wallet.sendtoaddress(self.wallet.getnewaddress(), 17)
self.wallet.syncwithvalidationinterfacequeue()
assert_equal(self.wallet.getbalances()["mine"]["untrusted_pending"], 17)
unspent = self.wallet.listunspent(minconf=0)[0]
self.wallet.sendall(recipients=[self.remainder_target], inputs=[unspent])
assert_equal(self.wallet.getbalance(), 0)
@cleanup
def sendall_does_ancestor_aware_funding(self):
self.log.info("Test that sendall does ancestor aware funding for unconfirmed inputs")
# higher parent feerate
self.def_wallet.sendtoaddress(address=self.wallet.getnewaddress(), amount=17, fee_rate=20)
self.wallet.syncwithvalidationinterfacequeue()
assert_equal(self.wallet.getbalances()["mine"]["untrusted_pending"], 17)
unspent = self.wallet.listunspent(minconf=0)[0]
parent_txid = unspent["txid"]
assert_equal(self.wallet.gettransaction(parent_txid)["confirmations"], 0)
res_1 = self.wallet.sendall(recipients=[self.def_wallet.getnewaddress()], inputs=[unspent], fee_rate=20, add_to_wallet=False, lock_unspents=True)
child_hex = res_1["hex"]
child_tx = self.wallet.decoderawtransaction(child_hex)
higher_parent_feerate_amount = child_tx["vout"][0]["value"]
# lower parent feerate
self.def_wallet.sendtoaddress(address=self.wallet.getnewaddress(), amount=17, fee_rate=10)
self.wallet.syncwithvalidationinterfacequeue()
assert_equal(self.wallet.getbalances()["mine"]["untrusted_pending"], 34)
unspent = self.wallet.listunspent(minconf=0)[0]
parent_txid = unspent["txid"]
assert_equal(self.wallet.gettransaction(parent_txid)["confirmations"], 0)
res_2 = self.wallet.sendall(recipients=[self.def_wallet.getnewaddress()], inputs=[unspent], fee_rate=20, add_to_wallet=False, lock_unspents=True)
child_hex = res_2["hex"]
child_tx = self.wallet.decoderawtransaction(child_hex)
lower_parent_feerate_amount = child_tx["vout"][0]["value"]
assert_greater_than(higher_parent_feerate_amount, lower_parent_feerate_amount)
# This tests needs to be the last one otherwise @cleanup will fail with "Transaction too large" error
def sendall_fails_with_transaction_too_large(self):
self.log.info("Test that sendall fails if resulting transaction is too large")
@ -460,6 +518,15 @@ class SendallTest(BitcoinTestFramework):
# Sendall only uses outputs with less than a given number of confirmation when using minconf
self.sendall_with_maxconf()
# Sendall spends unconfirmed change
self.sendall_spends_unconfirmed_change()
# Sendall spends unconfirmed inputs if they are specified
self.sendall_spends_unconfirmed_inputs_if_specified()
# Sendall does ancestor aware funding when spending an unconfirmed UTXO
self.sendall_does_ancestor_aware_funding()
# Sendall fails when many inputs result to too large transaction
self.sendall_fails_with_transaction_too_large()

Loading…
Cancel
Save