diff --git a/src/chain/chain.rs b/src/chain/chain.rs index 4c831629..8a426ff8 100644 --- a/src/chain/chain.rs +++ b/src/chain/chain.rs @@ -13,14 +13,10 @@ use super::{ error::{CFHeaderSyncError, CFilterSyncError, HeaderSyncError}, graph::{AcceptHeaderChanges, BlockTree, HeaderRejection}, CFHeaderChanges, ChainState, Filter, FilterCheck, FilterHeaderRequest, FilterRequest, - FilterRequestState, HeightMonitor, PeerId, + FilterRequestState, HeaderValidationExt, HeightMonitor, PeerId, }; use crate::IndexedFilter; -use crate::{ - chain::{header_batch::HeadersBatch, BlockHeaderChanges}, - messages::Event, - Dialog, Info, Progress, -}; +use crate::{chain::BlockHeaderChanges, messages::Event, Dialog, Info, Progress}; const FILTER_BASIC: u8 = 0x00; const CF_HEADER_BATCH_SIZE: u32 = 1_999; @@ -91,11 +87,18 @@ impl Chain { // Sync the chain with headers from a peer, adjusting to reorgs if needed pub(crate) fn sync_chain( &mut self, - message: Vec
, + header_batch: Vec
, ) -> Result, HeaderSyncError> { - let header_batch = HeadersBatch::new(message).map_err(|_| HeaderSyncError::EmptyMessage)?; + if header_batch.is_empty() { + return Err(HeaderSyncError::EmptyMessage); + } // If our chain already has the last header in the message there is no new information - if self.header_chain.contains(header_batch.last().block_hash()) { + if self.header_chain.contains( + header_batch + .last() + .expect("non-empty check above.") + .block_hash(), + ) { return Ok(Vec::new()); } // We check first if the peer is sending us nonsense @@ -153,21 +156,16 @@ impl Chain { } // These are invariants in all batches of headers we receive - fn sanity_check(&mut self, header_batch: &HeadersBatch) -> Result<(), HeaderSyncError> { - // All the headers connect with each other and is the difficulty adjustment not absurd + fn sanity_check(&mut self, header_batch: &[Header]) -> Result<(), HeaderSyncError> { if !header_batch.connected() { return Err(HeaderSyncError::HeadersNotConnected); } - - // All headers pass their own proof of work and the network minimum - if !header_batch.individually_valid_pow() { + if !header_batch.passes_own_pow() { return Err(HeaderSyncError::InvalidHeaderWork); } - - if !header_batch.bits_adhere_transition(self.network) { + if !header_batch.bits_adhere_transition_threshold(self.network) { return Err(HeaderSyncError::InvalidBits); } - Ok(()) } diff --git a/src/chain/header_batch.rs b/src/chain/header_batch.rs deleted file mode 100644 index 556a7b23..00000000 --- a/src/chain/header_batch.rs +++ /dev/null @@ -1,78 +0,0 @@ -use bitcoin::{block::Header, params::Params, Target}; - -use crate::impl_sourceless_error; - -pub(crate) struct HeadersBatch { - batch: Vec
, -} - -// The expected response for the majority of getheaders messages are 2000 headers that should be in order. -// This struct provides basic sanity checks and helper methods. -impl HeadersBatch { - pub(crate) fn new(batch: Vec
) -> Result { - if batch.is_empty() { - return Err(HeadersBatchError::EmptyVec); - } - Ok(HeadersBatch { batch }) - } - - // Are they all logically connected? - pub(crate) fn connected(&self) -> bool { - self.batch - .iter() - .zip(self.batch.iter().skip(1)) - .all(|(first, second)| first.block_hash().eq(&second.prev_blockhash)) - } - - // Are all the blocks of sufficient work and meet their own target? - pub(crate) fn individually_valid_pow(&self) -> bool { - !self.batch.iter().any(|header| { - let target = header.target(); - let valid_pow = header.validate_pow(target); - valid_pow.is_err() - }) - } - - // Do the targets not change drastically within the batch? - pub(crate) fn bits_adhere_transition(&self, params: impl AsRef) -> bool { - let params = params.as_ref(); - if params.allow_min_difficulty_blocks { - return true; - } - self.batch - .iter() - .zip(self.batch.iter().skip(1)) - .all(|(first, second)| { - let transition = Target::from_compact(first.bits).max_transition_threshold(params); - Target::from_compact(second.bits).le(&transition) - }) - } - - // The tip of the list - pub(crate) fn last(&self) -> &Header { - self.batch - .last() - .expect("headers have at least one element by construction") - } - - pub(crate) fn into_iter(self) -> impl Iterator { - self.batch.into_iter() - } -} - -#[derive(Debug)] -pub(crate) enum HeadersBatchError { - EmptyVec, -} - -impl core::fmt::Display for HeadersBatchError { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - HeadersBatchError::EmptyVec => { - write!(f, "no headers were found in the initialization vector.") - } - } - } -} - -impl_sourceless_error!(HeadersBatchError); diff --git a/src/chain/mod.rs b/src/chain/mod.rs index 7260a753..17133797 100644 --- a/src/chain/mod.rs +++ b/src/chain/mod.rs @@ -11,7 +11,6 @@ pub mod checkpoints; #[allow(dead_code)] pub(crate) mod error; pub(crate) mod graph; -pub(crate) mod header_batch; use std::collections::HashMap; @@ -20,7 +19,7 @@ use bitcoin::hashes::{sha256d, Hash}; use bitcoin::Amount; use bitcoin::{ bip158::BlockFilter, block::Header, params::Params, BlockHash, FilterHash, FilterHeader, - ScriptBuf, Work, + ScriptBuf, Target, Work, }; use crate::network::PeerId; @@ -284,6 +283,42 @@ impl ZerolikeExt for Work { } } +trait HeaderValidationExt { + // Headers are logically connected. + fn connected(&self) -> bool; + // Each header passes its own work target. + fn passes_own_pow(&self) -> bool; + // Targets do not change out of the acceptable range. + fn bits_adhere_transition_threshold(&self, params: impl AsRef) -> 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) -> 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) + }) + } +} + // Emulation of `GetBlockSubsidy` in Bitcoin Core: https://github.com/bitcoin/bitcoin/blob/master/src/validation.cpp#L1944 pub(crate) fn block_subsidy(height: u32) -> Amount { let halvings = height / SUBSIDY_HALVING_INTERVAL;