use crate::types::BlockHeader;
use blvm_spec_lock::spec_locked;
pub const MEDIAN_TIME_BLOCKS: usize = 11;
#[spec_locked("5.5")]
pub fn get_median_time_past<H: AsRef<BlockHeader>>(headers: &[H]) -> u64 {
if headers.is_empty() {
return 0;
}
let start_idx = headers.len().saturating_sub(MEDIAN_TIME_BLOCKS);
let recent_headers = &headers[start_idx..];
let n = recent_headers.len().min(MEDIAN_TIME_BLOCKS);
let mut timestamps = [0u64; MEDIAN_TIME_BLOCKS];
for (i, h) in recent_headers.iter().enumerate().take(n) {
timestamps[i] = h.as_ref().timestamp;
}
let timestamps = &mut timestamps[..n];
timestamps.sort_unstable();
if timestamps.is_empty() {
0
} else if timestamps.len() % 2 == 0 {
let mid = timestamps.len() / 2;
let lower = timestamps[mid - 1];
let upper = timestamps[mid];
debug_assert!(
lower <= upper,
"Lower median timestamp ({lower}) must be <= upper ({upper})"
);
let median = ((lower as u128 + upper as u128) / 2) as u64;
debug_assert!(
median >= lower && median <= upper,
"Median ({median}) must be between lower ({lower}) and upper ({upper})"
);
median
} else {
timestamps[timestamps.len() / 2]
}
}
#[spec_locked("5.5")]
pub fn get_median_time_past_reversed(recent_headers: &[BlockHeader]) -> u64 {
if recent_headers.is_empty() {
return 0;
}
let reversed: Vec<BlockHeader> = recent_headers.iter().rev().cloned().collect();
let start_idx = reversed.len().saturating_sub(MEDIAN_TIME_BLOCKS);
let headers = &reversed[start_idx..];
get_median_time_past(headers)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::BlockHeader;
fn create_header(timestamp: u64) -> BlockHeader {
BlockHeader {
version: 1,
prev_block_hash: [0u8; 32],
merkle_root: [0u8; 32],
timestamp,
bits: 0x1d00ffff,
nonce: 0,
}
}
#[test]
fn test_median_time_empty() {
let headers: Vec<BlockHeader> = vec![];
assert_eq!(get_median_time_past(&headers), 0);
}
#[test]
fn test_median_time_single() {
let headers = vec![create_header(1000)];
assert_eq!(get_median_time_past(&headers), 1000);
}
#[test]
fn test_median_time_three_blocks() {
let headers = vec![
create_header(1000),
create_header(2000),
create_header(3000),
];
assert_eq!(get_median_time_past(&headers), 2000);
}
#[test]
fn test_median_time_four_blocks() {
let headers = vec![
create_header(1000),
create_header(2000),
create_header(3000),
create_header(4000),
];
assert_eq!(get_median_time_past(&headers), 2500);
}
#[test]
fn test_median_time_even_branch_large_timestamps_no_overflow() {
let headers = [create_header(u64::MAX - 1), create_header(u64::MAX)];
assert_eq!(get_median_time_past(&headers), u64::MAX - 1);
}
#[test]
fn test_median_time_eleven_blocks() {
let headers: Vec<BlockHeader> = (1..=11).map(|i| create_header(i * 100)).collect();
assert_eq!(get_median_time_past(&headers), 600);
}
#[test]
fn test_median_time_more_than_eleven() {
let headers: Vec<BlockHeader> = (1..=20).map(|i| create_header(i * 100)).collect();
assert_eq!(get_median_time_past(&headers), 1500);
}
#[test]
fn test_median_time_unsorted() {
let headers = vec![
create_header(3000),
create_header(1000),
create_header(2000),
];
assert_eq!(get_median_time_past(&headers), 2000);
}
#[test]
fn test_median_time_reversed() {
let headers = vec![
create_header(3000), create_header(2000),
create_header(1000), ];
assert_eq!(get_median_time_past_reversed(&headers), 2000);
}
#[test]
fn test_median_time_past_bip113_example() {
let headers: Vec<BlockHeader> =
vec![100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100]
.into_iter()
.map(create_header)
.collect();
assert_eq!(get_median_time_past(&headers), 600);
}
}