Skip to content

Commit fbe5e12

Browse files
committed
Merge bitcoin/bitcoin#26675: wallet: For feebump, ignore abandoned descendant spends
f9ce0ea For feebump, ignore abandoned descendant spends (John Moffett) Pull request description: Closes #26667 To be eligible for fee-bumping, a transaction must not have any of its outputs (eg - change) spent in other unconfirmed transactions in the wallet. This behavior is currently [enforced](https://github.com/bitcoin/bitcoin/blob/9e229a542ff2107be43eff2e4b992841367f0366/src/wallet/feebumper.cpp#L25-L28) and [tested](https://github.com/bitcoin/bitcoin/blob/9e229a542ff2107be43eff2e4b992841367f0366/test/functional/wallet_bumpfee.py#L270-L286). However, this check shouldn't apply to spends in abandoned descendant transactions, as explained by #26667. `CWallet::IsSpent` already carves out an exception for abandoned transactions, so we can just use that. I've also added a new test to cover this case. ACKs for top commit: Sjors: re-utACK f9ce0ea achow101: ACK f9ce0ea furszy: ACK f9ce0ea Tree-SHA512: 19d957d1cf6747668bb114e27a305027bfca5a9bed2b1d9cc9e1b0bd4666486c7c4b60b045a7fe677eb9734d746f5de76390781fb1e9e0bceb4a46d20acd1749
2 parents 2f6a8e5 + f9ce0ea commit fbe5e12

File tree

2 files changed

+31
-2
lines changed

2 files changed

+31
-2
lines changed

src/wallet/wallet.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -647,8 +647,7 @@ bool CWallet::HasWalletSpend(const CTransactionRef& tx) const
647647
AssertLockHeld(cs_wallet);
648648
const uint256& txid = tx->GetHash();
649649
for (unsigned int i = 0; i < tx->vout.size(); ++i) {
650-
auto iter = mapTxSpends.find(COutPoint(txid, i));
651-
if (iter != mapTxSpends.end()) {
650+
if (IsSpent(COutPoint(txid, i))) {
652651
return true;
653652
}
654653
}

test/functional/wallet_bumpfee.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ def run_test(self):
8888
test_nonrbf_bumpfee_fails(self, peer_node, dest_address)
8989
test_notmine_bumpfee(self, rbf_node, peer_node, dest_address)
9090
test_bumpfee_with_descendant_fails(self, rbf_node, rbf_node_address, dest_address)
91+
test_bumpfee_with_abandoned_descendant_succeeds(self, rbf_node, rbf_node_address, dest_address)
9192
test_dust_to_fee(self, rbf_node, dest_address)
9293
test_watchonly_psbt(self, peer_node, rbf_node, dest_address)
9394
test_rebumping(self, rbf_node, dest_address)
@@ -286,6 +287,35 @@ def test_bumpfee_with_descendant_fails(self, rbf_node, rbf_node_address, dest_ad
286287
self.clear_mempool()
287288

288289

290+
def test_bumpfee_with_abandoned_descendant_succeeds(self, rbf_node, rbf_node_address, dest_address):
291+
self.log.info('Test that fee can be bumped when it has abandoned descendant')
292+
# parent is send-to-self, so we don't have to check which output is change when creating the child tx
293+
parent_id = spend_one_input(rbf_node, rbf_node_address)
294+
# Submit child transaction with low fee
295+
child_id = rbf_node.send(outputs={dest_address: 0.00020000},
296+
options={"inputs": [{"txid": parent_id, "vout": 0}], "fee_rate": 2})["txid"]
297+
assert child_id in rbf_node.getrawmempool()
298+
299+
# Restart the node with higher min relay fee so the descendant tx is no longer in mempool so that we can abandon it
300+
self.restart_node(1, ['-minrelaytxfee=0.00005'] + self.extra_args[1])
301+
rbf_node.walletpassphrase(WALLET_PASSPHRASE, WALLET_PASSPHRASE_TIMEOUT)
302+
self.connect_nodes(1, 0)
303+
assert parent_id in rbf_node.getrawmempool()
304+
assert child_id not in rbf_node.getrawmempool()
305+
# Should still raise an error even if not in mempool
306+
assert_raises_rpc_error(-8, "Transaction has descendants in the wallet", rbf_node.bumpfee, parent_id)
307+
# Now abandon the child transaction and bump the original
308+
rbf_node.abandontransaction(child_id)
309+
bumped_result = rbf_node.bumpfee(parent_id, {"fee_rate": HIGH})
310+
assert bumped_result['txid'] in rbf_node.getrawmempool()
311+
assert parent_id not in rbf_node.getrawmempool()
312+
# Cleanup
313+
self.restart_node(1, self.extra_args[1])
314+
rbf_node.walletpassphrase(WALLET_PASSPHRASE, WALLET_PASSPHRASE_TIMEOUT)
315+
self.connect_nodes(1, 0)
316+
self.clear_mempool()
317+
318+
289319
def test_small_output_with_feerate_succeeds(self, rbf_node, dest_address):
290320
self.log.info('Testing small output with feerate bump succeeds')
291321

0 commit comments

Comments
 (0)