]> git.bitcoin.ninja Git - rust-lightning/blob - lightning-block-sync/src/poll.rs
Merge pull request #2226 from alecchendev/2023-04-persist-network-graph-on-rgs
[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, BlockData, BlockHeaderData, BlockSource, BlockSourceError, BlockSourceResult};
4
5 use bitcoin::hash_types::BlockHash;
6 use bitcoin::network::constants::Network;
7 use lightning::chain::BestBlock;
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, Eq)]
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                 let pow_valid_block_hash = self.header
63                         .validate_pow(&self.header.target())
64                         .map_err(BlockSourceError::persistent)?;
65
66                 if pow_valid_block_hash != block_hash {
67                         return Err(BlockSourceError::persistent("invalid block hash"));
68                 }
69
70                 Ok(ValidatedBlockHeader { block_hash, inner: self })
71         }
72 }
73
74 impl Validate for BlockData {
75         type T = ValidatedBlock;
76
77         fn validate(self, block_hash: BlockHash) -> BlockSourceResult<Self::T> {
78                 let header = match &self {
79                         BlockData::FullBlock(block) => &block.header,
80                         BlockData::HeaderOnly(header) => header,
81                 };
82
83                 let pow_valid_block_hash = header
84                         .validate_pow(&header.target())
85                         .map_err(BlockSourceError::persistent)?;
86
87                 if pow_valid_block_hash != block_hash {
88                         return Err(BlockSourceError::persistent("invalid block hash"));
89                 }
90
91                 if let BlockData::FullBlock(block) = &self {
92                         if !block.check_merkle_root() {
93                                 return Err(BlockSourceError::persistent("invalid merkle root"));
94                         }
95
96                         if !block.check_witness_commitment() {
97                                 return Err(BlockSourceError::persistent("invalid witness commitment"));
98                         }
99                 }
100
101                 Ok(ValidatedBlock { block_hash, inner: self })
102         }
103 }
104
105 /// A block header with validated proof of work and corresponding block hash.
106 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
107 pub struct ValidatedBlockHeader {
108         pub(crate) block_hash: BlockHash,
109         inner: BlockHeaderData,
110 }
111
112 impl std::ops::Deref for ValidatedBlockHeader {
113         type Target = BlockHeaderData;
114
115         fn deref(&self) -> &Self::Target {
116                 &self.inner
117         }
118 }
119
120 impl ValidatedBlockHeader {
121         /// Checks that the header correctly builds on previous_header: the claimed work differential
122         /// matches the actual PoW and the difficulty transition is possible, i.e., within 4x.
123         fn check_builds_on(&self, previous_header: &ValidatedBlockHeader, network: Network) -> BlockSourceResult<()> {
124                 if self.header.prev_blockhash != previous_header.block_hash {
125                         return Err(BlockSourceError::persistent("invalid previous block hash"));
126                 }
127
128                 if self.height != previous_header.height + 1 {
129                         return Err(BlockSourceError::persistent("invalid block height"));
130                 }
131
132                 let work = self.header.work();
133                 if self.chainwork != previous_header.chainwork + work {
134                         return Err(BlockSourceError::persistent("invalid chainwork"));
135                 }
136
137                 if let Network::Bitcoin = network {
138                         if self.height % 2016 == 0 {
139                                 let target = self.header.target();
140                                 let previous_target = previous_header.header.target();
141                                 let min_target = previous_target >> 2;
142                                 let max_target = previous_target << 2;
143                                 if target > max_target || target < min_target {
144                                         return Err(BlockSourceError::persistent("invalid difficulty transition"))
145                                 }
146                         } else if self.header.bits != previous_header.header.bits {
147                                 return Err(BlockSourceError::persistent("invalid difficulty"))
148                         }
149                 }
150
151                 Ok(())
152         }
153
154     /// Returns the [`BestBlock`] corresponding to this validated block header, which can be passed
155     /// into [`ChannelManager::new`] as part of its [`ChainParameters`]. Useful for ensuring that
156     /// the [`SpvClient`] and [`ChannelManager`] are initialized to the same block during a fresh
157     /// start.
158     ///
159     /// [`SpvClient`]: crate::SpvClient
160     /// [`ChainParameters`]: lightning::ln::channelmanager::ChainParameters
161     /// [`ChannelManager`]: lightning::ln::channelmanager::ChannelManager
162     /// [`ChannelManager::new`]: lightning::ln::channelmanager::ChannelManager::new
163     pub fn to_best_block(&self) -> BestBlock {
164         BestBlock::new(self.block_hash, self.inner.height)
165     }
166 }
167
168 /// A block with validated data against its transaction list and corresponding block hash.
169 pub struct ValidatedBlock {
170         pub(crate) block_hash: BlockHash,
171         inner: BlockData,
172 }
173
174 impl std::ops::Deref for ValidatedBlock {
175         type Target = BlockData;
176
177         fn deref(&self) -> &Self::Target {
178                 &self.inner
179         }
180 }
181
182 mod sealed {
183         /// Used to prevent implementing [`super::Validate`] outside the crate but still allow its use.
184         pub trait Validate {}
185
186         impl Validate for crate::BlockHeaderData {}
187         impl Validate for crate::BlockData {}
188 }
189
190 /// The canonical `Poll` implementation used for a single `BlockSource`.
191 ///
192 /// Other `Poll` implementations should be built using `ChainPoller` as it provides the simplest way
193 /// of validating chain data and checking consistency.
194 pub struct ChainPoller<B: Deref<Target=T> + Sized + Send + Sync, T: BlockSource + ?Sized> {
195         block_source: B,
196         network: Network,
197 }
198
199 impl<B: Deref<Target=T> + Sized + Send + Sync, T: BlockSource + ?Sized> ChainPoller<B, T> {
200         /// Creates a new poller for the given block source.
201         ///
202         /// If the `network` parameter is mainnet, then the difficulty between blocks is checked for
203         /// validity.
204         pub fn new(block_source: B, network: Network) -> Self {
205                 Self { block_source, network }
206         }
207 }
208
209 impl<B: Deref<Target=T> + Sized + Send + Sync, T: BlockSource + ?Sized> Poll for ChainPoller<B, T> {
210         fn poll_chain_tip<'a>(&'a self, best_known_chain_tip: ValidatedBlockHeader) ->
211                 AsyncBlockSourceResult<'a, ChainTip>
212         {
213                 Box::pin(async move {
214                         let (block_hash, height) = self.block_source.get_best_block().await?;
215                         if block_hash == best_known_chain_tip.header.block_hash() {
216                                 return Ok(ChainTip::Common);
217                         }
218
219                         let chain_tip = self.block_source
220                                 .get_header(&block_hash, height).await?
221                                 .validate(block_hash)?;
222                         if chain_tip.chainwork > best_known_chain_tip.chainwork {
223                                 Ok(ChainTip::Better(chain_tip))
224                         } else {
225                                 Ok(ChainTip::Worse(chain_tip))
226                         }
227                 })
228         }
229
230         fn look_up_previous_header<'a>(&'a self, header: &'a ValidatedBlockHeader) ->
231                 AsyncBlockSourceResult<'a, ValidatedBlockHeader>
232         {
233                 Box::pin(async move {
234                         if header.height == 0 {
235                                 return Err(BlockSourceError::persistent("genesis block reached"));
236                         }
237
238                         let previous_hash = &header.header.prev_blockhash;
239                         let height = header.height - 1;
240                         let previous_header = self.block_source
241                                 .get_header(previous_hash, Some(height)).await?
242                                 .validate(*previous_hash)?;
243                         header.check_builds_on(&previous_header, self.network)?;
244
245                         Ok(previous_header)
246                 })
247         }
248
249         fn fetch_block<'a>(&'a self, header: &'a ValidatedBlockHeader) ->
250                 AsyncBlockSourceResult<'a, ValidatedBlock>
251         {
252                 Box::pin(async move {
253                         self.block_source
254                                 .get_block(&header.block_hash).await?
255                                 .validate(header.block_hash)
256                 })
257         }
258 }
259
260 #[cfg(test)]
261 mod tests {
262         use crate::*;
263         use crate::test_utils::Blockchain;
264         use super::*;
265         use bitcoin::util::uint::Uint256;
266
267         #[tokio::test]
268         async fn poll_empty_chain() {
269                 let mut chain = Blockchain::default().with_height(0);
270                 let best_known_chain_tip = chain.tip();
271                 chain.disconnect_tip();
272
273                 let poller = ChainPoller::new(&chain, Network::Bitcoin);
274                 match poller.poll_chain_tip(best_known_chain_tip).await {
275                         Err(e) => {
276                                 assert_eq!(e.kind(), BlockSourceErrorKind::Transient);
277                                 assert_eq!(e.into_inner().as_ref().to_string(), "empty chain");
278                         },
279                         Ok(_) => panic!("Expected error"),
280                 }
281         }
282
283         #[tokio::test]
284         async fn poll_chain_without_headers() {
285                 let chain = Blockchain::default().with_height(1).without_headers();
286                 let best_known_chain_tip = chain.at_height(0);
287
288                 let poller = ChainPoller::new(&chain, Network::Bitcoin);
289                 match poller.poll_chain_tip(best_known_chain_tip).await {
290                         Err(e) => {
291                                 assert_eq!(e.kind(), BlockSourceErrorKind::Persistent);
292                                 assert_eq!(e.into_inner().as_ref().to_string(), "header not found");
293                         },
294                         Ok(_) => panic!("Expected error"),
295                 }
296         }
297
298         #[tokio::test]
299         async fn poll_chain_with_invalid_pow() {
300                 let mut chain = Blockchain::default().with_height(1);
301                 let best_known_chain_tip = chain.at_height(0);
302
303                 // Invalidate the tip by changing its target.
304                 chain.blocks.last_mut().unwrap().header.bits =
305                         BlockHeader::compact_target_from_u256(&Uint256::from_be_bytes([0; 32]));
306
307                 let poller = ChainPoller::new(&chain, Network::Bitcoin);
308                 match poller.poll_chain_tip(best_known_chain_tip).await {
309                         Err(e) => {
310                                 assert_eq!(e.kind(), BlockSourceErrorKind::Persistent);
311                                 assert_eq!(e.into_inner().as_ref().to_string(), "block target correct but not attained");
312                         },
313                         Ok(_) => panic!("Expected error"),
314                 }
315         }
316
317         #[tokio::test]
318         async fn poll_chain_with_malformed_headers() {
319                 let chain = Blockchain::default().with_height(1).malformed_headers();
320                 let best_known_chain_tip = chain.at_height(0);
321
322                 let poller = ChainPoller::new(&chain, Network::Bitcoin);
323                 match poller.poll_chain_tip(best_known_chain_tip).await {
324                         Err(e) => {
325                                 assert_eq!(e.kind(), BlockSourceErrorKind::Persistent);
326                                 assert_eq!(e.into_inner().as_ref().to_string(), "invalid block hash");
327                         },
328                         Ok(_) => panic!("Expected error"),
329                 }
330         }
331
332         #[tokio::test]
333         async fn poll_chain_with_common_tip() {
334                 let chain = Blockchain::default().with_height(0);
335                 let best_known_chain_tip = chain.tip();
336
337                 let poller = ChainPoller::new(&chain, Network::Bitcoin);
338                 match poller.poll_chain_tip(best_known_chain_tip).await {
339                         Err(e) => panic!("Unexpected error: {:?}", e),
340                         Ok(tip) => assert_eq!(tip, ChainTip::Common),
341                 }
342         }
343
344         #[tokio::test]
345         async fn poll_chain_with_uncommon_tip_but_equal_chainwork() {
346                 let mut chain = Blockchain::default().with_height(1);
347                 let best_known_chain_tip = chain.tip();
348
349                 // Change the nonce to get a different block hash with the same chainwork.
350                 chain.blocks.last_mut().unwrap().header.nonce += 1;
351                 let worse_chain_tip = chain.tip();
352                 assert_eq!(best_known_chain_tip.chainwork, worse_chain_tip.chainwork);
353
354                 let poller = ChainPoller::new(&chain, Network::Bitcoin);
355                 match poller.poll_chain_tip(best_known_chain_tip).await {
356                         Err(e) => panic!("Unexpected error: {:?}", e),
357                         Ok(tip) => assert_eq!(tip, ChainTip::Worse(worse_chain_tip)),
358                 }
359         }
360
361         #[tokio::test]
362         async fn poll_chain_with_worse_tip() {
363                 let mut chain = Blockchain::default().with_height(1);
364                 let best_known_chain_tip = chain.tip();
365
366                 chain.disconnect_tip();
367                 let worse_chain_tip = chain.tip();
368
369                 let poller = ChainPoller::new(&chain, Network::Bitcoin);
370                 match poller.poll_chain_tip(best_known_chain_tip).await {
371                         Err(e) => panic!("Unexpected error: {:?}", e),
372                         Ok(tip) => assert_eq!(tip, ChainTip::Worse(worse_chain_tip)),
373                 }
374         }
375
376         #[tokio::test]
377         async fn poll_chain_with_better_tip() {
378                 let chain = Blockchain::default().with_height(1);
379                 let best_known_chain_tip = chain.at_height(0);
380
381                 let better_chain_tip = chain.tip();
382
383                 let poller = ChainPoller::new(&chain, Network::Bitcoin);
384                 match poller.poll_chain_tip(best_known_chain_tip).await {
385                         Err(e) => panic!("Unexpected error: {:?}", e),
386                         Ok(tip) => assert_eq!(tip, ChainTip::Better(better_chain_tip)),
387                 }
388         }
389 }