|
| 1 | +use crate::{AsyncBlockSourceResult, BlockHeaderData, BlockSourceError, BlockSourceResult}; |
| 2 | + |
| 3 | +use bitcoin::blockdata::block::Block; |
| 4 | +use bitcoin::hash_types::BlockHash; |
| 5 | +use bitcoin::network::constants::Network; |
| 6 | + |
| 7 | +/// The `Poll` trait defines behavior for polling block sources for a chain tip and retrieving |
| 8 | +/// related chain data. It serves as an adapter for `BlockSource`. |
| 9 | +pub trait Poll { |
| 10 | + /// Returns a chain tip in terms of its relationship to the provided chain tip. |
| 11 | + fn poll_chain_tip<'a>(&'a mut self, best_known_chain_tip: ValidatedBlockHeader) -> |
| 12 | + AsyncBlockSourceResult<'a, ChainTip>; |
| 13 | + |
| 14 | + /// Returns the header that preceded the given header in the chain. |
| 15 | + fn look_up_previous_header<'a>(&'a mut self, header: &'a ValidatedBlockHeader) -> |
| 16 | + AsyncBlockSourceResult<'a, ValidatedBlockHeader>; |
| 17 | + |
| 18 | + /// Returns the block associated with the given header. |
| 19 | + fn fetch_block<'a>(&'a mut self, header: &'a ValidatedBlockHeader) -> |
| 20 | + AsyncBlockSourceResult<'a, ValidatedBlock>; |
| 21 | +} |
| 22 | + |
| 23 | +/// A chain tip relative to another chain tip in terms of block hash and chainwork. |
| 24 | +#[derive(Clone, Debug, PartialEq)] |
| 25 | +pub enum ChainTip { |
| 26 | + /// A chain tip with the same hash as another chain's tip. |
| 27 | + Common, |
| 28 | + |
| 29 | + /// A chain tip with more chainwork than another chain's tip. |
| 30 | + Better(ValidatedBlockHeader), |
| 31 | + |
| 32 | + /// A chain tip with less or equal chainwork than another chain's tip. In either case, the |
| 33 | + /// hashes of each tip will be different. |
| 34 | + Worse(ValidatedBlockHeader), |
| 35 | +} |
| 36 | + |
| 37 | +/// The `Validate` trait defines behavior for validating chain data. |
| 38 | +pub trait Validate { |
| 39 | + /// The validated data wrapper which can be dereferenced to obtain the validated data. |
| 40 | + type T: std::ops::Deref<Target = Self>; |
| 41 | + |
| 42 | + /// Validates the chain data against the given block hash and any criteria needed to ensure that |
| 43 | + /// it is internally consistent. |
| 44 | + fn validate(self, block_hash: BlockHash) -> BlockSourceResult<Self::T>; |
| 45 | +} |
| 46 | + |
| 47 | +impl Validate for BlockHeaderData { |
| 48 | + type T = ValidatedBlockHeader; |
| 49 | + |
| 50 | + fn validate(self, block_hash: BlockHash) -> BlockSourceResult<Self::T> { |
| 51 | + self.header |
| 52 | + .validate_pow(&self.header.target()) |
| 53 | + .or_else(|e| Err(BlockSourceError::persistent(e)))?; |
| 54 | + |
| 55 | + if self.header.block_hash() != block_hash { |
| 56 | + return Err(BlockSourceError::persistent("invalid block hash")); |
| 57 | + } |
| 58 | + |
| 59 | + Ok(ValidatedBlockHeader { block_hash, inner: self }) |
| 60 | + } |
| 61 | +} |
| 62 | + |
| 63 | +impl Validate for Block { |
| 64 | + type T = ValidatedBlock; |
| 65 | + |
| 66 | + fn validate(self, block_hash: BlockHash) -> BlockSourceResult<Self::T> { |
| 67 | + if self.block_hash() != block_hash { |
| 68 | + return Err(BlockSourceError::persistent("invalid block hash")); |
| 69 | + } |
| 70 | + |
| 71 | + if !self.check_merkle_root() { |
| 72 | + return Err(BlockSourceError::persistent("invalid merkle root")); |
| 73 | + } |
| 74 | + |
| 75 | + if !self.check_witness_commitment() { |
| 76 | + return Err(BlockSourceError::persistent("invalid witness commitment")); |
| 77 | + } |
| 78 | + |
| 79 | + Ok(ValidatedBlock { block_hash, inner: self }) |
| 80 | + } |
| 81 | +} |
| 82 | + |
| 83 | +/// A block header with validated proof of work and corresponding block hash. |
| 84 | +#[derive(Clone, Copy, Debug, PartialEq)] |
| 85 | +pub struct ValidatedBlockHeader { |
| 86 | + block_hash: BlockHash, |
| 87 | + inner: BlockHeaderData, |
| 88 | +} |
| 89 | + |
| 90 | +impl std::ops::Deref for ValidatedBlockHeader { |
| 91 | + type Target = BlockHeaderData; |
| 92 | + |
| 93 | + fn deref(&self) -> &Self::Target { |
| 94 | + &self.inner |
| 95 | + } |
| 96 | +} |
| 97 | + |
| 98 | +impl ValidatedBlockHeader { |
| 99 | + /// Checks that the header correctly builds on previous_header: the claimed work differential |
| 100 | + /// matches the actual PoW and the difficulty transition is possible, i.e., within 4x. |
| 101 | + fn check_builds_on(&self, previous_header: &ValidatedBlockHeader, network: Network) -> BlockSourceResult<()> { |
| 102 | + if self.header.prev_blockhash != previous_header.block_hash { |
| 103 | + return Err(BlockSourceError::persistent("invalid previous block hash")); |
| 104 | + } |
| 105 | + |
| 106 | + if self.height != previous_header.height + 1 { |
| 107 | + return Err(BlockSourceError::persistent("invalid block height")); |
| 108 | + } |
| 109 | + |
| 110 | + let work = self.header.work(); |
| 111 | + if self.chainwork != previous_header.chainwork + work { |
| 112 | + return Err(BlockSourceError::persistent("invalid chainwork")); |
| 113 | + } |
| 114 | + |
| 115 | + if let Network::Bitcoin = network { |
| 116 | + if self.height % 2016 == 0 { |
| 117 | + let previous_work = previous_header.header.work(); |
| 118 | + if work > previous_work << 2 || work < previous_work >> 2 { |
| 119 | + return Err(BlockSourceError::persistent("invalid difficulty transition")) |
| 120 | + } |
| 121 | + } else if self.header.bits != previous_header.header.bits { |
| 122 | + return Err(BlockSourceError::persistent("invalid difficulty")) |
| 123 | + } |
| 124 | + } |
| 125 | + |
| 126 | + Ok(()) |
| 127 | + } |
| 128 | +} |
| 129 | + |
| 130 | +/// A block with validated data against its transaction list and corresponding block hash. |
| 131 | +pub struct ValidatedBlock { |
| 132 | + block_hash: BlockHash, |
| 133 | + inner: Block, |
| 134 | +} |
| 135 | + |
| 136 | +impl std::ops::Deref for ValidatedBlock { |
| 137 | + type Target = Block; |
| 138 | + |
| 139 | + fn deref(&self) -> &Self::Target { |
| 140 | + &self.inner |
| 141 | + } |
| 142 | +} |
0 commit comments