@@ -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 ( ) ) ;
0 commit comments