Skip to content

Commit 944e3cc

Browse files
committed
feat(chain): Signed txs should displace unsigned txs in TxGraph.
1 parent 43f0f8d commit 944e3cc

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
@@ -590,11 +590,20 @@ impl<A: Anchor> TxGraph<A> {
590590
let tx_node = self.txs.entry(txid).or_default();
591591
match tx_node {
592592
TxNodeInternal::Whole(existing_tx) => {
593-
debug_assert_eq!(
594-
existing_tx.as_ref(),
595-
tx.as_ref(),
596-
"tx of same txid should never change"
597-
);
593+
// We want to be able to replace an unsigned tx with a signed tx.
594+
// The tx with more weight has precedence (and tiebreak with the actual tx data).
595+
// We can also check whether the witness is valid and also prioritize signatures
596+
// with less weight, but that is more work and this solution is good enough.
597+
if existing_tx.as_ref() != tx.as_ref() {
598+
let (_, tx_with_precedence) = Ord::max(
599+
(existing_tx.weight(), existing_tx.as_ref()),
600+
(tx.weight(), tx.as_ref()),
601+
);
602+
if tx_with_precedence == tx.as_ref() {
603+
*existing_tx = tx.clone();
604+
changeset.txs.insert(tx);
605+
}
606+
}
598607
}
599608
partial_tx => {
600609
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,
@@ -282,6 +284,74 @@ fn insert_tx_displaces_txouts() {
282284
assert_eq!(tx_graph.get_txout(outpoint), Some(txout));
283285
}
284286

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

0 commit comments

Comments
 (0)