Skip to content

Commit dfe4f4e

Browse files
evanlinjinclaude
andcommitted
test(chain): add MTP integration test for canonicalize_with_mtp
Covers the previously-untested median-time-past path: a `LocalChain<Header>` whose block times equal their heights (so the MTP of an 11-block window is the middle height), with a tx confirmed at a known height. Asserts both the tip MTP and the per-tx MTP. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 50a9634 commit dfe4f4e

1 file changed

Lines changed: 86 additions & 0 deletions

File tree

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
#![cfg(feature = "miniscript")]
2+
3+
//! Integration test for median-time-past (MTP) computation via
4+
//! [`LocalChain::canonicalize_with_mtp`].
5+
6+
use std::collections::BTreeMap;
7+
8+
use bdk_chain::{local_chain::LocalChain, ChainPosition, ConfirmationBlockTime, TxGraph};
9+
use bdk_testenv::{hash, utils::new_tx};
10+
use bitcoin::{
11+
block::Header, hashes::Hash, Amount, BlockHash, OutPoint, ScriptBuf, Transaction, TxIn, TxOut,
12+
};
13+
14+
/// Build a block header with an explicit `time`.
15+
fn header(prev_blockhash: BlockHash, time: u32) -> Header {
16+
Header {
17+
version: bitcoin::block::Version::default(),
18+
prev_blockhash,
19+
merkle_root: bitcoin::hash_types::TxMerkleNode::all_zeros(),
20+
time,
21+
bits: bitcoin::CompactTarget::default(),
22+
nonce: 0,
23+
}
24+
}
25+
26+
/// Build a connected chain of blocks `0..=tip_height` where each block's timestamp equals its
27+
/// height (the block at height `h` has `time == h`).
28+
///
29+
/// With timestamps equal to heights, the median-time-past at height `h` — the median of the 11
30+
/// timestamps in `h-10..=h` — is simply the middle height, `h - 5`.
31+
fn chain_with_time_equal_to_height(tip_height: u32) -> LocalChain<Header> {
32+
let mut headers = vec![header(BlockHash::all_zeros(), 0)]; // genesis at height 0
33+
for h in 1..=tip_height {
34+
let prev = headers[(h - 1) as usize].block_hash();
35+
headers.push(header(prev, h));
36+
}
37+
let blocks: BTreeMap<u32, Header> = headers
38+
.into_iter()
39+
.enumerate()
40+
.map(|(height, hdr)| (height as u32, hdr))
41+
.collect();
42+
LocalChain::from_blocks(blocks).expect("chain connects from genesis")
43+
}
44+
45+
#[test]
46+
fn canonicalize_with_mtp_computes_median_time_past() {
47+
// A 13-block chain (heights 0..=12) where each block's time equals its height.
48+
let chain = chain_with_time_equal_to_height(12);
49+
50+
// A single transaction confirmed at height 11.
51+
let mut tx_graph = TxGraph::default();
52+
let tx = Transaction {
53+
input: vec![TxIn {
54+
previous_output: OutPoint::new(hash!("parent"), 0),
55+
..Default::default()
56+
}],
57+
output: vec![TxOut {
58+
value: Amount::from_sat(50_000),
59+
script_pubkey: ScriptBuf::new(),
60+
}],
61+
..new_tx(1)
62+
};
63+
let txid = tx.compute_txid();
64+
let _ = tx_graph.insert_tx(tx);
65+
let _ = tx_graph.insert_anchor(
66+
txid,
67+
ConfirmationBlockTime {
68+
block_id: chain.get(11).unwrap().block_id(),
69+
confirmation_time: 0,
70+
},
71+
);
72+
73+
// Canonicalize with MTP enabled.
74+
let view = chain.canonicalize_with_mtp(&tx_graph, chain.tip().block_id(), Default::default());
75+
76+
// Tip is at height 12, so its MTP is the median of heights 2..=12, which is 7.
77+
assert_eq!(view.tip_mtp(), Some(7));
78+
79+
// The transaction is confirmed at height 11, so its MTP is the median of heights 1..=11 == 6.
80+
let canonical_tx = view
81+
.txs()
82+
.find(|c| c.txid == txid)
83+
.expect("tx is canonical");
84+
assert!(matches!(canonical_tx.pos, ChainPosition::Confirmed { .. }));
85+
assert_eq!(canonical_tx.mtp, Some(6));
86+
}

0 commit comments

Comments
 (0)