| 
3 | 3 | #[macro_use]  | 
4 | 4 | mod common;  | 
5 | 5 | use bdk_chain::tx_graph::TxAncestors;  | 
6 |  | -use bdk_chain::{collections::*, BlockId, CanonicalizationParams, ConfirmationBlockTime};  | 
 | 6 | +use bdk_chain::{collections::*, Balance, BlockId, CanonicalizationParams, ConfirmationBlockTime};  | 
7 | 7 | use bdk_chain::{  | 
8 | 8 |     local_chain::LocalChain,  | 
9 | 9 |     tx_graph::{self, CalculateFeeError},  | 
10 | 10 |     tx_graph::{ChangeSet, TxGraph},  | 
11 | 11 |     Anchor, ChainOracle, ChainPosition, Merge,  | 
12 | 12 | };  | 
 | 13 | +use bdk_testenv::local_chain;  | 
13 | 14 | use bdk_testenv::{block_id, hash, utils::new_tx};  | 
14 | 15 | use bitcoin::hex::FromHex;  | 
15 | 16 | use bitcoin::Witness;  | 
@@ -1550,92 +1551,246 @@ fn test_get_first_seen_of_a_tx() {  | 
1550 | 1551 | 
 
  | 
1551 | 1552 | #[test]  | 
1552 | 1553 | fn test_list_canonical_txs_topological_order() {  | 
1553 |  | -    let txs = vec![new_tx(0), new_tx(1), new_tx(2)];  | 
1554 |  | -    let txids: Vec<Txid> = txs.iter().map(Transaction::compute_txid).collect();  | 
1555 |  | - | 
1556 |  | -    // graph  | 
1557 |  | -    let mut graph = TxGraph::<BlockId>::new(txs);  | 
1558 |  | - | 
1559 |  | -    let full_txs: Vec<_> = graph.full_txs().collect();  | 
1560 |  | -    assert_eq!(full_txs.len(), 3);  | 
1561 |  | - | 
1562 |  | -    let unseen_txs: Vec<_> = graph.txs_with_no_anchor_or_last_seen().collect();  | 
1563 |  | -    assert_eq!(unseen_txs.len(), 3);  | 
1564 |  | - | 
1565 | 1554 |     // chain  | 
1566 |  | -    let blocks: BTreeMap<u32, BlockHash> = [(0, hash!("g")), (1, hash!("A")), (2, hash!("B"))]  | 
1567 |  | -        .into_iter()  | 
1568 |  | -        .collect();  | 
1569 |  | -    let chain = LocalChain::from_blocks(blocks).unwrap();  | 
1570 |  | -    let canonical_txs: Vec<_> = graph  | 
1571 |  | -        .list_canonical_txs(  | 
1572 |  | -            &chain,  | 
1573 |  | -            chain.tip().block_id(),  | 
1574 |  | -            CanonicalizationParams::default(),  | 
1575 |  | -        )  | 
1576 |  | -        .collect();  | 
1577 |  | -    assert!(canonical_txs.is_empty());  | 
1578 |  | -    drop(canonical_txs);  | 
1579 |  | - | 
1580 |  | -    // tx0 with seen_at should be returned by canonical txs  | 
1581 |  | -    let _ = graph.insert_seen_at(txids[0], 2);  | 
1582 |  | -    let mut canonical_txs = graph.list_canonical_txs(  | 
1583 |  | -        &chain,  | 
1584 |  | -        chain.tip().block_id(),  | 
1585 |  | -        CanonicalizationParams::default(),  | 
1586 |  | -    );  | 
1587 |  | -    assert_eq!(  | 
1588 |  | -        canonical_txs.next().map(|tx| tx.tx_node.txid).unwrap(),  | 
1589 |  | -        txids[0]  | 
 | 1555 | +    let local_chain = local_chain!(  | 
 | 1556 | +        (0, hash!("A")),  | 
 | 1557 | +        (1, hash!("B")),  | 
 | 1558 | +        (2, hash!("C")),  | 
 | 1559 | +        (3, hash!("D")),  | 
 | 1560 | +        (4, hash!("E")),  | 
 | 1561 | +        (5, hash!("F")),  | 
 | 1562 | +        (6, hash!("G"))  | 
1590 | 1563 |     );  | 
1591 |  | -    drop(canonical_txs);  | 
1592 |  | - | 
1593 |  | -    // tx1 with anchor should be returned by canonical txs  | 
1594 |  | -    let _ = graph.insert_anchor(txids[1], block_id!(2, "B"));  | 
1595 |  | -    let canonical_txids: Vec<_> = graph  | 
1596 |  | -        .list_canonical_txs(  | 
1597 |  | -            &chain,  | 
1598 |  | -            chain.tip().block_id(),  | 
1599 |  | -            CanonicalizationParams::default(),  | 
1600 |  | -        )  | 
1601 |  | -        .map(|tx| tx.tx_node.txid)  | 
1602 |  | -        .collect();  | 
1603 |  | - | 
1604 |  | -    assert!(canonical_txids.contains(&txids[1]));  | 
1605 |  | -    assert_eq!(  | 
1606 |  | -        graph  | 
1607 |  | -            .txs_with_no_anchor_or_last_seen()  | 
1608 |  | -            .collect::<Vec<_>>()  | 
1609 |  | -            .len(),  | 
1610 |  | -        1  | 
1611 |  | -    );  | 
1612 |  | - | 
1613 |  | -    // tx2 with seen_at should be returned by canonical txs  | 
1614 |  | -    let _ = graph.insert_seen_at(txids[2], 1);  | 
1615 |  | -    let canonical_txids: Vec<_> = graph  | 
1616 |  | -        .list_canonical_txs(  | 
1617 |  | -            &chain,  | 
1618 |  | -            chain.tip().block_id(),  | 
1619 |  | -            CanonicalizationParams::default(),  | 
1620 |  | -        )  | 
1621 |  | -        .map(|tx| tx.tx_node.txid)  | 
1622 |  | -        .collect();  | 
1623 |  | - | 
1624 |  | -    assert!(canonical_txids.contains(&txids[2]));  | 
1625 |  | -    assert!(graph.txs_with_no_anchor_or_last_seen().next().is_none());  | 
1626 |  | - | 
1627 |  | -    println!("{:?}", canonical_txids);  | 
1628 |  | -    for txid in &canonical_txids {  | 
1629 |  | -        let tx_node = graph.get_tx_node(*txid);  | 
1630 |  | -        println!("{:?}", tx_node);  | 
 | 1564 | +    let chain_tip = local_chain.tip().block_id();  | 
 | 1565 | + | 
 | 1566 | +    // scenarios  | 
 | 1567 | +    let scenarios = [Scenario {  | 
 | 1568 | +        name: "C spend A, B spend A, and A is in the best chain",  | 
 | 1569 | +        tx_templates: &[  | 
 | 1570 | +            TxTemplate {  | 
 | 1571 | +                tx_name: "A",  | 
 | 1572 | +                inputs: &[TxInTemplate::Bogus],  | 
 | 1573 | +                outputs: &[TxOutTemplate::new(10000, Some(0))],  | 
 | 1574 | +                anchors: &[block_id!(1, "B")],  | 
 | 1575 | +                last_seen: None,  | 
 | 1576 | +                assume_canonical: false,  | 
 | 1577 | +            },  | 
 | 1578 | +            TxTemplate {  | 
 | 1579 | +                tx_name: "B",  | 
 | 1580 | +                inputs: &[TxInTemplate::PrevTx("A", 0), TxInTemplate::Bogus],  | 
 | 1581 | +                outputs: &[TxOutTemplate::new(5000, Some(0))],  | 
 | 1582 | +                anchors: &[block_id!(1, "B")],  | 
 | 1583 | +                last_seen: None,  | 
 | 1584 | +                assume_canonical: false,  | 
 | 1585 | +            },  | 
 | 1586 | +            TxTemplate {  | 
 | 1587 | +                tx_name: "C",  | 
 | 1588 | +                inputs: &[TxInTemplate::PrevTx("B", 0), TxInTemplate::Bogus],  | 
 | 1589 | +                outputs: &[TxOutTemplate::new(2500, Some(0))],  | 
 | 1590 | +                anchors: &[block_id!(1, "B")],  | 
 | 1591 | +                last_seen: None,  | 
 | 1592 | +                assume_canonical: false,  | 
 | 1593 | +            },  | 
 | 1594 | +        ],  | 
 | 1595 | +        exp_chain_txs: Vec::from(["A", "B", "C"]),  | 
 | 1596 | +        exp_chain_txouts: HashSet::from([("A", 0), ("B", 0), ("C", 0)]),  | 
 | 1597 | +        exp_unspents: HashSet::from([("C", 0)]),  | 
 | 1598 | +        exp_balance: Balance {  | 
 | 1599 | +            immature: Amount::ZERO,  | 
 | 1600 | +            trusted_pending: Amount::ZERO,  | 
 | 1601 | +            untrusted_pending: Amount::ZERO,  | 
 | 1602 | +            confirmed: Amount::from_sat(2500),  | 
 | 1603 | +        },  | 
 | 1604 | +    },  | 
 | 1605 | +    Scenario {  | 
 | 1606 | +        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",  | 
 | 1607 | +        tx_templates: &[TxTemplate {  | 
 | 1608 | +            tx_name: "a0",  | 
 | 1609 | +            inputs: &[TxInTemplate::Bogus],  | 
 | 1610 | +            outputs: &[TxOutTemplate::new(10000, Some(0)), TxOutTemplate::new(10000, Some(1))],  | 
 | 1611 | +            anchors: &[block_id!(1, "B")],  | 
 | 1612 | +            last_seen: None,  | 
 | 1613 | +            assume_canonical: false,  | 
 | 1614 | +        }, TxTemplate {  | 
 | 1615 | +            tx_name: "b0",  | 
 | 1616 | +            inputs: &[TxInTemplate::PrevTx("a0", 0), TxInTemplate::Bogus],  | 
 | 1617 | +            outputs: &[TxOutTemplate::new(10000, Some(0)), TxOutTemplate::new(10000, Some(1))],  | 
 | 1618 | +            anchors: &[block_id!(1, "B")],  | 
 | 1619 | +            last_seen: None,  | 
 | 1620 | +            assume_canonical: false,  | 
 | 1621 | +        },  | 
 | 1622 | +         TxTemplate {  | 
 | 1623 | +            tx_name: "c0",  | 
 | 1624 | +            inputs: &[TxInTemplate::PrevTx("b0", 0), TxInTemplate::Bogus],  | 
 | 1625 | +            outputs: &[TxOutTemplate::new(5000, Some(0))],  | 
 | 1626 | +            anchors: &[block_id!(1, "B")],  | 
 | 1627 | +            last_seen: None,  | 
 | 1628 | +            assume_canonical: false,  | 
 | 1629 | +        },  | 
 | 1630 | +         TxTemplate {  | 
 | 1631 | +            tx_name: "b1",  | 
 | 1632 | +            inputs: &[TxInTemplate::PrevTx("a0", 1), TxInTemplate::Bogus],  | 
 | 1633 | +            outputs: &[TxOutTemplate::new(10000, Some(0))],  | 
 | 1634 | +            anchors: &[block_id!(1, "B")],  | 
 | 1635 | +            last_seen: None,  | 
 | 1636 | +            assume_canonical: false,  | 
 | 1637 | +        },  | 
 | 1638 | +         TxTemplate {  | 
 | 1639 | +            tx_name: "c1",  | 
 | 1640 | +            inputs: &[TxInTemplate::PrevTx("b1", 0), TxInTemplate::Bogus],  | 
 | 1641 | +            outputs: &[TxOutTemplate::new(10000, Some(0))],  | 
 | 1642 | +            anchors: &[block_id!(1, "B")],  | 
 | 1643 | +            last_seen: None,  | 
 | 1644 | +            assume_canonical: false,  | 
 | 1645 | +        },  | 
 | 1646 | +         TxTemplate {  | 
 | 1647 | +            tx_name: "d0",  | 
 | 1648 | +            inputs: &[TxInTemplate::PrevTx("b0", 1), TxInTemplate::PrevTx("c1", 0),  TxInTemplate::Bogus],  | 
 | 1649 | +            outputs: &[TxOutTemplate::new(10000, Some(0))],  | 
 | 1650 | +            anchors: &[block_id!(1, "B")],  | 
 | 1651 | +            last_seen: None,  | 
 | 1652 | +            assume_canonical: false,  | 
 | 1653 | +        }],  | 
 | 1654 | +        exp_chain_txs: Vec::from(["a0", "b0", "c0", "b1", "c1", "d0"]),  | 
 | 1655 | +        exp_chain_txouts: HashSet::from([("a0", 0),("b0", 0),("b1", 0),("c0", 0),("c1", 0), ("d0", 0)]),  | 
 | 1656 | +        exp_unspents: HashSet::from([("c0", 0), ("d0", 0)]),  | 
 | 1657 | +        exp_balance: Default::default(),  | 
 | 1658 | +    }];  | 
 | 1659 | + | 
 | 1660 | +    for scenario in scenarios {  | 
 | 1661 | +        let env = init_graph(scenario.tx_templates.iter());  | 
 | 1662 | + | 
 | 1663 | +        let canonical_txs = env  | 
 | 1664 | +            .tx_graph  | 
 | 1665 | +            .list_canonical_txs(&local_chain, chain_tip, env.canonicalization_params.clone())  | 
 | 1666 | +            .map(|tx| tx.tx_node.txid)  | 
 | 1667 | +            .collect::<Vec<_>>();  | 
 | 1668 | + | 
 | 1669 | +        println!("{:?}", env.tx_name_to_txid);  | 
 | 1670 | +        println!("{:?}", scenario.exp_chain_txs);  | 
 | 1671 | +        let mut exp_txs: Vec<_> = scenario.exp_chain_txs.iter().collect::<Vec<_>>();  | 
 | 1672 | +        // exp_txs.sort();  | 
 | 1673 | +        let exp_txs: Vec<Txid> = exp_txs  | 
 | 1674 | +            .iter()  | 
 | 1675 | +            .map(|tx_name| {  | 
 | 1676 | +                println!("#{:?}", tx_name);  | 
 | 1677 | +                *env.tx_name_to_txid.get(*tx_name).expect("txid must exist")  | 
 | 1678 | +            })  | 
 | 1679 | +            .collect();  | 
 | 1680 | +        println!("{:?}", exp_txs);  | 
 | 1681 | +        assert_eq!(  | 
 | 1682 | +            canonical_txs, exp_txs,  | 
 | 1683 | +            "\n[{}] 'list_canonical_txs' failed to output the txs in topological order",  | 
 | 1684 | +            scenario.name  | 
 | 1685 | +        );  | 
1631 | 1686 |     }  | 
1632 | 1687 | 
 
  | 
1633 |  | -    let expected_txids = [txids[0], txids[2], txids[1]];  | 
1634 |  | -    for (idx, txid) in canonical_txids.iter().enumerate() {  | 
1635 |  | -        assert_eq!(expected_txids[idx], *txid);  | 
1636 |  | -    }  | 
 | 1688 | +    // assert  | 
1637 | 1689 | }  | 
1638 | 1690 | 
 
  | 
 | 1691 | +struct Scenario<'a> {  | 
 | 1692 | +    /// Name of the test scenario  | 
 | 1693 | +    name: &'a str,  | 
 | 1694 | +    /// Transaction templates  | 
 | 1695 | +    tx_templates: &'a [TxTemplate<'a, BlockId>],  | 
 | 1696 | +    /// Names of txs that must exist in the output of `list_canonical_txs`  | 
 | 1697 | +    exp_chain_txs: Vec<&'a str>,  | 
 | 1698 | +    /// Outpoints that must exist in the output of `filter_chain_txouts`  | 
 | 1699 | +    exp_chain_txouts: HashSet<(&'a str, u32)>,  | 
 | 1700 | +    /// Outpoints of UTXOs that must exist in the output of `filter_chain_unspents`  | 
 | 1701 | +    exp_unspents: HashSet<(&'a str, u32)>,  | 
 | 1702 | +    /// Expected balances  | 
 | 1703 | +    exp_balance: Balance,  | 
 | 1704 | +}  | 
 | 1705 | + | 
 | 1706 | +// #[test]  | 
 | 1707 | +// fn test_list_canonical_txs_topological_order() {  | 
 | 1708 | +//     let txs = vec![new_tx(0), new_tx(1), new_tx(2)];  | 
 | 1709 | +//     let txids: Vec<Txid> = txs.iter().map(Transaction::compute_txid).collect();  | 
 | 1710 | + | 
 | 1711 | +//     // graph  | 
 | 1712 | +//     let mut graph = TxGraph::<BlockId>::new(txs);  | 
 | 1713 | + | 
 | 1714 | +//     let full_txs: Vec<_> = graph.full_txs().collect();  | 
 | 1715 | +//     assert_eq!(full_txs.len(), 3);  | 
 | 1716 | + | 
 | 1717 | +//     let unseen_txs: Vec<_> = graph.txs_with_no_anchor_or_last_seen().collect();  | 
 | 1718 | +//     assert_eq!(unseen_txs.len(), 3);  | 
 | 1719 | + | 
 | 1720 | +//     // chain  | 
 | 1721 | +//     let blocks: BTreeMap<u32, BlockHash> = [(0, hash!("g")), (1, hash!("A")), (2, hash!("B"))]  | 
 | 1722 | +//         .into_iter()  | 
 | 1723 | +//         .collect();  | 
 | 1724 | +//     let chain = LocalChain::from_blocks(blocks).unwrap();  | 
 | 1725 | +//     let canonical_txs: Vec<_> = graph  | 
 | 1726 | +//         .list_canonical_txs(  | 
 | 1727 | +//             &chain,  | 
 | 1728 | +//             chain.tip().block_id(),  | 
 | 1729 | +//             CanonicalizationParams::default(),  | 
 | 1730 | +//         )  | 
 | 1731 | +//         .collect();  | 
 | 1732 | +//     assert!(canonical_txs.is_empty());  | 
 | 1733 | +//     drop(canonical_txs);  | 
 | 1734 | + | 
 | 1735 | +//     // tx0 with seen_at should be returned by canonical txs  | 
 | 1736 | +//     let _ = graph.insert_seen_at(txids[0], 2);  | 
 | 1737 | +//     let mut canonical_txs = graph.list_canonical_txs(  | 
 | 1738 | +//         &chain,  | 
 | 1739 | +//         chain.tip().block_id(),  | 
 | 1740 | +//         CanonicalizationParams::default(),  | 
 | 1741 | +//     );  | 
 | 1742 | +//     assert_eq!(  | 
 | 1743 | +//         canonical_txs.next().map(|tx| tx.tx_node.txid).unwrap(),  | 
 | 1744 | +//         txids[0]  | 
 | 1745 | +//     );  | 
 | 1746 | +//     drop(canonical_txs);  | 
 | 1747 | + | 
 | 1748 | +//     // tx1 with anchor should be returned by canonical txs  | 
 | 1749 | +//     let _ = graph.insert_anchor(txids[1], block_id!(2, "B"));  | 
 | 1750 | +//     let canonical_txids: Vec<_> = graph  | 
 | 1751 | +//         .list_canonical_txs(  | 
 | 1752 | +//             &chain,  | 
 | 1753 | +//             chain.tip().block_id(),  | 
 | 1754 | +//             CanonicalizationParams::default(),  | 
 | 1755 | +//         )  | 
 | 1756 | +//         .map(|tx| tx.tx_node.txid)  | 
 | 1757 | +//         .collect();  | 
 | 1758 | + | 
 | 1759 | +//     assert!(canonical_txids.contains(&txids[1]));  | 
 | 1760 | +//     assert_eq!(  | 
 | 1761 | +//         graph  | 
 | 1762 | +//             .txs_with_no_anchor_or_last_seen()  | 
 | 1763 | +//             .collect::<Vec<_>>()  | 
 | 1764 | +//             .len(),  | 
 | 1765 | +//         1  | 
 | 1766 | +//     );  | 
 | 1767 | + | 
 | 1768 | +//     // tx2 with seen_at should be returned by canonical txs  | 
 | 1769 | +//     let _ = graph.insert_seen_at(txids[2], 1);  | 
 | 1770 | +//     let canonical_txids: Vec<_> = graph  | 
 | 1771 | +//         .list_canonical_txs(  | 
 | 1772 | +//             &chain,  | 
 | 1773 | +//             chain.tip().block_id(),  | 
 | 1774 | +//             CanonicalizationParams::default(),  | 
 | 1775 | +//         )  | 
 | 1776 | +//         .map(|tx| tx.tx_node.txid)  | 
 | 1777 | +//         .collect();  | 
 | 1778 | + | 
 | 1779 | +//     assert!(canonical_txids.contains(&txids[2]));  | 
 | 1780 | +//     assert!(graph.txs_with_no_anchor_or_last_seen().next().is_none());  | 
 | 1781 | + | 
 | 1782 | +//     println!("{:?}", canonical_txids);  | 
 | 1783 | +//     for txid in &canonical_txids {  | 
 | 1784 | +//         let tx_node = graph.get_tx_node(*txid);  | 
 | 1785 | +//         println!("{:?}", tx_node);  | 
 | 1786 | +//     }  | 
 | 1787 | + | 
 | 1788 | +//     let expected_txids = [txids[0], txids[2], txids[1]];  | 
 | 1789 | +//     for (idx, txid) in canonical_txids.iter().enumerate() {  | 
 | 1790 | +//         assert_eq!(expected_txids[idx], *txid);  | 
 | 1791 | +//     }  | 
 | 1792 | +// }  | 
 | 1793 | + | 
1639 | 1794 | #[test]  | 
1640 | 1795 | fn test_canonical_txs_topological_order() {  | 
1641 | 1796 |     let previous_output = OutPoint::new(hash!("op"), 2);  | 
 | 
0 commit comments