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