Skip to content

Commit 3cb3e07

Browse files
committed
test: add scenarios for canonical_txs topological order
1 parent b2dbb7c commit 3cb3e07

File tree

1 file changed

+263
-0
lines changed

1 file changed

+263
-0
lines changed

crates/chain/tests/test_tx_graph.rs

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use bdk_chain::{
99
tx_graph::{ChangeSet, TxGraph},
1010
Anchor, ChainOracle, ChainPosition, Merge,
1111
};
12+
use bdk_testenv::local_chain;
1213
use bdk_testenv::{block_id, hash, utils::new_tx};
1314
use bitcoin::hex::FromHex;
1415
use 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

Comments
 (0)