pub(crate) mod block_queue;
#[allow(clippy::module_inception)]
pub(crate) mod chain;
pub mod checkpoints;
#[allow(dead_code)]
pub(crate) mod error;
pub(crate) mod graph;
use std::collections::VecDeque;
use bitcoin::constants::SUBSIDY_HALVING_INTERVAL;
use bitcoin::hashes::{sha256d, Hash};
use bitcoin::Amount;
use bitcoin::{
bip158::BlockFilter, block::Header, p2p::message_filter::CFHeaders, params::Params, BlockHash,
FilterHash, FilterHeader, ScriptBuf, Target, Work,
};
use crate::network::PeerId;
use crate::HeaderCheckpoint;
const MAX_PREV_STOP_HASHES: usize = 3;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct IndexedHeader {
pub height: u32,
pub header: Header,
}
impl std::cmp::PartialOrd for IndexedHeader {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl std::cmp::Ord for IndexedHeader {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.height.cmp(&other.height)
}
}
impl IndexedHeader {
pub(crate) fn new(height: u32, header: Header) -> Self {
Self { height, header }
}
}
impl IndexedHeader {
pub fn block_hash(&self) -> BlockHash {
self.header.block_hash()
}
pub fn prev_blockhash(&self) -> BlockHash {
self.header.prev_blockhash
}
}
#[derive(Debug, Clone)]
pub(crate) enum HeaderSyncEffect {
Added,
Empty,
Reorg(Vec<BlockHash>),
}
#[derive(Debug, Clone)]
pub enum BlockHeaderChanges {
Connected(IndexedHeader),
Reorganized {
accepted: Vec<IndexedHeader>,
reorganized: Vec<IndexedHeader>,
},
ForkAdded(IndexedHeader),
}
#[derive(Debug, Clone)]
pub enum ChainState {
Snapshot(Vec<IndexedHeader>),
Checkpoint(HeaderCheckpoint),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct FilterCommitment {
pub header: FilterHeader,
pub filter_hash: FilterHash,
}
#[derive(Debug, Clone)]
pub(crate) struct FilterRequestState {
pub last_filter_request: Option<FilterRequest>,
pub last_filter_header_request: Option<FilterHeaderRequest>,
pub pending_batch: Option<(PeerId, CFHeaderBatch)>,
pub agreement_state: FilterHeaderAgreements,
prev_cf_header_stop_hashes: VecDeque<BlockHash>,
}
impl FilterRequestState {
pub(crate) fn new(required: u8) -> Self {
Self {
last_filter_request: None,
last_filter_header_request: None,
pending_batch: None,
agreement_state: FilterHeaderAgreements::new(required),
prev_cf_header_stop_hashes: VecDeque::new(),
}
}
pub(crate) fn push_stop_hash(&mut self, hash: BlockHash) {
if self.prev_cf_header_stop_hashes.contains(&hash) {
return;
}
if self.prev_cf_header_stop_hashes.len() >= MAX_PREV_STOP_HASHES {
self.prev_cf_header_stop_hashes.pop_front();
}
self.prev_cf_header_stop_hashes.push_back(hash);
}
pub(crate) fn was_recently_requested(&self, hash: &BlockHash) -> bool {
self.prev_cf_header_stop_hashes.contains(hash)
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct FilterRequest {
#[allow(unused)]
pub start_height: u32,
pub stop_hash: BlockHash,
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct FilterHeaderRequest {
pub start_height: u32,
pub stop_hash: BlockHash,
pub expected_prev_filter_header: Option<FilterHeader>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct FilterHeaderAgreements {
current: u8,
required: u8,
}
impl FilterHeaderAgreements {
pub(crate) fn new(required: u8) -> Self {
Self {
current: 0,
required,
}
}
pub(crate) fn got_agreement(&mut self) {
self.current += 1;
}
pub(crate) fn enough_agree(&self) -> bool {
self.current.ge(&self.required)
}
pub(crate) fn reset_agreements(&mut self) {
self.current = 0;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum CFHeaderChanges {
AddedToQueue,
Extended,
Conflict,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct CFHeaderBatch {
inner: Vec<FilterCommitment>,
prev_filter_header: FilterHeader,
stop_hash: BlockHash,
}
impl CFHeaderBatch {
pub(crate) fn new(batch: CFHeaders) -> Self {
let mut headers: Vec<FilterCommitment> = vec![];
let mut prev_header = batch.previous_filter_header;
for hash in batch.filter_hashes {
let next_header = hash.filter_header(&prev_header);
headers.push(FilterCommitment {
header: next_header,
filter_hash: hash,
});
prev_header = next_header;
}
Self {
inner: headers,
prev_filter_header: batch.previous_filter_header,
stop_hash: batch.stop_hash,
}
}
fn prev_header(&self) -> &FilterHeader {
&self.prev_filter_header
}
fn stop_hash(&self) -> BlockHash {
self.stop_hash
}
fn len(&self) -> u32 {
self.inner.len() as u32
}
fn take_inner(&mut self) -> Vec<FilterCommitment> {
core::mem::take(&mut self.inner)
}
}
impl From<CFHeaders> for CFHeaderBatch {
fn from(val: CFHeaders) -> Self {
CFHeaderBatch::new(val)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct FilterCheck {
pub(crate) was_last_in_batch: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Filter {
filter_hash: FilterHash,
block_hash: BlockHash,
block_filter: BlockFilter,
}
#[allow(dead_code)]
impl Filter {
pub fn new(contents: Vec<u8>, block_hash: BlockHash) -> Self {
let hash = sha256d::Hash::hash(&contents);
let filter_hash = FilterHash::from_raw_hash(hash);
let block_filter = BlockFilter::new(&contents);
Self {
filter_hash,
block_hash,
block_filter,
}
}
pub fn filter_hash(&self) -> &FilterHash {
&self.filter_hash
}
pub fn block_hash(&self) -> BlockHash {
self.block_hash
}
pub fn contains_any<'a>(&'a self, scripts: impl Iterator<Item = &'a ScriptBuf>) -> bool {
self.block_filter
.match_any(&self.block_hash, scripts.map(|script| script.to_bytes()))
.expect("vec reader is infallible")
}
pub fn into_filter(self) -> BlockFilter {
self.block_filter
}
pub fn contents(self) -> Vec<u8> {
self.block_filter.content
}
}
trait HeightExt: Clone + Copy + std::hash::Hash + PartialEq + Eq + PartialOrd + Ord {
fn increment(&self) -> Self;
fn from_u64_checked(height: u64) -> Option<Self>;
fn is_adjustment_multiple(&self, params: impl AsRef<Params>) -> bool;
}
impl HeightExt for u32 {
fn increment(&self) -> Self {
self + 1
}
fn is_adjustment_multiple(&self, params: impl AsRef<Params>) -> bool {
*self as u64 % params.as_ref().difficulty_adjustment_interval() == 0
}
fn from_u64_checked(height: u64) -> Option<Self> {
height.try_into().ok()
}
}
trait ZerolikeExt {
fn zero() -> Self;
}
impl ZerolikeExt for Work {
fn zero() -> Self {
Self::from_be_bytes([0; 32])
}
}
trait HeaderValidationExt {
fn connected(&self) -> bool;
fn passes_own_pow(&self) -> bool;
fn bits_adhere_transition_threshold(&self, params: impl AsRef<Params>) -> bool;
}
impl HeaderValidationExt for &[Header] {
fn connected(&self) -> bool {
self.iter()
.zip(self.iter().skip(1))
.all(|(first, second)| first.block_hash().eq(&second.prev_blockhash))
}
fn passes_own_pow(&self) -> bool {
!self.iter().any(|header| {
let target = header.target();
let valid_pow = header.validate_pow(target);
valid_pow.is_err()
})
}
fn bits_adhere_transition_threshold(&self, params: impl AsRef<Params>) -> bool {
let params = params.as_ref();
if params.allow_min_difficulty_blocks {
return true;
}
self.iter().zip(self.iter().skip(1)).all(|(first, second)| {
let transition = Target::from_compact(first.bits).max_transition_threshold(params);
Target::from_compact(second.bits).le(&transition)
})
}
}
pub(crate) fn block_subsidy(height: u32) -> Amount {
let halvings = height / SUBSIDY_HALVING_INTERVAL;
if halvings >= 64 {
return Amount::ZERO;
}
let base = Amount::ONE_BTC.to_sat() * 50;
let subsidy = base >> halvings;
Amount::from_sat(subsidy)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_subsidy_calculation() {
let first_subsidy = block_subsidy(2);
assert_eq!(first_subsidy, Amount::from_btc(50.0).unwrap());
let first_subsidy = block_subsidy(209_999);
assert_eq!(first_subsidy, Amount::from_btc(50.0).unwrap());
let second_subsidy = block_subsidy(210_000);
assert_eq!(second_subsidy, Amount::from_btc(25.0).unwrap());
let fourth_subsidy = block_subsidy(630_000);
assert_eq!(fourth_subsidy, Amount::from_btc(6.25).unwrap());
let now = block_subsidy(902_000);
assert_eq!(now, Amount::from_btc(3.125).unwrap());
}
}