1use crate::{
19 ClientError, LOG_TARGET,
20 client::{SubscriptionType, SubstrateBlock, SubstrateBlockNumber},
21 subxt_client::SrcChainConfig,
22};
23use jsonrpsee::core::async_trait;
24use sp_core::H256;
25use std::sync::Arc;
26use subxt::{
27 OnlineClient, config::RpcConfigFor, error::OnlineClientAtBlockError,
28 rpcs::methods::LegacyRpcMethods,
29};
30use tokio::sync::RwLock;
31
32#[async_trait]
34pub trait BlockInfoProvider: Send + Sync {
35 async fn update_latest(
39 &self,
40 block: Arc<SubstrateBlock>,
41 subscription_type: SubscriptionType,
42 ) -> Option<Arc<SubstrateBlock>>;
43
44 async fn latest_finalized_block(&self) -> Arc<SubstrateBlock>;
46
47 async fn latest_block(&self) -> Arc<SubstrateBlock>;
49
50 async fn latest_block_number(&self) -> SubstrateBlockNumber {
52 self.latest_block().await.block_number()
53 }
54
55 async fn block_by_number(
57 &self,
58 block_number: SubstrateBlockNumber,
59 ) -> Result<Option<Arc<SubstrateBlock>>, ClientError>;
60
61 async fn block_by_hash(&self, hash: &H256) -> Result<Option<Arc<SubstrateBlock>>, ClientError>;
63}
64
65#[derive(Clone)]
67pub struct SubxtBlockInfoProvider {
68 latest_block: Arc<RwLock<Arc<SubstrateBlock>>>,
70
71 latest_finalized_block: Arc<RwLock<Arc<SubstrateBlock>>>,
73
74 rpc: LegacyRpcMethods<RpcConfigFor<SrcChainConfig>>,
76
77 api: OnlineClient<SrcChainConfig>,
79}
80
81impl SubxtBlockInfoProvider {
82 pub async fn new(
83 api: OnlineClient<SrcChainConfig>,
84 rpc: LegacyRpcMethods<RpcConfigFor<SrcChainConfig>>,
85 ) -> Result<Self, ClientError> {
86 let latest_finalized_block = Arc::new(api.at_current_block().await?);
87 let best_hash = rpc.chain_get_block_hash(None).await?.ok_or(ClientError::BlockNotFound)?;
88 let latest_block = Arc::new(api.at_block(best_hash).await?);
89 Ok(Self {
90 api,
91 rpc,
92 latest_block: Arc::new(RwLock::new(latest_block)),
93 latest_finalized_block: Arc::new(RwLock::new(latest_finalized_block)),
94 })
95 }
96
97 async fn is_canonical(&self, number: SubstrateBlockNumber, hash: H256) -> bool {
102 match self.rpc.chain_get_block_hash(Some(number.into())).await {
103 Ok(canonical) => canonical == Some(hash),
104 Err(err) => {
105 log::debug!(target: LOG_TARGET,
106 "Failed to check if block #{number} ({hash:?}) is canonical, keeping it as the latest block: {err:?}");
107 true
108 },
109 }
110 }
111
112 async fn update_finalized(&self, block: Arc<SubstrateBlock>) -> Option<Arc<SubstrateBlock>> {
116 let mut finalized = self.latest_finalized_block.write().await;
118 if block.block_number() >= finalized.block_number() {
119 *finalized = block;
120 }
121 let finalized_block = finalized.clone();
122 drop(finalized);
123
124 let mut best = self.latest_block.write().await;
126 if finalized_block.block_number() >= best.block_number() &&
127 finalized_block.block_hash() != best.block_hash()
128 {
129 log::debug!(target: LOG_TARGET,
130 "Advancing the latest block #{} ({:?}) to the finalized block #{} ({:?}): it is no longer ahead",
131 best.block_number(),
132 best.block_hash(),
133 finalized_block.block_number(),
134 finalized_block.block_hash());
135 *best = finalized_block.clone();
136 return Some(finalized_block);
137 }
138
139 None
140 }
141
142 async fn update_best(&self, block: Arc<SubstrateBlock>) -> Option<Arc<SubstrateBlock>> {
146 let is_same_or_above = |other: &SubstrateBlock| {
147 block.block_number() > other.block_number() || block.block_hash() == other.block_hash()
148 };
149
150 let mut best = self.latest_block.write().await;
151 if is_same_or_above(&best) {
152 let changed = block.block_hash() != best.block_hash();
153 *best = block.clone();
154 return changed.then_some(block);
155 }
156
157 let (best_number, best_hash) = (best.block_number(), best.block_hash());
158 drop(best);
159
160 let finalized = self.latest_finalized_block.read().await.clone();
162 if !is_same_or_above(&finalized) {
163 log::debug!(target: LOG_TARGET,
164 "Ignoring best block #{} ({:?}): it is neither the finalized block #{} ({:?}) nor above it",
165 block.block_number(),
166 block.block_hash(),
167 finalized.block_number(),
168 finalized.block_hash());
169 return None;
170 }
171
172 if self.is_canonical(best_number, best_hash).await {
174 return None;
175 }
176
177 let mut best = self.latest_block.write().await;
178 if best.block_hash() != best_hash {
179 debug_assert!(false, "the latest block must have a single writer");
180 log::warn!(target: LOG_TARGET,
181 "Ignoring best block #{} ({:?}): the latest block was concurrently replaced with #{} ({:?})",
182 block.block_number(),
183 block.block_hash(),
184 best.block_number(),
185 best.block_hash());
186 return None;
187 }
188
189 log::trace!(target: LOG_TARGET,
190 "Moving the latest block back from #{best_number} ({best_hash:?}) to #{} ({:?}): it is no longer canonical",
191 block.block_number(),
192 block.block_hash());
193 *best = block.clone();
194 Some(block)
195 }
196}
197
198#[async_trait]
199impl BlockInfoProvider for SubxtBlockInfoProvider {
200 async fn update_latest(
201 &self,
202 block: Arc<SubstrateBlock>,
203 subscription_type: SubscriptionType,
204 ) -> Option<Arc<SubstrateBlock>> {
205 match subscription_type {
206 SubscriptionType::FinalizedBlocks => self.update_finalized(block).await,
207 SubscriptionType::BestBlocks => self.update_best(block).await,
208 }
209 }
210
211 async fn latest_block(&self) -> Arc<SubstrateBlock> {
212 self.latest_block.read().await.clone()
213 }
214
215 async fn latest_finalized_block(&self) -> Arc<SubstrateBlock> {
216 self.latest_finalized_block.read().await.clone()
217 }
218
219 async fn block_by_number(
220 &self,
221 block_number: SubstrateBlockNumber,
222 ) -> Result<Option<Arc<SubstrateBlock>>, ClientError> {
223 let latest = self.latest_block().await;
224 if block_number == latest.block_number() {
225 return Ok(Some(latest));
226 }
227
228 let latest_finalized = self.latest_finalized_block().await;
229 if block_number == latest_finalized.block_number() {
230 return Ok(Some(latest_finalized));
231 }
232
233 let Some(hash) = self.rpc.chain_get_block_hash(Some(block_number.into())).await? else {
234 return Ok(None);
235 };
236
237 match self.api.at_block(hash).await {
238 Ok(block) => Ok(Some(Arc::new(block))),
239 Err(
240 OnlineClientAtBlockError::BlockHeaderNotFound { .. } |
241 OnlineClientAtBlockError::BlockNotFound { .. },
242 ) => Ok(None),
243 Err(err) => Err(err.into()),
244 }
245 }
246
247 async fn block_by_hash(&self, hash: &H256) -> Result<Option<Arc<SubstrateBlock>>, ClientError> {
248 let latest = self.latest_block().await;
249 if hash == &latest.block_hash() {
250 return Ok(Some(latest));
251 }
252
253 let latest_finalized = self.latest_finalized_block().await;
254 if hash == &latest_finalized.block_hash() {
255 return Ok(Some(latest_finalized));
256 }
257
258 match self.api.at_block(*hash).await {
259 Ok(block) => Ok(Some(Arc::new(block))),
260 Err(
261 OnlineClientAtBlockError::BlockHeaderNotFound { .. } |
262 OnlineClientAtBlockError::BlockNotFound { .. },
263 ) => {
264 log::trace!(target: LOG_TARGET, "block_by_hash: block {hash:?} not found");
265 Ok(None)
266 },
267 Err(err) => {
268 log::trace!(target: LOG_TARGET, "block_by_hash: failed to fetch block {hash:?}: {err:?}");
269 Err(err.into())
270 },
271 }
272 }
273}
274
275#[cfg(test)]
276pub mod test {
277 use super::*;
278 use crate::BlockInfo;
279 use codec::Decode;
280 use std::sync::{
281 Mutex,
282 atomic::{AtomicBool, AtomicUsize, Ordering},
283 };
284 use subxt::{
285 backend::LegacyBackend,
286 config::{
287 polkadot::PolkadotConfigBuilder,
288 substrate::{SpecVersionForRange, SubstrateHeader},
289 },
290 metadata::Metadata,
291 rpcs::{
292 Error as RpcError, RpcClient, UserError,
293 client::{MockRpcClient, mock_rpc_client::Json},
294 },
295 };
296
297 pub struct MockBlockInfoProvider;
299
300 pub struct MockBlockInfo {
301 pub number: SubstrateBlockNumber,
302 pub hash: H256,
303 }
304
305 impl BlockInfo for MockBlockInfo {
306 fn hash(&self) -> H256 {
307 self.hash
308 }
309 fn number(&self) -> SubstrateBlockNumber {
310 self.number
311 }
312 }
313
314 #[async_trait]
315 impl BlockInfoProvider for MockBlockInfoProvider {
316 async fn update_latest(
317 &self,
318 _block: Arc<SubstrateBlock>,
319 _subscription_type: SubscriptionType,
320 ) -> Option<Arc<SubstrateBlock>> {
321 None
322 }
323
324 async fn latest_finalized_block(&self) -> Arc<SubstrateBlock> {
325 unimplemented!()
326 }
327
328 async fn latest_block(&self) -> Arc<SubstrateBlock> {
329 unimplemented!()
330 }
331
332 async fn latest_block_number(&self) -> SubstrateBlockNumber {
333 2u64
334 }
335
336 async fn block_by_number(
337 &self,
338 _block_number: SubstrateBlockNumber,
339 ) -> Result<Option<Arc<SubstrateBlock>>, ClientError> {
340 Ok(None)
341 }
342
343 async fn block_by_hash(
344 &self,
345 _hash: &H256,
346 ) -> Result<Option<Arc<SubstrateBlock>>, ClientError> {
347 Ok(None)
348 }
349 }
350
351 pub(crate) fn chain_config() -> SrcChainConfig {
353 let metadata_bytes: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/revive_chain.scale"));
354 let metadata = Metadata::decode(&mut &metadata_bytes[..]).unwrap();
355 PolkadotConfigBuilder::new()
356 .set_metadata_for_spec_versions(std::iter::once((0u32, metadata.into())))
357 .set_spec_version_for_block_ranges(std::iter::once(SpecVersionForRange {
358 block_range: 0..u64::MAX,
359 spec_version: 0,
360 transaction_version: 0,
361 }))
362 .build()
363 }
364
365 #[derive(Clone, Copy)]
367 enum MockBlockId {
368 MainBranch(u64),
369 SideBranch(u64),
370 }
371
372 impl MockBlockId {
373 const MAIN_BRANCH_OFFSET: u8 = 0x01;
376 const SIDE_BRANCH_OFFSET: u8 = 0xa0;
377 const MAX_BLOCK_NUMBER: u64 = (u8::MAX - Self::SIDE_BRANCH_OFFSET) as u64;
379
380 fn hash(self) -> H256 {
383 let (offset, number) = match self {
384 MockBlockId::MainBranch(number) => (Self::MAIN_BRANCH_OFFSET, number),
385 MockBlockId::SideBranch(number) => (Self::SIDE_BRANCH_OFFSET, number),
386 };
387 assert!(
388 number <= Self::MAX_BLOCK_NUMBER,
389 "a mock block number must not exceed {}",
390 Self::MAX_BLOCK_NUMBER
391 );
392 H256::repeat_byte(offset + number as u8)
393 }
394
395 fn number(self) -> u64 {
396 match self {
397 MockBlockId::MainBranch(number) | MockBlockId::SideBranch(number) => number,
398 }
399 }
400
401 fn from_hash(hash: H256) -> Option<MockBlockId> {
404 let bytes = hash.as_fixed_bytes();
405 let byte = bytes[0];
406 if bytes.iter().any(|other| other != &byte) {
407 return None;
408 }
409 if let Some(number) = byte.checked_sub(Self::SIDE_BRANCH_OFFSET) {
410 Some(MockBlockId::SideBranch(number.into()))
411 } else if let Some(number) = byte.checked_sub(Self::MAIN_BRANCH_OFFSET) {
412 Some(MockBlockId::MainBranch(number.into()))
413 } else {
414 None
415 }
416 }
417 }
418
419 const _: () = assert!(
420 MockBlockId::MAIN_BRANCH_OFFSET as u64 + MockBlockId::MAX_BLOCK_NUMBER <
421 MockBlockId::SIDE_BRANCH_OFFSET as u64,
422 "a main-branch hash must stay below the side-branch range"
423 );
424
425 fn decode_params<ParamsTuple: serde::de::DeserializeOwned>(
427 params: Option<Box<serde_json::value::RawValue>>,
428 ) -> ParamsTuple {
429 let raw = params.expect("legacy RPC methods always send params");
430 serde_json::from_str(raw.get()).expect("params decode into the parameter tuple")
431 }
432
433 async fn block_at(
435 api: &OnlineClient<SrcChainConfig>,
436 block: MockBlockId,
437 ) -> Arc<SubstrateBlock> {
438 Arc::new(api.at_block(block.hash()).await.unwrap())
439 }
440
441 #[derive(Clone)]
443 struct MockChainHeads {
444 best_block: Arc<Mutex<MockBlockId>>,
446 finalized_block: MockBlockId,
448 block_hash_lookup_count: Arc<AtomicUsize>,
450 fail_block_hash_lookups: Arc<AtomicBool>,
452 report_no_best_block: Arc<AtomicBool>,
454 }
455
456 impl Default for MockChainHeads {
457 fn default() -> Self {
458 const INITIAL_BEST_BLOCK_NUMBER: u64 = 7;
459 const INITIAL_FINALIZED_BLOCK_NUMBER: u64 = 5;
460 Self {
461 best_block: Arc::new(Mutex::new(MockBlockId::MainBranch(
462 INITIAL_BEST_BLOCK_NUMBER,
463 ))),
464 finalized_block: MockBlockId::MainBranch(INITIAL_FINALIZED_BLOCK_NUMBER),
465 block_hash_lookup_count: Arc::default(),
466 fail_block_hash_lookups: Arc::default(),
467 report_no_best_block: Arc::default(),
468 }
469 }
470 }
471
472 impl MockChainHeads {
473 async fn import_best(
475 &self,
476 provider: &SubxtBlockInfoProvider,
477 api: &OnlineClient<SrcChainConfig>,
478 block: MockBlockId,
479 ) -> Option<Arc<SubstrateBlock>> {
480 *self.best_block.lock().unwrap() = block;
481 provider
482 .update_latest(block_at(api, block).await, SubscriptionType::BestBlocks)
483 .await
484 }
485
486 async fn clients(
488 &self,
489 ) -> (OnlineClient<SrcChainConfig>, LegacyRpcMethods<RpcConfigFor<SrcChainConfig>>) {
490 let config = chain_config();
491
492 let mock = MockRpcClient::builder()
493 .method_handler("chain_getBlockHash", {
494 let chain_heads = self.clone();
495 move |params: Option<Box<serde_json::value::RawValue>>| {
496 let (number,): (Option<u64>,) = decode_params(params);
497 chain_heads.block_hash_lookup_count.fetch_add(1, Ordering::SeqCst);
498 let response = if chain_heads.fail_block_hash_lookups.load(Ordering::SeqCst)
499 {
500 Err(RpcError::User(UserError {
501 code: -32000,
502 message: "scripted failure".into(),
503 data: None,
504 }))
505 } else {
506 match number {
507 None => {
508 if chain_heads.report_no_best_block.load(Ordering::SeqCst) {
509 Ok(None)
510 } else {
511 Ok(Some(chain_heads.best_block.lock().unwrap().hash()))
512 }
513 },
514 Some(number) => {
515 let best_block = *chain_heads.best_block.lock().unwrap();
516 Ok(if number > best_block.number() {
517 None
518 } else if number == best_block.number() {
519 Some(best_block.hash())
520 } else {
521 Some(MockBlockId::MainBranch(number).hash())
522 })
523 },
524 }
525 };
526 async move { response.map(Json) }
527 }
528 })
529 .method_handler("chain_getFinalizedHead", {
530 let finalized_hash = self.finalized_block.hash();
531 move |_params: Option<Box<serde_json::value::RawValue>>| async move {
532 Json(finalized_hash)
533 }
534 })
535 .method_handler(
536 "chain_getHeader",
537 |params: Option<Box<serde_json::value::RawValue>>| {
538 let (hash,): (H256,) = decode_params(params);
539 let header = MockBlockId::from_hash(hash).map(|block| SubstrateHeader {
540 parent_hash: H256::zero(),
541 number: block.number(),
542 state_root: H256::zero(),
543 extrinsics_root: H256::zero(),
544 digest: Default::default(),
545 });
546 async move { Json(header) }
547 },
548 )
549 .build();
550
551 let rpc_client = RpcClient::new(mock);
552 let backend = LegacyBackend::<SrcChainConfig>::builder().build(rpc_client.clone());
553 let api =
554 OnlineClient::<SrcChainConfig>::from_backend_with_config(config, Arc::new(backend))
555 .await
556 .unwrap();
557 let rpc = LegacyRpcMethods::<RpcConfigFor<SrcChainConfig>>::new(rpc_client);
558 (api, rpc)
559 }
560
561 async fn provider(&self) -> (SubxtBlockInfoProvider, OnlineClient<SrcChainConfig>) {
564 let (api, rpc) = self.clients().await;
565 let provider = SubxtBlockInfoProvider::new(api.clone(), rpc).await.unwrap();
566 self.block_hash_lookup_count.store(0, Ordering::SeqCst);
568 (provider, api)
569 }
570 }
571
572 #[tokio::test]
573 async fn construction_seeds_the_latest_and_finalized_blocks() {
574 let chain_heads = MockChainHeads::default();
575 let (provider, _api) = chain_heads.provider().await;
576
577 assert_eq!(
578 provider.latest_block().await.block_hash(),
579 chain_heads.best_block.lock().unwrap().hash(),
580 "the latest block starts at the chain's best block"
581 );
582 assert_eq!(
583 provider.latest_finalized_block().await.block_hash(),
584 chain_heads.finalized_block.hash(),
585 "the latest finalized block starts at the chain's finalized block"
586 );
587 }
588
589 #[tokio::test]
590 async fn construction_fails_without_a_best_block() {
591 let chain_heads = MockChainHeads::default();
592 let (api, rpc) = chain_heads.clients().await;
593
594 chain_heads.report_no_best_block.store(true, Ordering::SeqCst);
595 assert!(
596 matches!(SubxtBlockInfoProvider::new(api, rpc).await, Err(ClientError::BlockNotFound)),
597 "construction fails when the chain reports no best block"
598 );
599 }
600
601 #[tokio::test]
602 async fn best_block_updates_follow_the_chain_head() {
603 let chain_heads = MockChainHeads::default();
604 let (provider, api) = chain_heads.provider().await;
605 let best = chain_heads.best_block.lock().unwrap().number();
606
607 let head = chain_heads
608 .import_best(&provider, &api, MockBlockId::MainBranch(best + 1))
609 .await;
610 assert_eq!(
611 provider.latest_block().await.block_hash(),
612 MockBlockId::MainBranch(best + 1).hash(),
613 "a higher block becomes the latest block"
614 );
615 assert_eq!(
616 head.map(|head| head.block_hash()),
617 Some(MockBlockId::MainBranch(best + 1).hash()),
618 "a higher block is returned as the new latest block"
619 );
620 assert_eq!(
621 chain_heads.block_hash_lookup_count.load(Ordering::SeqCst),
622 0,
623 "no block hash lookup for a higher block"
624 );
625
626 let head = chain_heads
627 .import_best(&provider, &api, MockBlockId::SideBranch(best + 1))
628 .await;
629 assert_eq!(
630 provider.latest_block().await.block_hash(),
631 MockBlockId::SideBranch(best + 1).hash(),
632 "a same-number block from a side branch replaces the latest block"
633 );
634 assert_eq!(
635 head.map(|head| head.block_hash()),
636 Some(MockBlockId::SideBranch(best + 1).hash()),
637 "a same-number block from a side branch is returned as the new latest block"
638 );
639 assert_eq!(
640 chain_heads.block_hash_lookup_count.load(Ordering::SeqCst),
641 1,
642 "one block hash lookup to accept a same-number block from another branch"
643 );
644
645 chain_heads
646 .import_best(&provider, &api, MockBlockId::SideBranch(best + 2))
647 .await;
648 assert_eq!(
649 provider.latest_block().await.block_hash(),
650 MockBlockId::SideBranch(best + 2).hash(),
651 "a higher side-branch block becomes the latest block"
652 );
653 assert_eq!(
654 chain_heads.block_hash_lookup_count.load(Ordering::SeqCst),
655 1,
656 "no block hash lookup for a higher side-branch block"
657 );
658
659 let latest = provider.latest_block().await;
660 let head = provider
661 .update_latest(
662 block_at(&api, MockBlockId::SideBranch(best + 2)).await,
663 SubscriptionType::BestBlocks,
664 )
665 .await;
666 assert!(
667 !Arc::ptr_eq(&latest, &provider.latest_block().await),
668 "a repeat of the latest block replaces the cached one"
669 );
670 assert!(head.is_none(), "a repeat of the latest block does not change it");
671 assert_eq!(
672 chain_heads.block_hash_lookup_count.load(Ordering::SeqCst),
673 1,
674 "no block hash lookup for a repeat of the latest block"
675 );
676 }
677
678 #[tokio::test]
679 async fn reorgs_are_followed_and_replays_are_ignored() {
680 let chain_heads = MockChainHeads::default();
681 let (provider, api) = chain_heads.provider().await;
682 let best = chain_heads.best_block.lock().unwrap().number();
683 let finalized = chain_heads.finalized_block.number();
684
685 chain_heads
686 .import_best(&provider, &api, MockBlockId::SideBranch(best - 1))
687 .await;
688 assert_eq!(
689 provider.latest_block().await.block_hash(),
690 MockBlockId::SideBranch(best - 1).hash(),
691 "a lower block is accepted after a reorg to a shorter branch"
692 );
693 assert_eq!(
694 chain_heads.block_hash_lookup_count.load(Ordering::SeqCst),
695 1,
696 "one block hash lookup for the accepted lower block"
697 );
698
699 *chain_heads.best_block.lock().unwrap() = MockBlockId::MainBranch(best);
701
702 provider
703 .update_latest(
704 block_at(&api, MockBlockId::MainBranch(finalized)).await,
705 SubscriptionType::BestBlocks,
706 )
707 .await;
708 assert_eq!(
709 provider.latest_block().await.block_hash(),
710 MockBlockId::MainBranch(finalized).hash(),
711 "a lower block is accepted once the latest block is no longer canonical"
712 );
713 assert_eq!(
714 chain_heads.block_hash_lookup_count.load(Ordering::SeqCst),
715 2,
716 "one block hash lookup for each accepted lower block"
717 );
718
719 chain_heads.import_best(&provider, &api, MockBlockId::MainBranch(best)).await;
721 assert_eq!(
722 provider.latest_block().await.block_hash(),
723 MockBlockId::MainBranch(best).hash(),
724 "the chain's best block becomes the latest block"
725 );
726 assert_eq!(
727 chain_heads.block_hash_lookup_count.load(Ordering::SeqCst),
728 2,
729 "no block hash lookup for the chain's best block"
730 );
731
732 let head = provider
733 .update_latest(
734 block_at(&api, MockBlockId::MainBranch(best - 1)).await,
735 SubscriptionType::BestBlocks,
736 )
737 .await;
738 assert_eq!(
739 provider.latest_block().await.block_hash(),
740 MockBlockId::MainBranch(best).hash(),
741 "a lower block is ignored while the latest block is still canonical"
742 );
743 assert!(
744 head.is_none(),
745 "a lower block does not change a latest block that is still canonical"
746 );
747 assert_eq!(
748 chain_heads.block_hash_lookup_count.load(Ordering::SeqCst),
749 3,
750 "one more block hash lookup to ignore an old block"
751 );
752
753 provider
754 .update_latest(
755 block_at(&api, MockBlockId::SideBranch(finalized - 1)).await,
756 SubscriptionType::BestBlocks,
757 )
758 .await;
759 assert_eq!(
760 provider.latest_block().await.block_hash(),
761 MockBlockId::MainBranch(best).hash(),
762 "a block below the finalized block is ignored"
763 );
764 assert_eq!(
765 chain_heads.block_hash_lookup_count.load(Ordering::SeqCst),
766 3,
767 "no block hash lookup for a block below the finalized block"
768 );
769
770 let head = provider
771 .update_latest(
772 block_at(&api, MockBlockId::SideBranch(finalized)).await,
773 SubscriptionType::BestBlocks,
774 )
775 .await;
776 assert_eq!(
777 provider.latest_block().await.block_hash(),
778 MockBlockId::MainBranch(best).hash(),
779 "a block from another branch at the finalized block's number is ignored"
780 );
781 assert!(
782 head.is_none(),
783 "a block that contradicts the finalized block does not change the latest block"
784 );
785 assert_eq!(
786 chain_heads.block_hash_lookup_count.load(Ordering::SeqCst),
787 3,
788 "no block hash lookup for a block conflicting with the finalized block"
789 );
790
791 provider
792 .update_latest(
793 block_at(&api, MockBlockId::SideBranch(best)).await,
794 SubscriptionType::BestBlocks,
795 )
796 .await;
797 assert_eq!(
798 provider.latest_block().await.block_hash(),
799 MockBlockId::MainBranch(best).hash(),
800 "a same-number block from another branch is ignored while the latest block is still \
801 canonical"
802 );
803 assert_eq!(
804 chain_heads.block_hash_lookup_count.load(Ordering::SeqCst),
805 4,
806 "one more block hash lookup to ignore a same-number block"
807 );
808 }
809
810 #[tokio::test]
811 async fn failed_block_hash_lookups_keep_the_stored_best_block() {
812 let chain_heads = MockChainHeads::default();
813 let (provider, api) = chain_heads.provider().await;
814 let best = chain_heads.best_block.lock().unwrap().number();
815 let finalized = chain_heads.finalized_block.number();
816
817 chain_heads.fail_block_hash_lookups.store(true, Ordering::SeqCst);
818 provider
819 .update_latest(
820 block_at(&api, MockBlockId::MainBranch(finalized)).await,
821 SubscriptionType::BestBlocks,
822 )
823 .await;
824 assert_eq!(
825 provider.latest_block().await.block_hash(),
826 MockBlockId::MainBranch(best).hash(),
827 "an old block is ignored while the block hash lookup fails"
828 );
829 assert_eq!(
830 chain_heads.block_hash_lookup_count.load(Ordering::SeqCst),
831 1,
832 "one block hash lookup for the ignored old block"
833 );
834
835 chain_heads.fail_block_hash_lookups.store(false, Ordering::SeqCst);
836 chain_heads
837 .import_best(&provider, &api, MockBlockId::SideBranch(best - 1))
838 .await;
839 assert_eq!(
840 provider.latest_block().await.block_hash(),
841 MockBlockId::SideBranch(best - 1).hash(),
842 "a lower block is accepted once the block hash lookup succeeds"
843 );
844 assert_eq!(
845 chain_heads.block_hash_lookup_count.load(Ordering::SeqCst),
846 2,
847 "one more block hash lookup for the accepted lower block"
848 );
849 }
850
851 #[tokio::test]
852 async fn finalized_blocks_never_move_backwards() {
853 let chain_heads = MockChainHeads::default();
854 let (provider, api) = chain_heads.provider().await;
855 let best = chain_heads.best_block.lock().unwrap().number();
856 let finalized = chain_heads.finalized_block.number();
857
858 provider
859 .update_latest(
860 block_at(&api, MockBlockId::MainBranch(finalized - 1)).await,
861 SubscriptionType::FinalizedBlocks,
862 )
863 .await;
864 assert_eq!(
865 provider.latest_finalized_block().await.block_hash(),
866 MockBlockId::MainBranch(finalized).hash(),
867 "a lower finalized block is ignored"
868 );
869
870 let head = provider
871 .update_latest(
872 block_at(&api, MockBlockId::MainBranch(finalized + 1)).await,
873 SubscriptionType::FinalizedBlocks,
874 )
875 .await;
876 assert_eq!(
877 provider.latest_finalized_block().await.block_hash(),
878 MockBlockId::MainBranch(finalized + 1).hash(),
879 "a higher finalized block becomes the latest finalized block"
880 );
881 assert_eq!(
882 provider.latest_block().await.block_hash(),
883 MockBlockId::MainBranch(best).hash(),
884 "the latest block stays put while it is ahead of the finalized block"
885 );
886 assert!(head.is_none(), "a finalized block below the latest block does not change it");
887
888 provider
889 .update_latest(
890 block_at(&api, MockBlockId::MainBranch(best + 1)).await,
891 SubscriptionType::FinalizedBlocks,
892 )
893 .await;
894 assert_eq!(
895 provider.latest_finalized_block().await.block_hash(),
896 MockBlockId::MainBranch(best + 1).hash(),
897 "a higher finalized block becomes the latest finalized block"
898 );
899 assert_eq!(
900 provider.latest_block().await.block_hash(),
901 MockBlockId::MainBranch(best + 1).hash(),
902 "the latest block follows a finalized block ahead of it"
903 );
904
905 chain_heads
906 .import_best(&provider, &api, MockBlockId::SideBranch(best + 2))
907 .await;
908 provider
909 .update_latest(
910 block_at(&api, MockBlockId::MainBranch(best + 2)).await,
911 SubscriptionType::FinalizedBlocks,
912 )
913 .await;
914 assert_eq!(
915 provider.latest_block().await.block_hash(),
916 MockBlockId::MainBranch(best + 2).hash(),
917 "a finalized block replaces a same-numbered latest block from another branch"
918 );
919
920 let latest = provider.latest_block().await;
921 provider
922 .update_latest(
923 block_at(&api, MockBlockId::MainBranch(best + 2)).await,
924 SubscriptionType::FinalizedBlocks,
925 )
926 .await;
927 assert!(
928 Arc::ptr_eq(&latest, &provider.latest_block().await),
929 "the latest block is unchanged when is already the finalized block"
930 );
931 }
932
933 #[tokio::test]
934 async fn stale_finalized_blocks_still_pull_up_the_latest_block() {
935 let inverted_best_block_number = MockChainHeads::default().finalized_block.number() - 2;
936 let chain_heads = MockChainHeads {
937 best_block: Arc::new(Mutex::new(MockBlockId::MainBranch(inverted_best_block_number))),
938 ..MockChainHeads::default()
939 };
940 let (provider, api) = chain_heads.provider().await;
941 let finalized = chain_heads.finalized_block.number();
942
943 assert!(
944 provider.latest_finalized_block().await.block_number() >
945 provider.latest_block().await.block_number(),
946 "the latest block starts below the latest finalized block"
947 );
948
949 let head = provider
950 .update_latest(
951 block_at(&api, MockBlockId::MainBranch(finalized - 1)).await,
952 SubscriptionType::FinalizedBlocks,
953 )
954 .await;
955 assert_eq!(
956 provider.latest_finalized_block().await.block_hash(),
957 MockBlockId::MainBranch(finalized).hash(),
958 "a lower finalized block is ignored"
959 );
960 assert_eq!(
961 provider.latest_block().await.block_hash(),
962 MockBlockId::MainBranch(finalized).hash(),
963 "the latest block is pulled up to the latest finalized block"
964 );
965 assert_eq!(
966 head.map(|head| head.block_hash()),
967 Some(MockBlockId::MainBranch(finalized).hash()),
968 "the latest finalized block, not the lower one, is returned as the new latest block"
969 );
970 }
971
972 #[tokio::test]
973 async fn block_by_number_uses_the_cache_before_querying_the_chain() {
974 let chain_heads = MockChainHeads::default();
975 let (provider, _api) = chain_heads.provider().await;
976 let best = chain_heads.best_block.lock().unwrap().number();
977 let finalized = chain_heads.finalized_block.number();
978
979 let block = provider.block_by_number(best).await.unwrap().unwrap();
980 assert_eq!(
981 block.block_hash(),
982 MockBlockId::MainBranch(best).hash(),
983 "the latest block is returned for its number"
984 );
985 let block = provider.block_by_number(finalized).await.unwrap().unwrap();
986 assert_eq!(
987 block.block_hash(),
988 MockBlockId::MainBranch(finalized).hash(),
989 "the latest finalized block is returned for its number"
990 );
991 assert_eq!(
992 chain_heads.block_hash_lookup_count.load(Ordering::SeqCst),
993 0,
994 "no block hash lookup for the cached blocks"
995 );
996
997 let block = provider.block_by_number(best - 1).await.unwrap().unwrap();
998 assert_eq!(
999 block.block_hash(),
1000 MockBlockId::MainBranch(best - 1).hash(),
1001 "an uncached block is fetched from the chain"
1002 );
1003 assert_eq!(
1004 chain_heads.block_hash_lookup_count.load(Ordering::SeqCst),
1005 1,
1006 "one block hash lookup for an uncached block number"
1007 );
1008
1009 assert!(
1010 provider.block_by_number(best + 1).await.unwrap().is_none(),
1011 "no block exists above the chain's best block"
1012 );
1013 assert_eq!(
1014 chain_heads.block_hash_lookup_count.load(Ordering::SeqCst),
1015 2,
1016 "one more block hash lookup for a number above the chain's best block"
1017 );
1018
1019 chain_heads.fail_block_hash_lookups.store(true, Ordering::SeqCst);
1020 assert!(
1021 provider.block_by_number(best - 1).await.is_err(),
1022 "a block hash lookup failure is returned as an error"
1023 );
1024 }
1025
1026 #[tokio::test]
1027 async fn block_by_hash_uses_the_cache_before_querying_the_chain() {
1028 let chain_heads = MockChainHeads::default();
1029 let (provider, _api) = chain_heads.provider().await;
1030 let best = chain_heads.best_block.lock().unwrap().number();
1031 let finalized = chain_heads.finalized_block.number();
1032
1033 let block = provider
1034 .block_by_hash(&MockBlockId::MainBranch(best).hash())
1035 .await
1036 .unwrap()
1037 .unwrap();
1038 assert_eq!(block.block_number(), best, "the latest block is returned for its hash");
1039
1040 let block = provider
1041 .block_by_hash(&MockBlockId::MainBranch(finalized).hash())
1042 .await
1043 .unwrap()
1044 .unwrap();
1045 assert_eq!(
1046 block.block_number(),
1047 finalized,
1048 "the latest finalized block is returned for its hash"
1049 );
1050
1051 let block = provider
1052 .block_by_hash(&MockBlockId::SideBranch(best).hash())
1053 .await
1054 .unwrap()
1055 .unwrap();
1056 assert_eq!(
1057 block.block_hash(),
1058 MockBlockId::SideBranch(best).hash(),
1059 "an uncached block is fetched from the chain"
1060 );
1061 assert_eq!(block.block_number(), best, "the fetched block's number comes from its header");
1062
1063 assert!(
1064 provider.block_by_hash(&H256::zero()).await.unwrap().is_none(),
1065 "no block exists for an unknown hash"
1066 );
1067
1068 assert_eq!(
1069 chain_heads.block_hash_lookup_count.load(Ordering::SeqCst),
1070 0,
1071 "fetching by hash never asks the chain for a block hash"
1072 );
1073 }
1074}