Skip to content

Commit eb75bb2

Browse files
committed
test(chain): add ancestor package tests
1 parent 270eda6 commit eb75bb2

2 files changed

Lines changed: 231 additions & 1 deletion

File tree

crates/chain/src/canonical_view.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -492,7 +492,7 @@ impl<A: Anchor> CanonicalView<A> {
492492
result
493493
}
494494

495-
/// Compute the aggregate `(weight, fee)` for the unconfirmed ancestor
495+
/// Compute the aggregate `(weight, fee)` for the unconfirmed ancestor
496496
/// chain rooted at `txid`, including `txid` itself.
497497
///
498498
/// Returns `None` if:
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
#![cfg(feature = "std")]
2+
3+
use bdk_chain::{local_chain::LocalChain, AncestorPackage, CanonicalizationParams, TxGraph};
4+
use bdk_core::{BlockId, ConfirmationBlockTime};
5+
use bitcoin::{
6+
absolute, hashes::Hash, transaction, Amount, BlockHash, FeeRate, OutPoint, ScriptBuf,
7+
Transaction, TxIn, TxOut, Txid, Weight,
8+
};
9+
use std::collections::BTreeMap;
10+
11+
fn make_tx(inputs: &[OutPoint], output_values: &[Amount]) -> Transaction {
12+
Transaction {
13+
version: transaction::Version::TWO,
14+
lock_time: absolute::LockTime::ZERO,
15+
input: inputs
16+
.iter()
17+
.map(|prev| TxIn {
18+
previous_output: *prev,
19+
..Default::default()
20+
})
21+
.collect(),
22+
output: output_values
23+
.iter()
24+
.map(|&value| TxOut {
25+
value,
26+
script_pubkey: ScriptBuf::new(),
27+
})
28+
.collect(),
29+
}
30+
}
31+
32+
fn block_id(height: u32) -> BlockId {
33+
BlockId {
34+
height,
35+
hash: BlockHash::from_byte_array([height as u8; 32]),
36+
}
37+
}
38+
39+
fn anchor(height: u32) -> ConfirmationBlockTime {
40+
ConfirmationBlockTime {
41+
block_id: block_id(height),
42+
confirmation_time: 123456,
43+
}
44+
}
45+
46+
fn build_packages(
47+
graph: &TxGraph<ConfirmationBlockTime>,
48+
chain: &LocalChain,
49+
) -> BTreeMap<OutPoint, AncestorPackage> {
50+
let tip = chain.tip().block_id();
51+
graph
52+
.try_canonical_view(chain, tip, CanonicalizationParams::default())
53+
.expect("infallible chain oracle")
54+
.ancestor_packages()
55+
}
56+
57+
/// Set up a chain with a confirmed coinbase (1 BTC) at height 1.
58+
fn setup() -> (LocalChain, TxGraph<ConfirmationBlockTime>, Txid) {
59+
let mut graph = TxGraph::<ConfirmationBlockTime>::default();
60+
let chain =
61+
LocalChain::from_blocks([(0, BlockHash::all_zeros()), (1, block_id(1).hash)].into())
62+
.unwrap();
63+
64+
let coinbase = make_tx(&[OutPoint::null()], &[Amount::from_sat(100_000_000)]);
65+
let coinbase_txid = coinbase.compute_txid();
66+
let _ = graph.insert_tx(coinbase);
67+
let _ = graph.insert_anchor(coinbase_txid, anchor(1));
68+
69+
(chain, graph, coinbase_txid)
70+
}
71+
72+
#[test]
73+
fn single_unconfirmed_parent() {
74+
let (chain, mut graph, coinbase_txid) = setup();
75+
76+
// fee = 100_000_000 - 99_990_000 - 9_000 = 1_000
77+
let tx_1 = make_tx(
78+
&[OutPoint::new(coinbase_txid, 0)],
79+
&[Amount::from_sat(99_990_000), Amount::from_sat(9_000)],
80+
);
81+
let txid_1 = tx_1.compute_txid();
82+
let weight_1 = tx_1.weight();
83+
let _ = graph.insert_tx(tx_1);
84+
let _ = graph.insert_seen_at(txid_1, 1000);
85+
86+
let packages = build_packages(&graph, &chain);
87+
88+
let pkg_0 = packages.get(&OutPoint::new(txid_1, 0)).unwrap();
89+
let pkg_1 = packages.get(&OutPoint::new(txid_1, 1)).unwrap();
90+
91+
assert_eq!(pkg_0, pkg_1, "sibling UTXOs must have identical packages");
92+
assert_eq!(pkg_0.fee, Amount::from_sat(1_000));
93+
assert_eq!(pkg_0.weight, weight_1);
94+
}
95+
96+
#[test]
97+
fn two_level_unconfirmed_chain() {
98+
let (chain, mut graph, coinbase_txid) = setup();
99+
100+
// tx_1: fee = 5_000
101+
let tx_1 = make_tx(
102+
&[OutPoint::new(coinbase_txid, 0)],
103+
&[Amount::from_sat(99_995_000)],
104+
);
105+
let txid_1 = tx_1.compute_txid();
106+
let weight_1 = tx_1.weight();
107+
let _ = graph.insert_tx(tx_1);
108+
let _ = graph.insert_seen_at(txid_1, 1000);
109+
110+
// tx_2: spends TX1:0, fee = 5_000
111+
let tx_2 = make_tx(&[OutPoint::new(txid_1, 0)], &[Amount::from_sat(99_990_000)]);
112+
let txid_2 = tx_2.compute_txid();
113+
let weight_2 = tx_2.weight();
114+
let _ = graph.insert_tx(tx_2);
115+
let _ = graph.insert_seen_at(txid_2, 1001);
116+
117+
let packages = build_packages(&graph, &chain);
118+
119+
assert!(!packages.contains_key(&OutPoint::new(txid_1, 0)));
120+
121+
let pkg = packages.get(&OutPoint::new(txid_2, 0)).unwrap();
122+
assert_eq!(pkg.fee, Amount::from_sat(10_000));
123+
assert_eq!(pkg.weight, weight_1 + weight_2);
124+
}
125+
126+
#[test]
127+
fn stops_at_confirmed_boundary() {
128+
let (chain, mut graph, coinbase_txid) = setup();
129+
130+
// Confirmed tx_1
131+
let tx_1 = make_tx(
132+
&[OutPoint::new(coinbase_txid, 0)],
133+
&[Amount::from_sat(99_999_000)],
134+
);
135+
let txid_1 = tx_1.compute_txid();
136+
let _ = graph.insert_tx(tx_1);
137+
let _ = graph.insert_anchor(txid_1, anchor(1));
138+
139+
// Unconfirmed tx_2: spends confirmed TX1:0, fee = 2_000
140+
let tx_2 = make_tx(&[OutPoint::new(txid_1, 0)], &[Amount::from_sat(99_997_000)]);
141+
let txid_2 = tx_2.compute_txid();
142+
let weight_2 = tx_2.weight();
143+
let _ = graph.insert_tx(tx_2);
144+
let _ = graph.insert_seen_at(txid_2, 1000);
145+
146+
// Unconfirmed tx_3: spends TX2:0, fee = 5_000
147+
let tx_3 = make_tx(&[OutPoint::new(txid_2, 0)], &[Amount::from_sat(99_992_000)]);
148+
let txid_3 = tx_3.compute_txid();
149+
let weight_3 = tx_3.weight();
150+
let _ = graph.insert_tx(tx_3);
151+
let _ = graph.insert_seen_at(txid_3, 1001);
152+
153+
let packages = build_packages(&graph, &chain);
154+
let pkg = packages.get(&OutPoint::new(txid_3, 0)).unwrap();
155+
156+
assert_eq!(pkg.fee, Amount::from_sat(7_000));
157+
assert_eq!(pkg.weight, weight_2 + weight_3);
158+
}
159+
160+
#[test]
161+
fn shared_ancestor_counted_once() {
162+
let (chain, mut graph, coinbase_txid) = setup();
163+
164+
// Unconfirmed TX0: fee = 10_000_000
165+
let tx_0 = make_tx(
166+
&[OutPoint::new(coinbase_txid, 0)],
167+
&[Amount::from_sat(50_000_000), Amount::from_sat(40_000_000)],
168+
);
169+
let txid_0 = tx_0.compute_txid();
170+
let weight_0 = tx_0.weight();
171+
let _ = graph.insert_tx(tx_0);
172+
let _ = graph.insert_seen_at(txid_0, 1000);
173+
174+
// Unconfirmed TX1: spends TX0:0, fee = 5_000
175+
let tx_1 = make_tx(&[OutPoint::new(txid_0, 0)], &[Amount::from_sat(49_995_000)]);
176+
let txid_1 = tx_1.compute_txid();
177+
let weight_1 = tx_1.weight();
178+
let _ = graph.insert_tx(tx_1);
179+
let _ = graph.insert_seen_at(txid_1, 1001);
180+
181+
// Unconfirmed TX2: spends TX0:1, fee = 5_000
182+
let tx_2 = make_tx(&[OutPoint::new(txid_0, 1)], &[Amount::from_sat(39_995_000)]);
183+
let txid_2 = tx_2.compute_txid();
184+
let weight_2 = tx_2.weight();
185+
let _ = graph.insert_tx(tx_2);
186+
let _ = graph.insert_seen_at(txid_2, 1002);
187+
188+
// Unconfirmed TX3: spends TX1:0 and TX2:0, fee = 10_000
189+
let tx_3 = make_tx(
190+
&[OutPoint::new(txid_1, 0), OutPoint::new(txid_2, 0)],
191+
&[Amount::from_sat(89_980_000)],
192+
);
193+
let txid_3 = tx_3.compute_txid();
194+
let weight_3 = tx_3.weight();
195+
let _ = graph.insert_tx(tx_3);
196+
let _ = graph.insert_seen_at(txid_3, 1003);
197+
198+
let packages = build_packages(&graph, &chain);
199+
let pkg = packages.get(&OutPoint::new(txid_3, 0)).unwrap();
200+
201+
// TX0 counted once despite being ancestor of both TX1 and TX2.
202+
assert_eq!(
203+
pkg.fee,
204+
Amount::from_sat(10_000_000 + 5_000 + 5_000 + 10_000)
205+
);
206+
assert_eq!(pkg.weight, weight_0 + weight_1 + weight_2 + weight_3);
207+
}
208+
209+
#[test]
210+
fn fee_deficit_at_various_feerates() {
211+
let pkg = AncestorPackage {
212+
weight: Weight::from_wu(1000),
213+
fee: Amount::from_sat(250),
214+
};
215+
216+
// At 1 sat/vbyte (0.25 sat/wu): required = 250. Met exactly.
217+
let rate_1 = FeeRate::from_sat_per_vb_unchecked(1);
218+
assert_eq!(pkg.fee_deficit(rate_1), Amount::ZERO);
219+
220+
// At 2 sat/vbyte (0.5 sat/wu): required = 500. Deficit = 250.
221+
let rate_2 = FeeRate::from_sat_per_vb_unchecked(2);
222+
assert_eq!(pkg.fee_deficit(rate_2), Amount::from_sat(250));
223+
224+
// At 10 sat/vbyte (2.5 sat/wu): required = 2500. Deficit = 2250.
225+
let rate_10 = FeeRate::from_sat_per_vb_unchecked(10);
226+
assert_eq!(pkg.fee_deficit(rate_10), Amount::from_sat(2250));
227+
228+
// At 0 sat/vbyte: required = 0. Already met.
229+
assert_eq!(pkg.fee_deficit(FeeRate::ZERO), Amount::ZERO);
230+
}

0 commit comments

Comments
 (0)