1 //! A lightweight client for keeping in sync with chain activity.
3 //! Defines an [`SpvClient`] utility for polling one or more block sources for the best chain tip.
4 //! It is used to notify listeners of blocks connected or disconnected since the last poll. Useful
5 //! for keeping a Lightning node in sync with the chain.
7 //! Defines a [`BlockSource`] trait, which is an asynchronous interface for retrieving block headers
10 //! Enabling feature `rest-client` or `rpc-client` allows configuring the client to fetch blocks
11 //! using Bitcoin Core's REST or RPC interface, respectively.
13 //! Both features support either blocking I/O using `std::net::TcpStream` or, with feature `tokio`,
14 //! non-blocking I/O using `tokio::net::TcpStream` from inside a Tokio runtime.
16 //! [`SpvClient`]: struct.SpvClient.html
17 //! [`BlockSource`]: trait.BlockSource.html
19 #[cfg(any(feature = "rest-client", feature = "rpc-client"))]
25 #[cfg(feature = "rest-client")]
28 #[cfg(feature = "rpc-client")]
31 #[cfg(any(feature = "rest-client", feature = "rpc-client"))]
37 #[cfg(any(feature = "rest-client", feature = "rpc-client"))]
40 use crate::poll::{ChainTip, Poll, ValidatedBlockHeader};
42 use bitcoin::blockdata::block::{Block, BlockHeader};
43 use bitcoin::hash_types::BlockHash;
44 use bitcoin::util::uint::Uint256;
47 use lightning::chain::Listen;
49 use std::future::Future;
53 /// Abstract type for retrieving block headers and data.
54 pub trait BlockSource : Sync + Send {
55 /// Returns the header for a given hash. A height hint may be provided in case a block source
56 /// cannot easily find headers based on a hash. This is merely a hint and thus the returned
57 /// header must have the same hash as was requested. Otherwise, an error must be returned.
59 /// Implementations that cannot find headers based on the hash should return a `Transient` error
60 /// when `height_hint` is `None`.
61 fn get_header<'a>(&'a mut self, header_hash: &'a BlockHash, height_hint: Option<u32>) -> AsyncBlockSourceResult<'a, BlockHeaderData>;
63 /// Returns the block for a given hash. A headers-only block source should return a `Transient`
65 fn get_block<'a>(&'a mut self, header_hash: &'a BlockHash) -> AsyncBlockSourceResult<'a, Block>;
67 /// Returns the hash of the best block and, optionally, its height.
69 /// When polling a block source, [`Poll`] implementations may pass the height to [`get_header`]
70 /// to allow for a more efficient lookup.
72 /// [`Poll`]: poll/trait.Poll.html
73 /// [`get_header`]: #tymethod.get_header
74 fn get_best_block<'a>(&'a mut self) -> AsyncBlockSourceResult<(BlockHash, Option<u32>)>;
77 /// Result type for `BlockSource` requests.
78 type BlockSourceResult<T> = Result<T, BlockSourceError>;
80 // TODO: Replace with BlockSourceResult once `async` trait functions are supported. For details,
81 // see: https://areweasyncyet.rs.
82 /// Result type for asynchronous `BlockSource` requests.
83 type AsyncBlockSourceResult<'a, T> = Pin<Box<dyn Future<Output = BlockSourceResult<T>> + 'a + Send>>;
85 /// Error type for `BlockSource` requests.
87 /// Transient errors may be resolved when re-polling, but no attempt will be made to re-poll on
88 /// persistent errors.
90 pub struct BlockSourceError {
91 kind: BlockSourceErrorKind,
92 error: Box<dyn std::error::Error + Send + Sync>,
95 /// The kind of `BlockSourceError`, either persistent or transient.
96 #[derive(Clone, Copy, Debug, PartialEq)]
97 pub enum BlockSourceErrorKind {
98 /// Indicates an error that won't resolve when retrying a request (e.g., invalid data).
101 /// Indicates an error that may resolve when retrying a request (e.g., unresponsive).
105 impl BlockSourceError {
106 /// Creates a new persistent error originated from the given error.
107 pub fn persistent<E>(error: E) -> Self
108 where E: Into<Box<dyn std::error::Error + Send + Sync>> {
110 kind: BlockSourceErrorKind::Persistent,
115 /// Creates a new transient error originated from the given error.
116 pub fn transient<E>(error: E) -> Self
117 where E: Into<Box<dyn std::error::Error + Send + Sync>> {
119 kind: BlockSourceErrorKind::Transient,
124 /// Returns the kind of error.
125 pub fn kind(&self) -> BlockSourceErrorKind {
129 /// Converts the error into the underlying error.
130 pub fn into_inner(self) -> Box<dyn std::error::Error + Send + Sync> {
135 /// A block header and some associated data. This information should be available from most block
136 /// sources (and, notably, is available in Bitcoin Core's RPC and REST interfaces).
137 #[derive(Clone, Copy, Debug, PartialEq)]
138 pub struct BlockHeaderData {
139 /// The block header itself.
140 pub header: BlockHeader,
142 /// The block height where the genesis block has height 0.
145 /// The total chain work in expected number of double-SHA256 hashes required to build a chain
146 /// of equivalent weight.
147 pub chainwork: Uint256,
150 /// A lightweight client for keeping a listener in sync with the chain, allowing for Simplified
151 /// Payment Verification (SPV).
153 /// The client is parameterized by a chain poller which is responsible for polling one or more block
154 /// sources for the best chain tip. During this process it detects any chain forks, determines which
155 /// constitutes the best chain, and updates the listener accordingly with any blocks that were
156 /// connected or disconnected since the last poll.
158 /// Block headers for the best chain are maintained in the parameterized cache, allowing for a
159 /// custom cache eviction policy. This offers flexibility to those sensitive to resource usage.
160 /// Hence, there is a trade-off between a lower memory footprint and potentially increased network
161 /// I/O as headers are re-fetched during fork detection.
162 pub struct SpvClient<'a, P: Poll, C: Cache, L: Deref>
163 where L::Target: chain::Listen {
164 chain_tip: ValidatedBlockHeader,
166 chain_notifier: ChainNotifier<'a, C, L>,
169 /// The `Cache` trait defines behavior for managing a block header cache, where block headers are
170 /// keyed by block hash.
172 /// Used by [`ChainNotifier`] to store headers along the best chain, which is important for ensuring
173 /// that blocks can be disconnected if they are no longer accessible from a block source (e.g., if
174 /// the block source does not store stale forks indefinitely).
176 /// Implementations may define how long to retain headers such that it's unlikely they will ever be
177 /// needed to disconnect a block. In cases where block sources provide access to headers on stale
178 /// forks reliably, caches may be entirely unnecessary.
180 /// [`ChainNotifier`]: struct.ChainNotifier.html
182 /// Retrieves the block header keyed by the given block hash.
183 fn look_up(&self, block_hash: &BlockHash) -> Option<&ValidatedBlockHeader>;
185 /// Called when a block has been connected to the best chain to ensure it is available to be
186 /// disconnected later if needed.
187 fn block_connected(&mut self, block_hash: BlockHash, block_header: ValidatedBlockHeader);
189 /// Called when a block has been disconnected from the best chain. Once disconnected, a block's
190 /// header is no longer needed and thus can be removed.
191 fn block_disconnected(&mut self, block_hash: &BlockHash) -> Option<ValidatedBlockHeader>;
194 /// Unbounded cache of block headers keyed by block hash.
195 pub type UnboundedCache = std::collections::HashMap<BlockHash, ValidatedBlockHeader>;
197 impl Cache for UnboundedCache {
198 fn look_up(&self, block_hash: &BlockHash) -> Option<&ValidatedBlockHeader> {
202 fn block_connected(&mut self, block_hash: BlockHash, block_header: ValidatedBlockHeader) {
203 self.insert(block_hash, block_header);
206 fn block_disconnected(&mut self, block_hash: &BlockHash) -> Option<ValidatedBlockHeader> {
207 self.remove(block_hash)
211 impl<'a, P: Poll, C: Cache, L: Deref> SpvClient<'a, P, C, L> where L::Target: chain::Listen {
212 /// Creates a new SPV client using `chain_tip` as the best known chain tip.
214 /// Subsequent calls to [`poll_best_tip`] will poll for the best chain tip using the given chain
215 /// poller, which may be configured with one or more block sources to query. At least one block
216 /// source must provide headers back from the best chain tip to its common ancestor with
218 /// * `header_cache` is used to look up and store headers on the best chain
219 /// * `chain_listener` is notified of any blocks connected or disconnected
221 /// [`poll_best_tip`]: struct.SpvClient.html#method.poll_best_tip
223 chain_tip: ValidatedBlockHeader,
225 header_cache: &'a mut C,
228 let chain_notifier = ChainNotifier { header_cache, chain_listener };
229 Self { chain_tip, chain_poller, chain_notifier }
232 /// Polls for the best tip and updates the chain listener with any connected or disconnected
233 /// blocks accordingly.
235 /// Returns the best polled chain tip relative to the previous best known tip and whether any
236 /// blocks were indeed connected or disconnected.
237 pub async fn poll_best_tip(&mut self) -> BlockSourceResult<(ChainTip, bool)> {
238 let chain_tip = self.chain_poller.poll_chain_tip(self.chain_tip).await?;
239 let blocks_connected = match chain_tip {
240 ChainTip::Common => false,
241 ChainTip::Better(chain_tip) => {
242 debug_assert_ne!(chain_tip.block_hash, self.chain_tip.block_hash);
243 debug_assert!(chain_tip.chainwork > self.chain_tip.chainwork);
244 self.update_chain_tip(chain_tip).await
246 ChainTip::Worse(chain_tip) => {
247 debug_assert_ne!(chain_tip.block_hash, self.chain_tip.block_hash);
248 debug_assert!(chain_tip.chainwork <= self.chain_tip.chainwork);
252 Ok((chain_tip, blocks_connected))
255 /// Updates the chain tip, syncing the chain listener with any connected or disconnected
256 /// blocks. Returns whether there were any such blocks.
257 async fn update_chain_tip(&mut self, best_chain_tip: ValidatedBlockHeader) -> bool {
258 match self.chain_notifier.synchronize_listener(
259 best_chain_tip, &self.chain_tip, &mut self.chain_poller).await
262 self.chain_tip = best_chain_tip;
265 Err((_, Some(chain_tip))) if chain_tip.block_hash != self.chain_tip.block_hash => {
266 self.chain_tip = chain_tip;
274 /// Notifies [listeners] of blocks that have been connected or disconnected from the chain.
276 /// [listeners]: ../../lightning/chain/trait.Listen.html
277 pub struct ChainNotifier<'a, C: Cache, L: Deref> where L::Target: chain::Listen {
278 /// Cache for looking up headers before fetching from a block source.
279 header_cache: &'a mut C,
281 /// Listener that will be notified of connected or disconnected blocks.
285 /// Changes made to the chain between subsequent polls that transformed it from having one chain tip
288 /// Blocks are given in height-descending order. Therefore, blocks are first disconnected in order
289 /// before new blocks are connected in reverse order.
290 struct ChainDifference {
291 /// The most recent ancestor common between the chain tips.
293 /// If there are any disconnected blocks, this is where the chain forked.
294 common_ancestor: ValidatedBlockHeader,
296 /// Blocks that were disconnected from the chain since the last poll.
297 disconnected_blocks: Vec<ValidatedBlockHeader>,
299 /// Blocks that were connected to the chain since the last poll.
300 connected_blocks: Vec<ValidatedBlockHeader>,
303 impl<'a, C: Cache, L: Deref> ChainNotifier<'a, C, L> where L::Target: chain::Listen {
304 /// Finds the first common ancestor between `new_header` and `old_header`, disconnecting blocks
305 /// from `old_header` to get to that point and then connecting blocks until `new_header`.
307 /// Validates headers along the transition path, but doesn't fetch blocks until the chain is
308 /// disconnected to the fork point. Thus, this may return an `Err` that includes where the tip
309 /// ended up which may not be `new_header`. Note that the returned `Err` contains `Some` header
310 /// if and only if the transition from `old_header` to `new_header` is valid.
311 async fn synchronize_listener<P: Poll>(
313 new_header: ValidatedBlockHeader,
314 old_header: &ValidatedBlockHeader,
315 chain_poller: &mut P,
316 ) -> Result<(), (BlockSourceError, Option<ValidatedBlockHeader>)> {
317 let difference = self.find_difference(new_header, old_header, chain_poller).await
318 .map_err(|e| (e, None))?;
319 self.disconnect_blocks(difference.disconnected_blocks);
321 difference.common_ancestor,
322 difference.connected_blocks,
327 /// Returns the changes needed to produce the chain with `current_header` as its tip from the
328 /// chain with `prev_header` as its tip.
330 /// Walks backwards from `current_header` and `prev_header`, finding the common ancestor.
331 async fn find_difference<P: Poll>(
333 current_header: ValidatedBlockHeader,
334 prev_header: &ValidatedBlockHeader,
335 chain_poller: &mut P,
336 ) -> BlockSourceResult<ChainDifference> {
337 let mut disconnected_blocks = Vec::new();
338 let mut connected_blocks = Vec::new();
339 let mut current = current_header;
340 let mut previous = *prev_header;
342 // Found the common ancestor.
343 if current.block_hash == previous.block_hash {
347 // Walk back the chain, finding blocks needed to connect and disconnect. Only walk back
348 // the header with the greater height, or both if equal heights.
349 let current_height = current.height;
350 let previous_height = previous.height;
351 if current_height <= previous_height {
352 disconnected_blocks.push(previous);
353 previous = self.look_up_previous_header(chain_poller, &previous).await?;
355 if current_height >= previous_height {
356 connected_blocks.push(current);
357 current = self.look_up_previous_header(chain_poller, ¤t).await?;
361 let common_ancestor = current;
362 Ok(ChainDifference { common_ancestor, disconnected_blocks, connected_blocks })
365 /// Returns the previous header for the given header, either by looking it up in the cache or
366 /// fetching it if not found.
367 async fn look_up_previous_header<P: Poll>(
369 chain_poller: &mut P,
370 header: &ValidatedBlockHeader,
371 ) -> BlockSourceResult<ValidatedBlockHeader> {
372 match self.header_cache.look_up(&header.header.prev_blockhash) {
373 Some(prev_header) => Ok(*prev_header),
374 None => chain_poller.look_up_previous_header(header).await,
378 /// Notifies the chain listeners of disconnected blocks.
379 fn disconnect_blocks(&mut self, mut disconnected_blocks: Vec<ValidatedBlockHeader>) {
380 for header in disconnected_blocks.drain(..) {
381 if let Some(cached_header) = self.header_cache.block_disconnected(&header.block_hash) {
382 assert_eq!(cached_header, header);
384 self.chain_listener.block_disconnected(&header.header, header.height);
388 /// Notifies the chain listeners of connected blocks.
389 async fn connect_blocks<P: Poll>(
391 mut new_tip: ValidatedBlockHeader,
392 mut connected_blocks: Vec<ValidatedBlockHeader>,
393 chain_poller: &mut P,
394 ) -> Result<(), (BlockSourceError, Option<ValidatedBlockHeader>)> {
395 for header in connected_blocks.drain(..).rev() {
396 let block = chain_poller
397 .fetch_block(&header).await
398 .or_else(|e| Err((e, Some(new_tip))))?;
399 debug_assert_eq!(block.block_hash, header.block_hash);
401 self.header_cache.block_connected(header.block_hash, header);
402 self.chain_listener.block_connected(&block, header.height);
411 mod spv_client_tests {
412 use crate::test_utils::{Blockchain, NullChainListener};
415 use bitcoin::network::constants::Network;
418 async fn poll_from_chain_without_headers() {
419 let mut chain = Blockchain::default().with_height(3).without_headers();
420 let best_tip = chain.at_height(1);
422 let poller = poll::ChainPoller::new(&mut chain, Network::Testnet);
423 let mut cache = UnboundedCache::new();
424 let mut listener = NullChainListener {};
425 let mut client = SpvClient::new(best_tip, poller, &mut cache, &mut listener);
426 match client.poll_best_tip().await {
428 assert_eq!(e.kind(), BlockSourceErrorKind::Persistent);
429 assert_eq!(e.into_inner().as_ref().to_string(), "header not found");
431 Ok(_) => panic!("Expected error"),
433 assert_eq!(client.chain_tip, best_tip);
437 async fn poll_from_chain_with_common_tip() {
438 let mut chain = Blockchain::default().with_height(3);
439 let common_tip = chain.tip();
441 let poller = poll::ChainPoller::new(&mut chain, Network::Testnet);
442 let mut cache = UnboundedCache::new();
443 let mut listener = NullChainListener {};
444 let mut client = SpvClient::new(common_tip, poller, &mut cache, &mut listener);
445 match client.poll_best_tip().await {
446 Err(e) => panic!("Unexpected error: {:?}", e),
447 Ok((chain_tip, blocks_connected)) => {
448 assert_eq!(chain_tip, ChainTip::Common);
449 assert!(!blocks_connected);
452 assert_eq!(client.chain_tip, common_tip);
456 async fn poll_from_chain_with_better_tip() {
457 let mut chain = Blockchain::default().with_height(3);
458 let new_tip = chain.tip();
459 let old_tip = chain.at_height(1);
461 let poller = poll::ChainPoller::new(&mut chain, Network::Testnet);
462 let mut cache = UnboundedCache::new();
463 let mut listener = NullChainListener {};
464 let mut client = SpvClient::new(old_tip, poller, &mut cache, &mut listener);
465 match client.poll_best_tip().await {
466 Err(e) => panic!("Unexpected error: {:?}", e),
467 Ok((chain_tip, blocks_connected)) => {
468 assert_eq!(chain_tip, ChainTip::Better(new_tip));
469 assert!(blocks_connected);
472 assert_eq!(client.chain_tip, new_tip);
476 async fn poll_from_chain_with_better_tip_and_without_any_new_blocks() {
477 let mut chain = Blockchain::default().with_height(3).without_blocks(2..);
478 let new_tip = chain.tip();
479 let old_tip = chain.at_height(1);
481 let poller = poll::ChainPoller::new(&mut chain, Network::Testnet);
482 let mut cache = UnboundedCache::new();
483 let mut listener = NullChainListener {};
484 let mut client = SpvClient::new(old_tip, poller, &mut cache, &mut listener);
485 match client.poll_best_tip().await {
486 Err(e) => panic!("Unexpected error: {:?}", e),
487 Ok((chain_tip, blocks_connected)) => {
488 assert_eq!(chain_tip, ChainTip::Better(new_tip));
489 assert!(!blocks_connected);
492 assert_eq!(client.chain_tip, old_tip);
496 async fn poll_from_chain_with_better_tip_and_without_some_new_blocks() {
497 let mut chain = Blockchain::default().with_height(3).without_blocks(3..);
498 let new_tip = chain.tip();
499 let old_tip = chain.at_height(1);
501 let poller = poll::ChainPoller::new(&mut chain, Network::Testnet);
502 let mut cache = UnboundedCache::new();
503 let mut listener = NullChainListener {};
504 let mut client = SpvClient::new(old_tip, poller, &mut cache, &mut listener);
505 match client.poll_best_tip().await {
506 Err(e) => panic!("Unexpected error: {:?}", e),
507 Ok((chain_tip, blocks_connected)) => {
508 assert_eq!(chain_tip, ChainTip::Better(new_tip));
509 assert!(blocks_connected);
512 assert_eq!(client.chain_tip, chain.at_height(2));
516 async fn poll_from_chain_with_worse_tip() {
517 let mut chain = Blockchain::default().with_height(3);
518 let best_tip = chain.tip();
519 chain.disconnect_tip();
520 let worse_tip = chain.tip();
522 let poller = poll::ChainPoller::new(&mut chain, Network::Testnet);
523 let mut cache = UnboundedCache::new();
524 let mut listener = NullChainListener {};
525 let mut client = SpvClient::new(best_tip, poller, &mut cache, &mut listener);
526 match client.poll_best_tip().await {
527 Err(e) => panic!("Unexpected error: {:?}", e),
528 Ok((chain_tip, blocks_connected)) => {
529 assert_eq!(chain_tip, ChainTip::Worse(worse_tip));
530 assert!(!blocks_connected);
533 assert_eq!(client.chain_tip, best_tip);
538 mod chain_notifier_tests {
539 use crate::test_utils::{Blockchain, MockChainListener};
542 use bitcoin::network::constants::Network;
545 async fn sync_from_same_chain() {
546 let mut chain = Blockchain::default().with_height(3);
548 let new_tip = chain.tip();
549 let old_tip = chain.at_height(1);
550 let chain_listener = &MockChainListener::new()
551 .expect_block_connected(*chain.at_height(2))
552 .expect_block_connected(*new_tip);
553 let mut notifier = ChainNotifier {
554 header_cache: &mut chain.header_cache(0..=1),
557 let mut poller = poll::ChainPoller::new(&mut chain, Network::Testnet);
558 match notifier.synchronize_listener(new_tip, &old_tip, &mut poller).await {
559 Err((e, _)) => panic!("Unexpected error: {:?}", e),
565 async fn sync_from_different_chains() {
566 let mut test_chain = Blockchain::with_network(Network::Testnet).with_height(1);
567 let main_chain = Blockchain::with_network(Network::Bitcoin).with_height(1);
569 let new_tip = test_chain.tip();
570 let old_tip = main_chain.tip();
571 let chain_listener = &MockChainListener::new();
572 let mut notifier = ChainNotifier {
573 header_cache: &mut main_chain.header_cache(0..=1),
576 let mut poller = poll::ChainPoller::new(&mut test_chain, Network::Testnet);
577 match notifier.synchronize_listener(new_tip, &old_tip, &mut poller).await {
579 assert_eq!(e.kind(), BlockSourceErrorKind::Persistent);
580 assert_eq!(e.into_inner().as_ref().to_string(), "genesis block reached");
582 Ok(_) => panic!("Expected error"),
587 async fn sync_from_equal_length_fork() {
588 let main_chain = Blockchain::default().with_height(2);
589 let mut fork_chain = main_chain.fork_at_height(1);
591 let new_tip = fork_chain.tip();
592 let old_tip = main_chain.tip();
593 let chain_listener = &MockChainListener::new()
594 .expect_block_disconnected(*old_tip)
595 .expect_block_connected(*new_tip);
596 let mut notifier = ChainNotifier {
597 header_cache: &mut main_chain.header_cache(0..=2),
600 let mut poller = poll::ChainPoller::new(&mut fork_chain, Network::Testnet);
601 match notifier.synchronize_listener(new_tip, &old_tip, &mut poller).await {
602 Err((e, _)) => panic!("Unexpected error: {:?}", e),
608 async fn sync_from_shorter_fork() {
609 let main_chain = Blockchain::default().with_height(3);
610 let mut fork_chain = main_chain.fork_at_height(1);
611 fork_chain.disconnect_tip();
613 let new_tip = fork_chain.tip();
614 let old_tip = main_chain.tip();
615 let chain_listener = &MockChainListener::new()
616 .expect_block_disconnected(*old_tip)
617 .expect_block_disconnected(*main_chain.at_height(2))
618 .expect_block_connected(*new_tip);
619 let mut notifier = ChainNotifier {
620 header_cache: &mut main_chain.header_cache(0..=3),
623 let mut poller = poll::ChainPoller::new(&mut fork_chain, Network::Testnet);
624 match notifier.synchronize_listener(new_tip, &old_tip, &mut poller).await {
625 Err((e, _)) => panic!("Unexpected error: {:?}", e),
631 async fn sync_from_longer_fork() {
632 let mut main_chain = Blockchain::default().with_height(3);
633 let mut fork_chain = main_chain.fork_at_height(1);
634 main_chain.disconnect_tip();
636 let new_tip = fork_chain.tip();
637 let old_tip = main_chain.tip();
638 let chain_listener = &MockChainListener::new()
639 .expect_block_disconnected(*old_tip)
640 .expect_block_connected(*fork_chain.at_height(2))
641 .expect_block_connected(*new_tip);
642 let mut notifier = ChainNotifier {
643 header_cache: &mut main_chain.header_cache(0..=2),
646 let mut poller = poll::ChainPoller::new(&mut fork_chain, Network::Testnet);
647 match notifier.synchronize_listener(new_tip, &old_tip, &mut poller).await {
648 Err((e, _)) => panic!("Unexpected error: {:?}", e),
654 async fn sync_from_chain_without_headers() {
655 let mut chain = Blockchain::default().with_height(3).without_headers();
657 let new_tip = chain.tip();
658 let old_tip = chain.at_height(1);
659 let chain_listener = &MockChainListener::new();
660 let mut notifier = ChainNotifier {
661 header_cache: &mut chain.header_cache(0..=1),
664 let mut poller = poll::ChainPoller::new(&mut chain, Network::Testnet);
665 match notifier.synchronize_listener(new_tip, &old_tip, &mut poller).await {
666 Err((_, tip)) => assert_eq!(tip, None),
667 Ok(_) => panic!("Expected error"),
672 async fn sync_from_chain_without_any_new_blocks() {
673 let mut chain = Blockchain::default().with_height(3).without_blocks(2..);
675 let new_tip = chain.tip();
676 let old_tip = chain.at_height(1);
677 let chain_listener = &MockChainListener::new();
678 let mut notifier = ChainNotifier {
679 header_cache: &mut chain.header_cache(0..=3),
682 let mut poller = poll::ChainPoller::new(&mut chain, Network::Testnet);
683 match notifier.synchronize_listener(new_tip, &old_tip, &mut poller).await {
684 Err((_, tip)) => assert_eq!(tip, Some(old_tip)),
685 Ok(_) => panic!("Expected error"),
690 async fn sync_from_chain_without_some_new_blocks() {
691 let mut chain = Blockchain::default().with_height(3).without_blocks(3..);
693 let new_tip = chain.tip();
694 let old_tip = chain.at_height(1);
695 let chain_listener = &MockChainListener::new()
696 .expect_block_connected(*chain.at_height(2));
697 let mut notifier = ChainNotifier {
698 header_cache: &mut chain.header_cache(0..=3),
701 let mut poller = poll::ChainPoller::new(&mut chain, Network::Testnet);
702 match notifier.synchronize_listener(new_tip, &old_tip, &mut poller).await {
703 Err((_, tip)) => assert_eq!(tip, Some(chain.at_height(2))),
704 Ok(_) => panic!("Expected error"),