Define a Poll trait as an adaptor on BlockSource
authorJeffrey Czyz <jkczyz@gmail.com>
Mon, 1 Feb 2021 07:42:27 +0000 (23:42 -0800)
committerJeffrey Czyz <jkczyz@gmail.com>
Fri, 26 Feb 2021 06:54:41 +0000 (00:54 -0600)
SPV clients need to poll one or more block sources for the best chain
tip and to retrieve related chain data. The Poll trait serves as an
adaptor interface for BlockSource. Implementations may define an
appropriate polling strategy.

lightning-block-sync/src/lib.rs
lightning-block-sync/src/poll.rs [new file with mode: 0644]

index 58f77bdcabaf959c770c089bcb5ad960ac78e8b8..8a2f817cec115fcd481e7975e162914779fc64d9 100644 (file)
@@ -14,6 +14,8 @@
 #[cfg(any(feature = "rest-client", feature = "rpc-client"))]
 pub mod http;
 
+pub mod poll;
+
 #[cfg(feature = "rest-client")]
 pub mod rest;
 
diff --git a/lightning-block-sync/src/poll.rs b/lightning-block-sync/src/poll.rs
new file mode 100644 (file)
index 0000000..82398a4
--- /dev/null
@@ -0,0 +1,148 @@
+use crate::{AsyncBlockSourceResult, BlockHeaderData, BlockSourceError, BlockSourceResult};
+
+use bitcoin::blockdata::block::Block;
+use bitcoin::hash_types::BlockHash;
+use bitcoin::network::constants::Network;
+
+/// The `Poll` trait defines behavior for polling block sources for a chain tip and retrieving
+/// related chain data. It serves as an adapter for `BlockSource`.
+pub trait Poll {
+       /// Returns a chain tip in terms of its relationship to the provided chain tip.
+       fn poll_chain_tip<'a>(&'a mut self, best_known_chain_tip: ValidatedBlockHeader) ->
+               AsyncBlockSourceResult<'a, ChainTip>;
+
+       /// Returns the header that preceded the given header in the chain.
+       fn look_up_previous_header<'a>(&'a mut self, header: &'a ValidatedBlockHeader) ->
+               AsyncBlockSourceResult<'a, ValidatedBlockHeader>;
+
+       /// Returns the block associated with the given header.
+       fn fetch_block<'a>(&'a mut self, header: &'a ValidatedBlockHeader) ->
+               AsyncBlockSourceResult<'a, ValidatedBlock>;
+}
+
+/// A chain tip relative to another chain tip in terms of block hash and chainwork.
+#[derive(Clone, Debug, PartialEq)]
+pub enum ChainTip {
+       /// A chain tip with the same hash as another chain's tip.
+       Common,
+
+       /// A chain tip with more chainwork than another chain's tip.
+       Better(ValidatedBlockHeader),
+
+       /// A chain tip with less or equal chainwork than another chain's tip. In either case, the
+       /// hashes of each tip will be different.
+       Worse(ValidatedBlockHeader),
+}
+
+/// The `Validate` trait defines behavior for validating chain data.
+pub(crate) trait Validate {
+       /// The validated data wrapper which can be dereferenced to obtain the validated data.
+       type T: std::ops::Deref<Target = Self>;
+
+       /// Validates the chain data against the given block hash and any criteria needed to ensure that
+       /// it is internally consistent.
+       fn validate(self, block_hash: BlockHash) -> BlockSourceResult<Self::T>;
+}
+
+impl Validate for BlockHeaderData {
+       type T = ValidatedBlockHeader;
+
+       fn validate(self, block_hash: BlockHash) -> BlockSourceResult<Self::T> {
+               self.header
+                       .validate_pow(&self.header.target())
+                       .or_else(|e| Err(BlockSourceError::persistent(e)))?;
+
+               // TODO: Use the result of validate_pow instead of recomputing the block hash once upstream.
+               if self.header.block_hash() != block_hash {
+                       return Err(BlockSourceError::persistent("invalid block hash"));
+               }
+
+               Ok(ValidatedBlockHeader { block_hash, inner: self })
+       }
+}
+
+impl Validate for Block {
+       type T = ValidatedBlock;
+
+       fn validate(self, block_hash: BlockHash) -> BlockSourceResult<Self::T> {
+               self.header
+                       .validate_pow(&self.header.target())
+                       .or_else(|e| Err(BlockSourceError::persistent(e)))?;
+
+               // TODO: Use the result of validate_pow instead of recomputing the block hash once upstream.
+               if self.block_hash() != block_hash {
+                       return Err(BlockSourceError::persistent("invalid block hash"));
+               }
+
+               if !self.check_merkle_root() {
+                       return Err(BlockSourceError::persistent("invalid merkle root"));
+               }
+
+               if !self.check_witness_commitment() {
+                       return Err(BlockSourceError::persistent("invalid witness commitment"));
+               }
+
+               Ok(ValidatedBlock { block_hash, inner: self })
+       }
+}
+
+/// A block header with validated proof of work and corresponding block hash.
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub struct ValidatedBlockHeader {
+       block_hash: BlockHash,
+       inner: BlockHeaderData,
+}
+
+impl std::ops::Deref for ValidatedBlockHeader {
+       type Target = BlockHeaderData;
+
+       fn deref(&self) -> &Self::Target {
+               &self.inner
+       }
+}
+
+impl ValidatedBlockHeader {
+       /// Checks that the header correctly builds on previous_header: the claimed work differential
+       /// matches the actual PoW and the difficulty transition is possible, i.e., within 4x.
+       fn check_builds_on(&self, previous_header: &ValidatedBlockHeader, network: Network) -> BlockSourceResult<()> {
+               if self.header.prev_blockhash != previous_header.block_hash {
+                       return Err(BlockSourceError::persistent("invalid previous block hash"));
+               }
+
+               if self.height != previous_header.height + 1 {
+                       return Err(BlockSourceError::persistent("invalid block height"));
+               }
+
+               let work = self.header.work();
+               if self.chainwork != previous_header.chainwork + work {
+                       return Err(BlockSourceError::persistent("invalid chainwork"));
+               }
+
+               if let Network::Bitcoin = network {
+                       if self.height % 2016 == 0 {
+                               let previous_work = previous_header.header.work();
+                               if work > (previous_work << 2) || work < (previous_work >> 2) {
+                                       return Err(BlockSourceError::persistent("invalid difficulty transition"))
+                               }
+                       } else if self.header.bits != previous_header.header.bits {
+                               return Err(BlockSourceError::persistent("invalid difficulty"))
+                       }
+               }
+
+               Ok(())
+       }
+}
+
+/// A block with validated data against its transaction list and corresponding block hash.
+pub struct ValidatedBlock {
+       block_hash: BlockHash,
+       inner: Block,
+}
+
+impl std::ops::Deref for ValidatedBlock {
+       type Target = Block;
+
+       fn deref(&self) -> &Self::Target {
+               &self.inner
+       }
+}