X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning-block-sync%2Fsrc%2Flib.rs;h=4a01d4673b31e91d56c3cb350d995c1b7a3d7403;hb=2f734f97550014e5424e55523ed46d76b94b737d;hp=db536fe7d43778ccb062be5813548f2072353a44;hpb=4894d52d30399c21b7994952a8de0d1d7848c58d;p=rust-lightning diff --git a/lightning-block-sync/src/lib.rs b/lightning-block-sync/src/lib.rs index db536fe7..4a01d467 100644 --- a/lightning-block-sync/src/lib.rs +++ b/lightning-block-sync/src/lib.rs @@ -12,9 +12,14 @@ //! //! Both features support either blocking I/O using `std::net::TcpStream` or, with feature `tokio`, //! non-blocking I/O using `tokio::net::TcpStream` from inside a Tokio runtime. -//! -//! [`SpvClient`]: struct.SpvClient.html -//! [`BlockSource`]: trait.BlockSource.html + +#![deny(rustdoc::broken_intra_doc_links)] +#![deny(rustdoc::private_intra_doc_links)] + +#![deny(missing_docs)] +#![deny(unsafe_code)] + +#![cfg_attr(docsrs, feature(doc_auto_cfg))] #[cfg(any(feature = "rest-client", feature = "rpc-client"))] pub mod http; @@ -22,6 +27,8 @@ pub mod http; pub mod init; pub mod poll; +pub mod gossip; + #[cfg(feature = "rest-client")] pub mod rest; @@ -39,9 +46,9 @@ mod utils; use crate::poll::{ChainTip, Poll, ValidatedBlockHeader}; -use bitcoin::blockdata::block::{Block, BlockHeader}; +use bitcoin::blockdata::block::{Block, Header}; use bitcoin::hash_types::BlockHash; -use bitcoin::util::uint::Uint256; +use bitcoin::pow::Work; use lightning::chain; use lightning::chain::Listen; @@ -58,29 +65,28 @@ pub trait BlockSource : Sync + Send { /// /// Implementations that cannot find headers based on the hash should return a `Transient` error /// when `height_hint` is `None`. - fn get_header<'a>(&'a mut self, header_hash: &'a BlockHash, height_hint: Option) -> AsyncBlockSourceResult<'a, BlockHeaderData>; + fn get_header<'a>(&'a self, header_hash: &'a BlockHash, height_hint: Option) -> AsyncBlockSourceResult<'a, BlockHeaderData>; /// Returns the block for a given hash. A headers-only block source should return a `Transient` /// error. - fn get_block<'a>(&'a mut self, header_hash: &'a BlockHash) -> AsyncBlockSourceResult<'a, Block>; + fn get_block<'a>(&'a self, header_hash: &'a BlockHash) -> AsyncBlockSourceResult<'a, BlockData>; /// Returns the hash of the best block and, optionally, its height. /// /// When polling a block source, [`Poll`] implementations may pass the height to [`get_header`] /// to allow for a more efficient lookup. /// - /// [`Poll`]: poll/trait.Poll.html - /// [`get_header`]: #tymethod.get_header - fn get_best_block<'a>(&'a mut self) -> AsyncBlockSourceResult<(BlockHash, Option)>; + /// [`get_header`]: Self::get_header + fn get_best_block<'a>(&'a self) -> AsyncBlockSourceResult<(BlockHash, Option)>; } /// Result type for `BlockSource` requests. -type BlockSourceResult = Result; +pub type BlockSourceResult = Result; // TODO: Replace with BlockSourceResult once `async` trait functions are supported. For details, // see: https://areweasyncyet.rs. /// Result type for asynchronous `BlockSource` requests. -type AsyncBlockSourceResult<'a, T> = Pin> + 'a + Send>>; +pub type AsyncBlockSourceResult<'a, T> = Pin> + 'a + Send>>; /// Error type for `BlockSource` requests. /// @@ -93,7 +99,7 @@ pub struct BlockSourceError { } /// The kind of `BlockSourceError`, either persistent or transient. -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum BlockSourceErrorKind { /// Indicates an error that won't resolve when retrying a request (e.g., invalid data). Persistent, @@ -127,6 +133,9 @@ impl BlockSourceError { } /// Converts the error into the underlying error. + /// + /// May contain an [`std::io::Error`] from the [`BlockSource`]. See implementations for further + /// details, if any. pub fn into_inner(self) -> Box { self.error } @@ -134,17 +143,28 @@ impl BlockSourceError { /// A block header and some associated data. This information should be available from most block /// sources (and, notably, is available in Bitcoin Core's RPC and REST interfaces). -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct BlockHeaderData { /// The block header itself. - pub header: BlockHeader, + pub header: Header, /// The block height where the genesis block has height 0. pub height: u32, - /// The total chain work in expected number of double-SHA256 hashes required to build a chain - /// of equivalent weight. - pub chainwork: Uint256, + /// The total chain work required to build a chain of equivalent weight. + pub chainwork: Work, +} + +/// A block including either all its transactions or only the block header. +/// +/// [`BlockSource`] may be implemented to either always return full blocks or, in the case of +/// compact block filters (BIP 157/158), return header-only blocks when no pertinent transactions +/// match. See [`chain::Filter`] for details on how to notify a source of such transactions. +pub enum BlockData { + /// A block containing all its transactions. + FullBlock(Block), + /// A block header for when the block does not contain any pertinent transactions. + HeaderOnly(Header), } /// A lightweight client for keeping a listener in sync with the chain, allowing for Simplified @@ -176,8 +196,6 @@ where L::Target: chain::Listen { /// Implementations may define how long to retain headers such that it's unlikely they will ever be /// needed to disconnect a block. In cases where block sources provide access to headers on stale /// forks reliably, caches may be entirely unnecessary. -/// -/// [`ChainNotifier`]: struct.ChainNotifier.html pub trait Cache { /// Retrieves the block header keyed by the given block hash. fn look_up(&self, block_hash: &BlockHash) -> Option<&ValidatedBlockHeader>; @@ -218,7 +236,7 @@ impl<'a, P: Poll, C: Cache, L: Deref> SpvClient<'a, P, C, L> where L::Target: ch /// * `header_cache` is used to look up and store headers on the best chain /// * `chain_listener` is notified of any blocks connected or disconnected /// - /// [`poll_best_tip`]: struct.SpvClient.html#method.poll_best_tip + /// [`poll_best_tip`]: SpvClient::poll_best_tip pub fn new( chain_tip: ValidatedBlockHeader, chain_poller: P, @@ -273,7 +291,7 @@ impl<'a, P: Poll, C: Cache, L: Deref> SpvClient<'a, P, C, L> where L::Target: ch /// Notifies [listeners] of blocks that have been connected or disconnected from the chain. /// -/// [listeners]: ../../lightning/chain/trait.Listen.html +/// [listeners]: lightning::chain::Listen pub struct ChainNotifier<'a, C: Cache, L: Deref> where L::Target: chain::Listen { /// Cache for looking up headers before fetching from a block source. header_cache: &'a mut C, @@ -393,13 +411,22 @@ impl<'a, C: Cache, L: Deref> ChainNotifier<'a, C, L> where L::Target: chain::Lis chain_poller: &mut P, ) -> Result<(), (BlockSourceError, Option)> { for header in connected_blocks.drain(..).rev() { - let block = chain_poller + let height = header.height; + let block_data = chain_poller .fetch_block(&header).await - .or_else(|e| Err((e, Some(new_tip))))?; - debug_assert_eq!(block.block_hash, header.block_hash); + .map_err(|e| (e, Some(new_tip)))?; + debug_assert_eq!(block_data.block_hash, header.block_hash); + + match block_data.deref() { + BlockData::FullBlock(block) => { + self.chain_listener.block_connected(block, height); + }, + BlockData::HeaderOnly(header) => { + self.chain_listener.filtered_block_connected(header, &[], height); + }, + } self.header_cache.block_connected(header.block_hash, header); - self.chain_listener.block_connected(&block, header.height); new_tip = header; } @@ -704,4 +731,25 @@ mod chain_notifier_tests { Ok(_) => panic!("Expected error"), } } + + #[tokio::test] + async fn sync_from_chain_with_filtered_blocks() { + let mut chain = Blockchain::default().with_height(3).filtered_blocks(); + + let new_tip = chain.tip(); + let old_tip = chain.at_height(1); + let chain_listener = &MockChainListener::new() + .expect_filtered_block_connected(*chain.at_height(2)) + .expect_filtered_block_connected(*new_tip); + let mut notifier = ChainNotifier { + header_cache: &mut chain.header_cache(0..=1), + chain_listener, + }; + let mut poller = poll::ChainPoller::new(&mut chain, Network::Testnet); + match notifier.synchronize_listener(new_tip, &old_tip, &mut poller).await { + Err((e, _)) => panic!("Unexpected error: {:?}", e), + Ok(_) => {}, + } + } + }