2d46c7d81045e1c54240f3a429018d4a9af25114
[rust-lightning] / lib.rs
1 //! A lightweight client for keeping in sync with chain activity.
2 //!
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.
6 //!
7 //! Defines a [`BlockSource`] trait, which is an asynchronous interface for retrieving block headers
8 //! and data.
9 //!
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.
12 //!
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.
15 //!
16 //! [`SpvClient`]: struct.SpvClient.html
17 //! [`BlockSource`]: trait.BlockSource.html
18
19 #[cfg(any(feature = "rest-client", feature = "rpc-client"))]
20 pub mod http;
21
22 pub mod poll;
23
24 #[cfg(feature = "rest-client")]
25 pub mod rest;
26
27 #[cfg(feature = "rpc-client")]
28 pub mod rpc;
29
30 #[cfg(any(feature = "rest-client", feature = "rpc-client"))]
31 mod convert;
32
33 #[cfg(test)]
34 mod test_utils;
35
36 #[cfg(any(feature = "rest-client", feature = "rpc-client"))]
37 mod utils;
38
39 use crate::poll::{ChainTip, Poll, ValidatedBlockHeader};
40
41 use bitcoin::blockdata::block::{Block, BlockHeader};
42 use bitcoin::hash_types::BlockHash;
43 use bitcoin::util::uint::Uint256;
44
45 use std::future::Future;
46 use std::pin::Pin;
47
48 /// Abstract type for retrieving block headers and data.
49 pub trait BlockSource : Sync + Send {
50         /// Returns the header for a given hash. A height hint may be provided in case a block source
51         /// cannot easily find headers based on a hash. This is merely a hint and thus the returned
52         /// header must have the same hash as was requested. Otherwise, an error must be returned.
53         ///
54         /// Implementations that cannot find headers based on the hash should return a `Transient` error
55         /// when `height_hint` is `None`.
56         fn get_header<'a>(&'a mut self, header_hash: &'a BlockHash, height_hint: Option<u32>) -> AsyncBlockSourceResult<'a, BlockHeaderData>;
57
58         /// Returns the block for a given hash. A headers-only block source should return a `Transient`
59         /// error.
60         fn get_block<'a>(&'a mut self, header_hash: &'a BlockHash) -> AsyncBlockSourceResult<'a, Block>;
61
62         /// Returns the hash of the best block and, optionally, its height.
63         ///
64         /// When polling a block source, [`Poll`] implementations may pass the height to [`get_header`]
65         /// to allow for a more efficient lookup.
66         ///
67         /// [`Poll`]: poll/trait.Poll.html
68         /// [`get_header`]: #tymethod.get_header
69         fn get_best_block<'a>(&'a mut self) -> AsyncBlockSourceResult<(BlockHash, Option<u32>)>;
70 }
71
72 /// Result type for `BlockSource` requests.
73 type BlockSourceResult<T> = Result<T, BlockSourceError>;
74
75 // TODO: Replace with BlockSourceResult once `async` trait functions are supported. For details,
76 // see: https://areweasyncyet.rs.
77 /// Result type for asynchronous `BlockSource` requests.
78 type AsyncBlockSourceResult<'a, T> = Pin<Box<dyn Future<Output = BlockSourceResult<T>> + 'a + Send>>;
79
80 /// Error type for `BlockSource` requests.
81 ///
82 /// Transient errors may be resolved when re-polling, but no attempt will be made to re-poll on
83 /// persistent errors.
84 #[derive(Debug)]
85 pub struct BlockSourceError {
86         kind: BlockSourceErrorKind,
87         error: Box<dyn std::error::Error + Send + Sync>,
88 }
89
90 /// The kind of `BlockSourceError`, either persistent or transient.
91 #[derive(Clone, Copy, Debug, PartialEq)]
92 pub enum BlockSourceErrorKind {
93         /// Indicates an error that won't resolve when retrying a request (e.g., invalid data).
94         Persistent,
95
96         /// Indicates an error that may resolve when retrying a request (e.g., unresponsive).
97         Transient,
98 }
99
100 impl BlockSourceError {
101         /// Creates a new persistent error originated from the given error.
102         pub fn persistent<E>(error: E) -> Self
103         where E: Into<Box<dyn std::error::Error + Send + Sync>> {
104                 Self {
105                         kind: BlockSourceErrorKind::Persistent,
106                         error: error.into(),
107                 }
108         }
109
110         /// Creates a new transient error originated from the given error.
111         pub fn transient<E>(error: E) -> Self
112         where E: Into<Box<dyn std::error::Error + Send + Sync>> {
113                 Self {
114                         kind: BlockSourceErrorKind::Transient,
115                         error: error.into(),
116                 }
117         }
118
119         /// Returns the kind of error.
120         pub fn kind(&self) -> BlockSourceErrorKind {
121                 self.kind
122         }
123
124         /// Converts the error into the underlying error.
125         pub fn into_inner(self) -> Box<dyn std::error::Error + Send + Sync> {
126                 self.error
127         }
128 }
129
130 /// A block header and some associated data. This information should be available from most block
131 /// sources (and, notably, is available in Bitcoin Core's RPC and REST interfaces).
132 #[derive(Clone, Copy, Debug, PartialEq)]
133 pub struct BlockHeaderData {
134         /// The block header itself.
135         pub header: BlockHeader,
136
137         /// The block height where the genesis block has height 0.
138         pub height: u32,
139
140         /// The total chain work in expected number of double-SHA256 hashes required to build a chain
141         /// of equivalent weight.
142         pub chainwork: Uint256,
143 }
144
145 /// A lightweight client for keeping a listener in sync with the chain, allowing for Simplified
146 /// Payment Verification (SPV).
147 ///
148 /// The client is parameterized by a chain poller which is responsible for polling one or more block
149 /// sources for the best chain tip. During this process it detects any chain forks, determines which
150 /// constitutes the best chain, and updates the listener accordingly with any blocks that were
151 /// connected or disconnected since the last poll.
152 ///
153 /// Block headers for the best chain are maintained in the parameterized cache, allowing for a
154 /// custom cache eviction policy. This offers flexibility to those sensitive to resource usage.
155 /// Hence, there is a trade-off between a lower memory footprint and potentially increased network
156 /// I/O as headers are re-fetched during fork detection.
157 pub struct SpvClient<P: Poll, C: Cache, L: ChainListener> {
158         chain_tip: ValidatedBlockHeader,
159         chain_poller: P,
160         chain_notifier: ChainNotifier<C>,
161         chain_listener: L,
162 }
163
164 /// Adaptor used for notifying when blocks have been connected or disconnected from the chain.
165 ///
166 /// Used when needing to replay chain data upon startup or as new chain events occur.
167 pub trait ChainListener {
168         /// Notifies the listener that a block was added at the given height.
169         fn block_connected(&mut self, block: &Block, height: u32);
170
171         /// Notifies the listener that a block was removed at the given height.
172         fn block_disconnected(&mut self, header: &BlockHeader, height: u32);
173 }
174
175 /// The `Cache` trait defines behavior for managing a block header cache, where block headers are
176 /// keyed by block hash.
177 ///
178 /// Used by [`ChainNotifier`] to store headers along the best chain, which is important for ensuring
179 /// that blocks can be disconnected if they are no longer accessible from a block source (e.g., if
180 /// the block source does not store stale forks indefinitely).
181 ///
182 /// Implementations may define how long to retain headers such that it's unlikely they will ever be
183 /// needed to disconnect a block.  In cases where block sources provide access to headers on stale
184 /// forks reliably, caches may be entirely unnecessary.
185 ///
186 /// [`ChainNotifier`]: struct.ChainNotifier.html
187 pub trait Cache {
188         /// Retrieves the block header keyed by the given block hash.
189         fn look_up(&self, block_hash: &BlockHash) -> Option<&ValidatedBlockHeader>;
190
191         /// Called when a block has been connected to the best chain to ensure it is available to be
192         /// disconnected later if needed.
193         fn block_connected(&mut self, block_hash: BlockHash, block_header: ValidatedBlockHeader);
194
195         /// Called when a block has been disconnected from the best chain. Once disconnected, a block's
196         /// header is no longer needed and thus can be removed.
197         fn block_disconnected(&mut self, block_hash: &BlockHash) -> Option<ValidatedBlockHeader>;
198 }
199
200 /// Unbounded cache of block headers keyed by block hash.
201 pub type UnboundedCache = std::collections::HashMap<BlockHash, ValidatedBlockHeader>;
202
203 impl Cache for UnboundedCache {
204         fn look_up(&self, block_hash: &BlockHash) -> Option<&ValidatedBlockHeader> {
205                 self.get(block_hash)
206         }
207
208         fn block_connected(&mut self, block_hash: BlockHash, block_header: ValidatedBlockHeader) {
209                 self.insert(block_hash, block_header);
210         }
211
212         fn block_disconnected(&mut self, block_hash: &BlockHash) -> Option<ValidatedBlockHeader> {
213                 self.remove(block_hash)
214         }
215 }
216
217 impl<P: Poll, C: Cache, L: ChainListener> SpvClient<P, C, L> {
218         /// Creates a new SPV client using `chain_tip` as the best known chain tip.
219         ///
220         /// Subsequent calls to [`poll_best_tip`] will poll for the best chain tip using the given chain
221         /// poller, which may be configured with one or more block sources to query. At least one block
222         /// source must provide headers back from the best chain tip to its common ancestor with
223         /// `chain_tip`.
224         /// * `header_cache` is used to look up and store headers on the best chain
225         /// * `chain_listener` is notified of any blocks connected or disconnected
226         ///
227         /// [`poll_best_tip`]: struct.SpvClient.html#method.poll_best_tip
228         pub fn new(
229                 chain_tip: ValidatedBlockHeader,
230                 chain_poller: P,
231                 header_cache: C,
232                 chain_listener: L,
233         ) -> Self {
234                 let chain_notifier = ChainNotifier { header_cache };
235                 Self { chain_tip, chain_poller, chain_notifier, chain_listener }
236         }
237
238         /// Polls for the best tip and updates the chain listener with any connected or disconnected
239         /// blocks accordingly.
240         ///
241         /// Returns the best polled chain tip relative to the previous best known tip and whether any
242         /// blocks were indeed connected or disconnected.
243         pub async fn poll_best_tip(&mut self) -> BlockSourceResult<(ChainTip, bool)> {
244                 let chain_tip = self.chain_poller.poll_chain_tip(self.chain_tip).await?;
245                 let blocks_connected = match chain_tip {
246                         ChainTip::Common => false,
247                         ChainTip::Better(chain_tip) => {
248                                 debug_assert_ne!(chain_tip.block_hash, self.chain_tip.block_hash);
249                                 debug_assert!(chain_tip.chainwork > self.chain_tip.chainwork);
250                                 self.update_chain_tip(chain_tip).await
251                         },
252                         ChainTip::Worse(chain_tip) => {
253                                 debug_assert_ne!(chain_tip.block_hash, self.chain_tip.block_hash);
254                                 debug_assert!(chain_tip.chainwork <= self.chain_tip.chainwork);
255                                 false
256                         },
257                 };
258                 Ok((chain_tip, blocks_connected))
259         }
260
261         /// Updates the chain tip, syncing the chain listener with any connected or disconnected
262         /// blocks. Returns whether there were any such blocks.
263         async fn update_chain_tip(&mut self, best_chain_tip: ValidatedBlockHeader) -> bool {
264                 match self.chain_notifier.sync_listener(best_chain_tip, &self.chain_tip, &mut self.chain_poller, &mut self.chain_listener).await {
265                         Ok(_) => {
266                                 self.chain_tip = best_chain_tip;
267                                 true
268                         },
269                         Err((_, Some(chain_tip))) if chain_tip.block_hash != self.chain_tip.block_hash => {
270                                 self.chain_tip = chain_tip;
271                                 true
272                         },
273                         Err(_) => false,
274                 }
275         }
276 }
277
278 /// Notifies [listeners] of blocks that have been connected or disconnected from the chain.
279 ///
280 /// [listeners]: trait.ChainListener.html
281 struct ChainNotifier<C: Cache> {
282         /// Cache for looking up headers before fetching from a block source.
283         header_cache: C,
284 }
285
286 /// Changes made to the chain between subsequent polls that transformed it from having one chain tip
287 /// to another.
288 ///
289 /// Blocks are given in height-descending order. Therefore, blocks are first disconnected in order
290 /// before new blocks are connected in reverse order.
291 struct ChainDifference {
292         /// Blocks that were disconnected from the chain since the last poll.
293         disconnected_blocks: Vec<ValidatedBlockHeader>,
294
295         /// Blocks that were connected to the chain since the last poll.
296         connected_blocks: Vec<ValidatedBlockHeader>,
297 }
298
299 impl<C: Cache> ChainNotifier<C> {
300         /// Finds the fork point between `new_header` and `old_header`, disconnecting blocks from
301         /// `old_header` to get to that point and then connecting blocks until `new_header`.
302         ///
303         /// Validates headers along the transition path, but doesn't fetch blocks until the chain is
304         /// disconnected to the fork point. Thus, this may return an `Err` that includes where the tip
305         /// ended up which may not be `new_header`. Note that iff the returned `Err` contains `Some`
306         /// header then the transition from `old_header` to `new_header` is valid.
307         async fn sync_listener<L: ChainListener, P: Poll>(
308                 &mut self,
309                 new_header: ValidatedBlockHeader,
310                 old_header: &ValidatedBlockHeader,
311                 chain_poller: &mut P,
312                 chain_listener: &mut L,
313         ) -> Result<(), (BlockSourceError, Option<ValidatedBlockHeader>)> {
314                 let mut difference = self.find_difference(new_header, old_header, chain_poller).await
315                         .map_err(|e| (e, None))?;
316
317                 let mut new_tip = *old_header;
318                 for header in difference.disconnected_blocks.drain(..) {
319                         if let Some(cached_header) = self.header_cache.block_disconnected(&header.block_hash) {
320                                 assert_eq!(cached_header, header);
321                         }
322                         chain_listener.block_disconnected(&header.header, header.height);
323                         new_tip = header;
324                 }
325
326                 for header in difference.connected_blocks.drain(..).rev() {
327                         let block = chain_poller
328                                 .fetch_block(&header).await
329                                 .or_else(|e| Err((e, Some(new_tip))))?;
330                         debug_assert_eq!(block.block_hash, header.block_hash);
331
332                         self.header_cache.block_connected(header.block_hash, header);
333                         chain_listener.block_connected(&block, header.height);
334                         new_tip = header;
335                 }
336
337                 Ok(())
338         }
339
340         /// Returns the changes needed to produce the chain with `current_header` as its tip from the
341         /// chain with `prev_header` as its tip.
342         ///
343         /// Walks backwards from `current_header` and `prev_header`, finding the common ancestor.
344         async fn find_difference<P: Poll>(
345                 &self,
346                 current_header: ValidatedBlockHeader,
347                 prev_header: &ValidatedBlockHeader,
348                 chain_poller: &mut P,
349         ) -> BlockSourceResult<ChainDifference> {
350                 let mut disconnected_blocks = Vec::new();
351                 let mut connected_blocks = Vec::new();
352                 let mut current = current_header;
353                 let mut previous = *prev_header;
354                 loop {
355                         // Found the common ancestor.
356                         if current.block_hash == previous.block_hash {
357                                 break;
358                         }
359
360                         // Walk back the chain, finding blocks needed to connect and disconnect. Only walk back
361                         // the header with the greater height, or both if equal heights.
362                         let current_height = current.height;
363                         let previous_height = previous.height;
364                         if current_height <= previous_height {
365                                 disconnected_blocks.push(previous);
366                                 previous = self.look_up_previous_header(chain_poller, &previous).await?;
367                         }
368                         if current_height >= previous_height {
369                                 connected_blocks.push(current);
370                                 current = self.look_up_previous_header(chain_poller, &current).await?;
371                         }
372                 }
373
374                 Ok(ChainDifference { disconnected_blocks, connected_blocks })
375         }
376
377         /// Returns the previous header for the given header, either by looking it up in the cache or
378         /// fetching it if not found.
379         async fn look_up_previous_header<P: Poll>(
380                 &self,
381                 chain_poller: &mut P,
382                 header: &ValidatedBlockHeader,
383         ) -> BlockSourceResult<ValidatedBlockHeader> {
384                 match self.header_cache.look_up(&header.header.prev_blockhash) {
385                         Some(prev_header) => Ok(*prev_header),
386                         None => chain_poller.look_up_previous_header(header).await,
387                 }
388         }
389 }
390
391 #[cfg(test)]
392 mod spv_client_tests {
393         use crate::test_utils::{Blockchain, NullChainListener};
394         use super::*;
395
396         use bitcoin::network::constants::Network;
397
398         #[tokio::test]
399         async fn poll_from_chain_without_headers() {
400                 let mut chain = Blockchain::default().with_height(3).without_headers();
401                 let best_tip = chain.at_height(1);
402
403                 let poller = poll::ChainPoller::new(&mut chain, Network::Testnet);
404                 let cache = UnboundedCache::new();
405                 let mut client = SpvClient::new(best_tip, poller, cache, NullChainListener {});
406                 match client.poll_best_tip().await {
407                         Err(e) => {
408                                 assert_eq!(e.kind(), BlockSourceErrorKind::Persistent);
409                                 assert_eq!(e.into_inner().as_ref().to_string(), "header not found");
410                         },
411                         Ok(_) => panic!("Expected error"),
412                 }
413                 assert_eq!(client.chain_tip, best_tip);
414         }
415
416         #[tokio::test]
417         async fn poll_from_chain_with_common_tip() {
418                 let mut chain = Blockchain::default().with_height(3);
419                 let common_tip = chain.tip();
420
421                 let poller = poll::ChainPoller::new(&mut chain, Network::Testnet);
422                 let cache = UnboundedCache::new();
423                 let mut client = SpvClient::new(common_tip, poller, cache, NullChainListener {});
424                 match client.poll_best_tip().await {
425                         Err(e) => panic!("Unexpected error: {:?}", e),
426                         Ok((chain_tip, blocks_connected)) => {
427                                 assert_eq!(chain_tip, ChainTip::Common);
428                                 assert!(!blocks_connected);
429                         },
430                 }
431                 assert_eq!(client.chain_tip, common_tip);
432         }
433
434         #[tokio::test]
435         async fn poll_from_chain_with_better_tip() {
436                 let mut chain = Blockchain::default().with_height(3);
437                 let new_tip = chain.tip();
438                 let old_tip = chain.at_height(1);
439
440                 let poller = poll::ChainPoller::new(&mut chain, Network::Testnet);
441                 let cache = UnboundedCache::new();
442                 let mut client = SpvClient::new(old_tip, poller, cache, NullChainListener {});
443                 match client.poll_best_tip().await {
444                         Err(e) => panic!("Unexpected error: {:?}", e),
445                         Ok((chain_tip, blocks_connected)) => {
446                                 assert_eq!(chain_tip, ChainTip::Better(new_tip));
447                                 assert!(blocks_connected);
448                         },
449                 }
450                 assert_eq!(client.chain_tip, new_tip);
451         }
452
453         #[tokio::test]
454         async fn poll_from_chain_with_better_tip_and_without_any_new_blocks() {
455                 let mut chain = Blockchain::default().with_height(3).without_blocks(2..);
456                 let new_tip = chain.tip();
457                 let old_tip = chain.at_height(1);
458
459                 let poller = poll::ChainPoller::new(&mut chain, Network::Testnet);
460                 let cache = UnboundedCache::new();
461                 let mut client = SpvClient::new(old_tip, poller, cache, NullChainListener {});
462                 match client.poll_best_tip().await {
463                         Err(e) => panic!("Unexpected error: {:?}", e),
464                         Ok((chain_tip, blocks_connected)) => {
465                                 assert_eq!(chain_tip, ChainTip::Better(new_tip));
466                                 assert!(!blocks_connected);
467                         },
468                 }
469                 assert_eq!(client.chain_tip, old_tip);
470         }
471
472         #[tokio::test]
473         async fn poll_from_chain_with_better_tip_and_without_some_new_blocks() {
474                 let mut chain = Blockchain::default().with_height(3).without_blocks(3..);
475                 let new_tip = chain.tip();
476                 let old_tip = chain.at_height(1);
477
478                 let poller = poll::ChainPoller::new(&mut chain, Network::Testnet);
479                 let cache = UnboundedCache::new();
480                 let mut client = SpvClient::new(old_tip, poller, cache, NullChainListener {});
481                 match client.poll_best_tip().await {
482                         Err(e) => panic!("Unexpected error: {:?}", e),
483                         Ok((chain_tip, blocks_connected)) => {
484                                 assert_eq!(chain_tip, ChainTip::Better(new_tip));
485                                 assert!(blocks_connected);
486                         },
487                 }
488                 assert_eq!(client.chain_tip, chain.at_height(2));
489         }
490
491         #[tokio::test]
492         async fn poll_from_chain_with_worse_tip() {
493                 let mut chain = Blockchain::default().with_height(3);
494                 let best_tip = chain.tip();
495                 chain.disconnect_tip();
496                 let worse_tip = chain.tip();
497
498                 let poller = poll::ChainPoller::new(&mut chain, Network::Testnet);
499                 let cache = UnboundedCache::new();
500                 let mut client = SpvClient::new(best_tip, poller, cache, NullChainListener {});
501                 match client.poll_best_tip().await {
502                         Err(e) => panic!("Unexpected error: {:?}", e),
503                         Ok((chain_tip, blocks_connected)) => {
504                                 assert_eq!(chain_tip, ChainTip::Worse(worse_tip));
505                                 assert!(!blocks_connected);
506                         },
507                 }
508                 assert_eq!(client.chain_tip, best_tip);
509         }
510 }
511
512 #[cfg(test)]
513 mod chain_notifier_tests {
514         use crate::test_utils::{Blockchain, MockChainListener};
515         use super::*;
516
517         use bitcoin::network::constants::Network;
518
519         #[tokio::test]
520         async fn sync_from_same_chain() {
521                 let mut chain = Blockchain::default().with_height(3);
522
523                 let new_tip = chain.tip();
524                 let old_tip = chain.at_height(1);
525                 let mut listener = MockChainListener::new()
526                         .expect_block_connected(*chain.at_height(2))
527                         .expect_block_connected(*new_tip);
528                 let mut notifier = ChainNotifier { header_cache: chain.header_cache(0..=1) };
529                 let mut poller = poll::ChainPoller::new(&mut chain, Network::Testnet);
530                 match notifier.sync_listener(new_tip, &old_tip, &mut poller, &mut listener).await {
531                         Err((e, _)) => panic!("Unexpected error: {:?}", e),
532                         Ok(_) => {},
533                 }
534         }
535
536         #[tokio::test]
537         async fn sync_from_different_chains() {
538                 let mut test_chain = Blockchain::with_network(Network::Testnet).with_height(1);
539                 let main_chain = Blockchain::with_network(Network::Bitcoin).with_height(1);
540
541                 let new_tip = test_chain.tip();
542                 let old_tip = main_chain.tip();
543                 let mut listener = MockChainListener::new();
544                 let mut notifier = ChainNotifier { header_cache: main_chain.header_cache(0..=1) };
545                 let mut poller = poll::ChainPoller::new(&mut test_chain, Network::Testnet);
546                 match notifier.sync_listener(new_tip, &old_tip, &mut poller, &mut listener).await {
547                         Err((e, _)) => {
548                                 assert_eq!(e.kind(), BlockSourceErrorKind::Persistent);
549                                 assert_eq!(e.into_inner().as_ref().to_string(), "genesis block reached");
550                         },
551                         Ok(_) => panic!("Expected error"),
552                 }
553         }
554
555         #[tokio::test]
556         async fn sync_from_equal_length_fork() {
557                 let main_chain = Blockchain::default().with_height(2);
558                 let mut fork_chain = main_chain.fork_at_height(1);
559
560                 let new_tip = fork_chain.tip();
561                 let old_tip = main_chain.tip();
562                 let mut listener = MockChainListener::new()
563                         .expect_block_disconnected(*old_tip)
564                         .expect_block_connected(*new_tip);
565                 let mut notifier = ChainNotifier { header_cache: main_chain.header_cache(0..=2) };
566                 let mut poller = poll::ChainPoller::new(&mut fork_chain, Network::Testnet);
567                 match notifier.sync_listener(new_tip, &old_tip, &mut poller, &mut listener).await {
568                         Err((e, _)) => panic!("Unexpected error: {:?}", e),
569                         Ok(_) => {},
570                 }
571         }
572
573         #[tokio::test]
574         async fn sync_from_shorter_fork() {
575                 let main_chain = Blockchain::default().with_height(3);
576                 let mut fork_chain = main_chain.fork_at_height(1);
577                 fork_chain.disconnect_tip();
578
579                 let new_tip = fork_chain.tip();
580                 let old_tip = main_chain.tip();
581                 let mut listener = MockChainListener::new()
582                         .expect_block_disconnected(*old_tip)
583                         .expect_block_disconnected(*main_chain.at_height(2))
584                         .expect_block_connected(*new_tip);
585                 let mut notifier = ChainNotifier { header_cache: main_chain.header_cache(0..=3) };
586                 let mut poller = poll::ChainPoller::new(&mut fork_chain, Network::Testnet);
587                 match notifier.sync_listener(new_tip, &old_tip, &mut poller, &mut listener).await {
588                         Err((e, _)) => panic!("Unexpected error: {:?}", e),
589                         Ok(_) => {},
590                 }
591         }
592
593         #[tokio::test]
594         async fn sync_from_longer_fork() {
595                 let mut main_chain = Blockchain::default().with_height(3);
596                 let mut fork_chain = main_chain.fork_at_height(1);
597                 main_chain.disconnect_tip();
598
599                 let new_tip = fork_chain.tip();
600                 let old_tip = main_chain.tip();
601                 let mut listener = MockChainListener::new()
602                         .expect_block_disconnected(*old_tip)
603                         .expect_block_connected(*fork_chain.at_height(2))
604                         .expect_block_connected(*new_tip);
605                 let mut notifier = ChainNotifier { header_cache: main_chain.header_cache(0..=2) };
606                 let mut poller = poll::ChainPoller::new(&mut fork_chain, Network::Testnet);
607                 match notifier.sync_listener(new_tip, &old_tip, &mut poller, &mut listener).await {
608                         Err((e, _)) => panic!("Unexpected error: {:?}", e),
609                         Ok(_) => {},
610                 }
611         }
612
613         #[tokio::test]
614         async fn sync_from_chain_without_headers() {
615                 let mut chain = Blockchain::default().with_height(3).without_headers();
616
617                 let new_tip = chain.tip();
618                 let old_tip = chain.at_height(1);
619                 let mut listener = MockChainListener::new();
620                 let mut notifier = ChainNotifier { header_cache: chain.header_cache(0..=1) };
621                 let mut poller = poll::ChainPoller::new(&mut chain, Network::Testnet);
622                 match notifier.sync_listener(new_tip, &old_tip, &mut poller, &mut listener).await {
623                         Err((_, tip)) => assert_eq!(tip, None),
624                         Ok(_) => panic!("Expected error"),
625                 }
626         }
627
628         #[tokio::test]
629         async fn sync_from_chain_without_any_new_blocks() {
630                 let mut chain = Blockchain::default().with_height(3).without_blocks(2..);
631
632                 let new_tip = chain.tip();
633                 let old_tip = chain.at_height(1);
634                 let mut listener = MockChainListener::new();
635                 let mut notifier = ChainNotifier { header_cache: chain.header_cache(0..=3) };
636                 let mut poller = poll::ChainPoller::new(&mut chain, Network::Testnet);
637                 match notifier.sync_listener(new_tip, &old_tip, &mut poller, &mut listener).await {
638                         Err((_, tip)) => assert_eq!(tip, Some(old_tip)),
639                         Ok(_) => panic!("Expected error"),
640                 }
641         }
642
643         #[tokio::test]
644         async fn sync_from_chain_without_some_new_blocks() {
645                 let mut chain = Blockchain::default().with_height(3).without_blocks(3..);
646
647                 let new_tip = chain.tip();
648                 let old_tip = chain.at_height(1);
649                 let mut listener = MockChainListener::new()
650                         .expect_block_connected(*chain.at_height(2));
651                 let mut notifier = ChainNotifier { header_cache: chain.header_cache(0..=3) };
652                 let mut poller = poll::ChainPoller::new(&mut chain, Network::Testnet);
653                 match notifier.sync_listener(new_tip, &old_tip, &mut poller, &mut listener).await {
654                         Err((_, tip)) => assert_eq!(tip, Some(chain.at_height(2))),
655                         Ok(_) => panic!("Expected error"),
656                 }
657         }
658 }