Skip to content

Commit b8b1440

Browse files
committed
wip: add bench tests
1 parent 27a0c9b commit b8b1440

File tree

2 files changed

+261
-61
lines changed

2 files changed

+261
-61
lines changed

bench/benches/bench.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,9 @@ extern crate criterion;
33

44
use criterion::{criterion_group, criterion_main};
55

6-
criterion_group!(benches, bdk_chain::tx_graph::bench::filter_chain_unspents);
6+
criterion_group!(benches,
7+
bdk_chain::tx_graph::bench::filter_chain_unspents,
8+
bdk_chain::tx_graph::bench::list_canonical_txs,
9+
bdk_chain::tx_graph::bench::nested_conflicts,
10+
);
711
criterion_main!(benches);

crates/chain/src/tx_graph.rs

Lines changed: 256 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1554,113 +1554,309 @@ fn tx_outpoint_range(txid: Txid) -> RangeInclusive<OutPoint> {
15541554
OutPoint::new(txid, u32::MIN)..=OutPoint::new(txid, u32::MAX)
15551555
}
15561556

1557-
/// Bench
1558-
#[allow(unused)]
1559-
#[allow(missing_docs)]
1560-
#[cfg(bdk_bench)]
1561-
pub mod bench {
1562-
use std::str::FromStr;
1563-
1557+
#[cfg(any(test, bdk_bench))]
1558+
mod bench_util {
15641559
use bdk_core::{CheckPoint, ConfirmationBlockTime};
1565-
use bitcoin::absolute;
1566-
use bitcoin::hashes::Hash;
1567-
use bitcoin::transaction;
1568-
use bitcoin::{Address, BlockHash, Network, TxIn};
1569-
use criterion::Criterion;
1570-
use miniscript::Descriptor;
1571-
use miniscript::DescriptorPublicKey;
1560+
use bitcoin::{
1561+
absolute, constants, hashes::Hash, secp256k1::Secp256k1, transaction, BlockHash, Network,
1562+
TxIn,
1563+
};
1564+
use miniscript::{Descriptor, DescriptorPublicKey};
15721565

15731566
use super::*;
15741567
use crate::keychain_txout::KeychainTxOutIndex;
15751568
use crate::local_chain::LocalChain;
15761569
use crate::IndexedTxGraph;
15771570

15781571
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
1579-
enum Keychain {
1572+
pub enum Keychain {
15801573
External,
15811574
}
15821575

15831576
const EXTERNAL: &str = "tr([ab28dc00/86h/1h/0h]tpubDCdDtzAMZZrkwKBxwNcGCqe4FRydeD9rfMisoi7qLdraG79YohRfPW4YgdKQhpgASdvh612xXNY5xYzoqnyCgPbkpK4LSVcH5Xv4cK7johH/0/*)";
15841577

1585-
fn get_params() -> (
1586-
IndexedTxGraph<ConfirmationBlockTime, KeychainTxOutIndex<Keychain>>,
1587-
LocalChain,
1588-
) {
1589-
let genesis = bitcoin::constants::genesis_block(Network::Regtest).block_hash();
1578+
pub fn parse_descriptor(s: &str) -> Descriptor<DescriptorPublicKey> {
1579+
<Descriptor<DescriptorPublicKey>>::parse_descriptor(&Secp256k1::new(), s)
1580+
.unwrap()
1581+
.0
1582+
}
1583+
1584+
/// New tx guaranteed to have at least one output
1585+
pub fn new_tx(lt: u32) -> Transaction {
1586+
Transaction {
1587+
version: transaction::Version::TWO,
1588+
lock_time: absolute::LockTime::from_consensus(lt),
1589+
input: vec![],
1590+
output: vec![TxOut::NULL],
1591+
}
1592+
}
1593+
1594+
pub fn spk_at_index(txout_index: &KeychainTxOutIndex<Keychain>, index: u32) -> ScriptBuf {
1595+
txout_index
1596+
.get_descriptor(Keychain::External)
1597+
.unwrap()
1598+
.at_derivation_index(index)
1599+
.unwrap()
1600+
.script_pubkey()
1601+
}
1602+
1603+
type KeychainTxGraph = IndexedTxGraph<ConfirmationBlockTime, KeychainTxOutIndex<Keychain>>;
1604+
1605+
/// Initialize indexed tx-graph with one keychain and a local chain. Also insert the
1606+
/// first ancestor tx.
1607+
pub fn init_graph_chain() -> (KeychainTxGraph, LocalChain) {
1608+
let genesis = constants::genesis_block(Network::Regtest).block_hash();
15901609
let block_0 = BlockId {
15911610
height: 0,
15921611
hash: genesis,
15931612
};
1613+
// chain tip 100
15941614
let mut cp = CheckPoint::new(block_0);
1595-
let block_100 = BlockId {
1615+
let chain_tip = BlockId {
15961616
height: 100,
15971617
hash: BlockHash::all_zeros(),
15981618
};
1599-
cp = cp.push(block_100).unwrap();
1619+
cp = cp.push(chain_tip).unwrap();
16001620
let chain = LocalChain::from_tip(cp).unwrap();
16011621

1602-
let mut graph = IndexedTxGraph::new({
1603-
let mut index = KeychainTxOutIndex::new(10);
1604-
index
1605-
.insert_descriptor(Keychain::External, parse_descriptor(EXTERNAL))
1606-
.unwrap();
1607-
index
1608-
});
1622+
let desc = parse_descriptor(EXTERNAL);
1623+
let mut index = KeychainTxOutIndex::new(10);
1624+
index.insert_descriptor(Keychain::External, desc).unwrap();
1625+
let mut graph = IndexedTxGraph::new(index);
16091626

1610-
// insert funding tx (coinbase)
1611-
let addr_0 =
1612-
Address::from_str("bcrt1plhmjhj75nut38qwwm5w7xqysy25xhd4ckuv7zu5tey3nkmcwh3cqvan5mz")
1613-
.unwrap()
1614-
.assume_checked();
1615-
let tx_0 = Transaction {
1627+
// insert funding tx (coinbase) confirmed at chain tip
1628+
add_ancestor_tx(&mut graph, chain_tip, 0);
1629+
1630+
(graph, chain)
1631+
}
1632+
1633+
/// Add ancestor tx confirmed at `block_id` with `locktime` (used for uniqueness).
1634+
/// The transaction always pays 1 BTC to SPK 0.
1635+
pub fn add_ancestor_tx(graph: &mut KeychainTxGraph, block_id: BlockId, locktime: u32) {
1636+
let spk_0 = spk_at_index(&graph.index, 0);
1637+
let tx = Transaction {
1638+
input: vec![TxIn::default()],
16161639
output: vec![TxOut {
1617-
script_pubkey: addr_0.script_pubkey(),
16181640
value: Amount::ONE_BTC,
1641+
script_pubkey: spk_0,
16191642
}],
1620-
..new_tx(0)
1643+
..new_tx(locktime)
16211644
};
1622-
let txid_0 = tx_0.compute_txid();
1623-
let _ = graph.insert_tx(tx_0);
1645+
let txid = tx.compute_txid();
1646+
let _ = graph.insert_tx(tx);
16241647
let _ = graph.insert_anchor(
1625-
txid_0,
1648+
txid,
16261649
ConfirmationBlockTime {
1627-
block_id: block_100,
1650+
block_id,
16281651
confirmation_time: 100,
16291652
},
16301653
);
1654+
}
16311655

1632-
(graph, chain)
1656+
/// Add `n` conflicts to `graph` that spend the given `previous_output`, incrementing
1657+
/// the tx last-seen each time.
1658+
pub fn add_conflicts(n: u32, graph: &mut KeychainTxGraph, previous_output: OutPoint) {
1659+
let spk_1 = spk_at_index(&graph.index, 1);
1660+
for i in 1..n + 1 {
1661+
let tx = Transaction {
1662+
input: vec![TxIn {
1663+
previous_output,
1664+
..Default::default()
1665+
}],
1666+
output: vec![TxOut {
1667+
value: Amount::ONE_BTC - Amount::from_sat(i as u64 * 10),
1668+
script_pubkey: spk_1.clone(),
1669+
}],
1670+
..new_tx(i)
1671+
};
1672+
let update = TxUpdate {
1673+
txs: vec![Arc::new(tx)],
1674+
..Default::default()
1675+
};
1676+
let _ = graph.apply_update_at(update, Some(i as u64));
1677+
}
16331678
}
16341679

1635-
fn parse_descriptor(s: &str) -> miniscript::Descriptor<DescriptorPublicKey> {
1636-
<Descriptor<DescriptorPublicKey>>::parse_descriptor(
1637-
&bitcoin::secp256k1::Secp256k1::new(),
1638-
s,
1639-
)
1640-
.unwrap()
1641-
.0
1680+
/// Apply a chain of `n` unconfirmed txs where each subsequent tx spends the output
1681+
/// of the previous one.
1682+
pub fn chain_unconfirmed(n: u32, graph: &mut KeychainTxGraph, mut previous_output: OutPoint) {
1683+
for i in 0..n {
1684+
// create tx
1685+
let tx = Transaction {
1686+
input: vec![TxIn {
1687+
previous_output,
1688+
..Default::default()
1689+
}],
1690+
..new_tx(i)
1691+
};
1692+
let txid = tx.compute_txid();
1693+
let update = TxUpdate {
1694+
txs: vec![Arc::new(tx)],
1695+
..Default::default()
1696+
};
1697+
let _ = graph.apply_update_at(update, Some(21));
1698+
// store the next prevout
1699+
previous_output = OutPoint::new(txid, 0);
1700+
}
16421701
}
16431702

1644-
fn new_tx(lt: u32) -> Transaction {
1645-
Transaction {
1646-
version: transaction::Version::TWO,
1647-
lock_time: absolute::LockTime::from_consensus(lt),
1648-
input: vec![],
1649-
output: vec![],
1703+
/// Insert `n` txs where
1704+
/// - half spend ancestor A
1705+
/// - half spend ancestor B
1706+
/// - and one spends both
1707+
pub fn add_nested_conflicts(n: u32, graph: &mut KeychainTxGraph, chain: &LocalChain) {
1708+
// add ancestor B
1709+
add_ancestor_tx(graph, chain.tip().block_id(), 1);
1710+
1711+
let outpoints: Vec<_> = graph.index.outpoints().iter().map(|(_, op)| *op).collect();
1712+
assert!(outpoints.len() >= 2);
1713+
let op_a = outpoints[0];
1714+
let op_b = outpoints[1];
1715+
1716+
for i in 0..n {
1717+
let tx = if i == n / 2 {
1718+
// tx spends both A, B
1719+
Transaction {
1720+
input: vec![
1721+
TxIn {
1722+
previous_output: op_a,
1723+
..Default::default()
1724+
},
1725+
TxIn {
1726+
previous_output: op_b,
1727+
..Default::default()
1728+
},
1729+
],
1730+
..new_tx(i)
1731+
}
1732+
} else if i % 2 == 1 {
1733+
// tx spends A
1734+
Transaction {
1735+
input: vec![TxIn {
1736+
previous_output: op_a,
1737+
..Default::default()
1738+
}],
1739+
..new_tx(i)
1740+
}
1741+
} else {
1742+
// tx spends B
1743+
Transaction {
1744+
input: vec![TxIn {
1745+
previous_output: op_b,
1746+
..Default::default()
1747+
}],
1748+
..new_tx(i)
1749+
}
1750+
};
1751+
1752+
let update = TxUpdate {
1753+
txs: vec![Arc::new(tx)],
1754+
..Default::default()
1755+
};
1756+
let _ = graph.apply_update_at(update, Some(i as u64));
16501757
}
16511758
}
16521759

1760+
#[test]
1761+
fn test_add_conflicts() {
1762+
let (mut graph, chain) = init_graph_chain();
1763+
let txouts: Vec<_> = graph.graph().all_txouts().collect();
1764+
assert_eq!(txouts.len(), 1);
1765+
let prevout = txouts.iter().next().unwrap().0;
1766+
add_conflicts(3, &mut graph, prevout);
1767+
1768+
let unspent = graph.graph().filter_chain_unspents(
1769+
&chain,
1770+
chain.tip().block_id(),
1771+
graph.index.outpoints().clone(),
1772+
);
1773+
assert_eq!(unspent.count(), 1);
1774+
}
1775+
1776+
#[test]
1777+
fn test_chain_unconfirmed() {
1778+
let (mut graph, _) = init_graph_chain();
1779+
let (prevout, _txout) = graph
1780+
.graph()
1781+
.all_txouts()
1782+
.find(|(_, txout)| txout.value == Amount::ONE_BTC)
1783+
.expect("initial graph should have txout");
1784+
chain_unconfirmed(5, &mut graph, prevout);
1785+
assert_eq!(graph.graph().txs.len(), 6); // 1 onchain + 5 unconfirmed
1786+
}
1787+
1788+
#[test]
1789+
#[rustfmt::skip]
1790+
fn test_nested() {
1791+
let (mut graph, chain) = init_graph_chain();
1792+
let chain_tip = chain.tip().block_id();
1793+
let n = 5;
1794+
add_nested_conflicts(n, &mut graph, &chain);
1795+
1796+
let op = graph.index.outpoints().clone();
1797+
assert_eq!(graph.graph().full_txs().count() as u32, 2 + n); // 2 onchain + n unconfirmed
1798+
assert_eq!(graph.graph().filter_chain_txouts(&chain, chain_tip, op).count(), 2); // 2 onchain
1799+
assert_eq!(graph.graph().list_canonical_txs(&chain, chain_tip).count(), 2 + 2); // 2 onchain + 2 unconfirmed
1800+
}
1801+
}
1802+
1803+
/// Bench
1804+
#[allow(missing_docs)]
1805+
#[cfg(bdk_bench)]
1806+
pub mod bench {
1807+
use std::hint::black_box;
1808+
1809+
use criterion::Criterion;
1810+
1811+
use super::bench_util::*;
1812+
1813+
#[inline(never)]
16531814
pub fn filter_chain_unspents(bench: &mut Criterion) {
1654-
let (graph, chain) = get_params();
1655-
// TODO: insert conflicts
1815+
let (mut graph, chain) = black_box(init_graph_chain());
1816+
let prevout = graph.graph().all_txouts().next().unwrap().0;
1817+
black_box(add_conflicts(1000, &mut graph, prevout));
1818+
16561819
bench.bench_function("filter_chain_unspents", |b| {
16571820
b.iter(|| {
1658-
TxGraph::filter_chain_unspents(
1659-
graph.graph(),
1821+
let unspent = graph.graph().filter_chain_unspents(
16601822
&chain,
16611823
chain.tip().block_id(),
16621824
graph.index.outpoints().clone(),
1663-
)
1825+
);
1826+
assert_eq!(unspent.count(), 1);
1827+
})
1828+
});
1829+
}
1830+
1831+
#[inline(never)]
1832+
pub fn list_canonical_txs(bench: &mut Criterion) {
1833+
let (mut graph, chain) = black_box(init_graph_chain());
1834+
let prevout = graph.graph().all_txouts().next().unwrap().0;
1835+
black_box(chain_unconfirmed(100, &mut graph, prevout));
1836+
1837+
bench.bench_function("list_canonical_txs", |b| {
1838+
b.iter(|| {
1839+
let txs = graph
1840+
.graph()
1841+
.list_canonical_txs(&chain, chain.tip().block_id());
1842+
// 1 onchain + 100 unconfirmed
1843+
assert_eq!(txs.count(), 1 + 100);
1844+
})
1845+
});
1846+
}
1847+
1848+
#[inline(never)]
1849+
pub fn nested_conflicts(bench: &mut Criterion) {
1850+
let (mut graph, chain) = black_box(init_graph_chain());
1851+
black_box(add_nested_conflicts(2000, &mut graph, &chain));
1852+
let graph = graph.graph();
1853+
let chain_tip = chain.tip().block_id();
1854+
1855+
bench.bench_function("nested_conflicts", |b| {
1856+
b.iter(|| {
1857+
let txs = graph.list_canonical_txs(&chain, chain_tip);
1858+
// 2 onchain + 2 unconfirmed
1859+
assert_eq!(txs.count(), 4);
16641860
})
16651861
});
16661862
}

0 commit comments

Comments
 (0)