1use crate::archiver::SegmentHeadersStore;
15use crate::verifier::VerificationError;
16use crate::{SubspaceLink, aux_schema, slot_worker};
17use futures::StreamExt;
18use futures::channel::mpsc;
19use sc_client_api::BlockBackend;
20use sc_client_api::backend::AuxStore;
21use sc_consensus::StateAction;
22use sc_consensus::block_import::{
23 BlockCheckParams, BlockImport, BlockImportParams, ForkChoiceStrategy, ImportResult,
24};
25use sc_proof_of_time::source::pot_next_slot_input;
26use sc_proof_of_time::verifier::PotVerifier;
27use sp_api::{ApiError, ApiExt, ProvideRuntimeApi};
28use sp_block_builder::BlockBuilder as BlockBuilderApi;
29use sp_blockchain::HeaderBackend;
30use sp_consensus_slots::Slot;
31use sp_consensus_subspace::digests::{
32 SubspaceDigestItems, extract_pre_digest, extract_subspace_digest_items,
33};
34use sp_consensus_subspace::{PotNextSlotInput, SubspaceApi, SubspaceJustification};
35use sp_inherents::{CreateInherentDataProviders, InherentDataProvider};
36use sp_runtime::Justifications;
37use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor, One, Zero};
38use sp_weights::constants::WEIGHT_REF_TIME_PER_MILLIS;
39use std::marker::PhantomData;
40use std::sync::Arc;
41use std::time::Instant;
42use subspace_core_primitives::sectors::SectorId;
43use subspace_core_primitives::segments::{HistorySize, SegmentHeader, SegmentIndex};
44use subspace_core_primitives::solutions::SolutionRange;
45use subspace_core_primitives::{BlockNumber, PublicKey};
46use subspace_proof_of_space::Table;
47use subspace_verification::{PieceCheckParams, VerifySolutionParams, calculate_block_fork_weight};
48use tracing::warn;
49
50const BLOCK_WEIGHT_SWEEP_BATCH: u32 = 10_000;
52
53#[derive(Debug, Clone)]
56pub struct BlockImportingNotification<Block>
57where
58 Block: BlockT,
59{
60 pub block_number: NumberFor<Block>,
62 pub acknowledgement_sender: mpsc::Sender<()>,
65}
66use subspace_verification::Error as VerificationPrimitiveError;
67
68#[derive(Debug, thiserror::Error)]
70pub enum Error<Header: HeaderT> {
71 #[error("Inner block import error: {0}")]
73 InnerBlockImportError(#[from] sp_consensus::Error),
74 #[error("Digest item error: {0}")]
76 DigestItemError(#[from] sp_consensus_subspace::digests::Error),
77 #[error("Parent ({0}) of {1} unavailable. Cannot import")]
79 ParentUnavailable(Header::Hash, Header::Hash),
80 #[error("Genesis block unavailable. Cannot import")]
82 GenesisUnavailable,
83 #[error("Slot number must increase: parent slot: {0}, this slot: {1}")]
85 SlotMustIncrease(Slot, Slot),
86 #[error("Header {0:?} has a bad seal")]
88 HeaderBadSeal(Header::Hash),
89 #[error("Header {0:?} is unsealed")]
91 HeaderUnsealed(Header::Hash),
92 #[error("Bad reward signature on {0:?}")]
94 BadRewardSignature(Header::Hash),
95 #[error("Missing Subspace justification")]
97 MissingSubspaceJustification,
98 #[error("Invalid Subspace justification: {0}")]
100 InvalidSubspaceJustification(parity_scale_codec::Error),
101 #[error("Invalid Subspace justification contents")]
103 InvalidSubspaceJustificationContents,
104 #[error("Invalid proof of time")]
106 InvalidProofOfTime,
107 #[error(
109 "Solution distance {solution_distance} is outside of solution range \
110 {half_solution_range} (half of actual solution range) for slot {slot}"
111 )]
112 OutsideOfSolutionRange {
113 slot: Slot,
115 half_solution_range: SolutionRange,
117 solution_distance: SolutionRange,
119 },
120 #[error("Invalid proof of space")]
122 InvalidProofOfSpace,
123 #[error("Invalid audit chunk offset")]
125 InvalidAuditChunkOffset,
126 #[error("Invalid chunk: {0}")]
128 InvalidChunk(String),
129 #[error("Invalid chunk witness")]
131 InvalidChunkWitness,
132 #[error("Piece verification failed")]
134 InvalidPieceOffset {
135 slot: Slot,
137 piece_offset: u16,
139 max_pieces_in_sector: u16,
141 },
142 #[error("History size {solution} is in the future, current is {current}")]
144 FutureHistorySize {
145 current: HistorySize,
147 solution: HistorySize,
149 },
150 #[error("Piece verification failed for slot {0}")]
152 InvalidPiece(Slot),
153 #[error("Invalid solution range for block {0}")]
155 InvalidSolutionRange(Header::Hash),
156 #[error("Invalid set of segment headers")]
158 InvalidSetOfSegmentHeaders,
159 #[error("Stored segment header extrinsic was not found: {0:?}")]
161 SegmentHeadersExtrinsicNotFound(Vec<SegmentHeader>),
162 #[error("Segment header for index {0} not found")]
164 SegmentHeaderNotFound(SegmentIndex),
165 #[error(
167 "Different segment commitment for segment index {0} was found in storage, likely fork \
168 below archiving point"
169 )]
170 DifferentSegmentCommitment(SegmentIndex),
171 #[error("Segment commitment for segment index {0} not found")]
173 SegmentCommitmentNotFound(SegmentIndex),
174 #[error("Sector expired")]
176 SectorExpired {
177 expiration_history_size: HistorySize,
179 current_history_size: HistorySize,
181 },
182 #[error("Invalid history size")]
184 InvalidHistorySize,
185 #[error("Only root plot public key is allowed")]
187 OnlyRootPlotPublicKeyAllowed,
188 #[error("Checking inherents failed: {0}")]
190 CheckInherents(sp_inherents::Error),
191 #[error("Checking inherents unhandled error: {}", String::from_utf8_lossy(.0))]
193 CheckInherentsUnhandled(sp_inherents::InherentIdentifier),
194 #[error("Creating inherents failed: {0}")]
196 CreateInherents(sp_inherents::Error),
197 #[error(transparent)]
199 Client(#[from] sp_blockchain::Error),
200 #[error(transparent)]
202 RuntimeApi(#[from] ApiError),
203}
204
205impl<Header> From<VerificationError<Header>> for Error<Header>
206where
207 Header: HeaderT,
208{
209 #[inline]
210 fn from(error: VerificationError<Header>) -> Self {
211 match error {
212 VerificationError::HeaderBadSeal(block_hash) => Error::HeaderBadSeal(block_hash),
213 VerificationError::HeaderUnsealed(block_hash) => Error::HeaderUnsealed(block_hash),
214 VerificationError::BadRewardSignature(block_hash) => {
215 Error::BadRewardSignature(block_hash)
216 }
217 VerificationError::MissingSubspaceJustification => Error::MissingSubspaceJustification,
218 VerificationError::InvalidSubspaceJustification(error) => {
219 Error::InvalidSubspaceJustification(error)
220 }
221 VerificationError::InvalidSubspaceJustificationContents => {
222 Error::InvalidSubspaceJustificationContents
223 }
224 VerificationError::InvalidProofOfTime => Error::InvalidProofOfTime,
225 VerificationError::VerificationError(slot, error) => match error {
226 VerificationPrimitiveError::InvalidPieceOffset {
227 piece_offset,
228 max_pieces_in_sector,
229 } => Error::InvalidPieceOffset {
230 slot,
231 piece_offset,
232 max_pieces_in_sector,
233 },
234 VerificationPrimitiveError::FutureHistorySize { current, solution } => {
235 Error::FutureHistorySize { current, solution }
236 }
237 VerificationPrimitiveError::InvalidPiece => Error::InvalidPiece(slot),
238 VerificationPrimitiveError::OutsideSolutionRange {
239 half_solution_range,
240 solution_distance,
241 } => Error::OutsideOfSolutionRange {
242 slot,
243 half_solution_range,
244 solution_distance,
245 },
246 VerificationPrimitiveError::InvalidProofOfSpace => Error::InvalidProofOfSpace,
247 VerificationPrimitiveError::InvalidAuditChunkOffset => {
248 Error::InvalidAuditChunkOffset
249 }
250 VerificationPrimitiveError::InvalidChunk(error) => Error::InvalidChunk(error),
251 VerificationPrimitiveError::InvalidChunkWitness => Error::InvalidChunkWitness,
252 VerificationPrimitiveError::SectorExpired {
253 expiration_history_size,
254 current_history_size,
255 } => Error::SectorExpired {
256 expiration_history_size,
257 current_history_size,
258 },
259 VerificationPrimitiveError::InvalidHistorySize => Error::InvalidHistorySize,
260 },
261 }
262 }
263}
264
265impl<Header> From<Error<Header>> for String
266where
267 Header: HeaderT,
268{
269 #[inline]
270 fn from(error: Error<Header>) -> String {
271 error.to_string()
272 }
273}
274
275pub struct SubspaceBlockImport<PosTable, Block, Client, I, CIDP, AS>
277where
278 Block: BlockT,
279{
280 inner: I,
281 client: Arc<Client>,
282 subspace_link: SubspaceLink<Block>,
283 create_inherent_data_providers: CIDP,
284 segment_headers_store: SegmentHeadersStore<AS>,
285 pot_verifier: PotVerifier,
286 _pos_table: PhantomData<PosTable>,
287}
288
289impl<PosTable, Block, I, Client, CIDP, AS> Clone
290 for SubspaceBlockImport<PosTable, Block, Client, I, CIDP, AS>
291where
292 Block: BlockT,
293 I: Clone,
294 CIDP: Clone,
295{
296 fn clone(&self) -> Self {
297 SubspaceBlockImport {
298 inner: self.inner.clone(),
299 client: self.client.clone(),
300 subspace_link: self.subspace_link.clone(),
301 create_inherent_data_providers: self.create_inherent_data_providers.clone(),
302 segment_headers_store: self.segment_headers_store.clone(),
303 pot_verifier: self.pot_verifier.clone(),
304 _pos_table: PhantomData,
305 }
306 }
307}
308
309impl<PosTable, Block, Client, I, CIDP, AS> SubspaceBlockImport<PosTable, Block, Client, I, CIDP, AS>
310where
311 PosTable: Table,
312 Block: BlockT,
313 Client: ProvideRuntimeApi<Block> + BlockBackend<Block> + HeaderBackend<Block> + AuxStore,
314 Client::Api: BlockBuilderApi<Block> + SubspaceApi<Block, PublicKey> + ApiExt<Block>,
315 CIDP: CreateInherentDataProviders<Block, ()> + Send + Sync + 'static,
316 AS: AuxStore + Send + Sync + 'static,
317 BlockNumber: From<NumberFor<Block>>,
318{
319 pub fn new(
321 client: Arc<Client>,
322 block_import: I,
323 subspace_link: SubspaceLink<Block>,
324 create_inherent_data_providers: CIDP,
325 segment_headers_store: SegmentHeadersStore<AS>,
326 pot_verifier: PotVerifier,
327 ) -> Self {
328 Self {
329 client,
330 inner: block_import,
331 subspace_link,
332 create_inherent_data_providers,
333 segment_headers_store,
334 pot_verifier,
335 _pos_table: PhantomData,
336 }
337 }
338
339 async fn block_import_verification(
340 &self,
341 block_hash: Block::Hash,
342 header: Block::Header,
343 extrinsics: Option<Vec<Block::Extrinsic>>,
344 root_plot_public_key: &Option<PublicKey>,
345 subspace_digest_items: &SubspaceDigestItems<PublicKey>,
346 justifications: &Option<Justifications>,
347 ) -> Result<(), Error<Block::Header>> {
348 let block_number = *header.number();
349 let parent_hash = *header.parent_hash();
350
351 let pre_digest = &subspace_digest_items.pre_digest;
352 if let Some(root_plot_public_key) = root_plot_public_key
353 && &pre_digest.solution().public_key != root_plot_public_key
354 {
355 return Err(Error::OnlyRootPlotPublicKeyAllowed);
357 }
358
359 let parent_header = self
360 .client
361 .header(parent_hash)?
362 .ok_or(Error::ParentUnavailable(parent_hash, block_hash))?;
363
364 let parent_pre_digest = extract_pre_digest(&parent_header)?;
365 let parent_slot = parent_pre_digest.slot();
366
367 if pre_digest.slot() <= parent_slot {
369 return Err(Error::SlotMustIncrease(parent_slot, pre_digest.slot()));
370 }
371
372 let parent_subspace_digest_items = if block_number.is_one() {
373 None
374 } else {
375 Some(extract_subspace_digest_items::<_, PublicKey>(
376 &parent_header,
377 )?)
378 };
379
380 let correct_solution_range = if block_number.is_one() {
381 slot_worker::extract_solution_ranges_for_block(self.client.as_ref(), parent_hash)?.0
382 } else {
383 let parent_subspace_digest_items = parent_subspace_digest_items
384 .as_ref()
385 .expect("Always Some for non-first block; qed");
386
387 match parent_subspace_digest_items.next_solution_range {
388 Some(solution_range) => solution_range,
389 None => parent_subspace_digest_items.solution_range,
390 }
391 };
392
393 if subspace_digest_items.solution_range != correct_solution_range {
394 return Err(Error::InvalidSolutionRange(block_hash));
395 }
396
397 let chain_constants = self.subspace_link.chain_constants();
398
399 {
402 let runtime_api = self.client.runtime_api();
403 let parent_pot_parameters = runtime_api
404 .pot_parameters(parent_hash)
405 .map_err(|_| Error::ParentUnavailable(parent_hash, block_hash))?;
406
407 let pot_input = pot_next_slot_input::<Block>(
408 parent_header.number(),
409 parent_slot,
410 &parent_pot_parameters,
411 self.pot_verifier.genesis_seed(),
412 parent_pre_digest.pot_info().proof_of_time(),
413 );
414
415 if !self.pot_verifier.is_output_valid(
417 pot_input,
418 pre_digest.slot() - parent_slot,
419 pre_digest.pot_info().proof_of_time(),
420 parent_pot_parameters.next_parameters_change(),
421 ) {
422 return Err(Error::InvalidProofOfTime);
423 }
424
425 let Some(subspace_justification) = justifications
426 .as_ref()
427 .and_then(|justifications| {
428 justifications
429 .iter()
430 .find_map(SubspaceJustification::try_from_justification)
431 })
432 .transpose()
433 .map_err(Error::InvalidSubspaceJustification)?
434 else {
435 return Err(Error::MissingSubspaceJustification);
436 };
437
438 let SubspaceJustification::PotCheckpoints { seed, checkpoints } =
439 subspace_justification;
440
441 let future_slot = pre_digest.slot() + chain_constants.block_authoring_delay();
442
443 if block_number.is_one() {
444 if seed != self.pot_verifier.genesis_seed() {
446 return Err(Error::InvalidSubspaceJustificationContents);
447 }
448
449 if checkpoints.len() as u64 != *future_slot {
451 return Err(Error::InvalidSubspaceJustificationContents);
452 }
453 } else {
454 let parent_subspace_digest_items = parent_subspace_digest_items
455 .as_ref()
456 .expect("Always Some for non-first block; qed");
457
458 let parent_future_slot = parent_slot + chain_constants.block_authoring_delay();
459
460 let correct_input_parameters = PotNextSlotInput::derive(
461 subspace_digest_items.pot_slot_iterations,
462 parent_future_slot,
463 parent_subspace_digest_items
464 .pre_digest
465 .pot_info()
466 .future_proof_of_time(),
467 &subspace_digest_items.pot_parameters_change,
468 );
469
470 if seed != correct_input_parameters.seed {
471 return Err(Error::InvalidSubspaceJustificationContents);
472 }
473
474 if checkpoints.len() as u64 != (*future_slot - *parent_future_slot) {
476 return Err(Error::InvalidSubspaceJustificationContents);
477 }
478 }
479 }
480
481 let sector_id = SectorId::new(
482 pre_digest.solution().public_key.hash(),
483 pre_digest.solution().sector_index,
484 pre_digest.solution().history_size,
485 );
486
487 let max_pieces_in_sector = self
488 .client
489 .runtime_api()
490 .max_pieces_in_sector(parent_hash)?;
491 let piece_index = sector_id.derive_piece_index(
492 pre_digest.solution().piece_offset,
493 pre_digest.solution().history_size,
494 max_pieces_in_sector,
495 chain_constants.recent_segments(),
496 chain_constants.recent_history_fraction(),
497 );
498 let segment_index = piece_index.segment_index();
499
500 let segment_commitment = self
501 .segment_headers_store
502 .get_segment_header(segment_index)
503 .map(|segment_header| segment_header.segment_commitment())
504 .ok_or(Error::SegmentCommitmentNotFound(segment_index))?;
505
506 let sector_expiration_check_segment_commitment = self
507 .segment_headers_store
508 .get_segment_header(
509 subspace_digest_items
510 .pre_digest
511 .solution()
512 .history_size
513 .sector_expiration_check(chain_constants.min_sector_lifetime())
514 .ok_or(Error::InvalidHistorySize)?
515 .segment_index(),
516 )
517 .map(|segment_header| segment_header.segment_commitment());
518
519 subspace_verification::verify_solution::<PosTable, _>(
522 pre_digest.solution(),
523 pre_digest.slot().into(),
525 &VerifySolutionParams {
526 proof_of_time: subspace_digest_items.pre_digest.pot_info().proof_of_time(),
527 solution_range: subspace_digest_items.solution_range,
528 piece_check_params: Some(PieceCheckParams {
529 max_pieces_in_sector,
530 segment_commitment,
531 recent_segments: chain_constants.recent_segments(),
532 recent_history_fraction: chain_constants.recent_history_fraction(),
533 min_sector_lifetime: chain_constants.min_sector_lifetime(),
534 current_history_size: self.client.runtime_api().history_size(parent_hash)?,
535 sector_expiration_check_segment_commitment,
536 }),
537 },
538 &self.subspace_link.kzg,
539 )
540 .map_err(|error| VerificationError::VerificationError(pre_digest.slot(), error))?;
541
542 if let Some(extrinsics) = extrinsics {
546 let create_inherent_data_providers = self
547 .create_inherent_data_providers
548 .create_inherent_data_providers(parent_hash, ())
549 .await
550 .map_err(|error| Error::Client(sp_blockchain::Error::from(error)))?;
551
552 let inherent_data = create_inherent_data_providers
553 .create_inherent_data()
554 .await
555 .map_err(Error::CreateInherents)?;
556
557 let inherent_res = self.client.runtime_api().check_inherents(
558 parent_hash,
559 Block::new(header, extrinsics).into(),
560 inherent_data,
561 )?;
562
563 if !inherent_res.ok() {
564 for (i, e) in inherent_res.into_errors() {
565 match create_inherent_data_providers
566 .try_handle_error(&i, &e)
567 .await
568 {
569 Some(res) => res.map_err(Error::CheckInherents)?,
570 None => return Err(Error::CheckInherentsUnhandled(i)),
571 }
572 }
573 }
574 }
575
576 Ok(())
577 }
578}
579
580#[async_trait::async_trait]
581impl<PosTable, Block, Client, Inner, CIDP, AS> BlockImport<Block>
582 for SubspaceBlockImport<PosTable, Block, Client, Inner, CIDP, AS>
583where
584 PosTable: Table,
585 Block: BlockT,
586 Inner: BlockImport<Block, Error = sp_consensus::Error> + Send + Sync,
587 Client: ProvideRuntimeApi<Block>
588 + BlockBackend<Block>
589 + HeaderBackend<Block>
590 + AuxStore
591 + Send
592 + Sync,
593 Client::Api: BlockBuilderApi<Block> + SubspaceApi<Block, PublicKey> + ApiExt<Block>,
594 CIDP: CreateInherentDataProviders<Block, ()> + Send + Sync + 'static,
595 AS: AuxStore + Send + Sync + 'static,
596 BlockNumber: From<NumberFor<Block>>,
597{
598 type Error = Error<Block::Header>;
599
600 async fn import_block(
601 &self,
602 mut block: BlockImportParams<Block>,
603 ) -> Result<ImportResult, Self::Error> {
604 let block_hash = block.post_hash();
605 let block_number = *block.header.number();
606
607 match self.client.status(block_hash)? {
609 sp_blockchain::BlockStatus::InChain => {
610 block.fork_choice = Some(ForkChoiceStrategy::Custom(false));
611 return self
612 .inner
613 .import_block(block)
614 .await
615 .map_err(Error::InnerBlockImportError);
616 }
617 sp_blockchain::BlockStatus::Unknown => {}
618 }
619
620 let subspace_digest_items = extract_subspace_digest_items(&block.header)?;
621
622 if !matches!(block.state_action, StateAction::ApplyChanges(_)) {
627 let root_plot_public_key = self
628 .client
629 .runtime_api()
630 .root_plot_public_key(*block.header.parent_hash())?;
631
632 self.block_import_verification(
633 block_hash,
634 block.header.clone(),
635 block.body.clone(),
636 &root_plot_public_key,
637 &subspace_digest_items,
638 &block.justifications,
639 )
640 .await?;
641 }
642
643 let parent_weight = if block_number.is_one() {
645 0
647 } else {
648 aux_schema::load_block_weight(self.client.as_ref(), block.header.parent_hash())?
658 .unwrap_or_default()
659 };
660
661 let added_weight = calculate_block_fork_weight(subspace_digest_items.solution_range);
664 let total_weight = parent_weight.saturating_add(added_weight);
665
666 aux_schema::write_block_weight(block_hash, total_weight, |values| {
667 block
668 .auxiliary
669 .extend(values.iter().map(|(k, v)| (k.to_vec(), Some(v.to_vec()))))
670 });
671
672 {
673 let cleaned_to = aux_schema::load_block_weight_cleaned_to::<NumberFor<Block>, _>(
674 self.client.as_ref(),
675 )?
676 .unwrap_or_else(Zero::zero);
677 let finalized = self.client.info().finalized_number;
678 let batch: NumberFor<Block> = BLOCK_WEIGHT_SWEEP_BATCH.into();
679 let entries = aux_schema::build_canonical_sweep_entries(
680 cleaned_to,
681 finalized,
682 batch,
683 |height| self.client.hash(height),
684 )?;
685 block.auxiliary.extend(entries);
686 }
687
688 for (&segment_index, segment_commitment) in &subspace_digest_items.segment_commitments {
689 let found_segment_commitment = self
690 .segment_headers_store
691 .get_segment_header(segment_index)
692 .ok_or_else(|| Error::SegmentHeaderNotFound(segment_index))?
693 .segment_commitment();
694
695 if &found_segment_commitment != segment_commitment {
696 warn!(
697 "Different segment commitment for segment index {} was found in storage, \
698 likely fork below archiving point. expected {:?}, found {:?}",
699 segment_index, segment_commitment, found_segment_commitment
700 );
701 return Err(Error::DifferentSegmentCommitment(segment_index));
702 }
703 }
704
705 let fork_choice = {
729 let info = self.client.info();
730
731 let last_best_weight = if &info.best_hash == block.header.parent_hash() {
732 parent_weight
735 } else {
736 aux_schema::load_block_weight(&*self.client, info.best_hash)?.unwrap_or_default()
739 };
740
741 ForkChoiceStrategy::Custom(total_weight > last_best_weight)
742 };
743 block.fork_choice = Some(fork_choice);
744
745 let (acknowledgement_sender, mut acknowledgement_receiver) = mpsc::channel(0);
746
747 self.subspace_link
748 .block_importing_notification_sender
749 .notify(move || BlockImportingNotification {
750 block_number,
751 acknowledgement_sender,
752 });
753
754 while acknowledgement_receiver.next().await.is_some() {
755 }
757
758 let start = Instant::now();
759
760 let result = self
761 .inner
762 .import_block(block)
763 .await
764 .map_err(Error::InnerBlockImportError)?;
765
766 let actual_execution_time_ms = start.elapsed().as_millis();
767
768 let runtime_api = self.client.runtime_api();
769 let best_hash = self.client.info().best_hash;
770 let subspace_api_version = runtime_api
771 .api_version::<dyn SubspaceApi<Block, PublicKey>>(best_hash)
772 .ok()
773 .flatten()
774 .unwrap_or(1);
776
777 if subspace_api_version < 2 {
778 return Ok(result);
779 }
780
781 let reference_execution_time_ms =
785 (runtime_api.block_weight(best_hash)?.ref_time() / WEIGHT_REF_TIME_PER_MILLIS) + 200;
786
787 if actual_execution_time_ms > reference_execution_time_ms as u128 {
788 warn!(
789 ?best_hash,
790 ?reference_execution_time_ms,
791 "Slow Consensus block execution, took {actual_execution_time_ms} ms"
792 );
793 }
794
795 Ok(result)
796 }
797
798 async fn check_block(
799 &self,
800 block: BlockCheckParams<Block>,
801 ) -> Result<ImportResult, Self::Error> {
802 self.inner.check_block(block).await.map_err(Into::into)
803 }
804}