@@ -9,6 +9,7 @@ use bdk_chain::{
9
9
tx_graph:: { ChangeSet , TxGraph } ,
10
10
Anchor , ChainOracle , ChainPosition , Merge ,
11
11
} ;
12
+ use bdk_testenv:: local_chain;
12
13
use bdk_testenv:: { block_id, hash, utils:: new_tx} ;
13
14
use bitcoin:: hex:: FromHex ;
14
15
use bitcoin:: Witness ;
@@ -1540,3 +1541,265 @@ fn test_get_first_seen_of_a_tx() {
1540
1541
let first_seen = graph. get_tx_node ( txid) . unwrap ( ) . first_seen ;
1541
1542
assert_eq ! ( first_seen, Some ( seen_at) ) ;
1542
1543
}
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