Skip to content

Commit 13d689a

Browse files
committed
feat(chain,node): BlockTree::median_time_past_at + wire into apply_block TipState
Op: extend
1 parent 6e689ad commit 13d689a

2 files changed

Lines changed: 76 additions & 5 deletions

File tree

crates/chain/src/tree.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,40 @@ impl BlockTree {
127127
}
128128
locator
129129
}
130+
/// Returns the median time of the most recent `window` blocks, inclusive
131+
/// of `start_id`, walking backward via parent pointers.
132+
///
133+
/// BIP113 uses `window = 11`. When the chain has fewer than `window`
134+
/// blocks, the median is computed over however many exist. Returns `None`
135+
/// only when `start_id` is not in the tree.
136+
#[must_use]
137+
pub fn median_time_past_at(&self, start_id: NodeId, window: usize) -> Option<u32> {
138+
if window == 0 {
139+
return Some(0);
140+
}
141+
142+
let mut times = Vec::with_capacity(window);
143+
let mut cursor = start_id;
144+
while times.len() < window {
145+
let Ok(node) = self.node(cursor) else {
146+
if times.is_empty() {
147+
return None;
148+
}
149+
break;
150+
};
151+
times.push(node.header.time);
152+
let Some(parent) = node.parent else {
153+
break;
154+
};
155+
cursor = parent;
156+
}
157+
158+
if times.is_empty() {
159+
return None;
160+
}
161+
times.sort_unstable();
162+
Some(times[times.len() / 2])
163+
}
130164

131165
/// Inserts a header whose parent is inferred from `prev_blockhash`.
132166
pub fn insert_header(
@@ -312,6 +346,36 @@ mod tests {
312346
Ok(())
313347
}
314348

349+
#[test]
350+
fn median_time_past_at_returns_median_of_recent_timestamps()
351+
-> Result<(), Box<dyn std::error::Error>> {
352+
let mut tree = BlockTree::new();
353+
let mut prev_hash = BlockHash::all_zeros();
354+
let mut tip = None;
355+
356+
for i in 0..11_u32 {
357+
let header = BlockHeader {
358+
version: Version::ONE,
359+
prev_blockhash: prev_hash,
360+
merkle_root: TxMerkleNode::all_zeros(),
361+
time: 1_000_000 + i * 600,
362+
bits: CompactTarget::from_consensus(0x207f_ffff),
363+
nonce: 0,
364+
};
365+
prev_hash = header.block_hash();
366+
tip = Some(tree.insert_header(header, NodeStatus::HeaderValid)?);
367+
}
368+
369+
let Some(tip) = tip else {
370+
panic!("chain has 11 blocks should yield a tip");
371+
};
372+
let Some(mtp) = tree.median_time_past_at(tip, 11) else {
373+
panic!("chain has 11 blocks should yield Some");
374+
};
375+
assert_eq!(mtp, 1_003_000);
376+
Ok(())
377+
}
378+
315379
fn test_header(prev_blockhash: BlockHash, height: u32) -> BlockHeader {
316380
let mut merkle = [0_u8; 32];
317381
merkle[..4].copy_from_slice(&height.to_le_bytes());

crates/node/src/apply.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,18 @@ pub fn apply_block(
7676
}
7777

7878
let prev_tip_state = match prior.as_deref() {
79-
Some(tip) => bitcoin_rs_consensus::rust_path::TipState {
80-
height: Some(tip.height),
81-
block_hash: None,
82-
median_time_past: 0,
83-
},
79+
Some(tip) => {
80+
let mtp = handles
81+
.block_tree
82+
.read()
83+
.median_time_past_at(tip.tip_id, 11)
84+
.unwrap_or(0);
85+
bitcoin_rs_consensus::rust_path::TipState {
86+
height: Some(tip.height),
87+
block_hash: None,
88+
median_time_past: mtp,
89+
}
90+
}
8491
None => bitcoin_rs_consensus::rust_path::TipState {
8592
height: None,
8693
block_hash: None,

0 commit comments

Comments
 (0)