Skip to content

Commit

Permalink
feat: implement better tests for transaction conflict handling
Browse files Browse the repository at this point in the history
  • Loading branch information
LagginTimes committed Aug 15, 2023
1 parent feafaac commit 70d010c
Show file tree
Hide file tree
Showing 4 changed files with 792 additions and 6 deletions.
2 changes: 1 addition & 1 deletion crates/chain/src/indexed_tx_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::{
/// A struct that combines [`TxGraph`] and an [`Indexer`] implementation.
///
/// This structure ensures that [`TxGraph`] and [`Indexer`] are updated atomically.
#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct IndexedTxGraph<A, I> {
/// Transaction index.
pub index: I,
Expand Down
3 changes: 3 additions & 0 deletions crates/chain/tests/common/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
mod tx_template;
pub use tx_template::*;

#[allow(unused_macros)]
macro_rules! h {
($index:literal) => {{
Expand Down
138 changes: 138 additions & 0 deletions crates/chain/tests/common/tx_template.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
use std::collections::HashMap;

use bdk_chain::{
indexed_tx_graph::IndexedTxGraph, keychain::KeychainTxOutIndex, ConfirmationHeightAnchor,
};
use bitcoin::{
hashes::Hash, locktime::absolute::LockTime, OutPoint, ScriptBuf, Sequence, Transaction, TxIn,
TxOut, Txid, Witness,
};

/// Transaction template.
pub struct TxTemplate<'a, K, A> {
/// Uniquely identifies the transaction, before it can have a txid.
pub tx_name: &'a str,
pub inputs: &'a [TxInTemplate<'a>],
pub outputs: &'a [TxOutTemplate<K>],
pub anchors: &'a [A],
pub last_seen: Option<u64>,
}

impl<'a, K, A> Default for TxTemplate<'a, K, A> {
fn default() -> Self {
Self {
tx_name: Default::default(),
inputs: Default::default(),
outputs: Default::default(),
anchors: Default::default(),
last_seen: Default::default(),
}
}
}

impl<'a, K, A> Copy for TxTemplate<'a, K, A> {}

impl<'a, K, A> Clone for TxTemplate<'a, K, A> {
fn clone(&self) -> Self {
*self
}
}

#[allow(dead_code)]
pub enum TxInTemplate<'a> {
/// This will give a random txid and vout.
Bogus,

/// This is used for coinbase transactions because they do not have previous outputs.
Coinbase,

/// Contains the `tx_name` and `vout` that we are spending. The rule is that we must only spend
/// a previous transaction.
PrevTx(&'a str, usize),
}

pub struct TxOutTemplate<K> {
pub value: u64,
pub owned_spk: Option<(K, u32)>, // some = derive a spk from K, none = random spk
}

#[allow(dead_code)]
pub fn init_graph<'a>(
graph: IndexedTxGraph<ConfirmationHeightAnchor, KeychainTxOutIndex<()>>,
tx_templates: impl IntoIterator<Item = &'a TxTemplate<'a, (), ConfirmationHeightAnchor>>,
) -> (
IndexedTxGraph<ConfirmationHeightAnchor, KeychainTxOutIndex<()>>,
HashMap<&'a str, Txid>,
) {
let mut tx_ids = HashMap::<&'a str, Txid>::new();
let mut graph = graph;

for (bogus_txin_vout, tx_tmp) in tx_templates.into_iter().enumerate() {
let tx = Transaction {
version: 0,
lock_time: LockTime::ZERO,
input: tx_tmp
.inputs
.iter()
.map(|spend_tmp| match spend_tmp {
TxInTemplate::Bogus => TxIn {
// #TODO have actual random data
previous_output: OutPoint::new(Txid::all_zeros(), bogus_txin_vout as u32),
script_sig: ScriptBuf::new(),
sequence: Sequence::default(),
witness: Witness::new(),
},
TxInTemplate::Coinbase => TxIn {
previous_output: OutPoint::null(),
script_sig: ScriptBuf::new(),
sequence: Sequence::MAX,
witness: Witness::new(),
},
TxInTemplate::PrevTx(prev_name, prev_vout) => {
let prev_txid = tx_ids.get(prev_name).expect(
"txin template must spend from tx of template that comes before",
);
TxIn {
previous_output: OutPoint::new(*prev_txid, *prev_vout as _),
script_sig: ScriptBuf::new(),
sequence: Sequence::default(),
witness: Witness::new(),
}
}
})
.collect(),
output: tx_tmp
.outputs
.iter()
.map(|output_tmp| match &output_tmp.owned_spk {
None => TxOut {
value: output_tmp.value,
script_pubkey: ScriptBuf::new(),
},
Some((keychain, index)) => {
let descriptor = graph
.index
.keychains()
.get(keychain)
.expect("keychain must exist");
TxOut {
value: output_tmp.value,
script_pubkey: descriptor
.at_derivation_index(*index)
.unwrap()
.script_pubkey(),
}
}
})
.collect(),
};

tx_ids.insert(tx_tmp.tx_name, tx.clone().txid());
let _ = graph.insert_relevant_txs(
[&tx].iter().map(|tx| (*tx, tx_tmp.anchors.iter().cloned())),
tx_tmp.last_seen,
);
}

(graph, tx_ids)
}
Loading

0 comments on commit 70d010c

Please sign in to comment.