use crate::types::BlockHeader;
pub const LOCK_IN_PERIOD: u32 = 2016;
pub const ACTIVATION_THRESHOLD: u32 = 1916;
#[derive(Debug, Clone, Copy)]
pub struct Bip9Deployment {
pub bit: u8,
pub start_time: u64,
pub timeout: u64,
}
pub fn bip54_deployment_mainnet() -> Bip9Deployment {
Bip9Deployment {
bit: 15,
start_time: 0,
timeout: u64::MAX,
}
}
pub fn activation_height_from_headers<H: AsRef<BlockHeader>>(
headers: &[H],
current_height: u64,
current_time: u64,
deployment: &Bip9Deployment,
) -> Option<u64> {
if deployment.start_time >= deployment.timeout {
return None;
}
if current_time < deployment.start_time || current_time >= deployment.timeout {
return None;
}
if headers.len() < LOCK_IN_PERIOD as usize {
return None;
}
let mut count = 0u32;
for h in headers.iter().take(LOCK_IN_PERIOD as usize) {
let v = h.as_ref().version as u32;
if ((v >> deployment.bit) & 1) != 0 {
count += 1;
}
}
if count < ACTIVATION_THRESHOLD {
return None;
}
let period_end = current_height.saturating_sub(1);
let period_index = period_end / LOCK_IN_PERIOD as u64;
let activation_height = (period_index + 2).checked_mul(LOCK_IN_PERIOD as u64)?;
Some(activation_height)
}
#[inline]
pub fn merge_bip54_activation_candidate(
previous: Option<u64>,
candidate: Option<u64>,
) -> Option<u64> {
match (previous, candidate) {
(Some(a), Some(b)) => Some(a.min(b)),
(Some(a), None) => Some(a),
(None, Some(b)) => Some(b),
(None, None) => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::BlockHeader;
fn header(version: i64) -> BlockHeader {
BlockHeader {
version,
prev_block_hash: [0u8; 32],
merkle_root: [0u8; 32],
timestamp: 0,
bits: 0x1d00ffff,
nonce: 0,
}
}
#[test]
fn disabled_deployment_returns_none() {
let dep = Bip9Deployment {
bit: 0,
start_time: 100,
timeout: 100,
};
let headers: Vec<BlockHeader> = (0..2016).map(|_| header(1)).collect();
assert!(activation_height_from_headers(&headers, 4032, 150, &dep).is_none());
}
#[test]
fn active_after_lockin() {
let dep = Bip9Deployment {
bit: 0,
start_time: 0,
timeout: u64::MAX,
};
let headers: Vec<BlockHeader> = (0..2016).map(|_| header(1)).collect();
assert_eq!(
activation_height_from_headers(&headers, 4032, 1, &dep),
Some(6048)
);
assert_eq!(
activation_height_from_headers(&headers, 6048, 1, &dep),
Some(8064)
);
assert_eq!(
merge_bip54_activation_candidate(
activation_height_from_headers(&headers, 4032, 1, &dep),
activation_height_from_headers(&headers, 6048, 1, &dep),
),
Some(6048)
);
}
#[test]
fn not_active_before_activation_height() {
let dep = Bip9Deployment {
bit: 0,
start_time: 0,
timeout: u64::MAX,
};
let headers: Vec<BlockHeader> = (0..2016).map(|_| header(1)).collect();
let act = activation_height_from_headers(&headers, 4031, 1, &dep);
assert_eq!(act, Some(6048));
assert!(
!crate::bip_validation::is_bip54_active_at(4031, crate::types::Network::Mainnet, act),
"override height must not activate BIP54 before that height"
);
}
#[test]
fn huge_current_height_does_not_panic() {
let dep = Bip9Deployment {
bit: 0,
start_time: 0,
timeout: u64::MAX,
};
let headers: Vec<BlockHeader> = (0..2016).map(|_| header(1)).collect();
assert!(activation_height_from_headers(&headers, u64::MAX, 1, &dep).is_none());
}
}