Skip to content

Commit b17216b

Browse files
committed
feat(chain): Signed txs should displace unsigned txs in TxGraph.
1 parent 71bf53d commit b17216b

File tree

2 files changed

+84
-5
lines changed

2 files changed

+84
-5
lines changed

crates/chain/src/tx_graph.rs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -624,11 +624,20 @@ impl<A: Anchor> TxGraph<A> {
624624
let tx_node = self.txs.entry(txid).or_default();
625625
match tx_node {
626626
TxNodeInternal::Whole(existing_tx) => {
627-
debug_assert_eq!(
628-
existing_tx.as_ref(),
629-
tx.as_ref(),
630-
"tx of same txid should never change"
631-
);
627+
// We want to be able to replace an unsigned tx with a signed tx.
628+
// The tx with more weight has precedence (and tiebreak with the actual tx data).
629+
// We can also check whether the witness is valid and also prioritize signatures
630+
// with less weight, but that is more work and this solution is good enough.
631+
if existing_tx.as_ref() != tx.as_ref() {
632+
let (_, tx_with_precedence) = Ord::max(
633+
(existing_tx.weight(), existing_tx.as_ref()),
634+
(tx.weight(), tx.as_ref()),
635+
);
636+
if tx_with_precedence == tx.as_ref() {
637+
*existing_tx = tx.clone();
638+
changeset.txs.insert(tx);
639+
}
640+
}
632641
}
633642
partial_tx => {
634643
for txin in &tx.input {

crates/chain/tests/test_tx_graph.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ use bdk_chain::{
1010
Anchor, ChainOracle, ChainPosition, Merge,
1111
};
1212
use bdk_testenv::{block_id, hash, utils::new_tx};
13+
use bitcoin::hex::FromHex;
14+
use bitcoin::Witness;
1315
use bitcoin::{
1416
absolute, hashes::Hash, transaction, Amount, BlockHash, OutPoint, ScriptBuf, SignedAmount,
1517
Transaction, TxIn, TxOut, Txid,
@@ -284,6 +286,74 @@ fn insert_tx_displaces_txouts() {
284286
assert_eq!(tx_graph.get_txout(outpoint), Some(txout));
285287
}
286288

289+
#[test]
290+
fn insert_signed_tx_displaces_unsigned() {
291+
let previous_output = OutPoint::new(hash!("prev"), 2);
292+
let unsigned_tx = Transaction {
293+
version: transaction::Version::ONE,
294+
lock_time: absolute::LockTime::ZERO,
295+
input: vec![TxIn {
296+
previous_output,
297+
script_sig: ScriptBuf::default(),
298+
sequence: transaction::Sequence::ENABLE_RBF_NO_LOCKTIME,
299+
witness: Witness::default(),
300+
}],
301+
output: vec![TxOut {
302+
value: Amount::from_sat(24_000),
303+
script_pubkey: ScriptBuf::default(),
304+
}],
305+
};
306+
let signed_tx = Transaction {
307+
input: vec![TxIn {
308+
previous_output,
309+
script_sig: ScriptBuf::default(),
310+
sequence: transaction::Sequence::ENABLE_RBF_NO_LOCKTIME,
311+
witness: Witness::from_slice(&[
312+
// Random witness from mempool.space
313+
Vec::from_hex("d59118058bf9e8604cec5c0b4a13430b07286482784da313594e932faad074dc4bd27db7cbfff9ad32450db097342d0148ec21c3033b0c27888fd2fd0de2e9b5")
314+
.unwrap(),
315+
]),
316+
}],
317+
..unsigned_tx.clone()
318+
};
319+
320+
// Signed tx must displace unsigned.
321+
{
322+
let mut tx_graph = TxGraph::<ConfirmationBlockTime>::default();
323+
let changeset_insert_unsigned = tx_graph.insert_tx(unsigned_tx.clone());
324+
let changeset_insert_signed = tx_graph.insert_tx(signed_tx.clone());
325+
assert_eq!(
326+
changeset_insert_unsigned,
327+
ChangeSet {
328+
txs: [Arc::new(unsigned_tx.clone())].into(),
329+
..Default::default()
330+
}
331+
);
332+
assert_eq!(
333+
changeset_insert_signed,
334+
ChangeSet {
335+
txs: [Arc::new(signed_tx.clone())].into(),
336+
..Default::default()
337+
}
338+
);
339+
}
340+
341+
// Unsigned tx must not displace signed.
342+
{
343+
let mut tx_graph = TxGraph::<ConfirmationBlockTime>::default();
344+
let changeset_insert_signed = tx_graph.insert_tx(signed_tx.clone());
345+
let changeset_insert_unsigned = tx_graph.insert_tx(unsigned_tx.clone());
346+
assert_eq!(
347+
changeset_insert_signed,
348+
ChangeSet {
349+
txs: [Arc::new(signed_tx)].into(),
350+
..Default::default()
351+
}
352+
);
353+
assert!(changeset_insert_unsigned.is_empty());
354+
}
355+
}
356+
287357
#[test]
288358
fn insert_txout_does_not_displace_tx() {
289359
let mut tx_graph = TxGraph::<ConfirmationBlockTime>::default();

0 commit comments

Comments
 (0)