Skip to content

Commit 584b801

Browse files
committed
wip(fix(chain)): transitively confirmed assumed txs
- add new `test_canonical_view_task.rs` to handle different scenarios of chain position resolution. - TODO: assumed canonical txs should be confirmed if it's direct descendant is anchored.
1 parent 470ccd1 commit 584b801

1 file changed

Lines changed: 207 additions & 0 deletions

File tree

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
#![cfg(feature = "miniscript")]
2+
3+
mod common;
4+
5+
use bdk_chain::{BlockId, CanonicalReason, ChainPosition};
6+
use bdk_testenv::{block_id, hash, local_chain};
7+
use bitcoin::Txid;
8+
use common::*;
9+
use std::collections::HashSet;
10+
11+
struct Scenario<'a> {
12+
name: &'a str,
13+
tx_templates: &'a [TxTemplate<'a, BlockId>],
14+
exp_canonical_txs: HashSet<&'a str>,
15+
}
16+
17+
#[test]
18+
fn test_assumed_canonical_scenarios() {
19+
// Create a local chain
20+
let local_chain = local_chain![
21+
(0, hash!("genesis")),
22+
(1, hash!("block1")),
23+
(2, hash!("block2")),
24+
(3, hash!("block3")),
25+
(4, hash!("block4")),
26+
(5, hash!("block5")),
27+
(6, hash!("block6")),
28+
(7, hash!("block7")),
29+
(8, hash!("block8")),
30+
(9, hash!("block9")),
31+
(10, hash!("block10"))
32+
];
33+
let chain_tip = local_chain.tip().block_id();
34+
35+
// Create arrays before scenario to avoid lifetime issues
36+
let tx_templates = [
37+
TxTemplate {
38+
tx_name: "txA",
39+
inputs: &[TxInTemplate::Bogus],
40+
outputs: &[TxOutTemplate::new(100000, Some(0))],
41+
anchors: &[],
42+
last_seen: None,
43+
assume_canonical: false,
44+
},
45+
TxTemplate {
46+
tx_name: "txB",
47+
inputs: &[TxInTemplate::PrevTx("txA", 0)],
48+
outputs: &[TxOutTemplate::new(50000, Some(0))],
49+
anchors: &[block_id!(5, "block5")],
50+
last_seen: None,
51+
assume_canonical: false,
52+
},
53+
TxTemplate {
54+
tx_name: "txC",
55+
inputs: &[TxInTemplate::PrevTx("txB", 0)],
56+
outputs: &[TxOutTemplate::new(25000, Some(0))],
57+
anchors: &[],
58+
last_seen: None,
59+
assume_canonical: true,
60+
},
61+
];
62+
63+
let scenarios = vec![Scenario {
64+
name: "txC spends txB; txB spends txA; txB is anchored; txC is assumed canonical",
65+
tx_templates: &tx_templates,
66+
exp_canonical_txs: HashSet::from(["txA", "txB", "txC"]),
67+
}];
68+
69+
for scenario in scenarios {
70+
let env = init_graph(scenario.tx_templates);
71+
72+
// get the actual txid from given tx_name.
73+
let txid_c = *env.txid_to_name.get("txC").unwrap();
74+
75+
// build the expected `CanonicalReason` with specific descendant txid's
76+
//
77+
// in this scenario: txC is assumed canonical, and it's descendant of txB and txA
78+
// therefore the whole chain should become assumed canonical.
79+
//
80+
// the descendant txid field refers to the directly **assumed canonical txC**
81+
let exp_reasons = vec![
82+
(
83+
"txA",
84+
CanonicalReason::Assumed {
85+
descendant: Some(txid_c),
86+
},
87+
),
88+
(
89+
"txB",
90+
CanonicalReason::Assumed {
91+
descendant: Some(txid_c),
92+
},
93+
),
94+
("txC", CanonicalReason::Assumed { descendant: None }),
95+
];
96+
97+
// build task & canonicalize
98+
let canonical_params = env.canonicalization_params;
99+
let canonical_task = env.tx_graph.canonical_task(chain_tip, canonical_params);
100+
let canonical_txs = local_chain.canonicalize(canonical_task);
101+
102+
// assert canonical transactions
103+
let exp_canonical_txids: HashSet<Txid> = scenario
104+
.exp_canonical_txs
105+
.iter()
106+
.map(|tx_name| {
107+
*env.txid_to_name
108+
.get(tx_name)
109+
.expect("txid should exist for tx_name")
110+
})
111+
.collect::<HashSet<Txid>>();
112+
113+
let canonical_txids = canonical_txs
114+
.txs()
115+
.map(|canonical_tx| canonical_tx.txid)
116+
.collect::<HashSet<Txid>>();
117+
118+
assert_eq!(
119+
canonical_txids, exp_canonical_txids,
120+
"[{}] canonical transactions mismatch",
121+
scenario.name
122+
);
123+
124+
// assert canonical reasons
125+
for (tx_name, exp_reason) in exp_reasons {
126+
let txid = env
127+
.txid_to_name
128+
.get(tx_name)
129+
.expect("txid should exist for tx_name");
130+
131+
let canonical_reason = canonical_txs
132+
.txs()
133+
.find(|ctx| &ctx.txid == txid)
134+
.expect("expected txid should exist in canonical txs")
135+
.pos;
136+
137+
assert_eq!(
138+
canonical_reason, exp_reason,
139+
"[{}] canonical reason mismatch for {}",
140+
scenario.name, tx_name
141+
)
142+
}
143+
144+
let txid_b = *env.txid_to_name.get("txB").unwrap();
145+
146+
// build the expected `ChainPosition` with specific txid's for transitively confirmed txs.
147+
//
148+
// in this scenario:
149+
//
150+
// txA: should be confirmed transitively by txB.
151+
// txB: should be confirmed, has a direct anchor(block5).
152+
// txC: should be unconfirmed, has been assumed canonical though has no direct anchors.
153+
let exp_positions = vec![
154+
(
155+
"txA",
156+
ChainPosition::Confirmed {
157+
anchor: block_id!(5, "block5"),
158+
transitively: Some(txid_b),
159+
},
160+
),
161+
(
162+
"txB",
163+
ChainPosition::Confirmed {
164+
anchor: block_id!(5, "block5"),
165+
transitively: None,
166+
},
167+
),
168+
(
169+
"txC",
170+
ChainPosition::Unconfirmed {
171+
first_seen: None,
172+
last_seen: None,
173+
},
174+
),
175+
];
176+
177+
// build task & resolve positions
178+
let view_task = canonical_txs.view_task(&env.tx_graph);
179+
let canonical_view = local_chain.canonicalize(view_task);
180+
181+
// assert final positions
182+
for (tx_name, exp_position) in exp_positions {
183+
let txid = *env
184+
.txid_to_name
185+
.get(tx_name)
186+
.expect("txid should exist for tx_name");
187+
188+
let canonical_position = canonical_view
189+
.txs()
190+
.find(|ctx| ctx.txid == txid)
191+
.expect("expected txid should exist in canonical view")
192+
.pos;
193+
194+
assert_eq!(
195+
canonical_position, exp_position,
196+
"[{}] canonical position mismatch for {}",
197+
scenario.name, tx_name
198+
);
199+
}
200+
201+
// successfully asserted whole scenario
202+
println!(
203+
"[{}] scenario passed: all assertions successful",
204+
scenario.name
205+
);
206+
}
207+
}

0 commit comments

Comments
 (0)