sidechain_mc_hash/lib.rs
1//! Crate implementing a mechanism for Partner Chain blocks to reference a stable Cardano main chain block.
2//!
3//! # Purpose of this crate
4//!
5//! This crate provides a base for all other Partner Chain Toolkit's features that require Cardano observability.
6//! Whenever some part of Cardano state is observed and included in a Partner Chain's own state, all Partner Chain
7//! nodes need to agree on what data was eligible for inclusion when a PC block was produced. This is
8//! best achieved by first agreeing on a stable Cardano block which then serves as the upper boundary of all observed
9//! Cardano data. This _main chain reference block_ is selected by the block producing node and must be accepted by
10//! other nodes as being stable at the time of block production.
11//!
12//! This crate implements the [InherentDataProvider] and [InherentDigest] mechanism for selecting a reference main
13//! chain block as part of inherent data creation step and saving it as a _main chain reference hash_ in the block
14//! header.
15//!
16//! # Prerequisites
17//!
18//! This feature uses the [InherentDigest] mechanism from [sp_partner_chains_consensus_aura] crate for storing inherent
19//! data in the block header. Your node must use the [PartnerChainsProposer] defined by that crate for this feature to work.
20//!
21//! # Adding to the node
22//!
23//! To add the feature to your Partner Chain node, follow the instructions below.
24//! Refer to the demo node implementation for reference.
25//!
26//! ## Data source
27//!
28//! A data source implementing the [McHashDataSource] interface trait should be added to your node. A Db-Sync
29//! implementation can be found in the `partner-chains-db-sync-data-sources` crate. Refer to its documentation on
30//! how to configure and use it.
31//!
32//! ## Adding the inherent data provider
33//!
34//! [McHashInherentDataProvider] should be added to your inherent data provider stack for both block proposal and
35//! verification, using dedicated constructor for each.
36//!
37//! The main chain reference hash provided by [McHashInherentDataProvider] is meant to be used by other inherent data
38//! providers from the Partner Chains Toolkit which require Cardano observability. As such, it should be created first in
39//! your node's IDP stack, so that [McHashInherentDataProvider::mc_hash] and [McHashInherentDataProvider::previous_mc_hash]
40//! can be used to pass the reference hashes to other IDPs.
41//!
42//! As an example, creation for proposal would look like the following:
43//! ```rust
44//! # use std::sync::Arc;
45//! # use sidechain_mc_hash::*;
46//! use sp_consensus_slots::{Slot, SlotDuration};
47//! use sp_runtime::traits::Block as BlockT;
48//!
49//! struct ProposalCIDP {
50//! // the data source should be created as part of your node's setup
51//! mc_hash_data_source: Arc<dyn McHashDataSource + Send + Sync>,
52//! // slot duration should either be a part of your node's configuration or be
53//! // retrieved using `parent_hash` and your consensus mechanism's runtime API
54//! slot_duration: SlotDuration,
55//! }
56//!
57//! #[async_trait::async_trait]
58//! impl<Block> sp_inherents::CreateInherentDataProviders<Block, ()> for ProposalCIDP
59//! where
60//! Block: BlockT + Send + Sync
61//! {
62//! type InherentDataProviders = McHashInherentDataProvider;
63//!
64//! async fn create_inherent_data_providers(
65//! &self,
66//! parent_hash: <Block as BlockT>::Hash,
67//! _extra_args: (),
68//! ) -> Result<Self::InherentDataProviders, Box<dyn std::error::Error + Send + Sync>> {
69//! let parent_header: <Block as BlockT>::Header = unimplemented!("Retrieved from block store");
70//! let slot: Slot = unimplemented!("Provided by the consensus IDP");
71//!
72//! let mc_hash = McHashInherentDataProvider::new_proposal(
73//! parent_header,
74//! self.mc_hash_data_source.as_ref(),
75//! slot,
76//! self.slot_duration,
77//! ).await?;
78//!
79//! // Other providers can now use mc_hash.mc_hash() and mc_hash.previous_mc_hash()
80//!
81//! Ok((mc_hash /* other inherent data providers */))
82//! }
83//! }
84//! ```
85//!
86//! For block validation, creation of the IDP would look like this:
87//! ```rust
88//! # use std::sync::Arc;
89//! # use sidechain_mc_hash::*;
90//! # use sp_consensus_slots::{Slot, SlotDuration};
91//! # use sp_runtime::traits::Block as BlockT;
92//! use sidechain_domain::McBlockHash;
93//!
94//! struct VerificationIDP {
95//! // the data source should be created as part of your node's setup
96//! mc_hash_data_source: Arc<dyn McHashDataSource + Send + Sync>,
97//! // slot duration should either be a part of your node's configuration or be
98//! // retrieved using `parent_hash` and your consensus mechanism's runtime API
99//! slot_duration: SlotDuration,
100//! }
101//!
102//! #[async_trait::async_trait]
103//! impl<Block> sp_inherents::CreateInherentDataProviders<Block, (Slot, McBlockHash)> for VerificationIDP
104//! where
105//! Block: BlockT + Send + Sync
106//! {
107//! type InherentDataProviders = McHashInherentDataProvider;
108//!
109//! async fn create_inherent_data_providers(
110//! &self,
111//! parent_hash: <Block as BlockT>::Hash,
112//! (block_slot_from_header, mc_hash_from_header): (Slot, McBlockHash),
113//! ) -> Result<Self::InherentDataProviders, Box<dyn std::error::Error + Send + Sync>> {
114//! let parent_header: <Block as BlockT>::Header = unimplemented!("Retrieved from block store");
115//! let slot: Slot = unimplemented!("Provided by the consensus IDP");
116//! let parent_slot: Option<Slot> = unimplemented!("Read from previous block using runtime API");
117//!
118//! let mc_hash = McHashInherentDataProvider::new_verification(
119//! parent_header,
120//! parent_slot,
121//! block_slot_from_header,
122//! mc_hash_from_header,
123//! self.slot_duration,
124//! self.mc_hash_data_source.as_ref(),
125//! )
126//! .await?;
127//!
128//! // Other providers can now use mc_hash.mc_hash() and mc_hash.previous_mc_hash()
129//!
130//! Ok((mc_hash /* other inherent data providers */))
131//! }
132//! }
133//! ```
134//!
135//! Note that for verification the implementation now accepts additional arguments of types [Slot] and [McBlockHash]
136//! which are provided by the import queue based on the header. In this case, the [McBlockHash] value comes from the
137//! [InherentDigest] defined in next section.
138//!
139//! ## Import queue configuration
140//!
141//! To be able to save and retrieve inherent data in the block header, your node should use the [PartnerChainsProposer]
142//! instead of the stock proposer provided by Substrate. Refer to its documentation for more information.
143//!
144//! For this feature to work, the only modification needed is to set [McHashInherentDigest] as the inherent digest
145//! type of the proposer. This will cause it to save the inherent data provided by [McHashInherentDataProvider] to be
146//! also saved in the block header and available for inspection and verification. Assuming your node uses
147//! [PartnerChainsProposerFactory], this should look like the following:
148//!
149//! ```rust
150//! use sidechain_mc_hash::McHashInherentDigest;
151//! use sp_consensus::Environment;
152//! use sp_partner_chains_consensus_aura::block_proposal::PartnerChainsProposerFactory;
153//! use sp_runtime::traits::Block as BlockT;
154//!
155//! fn new_full<Block: BlockT, ProposerFactory: Environment<Block>>(
156//! base_proposer_factory: ProposerFactory,
157//! ) {
158//! // .. other node setup logic
159//! let proposer_factory: PartnerChainsProposerFactory<
160//! Block,
161//! ProposerFactory,
162//! McHashInherentDigest,
163//! > = PartnerChainsProposerFactory::new(base_proposer_factory);
164//! // ..
165//! }
166//! ```
167//!
168//! [PartnerChainsProposer]: sp_partner_chains_consensus_aura::block_proposal::PartnerChainsProposer
169//! [PartnerChainsProposerFactory]: sp_partner_chains_consensus_aura::block_proposal::PartnerChainsProposerFactory
170
171#![warn(missing_docs)]
172use crate::McHashInherentError::StableBlockNotFound;
173use async_trait::async_trait;
174use sidechain_domain::{byte_string::ByteString, *};
175use sp_blockchain::HeaderBackend;
176use sp_consensus_slots::{Slot, SlotDuration};
177use sp_inherents::{InherentData, InherentDataProvider, InherentIdentifier};
178use sp_partner_chains_consensus_aura::inherent_digest::InherentDigest;
179use sp_runtime::{
180 DigestItem,
181 traits::{Block as BlockT, Header as HeaderT, Zero},
182};
183use sp_timestamp::Timestamp;
184use std::{error::Error, ops::Deref};
185
186#[cfg(test)]
187mod test;
188
189/// Inherent identifier under which the main chain block reference is provided
190///
191/// Data under this ID is not used by any pallet and is instead put in the block
192/// header under [MC_HASH_DIGEST_ID] using [InherentDigest].
193pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"scmchash";
194
195/// Digest ID of the main chain reference block hash
196///
197/// Inherent data provided for [INHERENT_IDENTIFIER] is saved with this digest ID
198/// in the block header using [InherentDigest].
199pub const MC_HASH_DIGEST_ID: [u8; 4] = *b"mcsh";
200
201/// Inherent data provider that provides the hash of the latest stable Cardano block
202/// (main chain reference block) observed by the block producer to be included in the
203/// header of the currently produced PC block.
204///
205/// This IDP also exposes further information about the currently referenced Cardano
206/// block and the block referenced by the parent block of the current one, for use by
207/// other inherent data providers.
208///
209/// ## Creation
210///
211/// This inherent data provider has two different constructors that should be used
212/// depending on whether it is being used for proposing or verifying a block:
213/// - [new_proposal][McHashInherentDataProvider::new_proposal] which selects the main
214/// chain reference block
215/// - [new_verification][McHashInherentDataProvider::new_verification] which verifies
216/// the main chain reference in the block header
217#[derive(Debug)]
218pub struct McHashInherentDataProvider {
219 mc_block: MainchainBlock,
220 previous_mc_block: Option<MainchainBlock>,
221}
222
223/// Error type returned by constructors of [McHashInherentDataProvider]
224#[derive(Debug, thiserror::Error)]
225pub enum McHashInherentError {
226 /// Signals that the data source returned an error
227 #[error("{0}")]
228 DataSourceError(Box<dyn Error + Send + Sync>),
229 /// Signals that no stable Cardano block was found within timestamp constraints
230 #[error(
231 "Stable block not found at {0}. It means that the main chain wasn't producing blocks for a long time."
232 )]
233 StableBlockNotFound(Timestamp),
234 /// Signals that a slot beyond supported range of [u64] was passed
235 #[error("Slot represents a timestamp bigger than of u64::MAX")]
236 SlotTooBig,
237 /// Signals that a Cardano block referenced by a main chain reference hash could not be found
238 #[error(
239 "Main chain state {0} referenced in imported block at slot {1} with timestamp {2} not found"
240 )]
241 McStateReferenceInvalid(McBlockHash, Slot, Timestamp),
242 /// Signals that a main chain reference hash points to a Cardano block earlier than the one referenced
243 /// by the previous Partner Chain block
244 #[error(
245 "Main chain state {0} referenced in imported block at slot {1} corresponds to main chain block number which is lower than its parent's {2}<{3}"
246 )]
247 McStateReferenceRegressed(McBlockHash, Slot, McBlockNumber, McBlockNumber),
248 /// Signals that a main chain reference hash is either missing from the block diget or can not be decoded
249 #[error("Failed to retrieve MC hash from digest: {0}")]
250 DigestError(String),
251 /// Signals that block details could not be retrieved for a stable Cardano block
252 #[error("Failed to retrieve MC Block that was verified as stable by its hash")]
253 StableBlockNotFoundByHash(McBlockHash),
254}
255
256impl From<MainchainBlock> for McHashInherentDataProvider {
257 fn from(mc_block: MainchainBlock) -> Self {
258 Self { mc_block, previous_mc_block: None }
259 }
260}
261
262impl Deref for McHashInherentDataProvider {
263 type Target = MainchainBlock;
264
265 fn deref(&self) -> &Self::Target {
266 &self.mc_block
267 }
268}
269
270/// Data source API used by [McHashInherentDataProvider]
271#[async_trait]
272pub trait McHashDataSource {
273 /// Query for the currently latest stable block with timestamp within the `allowable_range(reference_timestamp) = [reference_timestamp - seconds(max_slot_boundary), reference_timestamp - seconds(slot_boundary)]`
274 /// where `max_slot_boundary` is `3 * security_parameter/active_slot_coeff` (`3k/f`) and `min_slot_boundary` is `security_parameter/active_slot_coeff` (`k/f`).
275 /// # Arguments
276 /// * `reference_timestamp` - restricts the timestamps of MC blocks
277 ///
278 /// # Returns
279 /// * `Some(block)` - the latest stable block, with timestamp in the allowable range
280 /// * `None` - none of the blocks is stable, and with timestamp valid in according to `reference_timestamp`
281 async fn get_latest_stable_block_for(
282 &self,
283 reference_timestamp: Timestamp,
284 ) -> Result<Option<MainchainBlock>, Box<dyn std::error::Error + Send + Sync>>;
285
286 /// Find block by hash, filtered by block timestamp being in `allowable_range(reference_timestamp)`
287 /// # Arguments
288 /// * `hash` - the hash of the block
289 /// * `reference_timestamp` - restricts the timestamp of the MC block
290 ///
291 /// # Returns
292 /// * `Some(block)` - the block with given hash, with timestamp in the allowable range
293 /// * `None` - no stable block with given hash and timestamp in the allowable range exists
294 async fn get_stable_block_for(
295 &self,
296 hash: McBlockHash,
297 reference_timestamp: Timestamp,
298 ) -> Result<Option<MainchainBlock>, Box<dyn std::error::Error + Send + Sync>>;
299
300 /// Find block by hash.
301 /// # Arguments
302 /// * `hash` - the hash of the block
303 ///
304 /// # Returns
305 /// * `Some(block)` - the block with given hash
306 /// * `None` - no block with the given hash was found
307 async fn get_block_by_hash(
308 &self,
309 hash: McBlockHash,
310 ) -> Result<Option<MainchainBlock>, Box<dyn std::error::Error + Send + Sync>>;
311}
312
313impl McHashInherentDataProvider {
314 /// Creates a new [McHashInherentDataProvider] for proposing a new Partner Chain block by querying the
315 /// current state of Cardano and returning an instance referencing the latest stable block there.
316 ///
317 /// # Arguments
318 /// - `parent_header`: header of the parent of the block being produced
319 /// - `data_source`: data source implementing [McHashDataSource]
320 /// - `slot`: current Partner Chain slot
321 /// - `slot_duration`: duration of the Partner Chain slot
322 ///
323 /// The referenced Cardano block is guaranteed to be later or equal to the one referenced by the parent block
324 /// and within a bounded time distance from `slot`.
325 pub async fn new_proposal<Header>(
326 parent_header: Header,
327 data_source: &(dyn McHashDataSource + Send + Sync),
328 slot: Slot,
329 slot_duration: SlotDuration,
330 ) -> Result<Self, McHashInherentError>
331 where
332 Header: HeaderT,
333 {
334 let slot_start_timestamp =
335 slot.timestamp(slot_duration).ok_or(McHashInherentError::SlotTooBig)?;
336 let mc_block = data_source
337 .get_latest_stable_block_for(slot_start_timestamp)
338 .await
339 .map_err(McHashInherentError::DataSourceError)?
340 .ok_or(StableBlockNotFound(slot_start_timestamp))?;
341
342 match McHashInherentDigest::value_from_digest(&parent_header.digest().logs).ok() {
343 // If parent block references some MC state, it is illegal to propose older state
344 Some(parent_mc_hash) => {
345 let parent_stable_mc_block = data_source
346 .get_block_by_hash(parent_mc_hash.clone())
347 .await
348 .map_err(McHashInherentError::DataSourceError)?
349 .ok_or(McHashInherentError::StableBlockNotFoundByHash(parent_mc_hash))?;
350
351 if mc_block.number >= parent_stable_mc_block.number {
352 Ok(Self { mc_block, previous_mc_block: Some(parent_stable_mc_block) })
353 } else {
354 Ok(Self {
355 mc_block: parent_stable_mc_block.clone(),
356 previous_mc_block: Some(parent_stable_mc_block),
357 })
358 }
359 },
360 None => Ok(Self { mc_block, previous_mc_block: None }),
361 }
362 }
363
364 /// Verifies a Cardano reference hash and creates a new [McHashInherentDataProvider] for an imported Partner Chain block.
365 ///
366 /// # Arguments
367 /// - `parent_header`: header of the parent of the block being produced or validated
368 /// - `parent_slot`: slot of the parent block. [None] for genesis
369 /// - `verified_block_slot`: Partner Chain slot of the block being verified
370 /// - `mc_state_reference_hash`: Cardano block hash referenced by the block being verified
371 /// - `slot_duration`: duration of the Partner Chain slot
372 /// - `block_source`: data source implementing [McHashDataSource]
373 ///
374 /// # Returns
375 /// This function will return an error if `mc_state_reference_hash` is not found or is before the block referenced by
376 /// the parent of the block being verified.
377 ///
378 /// Otherwise, the returned [McHashInherentDataProvider] instance will contain block data for `mc_state_reference_hash`.
379 pub async fn new_verification<Header>(
380 parent_header: Header,
381 parent_slot: Option<Slot>,
382 verified_block_slot: Slot,
383 mc_state_reference_hash: McBlockHash,
384 slot_duration: SlotDuration,
385 block_source: &(dyn McHashDataSource + Send + Sync),
386 ) -> Result<Self, McHashInherentError>
387 where
388 Header: HeaderT,
389 {
390 let mc_state_reference_block = get_mc_state_reference(
391 verified_block_slot,
392 mc_state_reference_hash.clone(),
393 slot_duration,
394 block_source,
395 )
396 .await?;
397
398 let Some(parent_slot) = parent_slot else {
399 // genesis block doesn't contain MC reference
400 return Ok(Self::from(mc_state_reference_block));
401 };
402
403 let parent_mc_hash = McHashInherentDigest::value_from_digest(&parent_header.digest().logs)
404 .map_err(|err| McHashInherentError::DigestError(err.to_string()))?;
405 let parent_mc_state_reference_block =
406 get_mc_state_reference(parent_slot, parent_mc_hash, slot_duration, block_source)
407 .await?;
408
409 if mc_state_reference_block.number < parent_mc_state_reference_block.number {
410 Err(McHashInherentError::McStateReferenceRegressed(
411 mc_state_reference_hash,
412 verified_block_slot,
413 mc_state_reference_block.number,
414 parent_mc_state_reference_block.number,
415 ))
416 } else {
417 Ok(Self {
418 mc_block: mc_state_reference_block,
419 previous_mc_block: Some(parent_mc_state_reference_block),
420 })
421 }
422 }
423
424 /// Returns the Cardano epoch containing the reference block
425 pub fn mc_epoch(&self) -> McEpochNumber {
426 self.mc_block.epoch
427 }
428
429 /// Returns the reference block's number
430 pub fn mc_block(&self) -> McBlockNumber {
431 self.mc_block.number
432 }
433
434 /// Returns the reference block's hash
435 pub fn mc_hash(&self) -> McBlockHash {
436 self.mc_block.hash.clone()
437 }
438
439 /// Returns the previous reference block's hash
440 pub fn previous_mc_hash(&self) -> Option<McBlockHash> {
441 self.previous_mc_block.as_ref().map(|block| block.hash.clone())
442 }
443}
444
445async fn get_mc_state_reference(
446 verified_block_slot: Slot,
447 verified_block_mc_hash: McBlockHash,
448 slot_duration: SlotDuration,
449 data_source: &(dyn McHashDataSource + Send + Sync),
450) -> Result<MainchainBlock, McHashInherentError> {
451 let timestamp = verified_block_slot
452 .timestamp(slot_duration)
453 .ok_or(McHashInherentError::SlotTooBig)?;
454 data_source
455 .get_stable_block_for(verified_block_mc_hash.clone(), timestamp)
456 .await
457 .map_err(McHashInherentError::DataSourceError)?
458 .ok_or(McHashInherentError::McStateReferenceInvalid(
459 verified_block_mc_hash,
460 verified_block_slot,
461 timestamp,
462 ))
463}
464
465#[async_trait::async_trait]
466impl InherentDataProvider for McHashInherentDataProvider {
467 async fn provide_inherent_data(
468 &self,
469 inherent_data: &mut InherentData,
470 ) -> Result<(), sp_inherents::Error> {
471 inherent_data.put_data(INHERENT_IDENTIFIER, &self.mc_block.hash)
472 }
473
474 async fn try_handle_error(
475 &self,
476 identifier: &InherentIdentifier,
477 _error: &[u8],
478 ) -> Option<Result<(), sp_inherents::Error>> {
479 if *identifier == INHERENT_IDENTIFIER {
480 panic!("BUG: {:?} inherent shouldn't return any errors", INHERENT_IDENTIFIER)
481 } else {
482 None
483 }
484 }
485}
486
487/// [InherentDigest] implementation for the main chain reference hash
488pub struct McHashInherentDigest;
489
490impl McHashInherentDigest {
491 /// Creates a [DigestItem] containing the given main chain reference hash
492 pub fn from_mc_block_hash(mc_block_hash: McBlockHash) -> Vec<DigestItem> {
493 vec![DigestItem::PreRuntime(MC_HASH_DIGEST_ID, mc_block_hash.0.to_vec())]
494 }
495}
496
497impl InherentDigest for McHashInherentDigest {
498 type Value = McBlockHash;
499
500 fn from_inherent_data(
501 inherent_data: &InherentData,
502 ) -> Result<Vec<sp_runtime::DigestItem>, Box<dyn Error + Send + Sync>> {
503 let mc_hash = inherent_data
504 .get_data::<McBlockHash>(&INHERENT_IDENTIFIER)
505 .map_err(|err| format!("Failed to retrieve main chain hash from inherent data: {err}"))?
506 .ok_or("Main chain hash missing from inherent data".to_string())?;
507 Ok(Self::from_mc_block_hash(mc_hash))
508 }
509
510 fn value_from_digest(
511 digest: &[DigestItem],
512 ) -> Result<Self::Value, Box<dyn Error + Send + Sync>> {
513 for item in digest {
514 if let DigestItem::PreRuntime(id, data) = item {
515 if *id == MC_HASH_DIGEST_ID {
516 let data = data.clone().try_into().map_err(|_| {
517 format!("Invalid MC hash referenced by block author in digest: {:?}\nMC hash must be exactly 32 bytes long.", ByteString(data.to_vec()))
518 })?;
519 return Ok(McBlockHash(data));
520 }
521 }
522 }
523 Err("Main chain block hash missing from digest".into())
524 }
525}
526
527#[allow(missing_docs)]
528pub fn get_inherent_digest_value_for_block<ID: InherentDigest, Block: BlockT, C>(
529 client: &C,
530 block_hash: Block::Hash,
531) -> Result<Option<ID::Value>, Box<dyn Error + Send + Sync>>
532where
533 C: HeaderBackend<Block>,
534 Block::Hash: std::fmt::Debug,
535{
536 let header = (client.header(block_hash))
537 .map_err(|err| format!("Failed to retrieve header for hash {block_hash:?}: {err:?}"))?
538 .ok_or(format!("Header for hash {block_hash:?} does not exist"))?;
539
540 if header.number().is_zero() {
541 Ok(None)
542 } else {
543 let value = ID::value_from_digest(&header.digest().logs)
544 .map_err(|err| format!("Failed to retrieve inherent digest from header: {err:?}"))?;
545 Ok(Some(value))
546 }
547}
548
549#[allow(missing_docs)]
550pub fn get_mc_hash_for_block<Block: BlockT, C>(
551 client: &C,
552 block_hash: Block::Hash,
553) -> Result<Option<McBlockHash>, Box<dyn Error + Send + Sync>>
554where
555 C: HeaderBackend<Block>,
556 Block::Hash: std::fmt::Debug,
557{
558 get_inherent_digest_value_for_block::<McHashInherentDigest, Block, C>(client, block_hash)
559}
560
561/// Mock implementations of components in this crate for use in tests
562#[cfg(any(feature = "mock", test))]
563pub mod mock {
564 use super::*;
565 use derive_new::new;
566
567 /// Mock implementation of [McHashInherentDataProvider] which always returns the same block
568 pub struct MockMcHashInherentDataProvider {
569 /// Main chain reference block to be returned by this mock
570 pub mc_hash: McBlockHash,
571 }
572
573 #[async_trait::async_trait]
574 impl sp_inherents::InherentDataProvider for MockMcHashInherentDataProvider {
575 async fn provide_inherent_data(
576 &self,
577 inherent_data: &mut InherentData,
578 ) -> Result<(), sp_inherents::Error> {
579 inherent_data.put_data(INHERENT_IDENTIFIER, &self.mc_hash)
580 }
581
582 async fn try_handle_error(
583 &self,
584 _identifier: &InherentIdentifier,
585 _error: &[u8],
586 ) -> Option<Result<(), sp_inherents::Error>> {
587 None
588 }
589 }
590
591 /// Mock implementation of [McHashDataSource]
592 #[derive(new, Clone)]
593 pub struct MockMcHashDataSource {
594 /// Stable blocks ordered from oldest to newest
595 pub stable_blocks: Vec<MainchainBlock>,
596 /// Unstable blocks ordered from oldest to newest
597 pub unstable_blocks: Vec<MainchainBlock>,
598 }
599
600 impl From<Vec<MainchainBlock>> for MockMcHashDataSource {
601 fn from(stable_blocks: Vec<MainchainBlock>) -> Self {
602 Self { stable_blocks, unstable_blocks: vec![] }
603 }
604 }
605
606 #[async_trait]
607 impl McHashDataSource for MockMcHashDataSource {
608 async fn get_latest_stable_block_for(
609 &self,
610 _reference_timestamp: Timestamp,
611 ) -> Result<Option<MainchainBlock>, Box<dyn std::error::Error + Send + Sync>> {
612 Ok(self.stable_blocks.last().cloned())
613 }
614
615 async fn get_stable_block_for(
616 &self,
617 hash: McBlockHash,
618 _reference_timestamp: Timestamp,
619 ) -> Result<Option<MainchainBlock>, Box<dyn std::error::Error + Send + Sync>> {
620 Ok(self.stable_blocks.iter().find(|b| b.hash == hash).cloned())
621 }
622
623 async fn get_block_by_hash(
624 &self,
625 hash: McBlockHash,
626 ) -> Result<Option<MainchainBlock>, Box<dyn std::error::Error + Send + Sync>> {
627 Ok(self
628 .stable_blocks
629 .iter()
630 .find(|b| b.hash == hash)
631 .cloned()
632 .or_else(|| self.unstable_blocks.iter().find(|b| b.hash == hash).cloned()))
633 }
634 }
635}