@@ -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. first ( ) . 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