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