|
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