use std::{
collections::{BTreeMap, HashSet},
ops::RangeBounds,
str::FromStr,
sync::Arc,
};
use crate::{block, parameters::Network, BoxError};
#[cfg(test)]
mod tests;
const MAINNET_CHECKPOINTS: &str = include_str!("main-checkpoints.txt");
pub(crate) const TESTNET_CHECKPOINTS: &str = include_str!("test-checkpoints.txt");
lazy_static::lazy_static! {
static ref MAINNET_CHECKPOINT_LIST: Arc<CheckpointList> =
Arc::new(MAINNET_CHECKPOINTS.parse().expect("hard-coded mainnet checkpoint list parses"));
pub(crate) static ref TESTNET_CHECKPOINT_LIST: Arc<CheckpointList> =
Arc::new(TESTNET_CHECKPOINTS.parse().expect("hard-coded testnet checkpoint list parses"));
}
impl Network {
pub fn genesis_hash(&self) -> block::Hash {
match self {
Network::Mainnet => "00040fe8ec8471911baa1db1266ea15dd06b4a8a5c453883c000b031973dce08"
.parse()
.expect("hard-coded hash parses"),
Network::Testnet(params) => params.genesis_hash(),
}
}
pub fn checkpoint_list(&self) -> Arc<CheckpointList> {
match self {
Network::Mainnet => MAINNET_CHECKPOINT_LIST.clone(),
Network::Testnet(params) => params.checkpoints(),
}
}
}
fn checkpoint_height_and_hash(checkpoint: &str) -> Result<(block::Height, block::Hash), BoxError> {
let fields = checkpoint.split(' ').collect::<Vec<_>>();
if let [height, hash] = fields[..] {
Ok((height.parse()?, hash.parse()?))
} else {
Err(format!("Invalid checkpoint format: expected 2 space-separated fields but found {}: '{checkpoint}'", fields.len()).into())
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct CheckpointList(BTreeMap<block::Height, block::Hash>);
impl FromStr for CheckpointList {
type Err = BoxError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut checkpoint_list: Vec<(block::Height, block::Hash)> = Vec::new();
for checkpoint in s.lines() {
checkpoint_list.push(checkpoint_height_and_hash(checkpoint)?);
}
CheckpointList::from_list(checkpoint_list)
}
}
impl CheckpointList {
pub fn from_list(
list: impl IntoIterator<Item = (block::Height, block::Hash)>,
) -> Result<Self, BoxError> {
let original_checkpoints: Vec<(block::Height, block::Hash)> = list.into_iter().collect();
let original_len = original_checkpoints.len();
let checkpoints: BTreeMap<block::Height, block::Hash> =
original_checkpoints.into_iter().collect();
match checkpoints.iter().next() {
Some((block::Height(0), _hash)) => {}
Some(_) => Err("checkpoints must start at the genesis block height 0")?,
None => Err("there must be at least one checkpoint, for the genesis block")?,
};
if checkpoints.len() != original_len {
Err("checkpoint heights must be unique")?;
}
let block_hashes: HashSet<&block::Hash> = checkpoints.values().collect();
if block_hashes.len() != original_len {
Err("checkpoint hashes must be unique")?;
}
if block_hashes.contains(&block::Hash([0; 32])) {
Err("checkpoint list contains invalid checkpoint hash: found null hash")?;
}
let checkpoints = CheckpointList(checkpoints);
if checkpoints.max_height() > block::Height::MAX {
Err("checkpoint list contains invalid checkpoint: checkpoint height is greater than the maximum block height")?;
}
Ok(checkpoints)
}
pub fn contains(&self, height: block::Height) -> bool {
self.0.contains_key(&height)
}
pub fn hash(&self, height: block::Height) -> Option<block::Hash> {
self.0.get(&height).cloned()
}
pub fn max_height(&self) -> block::Height {
self.max_height_in_range(..)
.expect("checkpoint lists must have at least one checkpoint")
}
pub fn min_height_in_range<R>(&self, range: R) -> Option<block::Height>
where
R: RangeBounds<block::Height>,
{
self.0.range(range).map(|(height, _)| *height).next()
}
pub fn max_height_in_range<R>(&self, range: R) -> Option<block::Height>
where
R: RangeBounds<block::Height>,
{
self.0.range(range).map(|(height, _)| *height).next_back()
}
pub fn iter(&self) -> impl Iterator<Item = (&block::Height, &block::Hash)> {
self.0.iter()
}
pub fn iter_cloned(&self) -> impl Iterator<Item = (block::Height, block::Hash)> + '_ {
self.iter().map(|(&height, &hash)| (height, hash))
}
pub fn prev_checkpoint_index(&self, height: block::Height) -> usize {
self.0
.keys()
.rposition(|&key| key <= height)
.expect("checkpoints must start at the genesis block height 0")
}
#[allow(clippy::len_without_is_empty)]
pub fn len(&self) -> usize {
self.0.len()
}
}