@@ -9,6 +9,7 @@ use bdk_chain::{
99 tx_graph:: { ChangeSet , TxGraph } ,
1010 Anchor , ChainOracle , ChainPosition , Merge ,
1111} ;
12+ use bdk_testenv:: local_chain;
1213use bdk_testenv:: { block_id, hash, utils:: new_tx} ;
1314use bitcoin:: hex:: FromHex ;
1415use bitcoin:: Witness ;
@@ -1540,3 +1541,265 @@ fn test_get_first_seen_of_a_tx() {
15401541 let first_seen = graph. get_tx_node ( txid) . unwrap ( ) . first_seen ;
15411542 assert_eq ! ( first_seen, Some ( seen_at) ) ;
15421543}
1544+
1545+ struct Scenario < ' a > {
1546+ /// Name of the test scenario
1547+ name : & ' a str ,
1548+ /// Transaction templates
1549+ tx_templates : & ' a [ TxTemplate < ' a , BlockId > ] ,
1550+ /// Names of txs that must exist in the output of `list_canonical_txs`
1551+ exp_chain_txs : Vec < & ' a str > ,
1552+ }
1553+
1554+ #[ test]
1555+ fn test_list_canonical_txs_topological_order ( ) {
1556+ // chain
1557+ let local_chain = local_chain ! (
1558+ ( 0 , hash!( "A" ) ) ,
1559+ ( 1 , hash!( "B" ) ) ,
1560+ ( 2 , hash!( "C" ) ) ,
1561+ ( 3 , hash!( "D" ) ) ,
1562+ ( 4 , hash!( "E" ) ) ,
1563+ ( 5 , hash!( "F" ) ) ,
1564+ ( 6 , hash!( "G" ) )
1565+ ) ;
1566+ let chain_tip = local_chain. tip ( ) . block_id ( ) ;
1567+
1568+ let scenarios = [
1569+ // a0
1570+ // \
1571+ // b0
1572+ // \
1573+ // c0
1574+ Scenario {
1575+ name : "C spend A, B spend A, and A is in the best chain" ,
1576+ tx_templates : & [
1577+ TxTemplate {
1578+ tx_name : "A" ,
1579+ inputs : & [ ] ,
1580+ outputs : & [ TxOutTemplate :: new ( 10000 , Some ( 0 ) ) ] ,
1581+ anchors : & [ block_id ! ( 1 , "B" ) ] ,
1582+ last_seen : None ,
1583+ assume_canonical : false ,
1584+ } ,
1585+ TxTemplate {
1586+ tx_name : "B" ,
1587+ inputs : & [ TxInTemplate :: PrevTx ( "A" , 0 ) ] ,
1588+ outputs : & [ TxOutTemplate :: new ( 5000 , Some ( 0 ) ) ] ,
1589+ anchors : & [ block_id ! ( 1 , "B" ) ] ,
1590+ last_seen : None ,
1591+ assume_canonical : false ,
1592+ } ,
1593+ TxTemplate {
1594+ tx_name : "C" ,
1595+ inputs : & [ TxInTemplate :: PrevTx ( "B" , 0 ) ] ,
1596+ outputs : & [ TxOutTemplate :: new ( 2500 , Some ( 0 ) ) ] ,
1597+ anchors : & [ block_id ! ( 1 , "B" ) ] ,
1598+ last_seen : None ,
1599+ assume_canonical : false ,
1600+ } ,
1601+ ] ,
1602+ exp_chain_txs : Vec :: from ( [ "A" , "B" , "C" ] ) ,
1603+ } ,
1604+ // a0
1605+ // / \
1606+ // b0 b1
1607+ // / \ \
1608+ // c0 \ c1
1609+ // \ /
1610+ // d0
1611+ Scenario {
1612+ name : "c0 spend b0, b0 spend a0, d0 spends both b0 and c1, c1 spend b1, b1 spend a0, and a0 is in the best chain" ,
1613+ tx_templates : & [ TxTemplate {
1614+ tx_name : "a0" ,
1615+ inputs : & [ ] ,
1616+ outputs : & [ TxOutTemplate :: new ( 10000 , Some ( 0 ) ) , TxOutTemplate :: new ( 10000 , Some ( 1 ) ) ] ,
1617+ anchors : & [ block_id ! ( 1 , "B" ) ] ,
1618+ last_seen : None ,
1619+ assume_canonical : false ,
1620+ } , TxTemplate {
1621+ tx_name : "b0" ,
1622+ inputs : & [ TxInTemplate :: PrevTx ( "a0" , 0 ) ] ,
1623+ outputs : & [ TxOutTemplate :: new ( 10000 , Some ( 0 ) ) , TxOutTemplate :: new ( 10000 , Some ( 1 ) ) ] ,
1624+ anchors : & [ block_id ! ( 1 , "B" ) ] ,
1625+ last_seen : None ,
1626+ assume_canonical : false ,
1627+ } ,
1628+ TxTemplate {
1629+ tx_name : "c0" ,
1630+ inputs : & [ TxInTemplate :: PrevTx ( "b0" , 0 ) ] ,
1631+ outputs : & [ TxOutTemplate :: new ( 5000 , Some ( 0 ) ) ] ,
1632+ anchors : & [ block_id ! ( 1 , "B" ) ] ,
1633+ last_seen : None ,
1634+ assume_canonical : false ,
1635+ } ,
1636+ TxTemplate {
1637+ tx_name : "b1" ,
1638+ inputs : & [ TxInTemplate :: PrevTx ( "a0" , 1 ) ] ,
1639+ outputs : & [ TxOutTemplate :: new ( 10000 , Some ( 0 ) ) ] ,
1640+ anchors : & [ block_id ! ( 1 , "B" ) ] ,
1641+ last_seen : None ,
1642+ assume_canonical : false ,
1643+ } ,
1644+ TxTemplate {
1645+ tx_name : "c1" ,
1646+ inputs : & [ TxInTemplate :: PrevTx ( "b1" , 0 ) ] ,
1647+ outputs : & [ TxOutTemplate :: new ( 10000 , Some ( 0 ) ) ] ,
1648+ anchors : & [ block_id ! ( 1 , "B" ) ] ,
1649+ last_seen : None ,
1650+ assume_canonical : false ,
1651+ } ,
1652+ TxTemplate {
1653+ tx_name : "d0" ,
1654+ inputs : & [ TxInTemplate :: PrevTx ( "b0" , 1 ) , TxInTemplate :: PrevTx ( "c1" , 0 ) , ] ,
1655+ outputs : & [ TxOutTemplate :: new ( 10000 , Some ( 0 ) ) ] ,
1656+ anchors : & [ block_id ! ( 1 , "B" ) ] ,
1657+ last_seen : None ,
1658+ assume_canonical : false ,
1659+ } ] ,
1660+ exp_chain_txs : Vec :: from ( [ "a0" , "b0" , "c0" , "b1" , "c1" , "d0" ] ) ,
1661+ } ,
1662+ // a0
1663+ // / \ \
1664+ // e0 / b1
1665+ // / / \
1666+ // f0 / \
1667+ // \/ \
1668+ // b0 \
1669+ // / \ /
1670+ // c0 \ c1
1671+ // \ /
1672+ // d0
1673+ Scenario {
1674+ name : "c0 spend b0, b0 spends both f0 and a0, f0 spend e0, e0 spend a0, d0 spends both b0 and c1, c1 spend b1, b1 spend a0, and a0 is in the best chain" ,
1675+ tx_templates : & [ TxTemplate {
1676+ tx_name : "a0" ,
1677+ inputs : & [ ] ,
1678+ outputs : & [ TxOutTemplate :: new ( 10000 , Some ( 0 ) ) , TxOutTemplate :: new ( 10000 , Some ( 1 ) ) , TxOutTemplate :: new ( 10000 , Some ( 2 ) ) ] ,
1679+ // outputs: &[TxOutTemplate::new(10000, Some(1)), TxOutTemplate::new(10000, Some(2))],
1680+ anchors : & [ block_id ! ( 1 , "B" ) ] ,
1681+ last_seen : None ,
1682+ assume_canonical : false ,
1683+ } ,
1684+ TxTemplate {
1685+ tx_name : "e0" ,
1686+ inputs : & [ TxInTemplate :: PrevTx ( "a0" , 0 ) ] ,
1687+ outputs : & [ TxOutTemplate :: new ( 10000 , Some ( 0 ) ) ] ,
1688+ anchors : & [ block_id ! ( 1 , "B" ) ] ,
1689+ last_seen : None ,
1690+ assume_canonical : false ,
1691+ } ,
1692+ TxTemplate {
1693+ tx_name : "f0" ,
1694+ inputs : & [ TxInTemplate :: PrevTx ( "e0" , 0 ) ] ,
1695+ outputs : & [ TxOutTemplate :: new ( 10000 , Some ( 0 ) ) ] ,
1696+ anchors : & [ block_id ! ( 1 , "B" ) ] ,
1697+ last_seen : None ,
1698+ assume_canonical : false ,
1699+ } ,
1700+ TxTemplate {
1701+ tx_name : "b0" ,
1702+ inputs : & [ TxInTemplate :: PrevTx ( "f0" , 0 ) , TxInTemplate :: PrevTx ( "a0" , 1 ) ] ,
1703+ outputs : & [ TxOutTemplate :: new ( 10000 , Some ( 0 ) ) , TxOutTemplate :: new ( 10000 , Some ( 1 ) ) ] ,
1704+ anchors : & [ block_id ! ( 1 , "B" ) ] ,
1705+ last_seen : None ,
1706+ assume_canonical : false ,
1707+ } ,
1708+ TxTemplate {
1709+ tx_name : "c0" ,
1710+ inputs : & [ TxInTemplate :: PrevTx ( "b0" , 0 ) ] ,
1711+ outputs : & [ TxOutTemplate :: new ( 5000 , Some ( 0 ) ) ] ,
1712+ anchors : & [ block_id ! ( 1 , "B" ) ] ,
1713+ last_seen : None ,
1714+ assume_canonical : false ,
1715+ } ,
1716+ TxTemplate {
1717+ tx_name : "b1" ,
1718+ inputs : & [ TxInTemplate :: PrevTx ( "a0" , 2 ) ] ,
1719+ outputs : & [ TxOutTemplate :: new ( 10000 , Some ( 0 ) ) ] ,
1720+ anchors : & [ block_id ! ( 1 , "B" ) ] ,
1721+ last_seen : None ,
1722+ assume_canonical : false ,
1723+ } ,
1724+ TxTemplate {
1725+ tx_name : "c1" ,
1726+ inputs : & [ TxInTemplate :: PrevTx ( "b1" , 0 ) ] ,
1727+ outputs : & [ TxOutTemplate :: new ( 10000 , Some ( 0 ) ) ] ,
1728+ anchors : & [ block_id ! ( 1 , "B" ) ] ,
1729+ last_seen : None ,
1730+ assume_canonical : false ,
1731+ } ,
1732+ TxTemplate {
1733+ tx_name : "d0" ,
1734+ inputs : & [ TxInTemplate :: PrevTx ( "b0" , 1 ) , TxInTemplate :: PrevTx ( "c1" , 0 ) , ] ,
1735+ outputs : & [ TxOutTemplate :: new ( 10000 , Some ( 0 ) ) ] ,
1736+ anchors : & [ block_id ! ( 1 , "B" ) ] ,
1737+ last_seen : None ,
1738+ assume_canonical : false ,
1739+ } ] ,
1740+ exp_chain_txs : Vec :: from ( [ "a0" , "e0" , "f0" , "b0" , "c0" , "b1" , "c1" , "d0" ] ) ,
1741+ } ] ;
1742+
1743+ for ( _, scenario) in scenarios. iter ( ) . enumerate ( ) {
1744+ let env = init_graph ( scenario. tx_templates . iter ( ) ) ;
1745+
1746+ let canonical_txs = env
1747+ . tx_graph
1748+ . list_canonical_txs ( & local_chain, chain_tip, env. canonicalization_params . clone ( ) )
1749+ . map ( |tx| tx. tx_node . txid )
1750+ . collect :: < BTreeSet < _ > > ( ) ;
1751+
1752+ let exp_txs = scenario
1753+ . exp_chain_txs
1754+ . iter ( )
1755+ . map ( |txid| * env. tx_name_to_txid . get ( txid) . expect ( "txid must exist" ) )
1756+ . collect :: < BTreeSet < _ > > ( ) ;
1757+
1758+ assert_eq ! (
1759+ canonical_txs, exp_txs,
1760+ "\n [{}] 'list_canonical_txs' failed" ,
1761+ scenario. name
1762+ ) ;
1763+
1764+ let canonical_txs = canonical_txs. iter ( ) . map ( |txid| * txid) . collect :: < Vec < _ > > ( ) ;
1765+
1766+ assert ! (
1767+ is_txs_in_topological_order( canonical_txs, env. tx_graph) ,
1768+ "\n [{}] 'list_canonical_txs' failed to output the txs in topological order" ,
1769+ scenario. name
1770+ ) ;
1771+ }
1772+ }
1773+
1774+ fn is_txs_in_topological_order ( txs : Vec < Txid > , tx_graph : TxGraph < BlockId > ) -> bool {
1775+ let enumerated_txs = txs
1776+ . iter ( )
1777+ . enumerate ( )
1778+ . map ( |( i, txid) | ( i, * txid) )
1779+ . collect :: < Vec < ( usize , Txid ) > > ( ) ;
1780+
1781+ let txid_to_pos = enumerated_txs
1782+ . iter ( )
1783+ . map ( |( i, txid) | ( * txid, * i) )
1784+ . collect :: < HashMap < Txid , usize > > ( ) ;
1785+
1786+ for ( pos, txid) in enumerated_txs {
1787+ let descendants_pos: Vec < ( & usize , Txid ) > = tx_graph
1788+ . walk_descendants ( txid, |_depth, this_txid| {
1789+ let pos = txid_to_pos. get ( & this_txid) . unwrap ( ) ;
1790+ Some ( ( pos, this_txid) )
1791+ } )
1792+ . collect ( ) ;
1793+
1794+ for ( desc_pos, this_txid) in descendants_pos {
1795+ if desc_pos < & pos {
1796+ println ! (
1797+ "ancestor: ({:?}, {:?}) , descendant ({:?}, {:?})" ,
1798+ txid, pos, this_txid, desc_pos
1799+ ) ;
1800+ return false ;
1801+ }
1802+ }
1803+ }
1804+ return true ;
1805+ }
0 commit comments