1use crate::{BlockData, BlockHeaderData, BlockSource, BlockSourceError, BlockSourceResult};
4
5use bitcoin::hash_types::BlockHash;
6use bitcoin::network::Network;
7use lightning::chain::BlockLocator;
8
9use std::future::Future;
10use std::ops::Deref;
11
12pub trait Poll {
20 fn poll_chain_tip<'a>(
22 &'a self, best_known_chain_tip: ValidatedBlockHeader,
23 ) -> impl Future<Output = BlockSourceResult<ChainTip>> + Send + 'a;
24
25 fn look_up_previous_header<'a>(
27 &'a self, header: &'a ValidatedBlockHeader,
28 ) -> impl Future<Output = BlockSourceResult<ValidatedBlockHeader>> + Send + 'a;
29
30 fn fetch_block<'a>(
32 &'a self, header: &'a ValidatedBlockHeader,
33 ) -> impl Future<Output = BlockSourceResult<ValidatedBlock>> + Send + 'a;
34
35 fn get_header<'a>(
37 &'a self, block_hash: &'a BlockHash, height_hint: Option<u32>,
38 ) -> impl Future<Output = BlockSourceResult<ValidatedBlockHeader>> + Send + 'a;
39}
40
41#[derive(Clone, Debug, PartialEq, Eq)]
43pub enum ChainTip {
44 Common,
46
47 Better(ValidatedBlockHeader),
49
50 Worse(ValidatedBlockHeader),
53}
54
55pub trait Validate: sealed::Validate {
59 type T: std::ops::Deref<Target = Self>;
61
62 fn validate(self, block_hash: BlockHash) -> BlockSourceResult<Self::T>;
65}
66
67impl Validate for BlockHeaderData {
68 type T = ValidatedBlockHeader;
69
70 fn validate(self, block_hash: BlockHash) -> BlockSourceResult<Self::T> {
71 let pow_valid_block_hash =
72 self.header.validate_pow(self.header.target()).map_err(BlockSourceError::persistent)?;
73
74 if pow_valid_block_hash != block_hash {
75 return Err(BlockSourceError::persistent("invalid block hash"));
76 }
77
78 Ok(ValidatedBlockHeader { block_hash, inner: self })
79 }
80}
81
82impl Validate for BlockData {
83 type T = ValidatedBlock;
84
85 fn validate(self, block_hash: BlockHash) -> BlockSourceResult<Self::T> {
86 let header = match &self {
87 BlockData::FullBlock(block) => &block.header,
88 BlockData::HeaderOnly(header) => header,
89 };
90
91 let pow_valid_block_hash =
92 header.validate_pow(header.target()).map_err(BlockSourceError::persistent)?;
93
94 if pow_valid_block_hash != block_hash {
95 return Err(BlockSourceError::persistent("invalid block hash"));
96 }
97
98 if let BlockData::FullBlock(block) = &self {
99 if !block.check_merkle_root() {
100 return Err(BlockSourceError::persistent("invalid merkle root"));
101 }
102
103 if !block.check_witness_commitment() {
104 return Err(BlockSourceError::persistent("invalid witness commitment"));
105 }
106 }
107
108 Ok(ValidatedBlock { block_hash, inner: self })
109 }
110}
111
112#[derive(Clone, Copy, Debug, PartialEq, Eq)]
114pub struct ValidatedBlockHeader {
115 pub(crate) block_hash: BlockHash,
116 inner: BlockHeaderData,
117}
118
119impl std::ops::Deref for ValidatedBlockHeader {
120 type Target = BlockHeaderData;
121
122 fn deref(&self) -> &Self::Target {
123 &self.inner
124 }
125}
126
127impl ValidatedBlockHeader {
128 fn check_builds_on(
131 &self, previous_header: &ValidatedBlockHeader, network: Network,
132 ) -> BlockSourceResult<()> {
133 if self.header.prev_blockhash != previous_header.block_hash {
134 return Err(BlockSourceError::persistent("invalid previous block hash"));
135 }
136
137 if self.height != previous_header.height + 1 {
138 return Err(BlockSourceError::persistent("invalid block height"));
139 }
140
141 let work = self.header.work();
142 if self.chainwork != previous_header.chainwork + work {
143 return Err(BlockSourceError::persistent("invalid chainwork"));
144 }
145
146 if let Network::Bitcoin = network {
147 if self.height % 2016 == 0 {
148 let target = self.header.target();
149 let previous_target = previous_header.header.target();
150 let min_target = previous_target.min_transition_threshold();
151 let max_target = previous_target.max_transition_threshold_unchecked();
152 if target > max_target || target < min_target {
153 return Err(BlockSourceError::persistent("invalid difficulty transition"));
154 }
155 } else if self.header.bits != previous_header.header.bits {
156 return Err(BlockSourceError::persistent("invalid difficulty"));
157 }
158 }
159
160 Ok(())
161 }
162
163 pub fn to_block_locator(&self) -> BlockLocator {
173 BlockLocator::new(self.block_hash, self.inner.height)
174 }
175}
176
177pub struct ValidatedBlock {
179 pub(crate) block_hash: BlockHash,
180 inner: BlockData,
181}
182
183impl std::ops::Deref for ValidatedBlock {
184 type Target = BlockData;
185
186 fn deref(&self) -> &Self::Target {
187 &self.inner
188 }
189}
190
191mod sealed {
192 pub trait Validate {}
194
195 impl Validate for crate::BlockHeaderData {}
196 impl Validate for crate::BlockData {}
197}
198
199pub struct ChainPoller<B: Deref<Target = T> + Sized + Send + Sync, T: BlockSource + ?Sized> {
204 block_source: B,
205 network: Network,
206}
207
208impl<B: Deref<Target = T> + Sized + Send + Sync, T: BlockSource + ?Sized> ChainPoller<B, T> {
209 pub fn new(block_source: B, network: Network) -> Self {
214 Self { block_source, network }
215 }
216}
217
218impl<B: Deref<Target = T> + Sized + Send + Sync, T: BlockSource + ?Sized> Poll
219 for ChainPoller<B, T>
220{
221 fn poll_chain_tip<'a>(
222 &'a self, best_known_chain_tip: ValidatedBlockHeader,
223 ) -> impl Future<Output = BlockSourceResult<ChainTip>> + Send + 'a {
224 async move {
225 let (block_hash, height) = self.block_source.get_best_block().await?;
226 if block_hash == best_known_chain_tip.header.block_hash() {
227 return Ok(ChainTip::Common);
228 }
229
230 let chain_tip =
231 self.block_source.get_header(&block_hash, height).await?.validate(block_hash)?;
232 if chain_tip.chainwork > best_known_chain_tip.chainwork {
233 Ok(ChainTip::Better(chain_tip))
234 } else {
235 Ok(ChainTip::Worse(chain_tip))
236 }
237 }
238 }
239
240 fn look_up_previous_header<'a>(
241 &'a self, header: &'a ValidatedBlockHeader,
242 ) -> impl Future<Output = BlockSourceResult<ValidatedBlockHeader>> + Send + 'a {
243 async move {
244 if header.height == 0 {
245 return Err(BlockSourceError::persistent("genesis block reached"));
246 }
247
248 let previous_hash = &header.header.prev_blockhash;
249 let height = header.height - 1;
250 let previous_header = self
251 .block_source
252 .get_header(previous_hash, Some(height))
253 .await?
254 .validate(*previous_hash)?;
255 header.check_builds_on(&previous_header, self.network)?;
256
257 Ok(previous_header)
258 }
259 }
260
261 fn fetch_block<'a>(
262 &'a self, header: &'a ValidatedBlockHeader,
263 ) -> impl Future<Output = BlockSourceResult<ValidatedBlock>> + Send + 'a {
264 async move { self.block_source.get_block(&header.block_hash).await?.validate(header.block_hash) }
265 }
266
267 fn get_header<'a>(
268 &'a self, block_hash: &'a BlockHash, height_hint: Option<u32>,
269 ) -> impl Future<Output = BlockSourceResult<ValidatedBlockHeader>> + Send + 'a {
270 Box::pin(async move {
271 self.block_source.get_header(block_hash, height_hint).await?.validate(*block_hash)
272 })
273 }
274}
275
276#[cfg(test)]
277mod tests {
278 use super::*;
279 use crate::test_utils::Blockchain;
280 use crate::*;
281
282 #[tokio::test]
283 async fn poll_empty_chain() {
284 let mut chain = Blockchain::default().with_height(0);
285 let best_known_chain_tip = chain.tip();
286 chain.disconnect_tip();
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::Transient);
292 assert_eq!(e.into_inner().as_ref().to_string(), "empty chain");
293 },
294 Ok(_) => panic!("Expected error"),
295 }
296 }
297
298 #[tokio::test]
299 async fn poll_chain_without_headers() {
300 let chain = Blockchain::default().with_height(1).without_headers();
301 let best_known_chain_tip = chain.at_height(0);
302
303 let poller = ChainPoller::new(&chain, Network::Bitcoin);
304 match poller.poll_chain_tip(best_known_chain_tip).await {
305 Err(e) => {
306 assert_eq!(e.kind(), BlockSourceErrorKind::Persistent);
307 assert_eq!(e.into_inner().as_ref().to_string(), "header not found");
308 },
309 Ok(_) => panic!("Expected error"),
310 }
311 }
312
313 #[tokio::test]
314 async fn poll_chain_with_invalid_pow() {
315 let mut chain = Blockchain::default().with_height(1);
316 let best_known_chain_tip = chain.at_height(0);
317
318 chain.blocks.last_mut().unwrap().header.bits =
320 bitcoin::Target::from_be_bytes([0x01; 32]).to_compact_lossy();
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!(
327 e.into_inner().as_ref().to_string(),
328 "block target correct but not attained"
329 );
330 },
331 Ok(_) => panic!("Expected error"),
332 }
333 }
334
335 #[tokio::test]
336 async fn poll_chain_with_malformed_headers() {
337 let chain = Blockchain::default().with_height(1).malformed_headers();
338 let best_known_chain_tip = chain.at_height(0);
339
340 let poller = ChainPoller::new(&chain, Network::Bitcoin);
341 match poller.poll_chain_tip(best_known_chain_tip).await {
342 Err(e) => {
343 assert_eq!(e.kind(), BlockSourceErrorKind::Persistent);
344 assert_eq!(e.into_inner().as_ref().to_string(), "invalid block hash");
345 },
346 Ok(_) => panic!("Expected error"),
347 }
348 }
349
350 #[tokio::test]
351 async fn poll_chain_with_common_tip() {
352 let chain = Blockchain::default().with_height(0);
353 let best_known_chain_tip = chain.tip();
354
355 let poller = ChainPoller::new(&chain, Network::Bitcoin);
356 match poller.poll_chain_tip(best_known_chain_tip).await {
357 Err(e) => panic!("Unexpected error: {:?}", e),
358 Ok(tip) => assert_eq!(tip, ChainTip::Common),
359 }
360 }
361
362 #[tokio::test]
363 async fn poll_chain_with_uncommon_tip_but_equal_chainwork() {
364 let mut chain = Blockchain::default().with_height(1);
365 let best_known_chain_tip = chain.tip();
366
367 chain.blocks.last_mut().unwrap().header.nonce += 1;
369 let worse_chain_tip = chain.tip();
370 assert_eq!(best_known_chain_tip.chainwork, worse_chain_tip.chainwork);
371
372 let poller = ChainPoller::new(&chain, Network::Bitcoin);
373 match poller.poll_chain_tip(best_known_chain_tip).await {
374 Err(e) => panic!("Unexpected error: {:?}", e),
375 Ok(tip) => assert_eq!(tip, ChainTip::Worse(worse_chain_tip)),
376 }
377 }
378
379 #[tokio::test]
380 async fn poll_chain_with_worse_tip() {
381 let mut chain = Blockchain::default().with_height(1);
382 let best_known_chain_tip = chain.tip();
383
384 chain.disconnect_tip();
385 let worse_chain_tip = chain.tip();
386
387 let poller = ChainPoller::new(&chain, Network::Bitcoin);
388 match poller.poll_chain_tip(best_known_chain_tip).await {
389 Err(e) => panic!("Unexpected error: {:?}", e),
390 Ok(tip) => assert_eq!(tip, ChainTip::Worse(worse_chain_tip)),
391 }
392 }
393
394 #[tokio::test]
395 async fn poll_chain_with_better_tip() {
396 let chain = Blockchain::default().with_height(1);
397 let best_known_chain_tip = chain.at_height(0);
398
399 let better_chain_tip = chain.tip();
400
401 let poller = ChainPoller::new(&chain, Network::Bitcoin);
402 match poller.poll_chain_tip(best_known_chain_tip).await {
403 Err(e) => panic!("Unexpected error: {:?}", e),
404 Ok(tip) => assert_eq!(tip, ChainTip::Better(better_chain_tip)),
405 }
406 }
407}