bitcoin: drop spend txid for coins whose spending tx gets RBF'd

Even if the RBF does not spend this coin anymore.
This commit is contained in:
Antoine Poinsot 2023-11-16 17:38:25 +01:00
parent 544167dee4
commit cf7c4fbac9
No known key found for this signature in database
GPG Key ID: E13FC145CD3F4304
2 changed files with 30 additions and 24 deletions

View File

@ -269,28 +269,36 @@ impl BitcoinInterface for d::BitcoinD {
// If a conflicting transaction was confirmed instead, replace the txid of the
// spender for this coin with it and mark it as confirmed.
// If a conflicting transaction which doesn't spend this coin was mined or accepted in
// our local mempool, mark this spend as expired.
enum Conflict {
// A replacement spending transaction was confirmed.
Replaced((bitcoin::Txid, Block)),
// A transaction conflicting with the former spending transaction was confirmed,
// but it doesn't spend this outpoint anymore.
// A transaction conflicting with the former spending transaction was confirmed or
// included in our local mempool.
Dropped,
}
let conflict = res.conflicting_txs.iter().find_map(|txid| {
tx_getter.get_transaction(txid).and_then(|tx| {
// FIXME: if a conflict was mined we should somehow wipe the spend_txid of this
// coin.
tx.block.map(|block| {
// Being part of our watchonly wallet isn't enough, as it could be a
// conflicting transaction which spends a different set of coins. Make sure
// it does actually spend this coin.
for txin in tx.tx.input {
if &txin.previous_output == op {
return Conflict::Replaced((*txid, block));
tx.block
.map(|block| {
// Being part of our watchonly wallet isn't enough, as it could be a
// conflicting transaction which spends a different set of coins. Make sure
// it does actually spend this coin.
for txin in tx.tx.input {
if &txin.previous_output == op {
return Conflict::Replaced((*txid, block));
}
}
}
Conflict::Dropped
})
Conflict::Dropped
})
.or_else(|| {
if self.is_in_mempool(txid) {
Some(Conflict::Dropped)
} else {
None
}
})
})
});
match conflict {

View File

@ -454,7 +454,7 @@ def test_spend_replacement(lianad, bitcoind):
# Now RBF the first transaction by the second one. The third coin should be
# newly marked as spending, the second one's spend_txid should be updated and
# the first one should not be updated until the second one is mined.
# the first one's spend txid should be dropped.
second_txid = sign_and_broadcast_psbt(lianad, second_psbt)
wait_for(
lambda: all(
@ -462,9 +462,9 @@ def test_spend_replacement(lianad, bitcoind):
for c in lianad.rpc.listcoins([], second_outpoints)["coins"]
)
)
assert (
lianad.rpc.listcoins([], [first_outpoints[0]])["coins"][0]["spend_info"]["txid"]
== first_txid
wait_for(
lambda: lianad.rpc.listcoins([], [first_outpoints[0]])["coins"][0]["spend_info"]
is None
)
# Now RBF the second transaction with a send-to-self, just because.
@ -476,11 +476,10 @@ def test_spend_replacement(lianad, bitcoind):
)
)
assert (
lianad.rpc.listcoins([], [first_outpoints[0]])["coins"][0]["spend_info"]["txid"]
== first_txid
lianad.rpc.listcoins([], [first_outpoints[0]])["coins"][0]["spend_info"] is None
)
# Once the RBF gets mined, the first coin's spend txid is wiped.
# Once the RBF is mined, we detect it as confirmed and the first coin is still unspent.
bitcoind.generate_block(1, wait_for_mempool=third_txid)
wait_for(
lambda: all(
@ -488,7 +487,6 @@ def test_spend_replacement(lianad, bitcoind):
for c in lianad.rpc.listcoins([], second_outpoints)["coins"]
)
)
wait_for(
lambda: lianad.rpc.listcoins([], [first_outpoints[0]])["coins"][0]["spend_info"]
is None
assert (
lianad.rpc.listcoins([], [first_outpoints[0]])["coins"][0]["spend_info"] is None
)