]> git.bitcoin.ninja Git - rust-lightning/blob - lightning-block-sync/src/poll.rs
Merge pull request #1406 from TheBlueMatt/2022-04-scorer-precision
[rust-lightning] / lightning-block-sync / src / poll.rs
1 //! Adapters that make one or more [`BlockSource`]s simpler to poll for new chain tip transitions.
2
3 use crate::{AsyncBlockSourceResult, BlockHeaderData, BlockSource, BlockSourceError, BlockSourceResult};
4
5 use bitcoin::blockdata::block::Block;
6 use bitcoin::hash_types::BlockHash;
7 use bitcoin::network::constants::Network;
8
9 use std::ops::Deref;
10
11 /// The `Poll` trait defines behavior for polling block sources for a chain tip and retrieving
12 /// related chain data. It serves as an adapter for `BlockSource`.
13 ///
14 /// [`ChainPoller`] adapts a single `BlockSource`, while any other implementations of `Poll` are
15 /// required to be built in terms of it to ensure chain data validity.
16 ///
17 /// [`ChainPoller`]: ../struct.ChainPoller.html
18 pub trait Poll {
19         /// Returns a chain tip in terms of its relationship to the provided chain tip.
20         fn poll_chain_tip<'a>(&'a self, best_known_chain_tip: ValidatedBlockHeader) ->
21                 AsyncBlockSourceResult<'a, ChainTip>;
22
23         /// Returns the header that preceded the given header in the chain.
24         fn look_up_previous_header<'a>(&'a self, header: &'a ValidatedBlockHeader) ->
25                 AsyncBlockSourceResult<'a, ValidatedBlockHeader>;
26
27         /// Returns the block associated with the given header.
28         fn fetch_block<'a>(&'a self, header: &'a ValidatedBlockHeader) ->
29                 AsyncBlockSourceResult<'a, ValidatedBlock>;
30 }
31
32 /// A chain tip relative to another chain tip in terms of block hash and chainwork.
33 #[derive(Clone, Debug, PartialEq)]
34 pub enum ChainTip {
35         /// A chain tip with the same hash as another chain's tip.
36         Common,
37
38         /// A chain tip with more chainwork than another chain's tip.
39         Better(ValidatedBlockHeader),
40
41         /// A chain tip with less or equal chainwork than another chain's tip. In either case, the
42         /// hashes of each tip will be different.
43         Worse(ValidatedBlockHeader),
44 }
45
46 /// The `Validate` trait defines behavior for validating chain data.
47 ///
48 /// This trait is sealed and not meant to be implemented outside of this crate.
49 pub trait Validate: sealed::Validate {
50         /// The validated data wrapper which can be dereferenced to obtain the validated data.
51         type T: std::ops::Deref<Target = Self>;
52
53         /// Validates the chain data against the given block hash and any criteria needed to ensure that
54         /// it is internally consistent.
55         fn validate(self, block_hash: BlockHash) -> BlockSourceResult<Self::T>;
56 }
57
58 impl Validate for BlockHeaderData {
59         type T = ValidatedBlockHeader;
60
61         fn validate(self, block_hash: BlockHash) -> BlockSourceResult<Self::T> {
62                 self.header
63                         .validate_pow(&self.header.target())
64                         .or_else(|e| Err(BlockSourceError::persistent(e)))?;
65
66                 // TODO: Use the result of validate_pow instead of recomputing the block hash once upstream.
67                 if self.header.block_hash() != block_hash {
68                         return Err(BlockSourceError::persistent("invalid block hash"));
69                 }
70
71                 Ok(ValidatedBlockHeader { block_hash, inner: self })
72         }
73 }
74
75 impl Validate for Block {
76         type T = ValidatedBlock;
77
78         fn validate(self, block_hash: BlockHash) -> BlockSourceResult<Self::T> {
79                 self.header
80                         .validate_pow(&self.header.target())
81                         .or_else(|e| Err(BlockSourceError::persistent(e)))?;
82
83                 // TODO: Use the result of validate_pow instead of recomputing the block hash once upstream.
84                 if self.block_hash() != block_hash {
85                         return Err(BlockSourceError::persistent("invalid block hash"));
86                 }
87
88                 if !self.check_merkle_root() {
89                         return Err(BlockSourceError::persistent("invalid merkle root"));
90                 }
91
92                 if !self.check_witness_commitment() {
93                         return Err(BlockSourceError::persistent("invalid witness commitment"));
94                 }
95
96                 Ok(ValidatedBlock { block_hash, inner: self })
97         }
98 }
99
100 /// A block header with validated proof of work and corresponding block hash.
101 #[derive(Clone, Copy, Debug, PartialEq)]
102 pub struct ValidatedBlockHeader {
103         pub(crate) block_hash: BlockHash,
104         inner: BlockHeaderData,
105 }
106
107 impl std::ops::Deref for ValidatedBlockHeader {
108         type Target = BlockHeaderData;
109
110         fn deref(&self) -> &Self::Target {
111                 &self.inner
112         }
113 }
114
115 impl ValidatedBlockHeader {
116         /// Checks that the header correctly builds on previous_header: the claimed work differential
117         /// matches the actual PoW and the difficulty transition is possible, i.e., within 4x.
118         fn check_builds_on(&self, previous_header: &ValidatedBlockHeader, network: Network) -> BlockSourceResult<()> {
119                 if self.header.prev_blockhash != previous_header.block_hash {
120                         return Err(BlockSourceError::persistent("invalid previous block hash"));
121                 }
122
123                 if self.height != previous_header.height + 1 {
124                         return Err(BlockSourceError::persistent("invalid block height"));
125                 }
126
127                 let work = self.header.work();
128                 if self.chainwork != previous_header.chainwork + work {
129                         return Err(BlockSourceError::persistent("invalid chainwork"));
130                 }
131
132                 if let Network::Bitcoin = network {
133                         if self.height % 2016 == 0 {
134                                 let previous_work = previous_header.header.work();
135                                 if work > (previous_work << 2) || work < (previous_work >> 2) {
136                                         return Err(BlockSourceError::persistent("invalid difficulty transition"))
137                                 }
138                         } else if self.header.bits != previous_header.header.bits {
139                                 return Err(BlockSourceError::persistent("invalid difficulty"))
140                         }
141                 }
142
143                 Ok(())
144         }
145 }
146
147 /// A block with validated data against its transaction list and corresponding block hash.
148 pub struct ValidatedBlock {
149         pub(crate) block_hash: BlockHash,
150         inner: Block,
151 }
152
153 impl std::ops::Deref for ValidatedBlock {
154         type Target = Block;
155
156         fn deref(&self) -> &Self::Target {
157                 &self.inner
158         }
159 }
160
161 mod sealed {
162         /// Used to prevent implementing [`super::Validate`] outside the crate but still allow its use.
163         pub trait Validate {}
164
165         impl Validate for crate::BlockHeaderData {}
166         impl Validate for bitcoin::blockdata::block::Block {}
167 }
168
169 /// The canonical `Poll` implementation used for a single `BlockSource`.
170 ///
171 /// Other `Poll` implementations should be built using `ChainPoller` as it provides the simplest way
172 /// of validating chain data and checking consistency.
173 pub struct ChainPoller<B: Deref<Target=T> + Sized, T: BlockSource> {
174         block_source: B,
175         network: Network,
176 }
177
178 impl<B: Deref<Target=T> + Sized, T: BlockSource> ChainPoller<B, T> {
179         /// Creates a new poller for the given block source.
180         ///
181         /// If the `network` parameter is mainnet, then the difficulty between blocks is checked for
182         /// validity.
183         pub fn new(block_source: B, network: Network) -> Self {
184                 Self { block_source, network }
185         }
186 }
187
188 impl<B: Deref<Target=T> + Sized + Send + Sync, T: BlockSource> Poll for ChainPoller<B, T> {
189         fn poll_chain_tip<'a>(&'a self, best_known_chain_tip: ValidatedBlockHeader) ->
190                 AsyncBlockSourceResult<'a, ChainTip>
191         {
192                 Box::pin(async move {
193                         let (block_hash, height) = self.block_source.get_best_block().await?;
194                         if block_hash == best_known_chain_tip.header.block_hash() {
195                                 return Ok(ChainTip::Common);
196                         }
197
198                         let chain_tip = self.block_source
199                                 .get_header(&block_hash, height).await?
200                                 .validate(block_hash)?;
201                         if chain_tip.chainwork > best_known_chain_tip.chainwork {
202                                 Ok(ChainTip::Better(chain_tip))
203                         } else {
204                                 Ok(ChainTip::Worse(chain_tip))
205                         }
206                 })
207         }
208
209         fn look_up_previous_header<'a>(&'a self, header: &'a ValidatedBlockHeader) ->
210                 AsyncBlockSourceResult<'a, ValidatedBlockHeader>
211         {
212                 Box::pin(async move {
213                         if header.height == 0 {
214                                 return Err(BlockSourceError::persistent("genesis block reached"));
215                         }
216
217                         let previous_hash = &header.header.prev_blockhash;
218                         let height = header.height - 1;
219                         let previous_header = self.block_source
220                                 .get_header(previous_hash, Some(height)).await?
221                                 .validate(*previous_hash)?;
222                         header.check_builds_on(&previous_header, self.network)?;
223
224                         Ok(previous_header)
225                 })
226         }
227
228         fn fetch_block<'a>(&'a self, header: &'a ValidatedBlockHeader) ->
229                 AsyncBlockSourceResult<'a, ValidatedBlock>
230         {
231                 Box::pin(async move {
232                         self.block_source
233                                 .get_block(&header.block_hash).await?
234                                 .validate(header.block_hash)
235                 })
236         }
237 }
238
239 #[cfg(test)]
240 mod tests {
241         use crate::*;
242         use crate::test_utils::Blockchain;
243         use super::*;
244         use bitcoin::util::uint::Uint256;
245
246         #[tokio::test]
247         async fn poll_empty_chain() {
248                 let mut chain = Blockchain::default().with_height(0);
249                 let best_known_chain_tip = chain.tip();
250                 chain.disconnect_tip();
251
252                 let poller = ChainPoller::new(&chain, Network::Bitcoin);
253                 match poller.poll_chain_tip(best_known_chain_tip).await {
254                         Err(e) => {
255                                 assert_eq!(e.kind(), BlockSourceErrorKind::Transient);
256                                 assert_eq!(e.into_inner().as_ref().to_string(), "empty chain");
257                         },
258                         Ok(_) => panic!("Expected error"),
259                 }
260         }
261
262         #[tokio::test]
263         async fn poll_chain_without_headers() {
264                 let chain = Blockchain::default().with_height(1).without_headers();
265                 let best_known_chain_tip = chain.at_height(0);
266
267                 let poller = ChainPoller::new(&chain, Network::Bitcoin);
268                 match poller.poll_chain_tip(best_known_chain_tip).await {
269                         Err(e) => {
270                                 assert_eq!(e.kind(), BlockSourceErrorKind::Persistent);
271                                 assert_eq!(e.into_inner().as_ref().to_string(), "header not found");
272                         },
273                         Ok(_) => panic!("Expected error"),
274                 }
275         }
276
277         #[tokio::test]
278         async fn poll_chain_with_invalid_pow() {
279                 let mut chain = Blockchain::default().with_height(1);
280                 let best_known_chain_tip = chain.at_height(0);
281
282                 // Invalidate the tip by changing its target.
283                 chain.blocks.last_mut().unwrap().header.bits =
284                         BlockHeader::compact_target_from_u256(&Uint256::from_be_bytes([0; 32]));
285
286                 let poller = ChainPoller::new(&chain, Network::Bitcoin);
287                 match poller.poll_chain_tip(best_known_chain_tip).await {
288                         Err(e) => {
289                                 assert_eq!(e.kind(), BlockSourceErrorKind::Persistent);
290                                 assert_eq!(e.into_inner().as_ref().to_string(), "block target correct but not attained");
291                         },
292                         Ok(_) => panic!("Expected error"),
293                 }
294         }
295
296         #[tokio::test]
297         async fn poll_chain_with_malformed_headers() {
298                 let chain = Blockchain::default().with_height(1).malformed_headers();
299                 let best_known_chain_tip = chain.at_height(0);
300
301                 let poller = ChainPoller::new(&chain, Network::Bitcoin);
302                 match poller.poll_chain_tip(best_known_chain_tip).await {
303                         Err(e) => {
304                                 assert_eq!(e.kind(), BlockSourceErrorKind::Persistent);
305                                 assert_eq!(e.into_inner().as_ref().to_string(), "invalid block hash");
306                         },
307                         Ok(_) => panic!("Expected error"),
308                 }
309         }
310
311         #[tokio::test]
312         async fn poll_chain_with_common_tip() {
313                 let chain = Blockchain::default().with_height(0);
314                 let best_known_chain_tip = chain.tip();
315
316                 let poller = ChainPoller::new(&chain, Network::Bitcoin);
317                 match poller.poll_chain_tip(best_known_chain_tip).await {
318                         Err(e) => panic!("Unexpected error: {:?}", e),
319                         Ok(tip) => assert_eq!(tip, ChainTip::Common),
320                 }
321         }
322
323         #[tokio::test]
324         async fn poll_chain_with_uncommon_tip_but_equal_chainwork() {
325                 let mut chain = Blockchain::default().with_height(1);
326                 let best_known_chain_tip = chain.tip();
327
328                 // Change the nonce to get a different block hash with the same chainwork.
329                 chain.blocks.last_mut().unwrap().header.nonce += 1;
330                 let worse_chain_tip = chain.tip();
331                 assert_eq!(best_known_chain_tip.chainwork, worse_chain_tip.chainwork);
332
333                 let poller = ChainPoller::new(&chain, Network::Bitcoin);
334                 match poller.poll_chain_tip(best_known_chain_tip).await {
335                         Err(e) => panic!("Unexpected error: {:?}", e),
336                         Ok(tip) => assert_eq!(tip, ChainTip::Worse(worse_chain_tip)),
337                 }
338         }
339
340         #[tokio::test]
341         async fn poll_chain_with_worse_tip() {
342                 let mut chain = Blockchain::default().with_height(1);
343                 let best_known_chain_tip = chain.tip();
344
345                 chain.disconnect_tip();
346                 let worse_chain_tip = chain.tip();
347
348                 let poller = ChainPoller::new(&chain, Network::Bitcoin);
349                 match poller.poll_chain_tip(best_known_chain_tip).await {
350                         Err(e) => panic!("Unexpected error: {:?}", e),
351                         Ok(tip) => assert_eq!(tip, ChainTip::Worse(worse_chain_tip)),
352                 }
353         }
354
355         #[tokio::test]
356         async fn poll_chain_with_better_tip() {
357                 let chain = Blockchain::default().with_height(1);
358                 let best_known_chain_tip = chain.at_height(0);
359
360                 let better_chain_tip = chain.tip();
361
362                 let poller = ChainPoller::new(&chain, Network::Bitcoin);
363                 match poller.poll_chain_tip(best_known_chain_tip).await {
364                         Err(e) => panic!("Unexpected error: {:?}", e),
365                         Ok(tip) => assert_eq!(tip, ChainTip::Better(better_chain_tip)),
366                 }
367         }
368 }