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}