Skip to content
This repository has been archived by the owner on Mar 14, 2023. It is now read-only.

Commit

Permalink
Test ChainGraph's eviction logic
Browse files Browse the repository at this point in the history
As mentioned by @LLFourn in #76:

I think #74 also introduced a bug where an update can evict a
transaction in the chain without invalidating its block by having a new
transaction that conflicts with it. We shouldn't allow this and this
should mean the update is invalid. Test for this too.

Additionally, `ChainGraph` logic now checks both graphs (original and
update) for full tx during determining txs to evict.
  • Loading branch information
evanlinjin committed Dec 7, 2022
1 parent e98c870 commit e4639fd
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 3 deletions.
2 changes: 1 addition & 1 deletion bdk_core/src/chain_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ impl<I: ChainIndex> ChainGraph<I> {
// skip txids that already exist in the original chain (for efficiency)
.filter(|&(_, txid)| self.chain.tx_index(*txid).is_none())
// skip txids that do not have full txs, as we can't check for conflicts for them
.filter_map(|&(_, txid)| update.graph.tx(txid))
.filter_map(|&(_, txid)| update.graph.tx(txid).or_else(|| self.graph.tx(txid)))
// choose original txs that conflicts with the update
.flat_map(|update_tx| {
self.graph
Expand Down
115 changes: 113 additions & 2 deletions bdk_core/tests/test_chain_graph.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
use bdk_core::{chain_graph::ChainGraph, TxHeight};
use bitcoin::{OutPoint, PackedLockTime, Transaction, TxIn, TxOut};
#[macro_use]
mod common;

use bdk_core::{
chain_graph::{ChainGraph, ChangeSet},
sparse_chain,
tx_graph::Additions,
BlockId, TxHeight,
};
use bitcoin::{OutPoint, PackedLockTime, Script, Sequence, Transaction, TxIn, TxOut, Witness};

#[test]
fn test_spent_by() {
Expand Down Expand Up @@ -47,3 +55,106 @@ fn test_spent_by() {
assert_eq!(cg1.spent_by(op), Some((&TxHeight::Unconfirmed, tx2.txid())));
assert_eq!(cg2.spent_by(op), Some((&TxHeight::Unconfirmed, tx3.txid())));
}

#[test]
fn update_evicts_conflicting_tx() {
let cp_a = BlockId {
height: 0,
hash: h!("A"),
};
let cp_b = BlockId {
height: 1,
hash: h!("B"),
};

let tx_a = Transaction {
version: 0x01,
lock_time: PackedLockTime(0),
input: vec![],
output: vec![TxOut::default()],
};

let tx_b = Transaction {
version: 0x01,
lock_time: PackedLockTime(0),
input: vec![TxIn {
previous_output: OutPoint::new(tx_a.txid(), 0),
script_sig: Script::new(),
sequence: Sequence::default(),
witness: Witness::new(),
}],
output: vec![TxOut::default()],
};

let tx_b2 = Transaction {
version: 0x02,
lock_time: PackedLockTime(0),
input: vec![TxIn {
previous_output: OutPoint::new(tx_a.txid(), 0),
script_sig: Script::new(),
sequence: Sequence::default(),
witness: Witness::new(),
}],
output: vec![TxOut::default(), TxOut::default()],
};

let cg1 = {
let mut cg = ChainGraph::default();
cg.insert_checkpoint(cp_a).expect("should insert cp");
cg.insert_tx(tx_a.clone(), TxHeight::Confirmed(0))
.expect("should insert tx");
cg.insert_tx(tx_b.clone(), TxHeight::Unconfirmed)
.expect("should insert tx");
cg
};
let cg2 = {
let mut cg = ChainGraph::default();
cg.insert_tx(tx_b2.clone(), TxHeight::Unconfirmed)
.expect("should insert tx");
cg
};
assert_eq!(
cg1.determine_changeset(&cg2),
Ok(ChangeSet::<TxHeight> {
chain: sparse_chain::ChangeSet {
checkpoints: Default::default(),
txids: [
(tx_b.txid(), None),
(tx_b2.txid(), Some(TxHeight::Unconfirmed))
]
.into()
},
graph: Additions {
tx: [tx_b2.clone()].into(),
txout: [].into()
},
}),
"tx should be evicted from mempool"
);

let cg1 = {
let mut cg = ChainGraph::default();
cg.insert_checkpoint(cp_a).expect("should insert cp");
cg.insert_checkpoint(cp_b).expect("should insert cp");
cg.insert_tx(tx_a.clone(), TxHeight::Confirmed(0))
.expect("should insert tx");
cg.insert_tx(tx_b.clone(), TxHeight::Confirmed(1))
.expect("should insert tx");
cg
};
let cg2 = {
let mut cg = ChainGraph::default();
cg.insert_tx(tx_b2.clone(), TxHeight::Unconfirmed)
.expect("should insert tx");
cg
};
assert_eq!(
cg1.determine_changeset(&cg2),
Err(sparse_chain::UpdateFailure::InconsistentTx {
inconsistent_txid: tx_b.txid(),
original_index: TxHeight::Confirmed(1),
update_index: None
}),
"fail if tx is evicted from valid block"
);
}

0 comments on commit e4639fd

Please sign in to comment.