sc_consensus_subspace/
block_import.rs

1//! Block import for Subspace, which includes stateful verification and corresponding notifications.
2//!
3//! In most cases block import happens after stateless block verification using [`verifier`](crate::verifier),
4//! the only exception to that is locally authored blocks.
5//!
6//! Since [`verifier`](crate::verifier) is stateless, the remaining checks in block import are those
7//! that require presence of the parent block or its state in the database. Specifically for Proof
8//! of Time individual checkpoints are assumed to be checked already and only PoT inputs need to be
9//! checked to correspond to the state of the parent block.
10//!
11//! After all checks and right before importing the block notification ([`SubspaceLink::block_importing_notification_stream`])
12//! will be sent that [`archiver`](crate::archiver) among other things is subscribed to.
13
14use crate::archiver::SegmentHeadersStore;
15use crate::verifier::VerificationError;
16use crate::{aux_schema, slot_worker, SubspaceLink};
17use futures::channel::mpsc;
18use futures::StreamExt;
19use sc_client_api::backend::AuxStore;
20use sc_client_api::BlockBackend;
21use sc_consensus::block_import::{
22    BlockCheckParams, BlockImport, BlockImportParams, ForkChoiceStrategy, ImportResult,
23};
24use sc_consensus::StateAction;
25use sc_proof_of_time::verifier::PotVerifier;
26use sp_api::{ApiError, ApiExt, ProvideRuntimeApi};
27use sp_block_builder::BlockBuilder as BlockBuilderApi;
28use sp_blockchain::HeaderBackend;
29use sp_consensus_slots::Slot;
30use sp_consensus_subspace::digests::{
31    extract_pre_digest, extract_subspace_digest_items, SubspaceDigestItems,
32};
33use sp_consensus_subspace::{PotNextSlotInput, SubspaceApi, SubspaceJustification};
34use sp_inherents::{CreateInherentDataProviders, InherentDataProvider};
35use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor, One};
36use sp_runtime::Justifications;
37use std::marker::PhantomData;
38use std::sync::Arc;
39use subspace_core_primitives::sectors::SectorId;
40use subspace_core_primitives::segments::{HistorySize, SegmentHeader, SegmentIndex};
41use subspace_core_primitives::solutions::SolutionRange;
42use subspace_core_primitives::{BlockNumber, PublicKey};
43use subspace_proof_of_space::Table;
44use subspace_verification::{calculate_block_weight, PieceCheckParams, VerifySolutionParams};
45use tracing::warn;
46
47/// Notification with number of the block that is about to be imported and acknowledgement sender
48/// that can be used to pause block production if desired.
49#[derive(Debug, Clone)]
50pub struct BlockImportingNotification<Block>
51where
52    Block: BlockT,
53{
54    /// Block number
55    pub block_number: NumberFor<Block>,
56    /// Sender for pausing the block import when operator is not fast enough to process
57    /// the consensus block.
58    pub acknowledgement_sender: mpsc::Sender<()>,
59}
60use subspace_verification::Error as VerificationPrimitiveError;
61
62/// Errors encountered by the Subspace authorship task.
63#[derive(Debug, thiserror::Error)]
64pub enum Error<Header: HeaderT> {
65    /// Inner block import error
66    #[error("Inner block import error: {0}")]
67    InnerBlockImportError(#[from] sp_consensus::Error),
68    /// Error during digest item extraction
69    #[error("Digest item error: {0}")]
70    DigestItemError(#[from] sp_consensus_subspace::digests::Error),
71    /// Parent unavailable. Cannot import
72    #[error("Parent ({0}) of {1} unavailable. Cannot import")]
73    ParentUnavailable(Header::Hash, Header::Hash),
74    /// Genesis block unavailable. Cannot import
75    #[error("Genesis block unavailable. Cannot import")]
76    GenesisUnavailable,
77    /// Slot number must increase
78    #[error("Slot number must increase: parent slot: {0}, this slot: {1}")]
79    SlotMustIncrease(Slot, Slot),
80    /// Header has a bad seal
81    #[error("Header {0:?} has a bad seal")]
82    HeaderBadSeal(Header::Hash),
83    /// Header is unsealed
84    #[error("Header {0:?} is unsealed")]
85    HeaderUnsealed(Header::Hash),
86    /// Bad reward signature
87    #[error("Bad reward signature on {0:?}")]
88    BadRewardSignature(Header::Hash),
89    /// Missing Subspace justification
90    #[error("Missing Subspace justification")]
91    MissingSubspaceJustification,
92    /// Invalid Subspace justification
93    #[error("Invalid Subspace justification: {0}")]
94    InvalidSubspaceJustification(parity_scale_codec::Error),
95    /// Invalid Subspace justification contents
96    #[error("Invalid Subspace justification contents")]
97    InvalidSubspaceJustificationContents,
98    /// Invalid proof of time
99    #[error("Invalid proof of time")]
100    InvalidProofOfTime,
101    /// Solution is outside of solution range
102    #[error(
103        "Solution distance {solution_distance} is outside of solution range \
104        {half_solution_range} (half of actual solution range) for slot {slot}"
105    )]
106    OutsideOfSolutionRange {
107        /// Time slot
108        slot: Slot,
109        /// Half of solution range
110        half_solution_range: SolutionRange,
111        /// Solution distance
112        solution_distance: SolutionRange,
113    },
114    /// Invalid proof of space
115    #[error("Invalid proof of space")]
116    InvalidProofOfSpace,
117    /// Invalid audit chunk offset
118    #[error("Invalid audit chunk offset")]
119    InvalidAuditChunkOffset,
120    /// Invalid chunk
121    #[error("Invalid chunk: {0}")]
122    InvalidChunk(String),
123    /// Invalid chunk witness
124    #[error("Invalid chunk witness")]
125    InvalidChunkWitness,
126    /// Piece verification failed
127    #[error("Piece verification failed")]
128    InvalidPieceOffset {
129        /// Time slot
130        slot: Slot,
131        /// Index of the piece that failed verification
132        piece_offset: u16,
133        /// How many pieces one sector is supposed to contain (max)
134        max_pieces_in_sector: u16,
135    },
136    /// Piece verification failed
137    #[error("Piece verification failed for slot {0}")]
138    InvalidPiece(Slot),
139    /// Block has invalid associated solution range
140    #[error("Invalid solution range for block {0}")]
141    InvalidSolutionRange(Header::Hash),
142    /// Invalid set of segment headers
143    #[error("Invalid set of segment headers")]
144    InvalidSetOfSegmentHeaders,
145    /// Stored segment header extrinsic was not found
146    #[error("Stored segment header extrinsic was not found: {0:?}")]
147    SegmentHeadersExtrinsicNotFound(Vec<SegmentHeader>),
148    /// Segment header not found
149    #[error("Segment header for index {0} not found")]
150    SegmentHeaderNotFound(SegmentIndex),
151    /// Different segment commitment found
152    #[error(
153        "Different segment commitment for segment index {0} was found in storage, likely fork \
154        below archiving point"
155    )]
156    DifferentSegmentCommitment(SegmentIndex),
157    /// Segment commitment not found
158    #[error("Segment commitment for segment index {0} not found")]
159    SegmentCommitmentNotFound(SegmentIndex),
160    /// Sector expired
161    #[error("Sector expired")]
162    SectorExpired {
163        /// Expiration history size
164        expiration_history_size: HistorySize,
165        /// Current history size
166        current_history_size: HistorySize,
167    },
168    /// Invalid history size
169    #[error("Invalid history size")]
170    InvalidHistorySize,
171    /// Only root plot public key is allowed
172    #[error("Only root plot public key is allowed")]
173    OnlyRootPlotPublicKeyAllowed,
174    /// Check inherents error
175    #[error("Checking inherents failed: {0}")]
176    CheckInherents(sp_inherents::Error),
177    /// Unhandled check inherents error
178    #[error("Checking inherents unhandled error: {}", String::from_utf8_lossy(.0))]
179    CheckInherentsUnhandled(sp_inherents::InherentIdentifier),
180    /// Create inherents error.
181    #[error("Creating inherents failed: {0}")]
182    CreateInherents(sp_inherents::Error),
183    /// Client error
184    #[error(transparent)]
185    Client(#[from] sp_blockchain::Error),
186    /// Runtime Api error.
187    #[error(transparent)]
188    RuntimeApi(#[from] ApiError),
189}
190
191impl<Header> From<VerificationError<Header>> for Error<Header>
192where
193    Header: HeaderT,
194{
195    #[inline]
196    fn from(error: VerificationError<Header>) -> Self {
197        match error {
198            VerificationError::HeaderBadSeal(block_hash) => Error::HeaderBadSeal(block_hash),
199            VerificationError::HeaderUnsealed(block_hash) => Error::HeaderUnsealed(block_hash),
200            VerificationError::BadRewardSignature(block_hash) => {
201                Error::BadRewardSignature(block_hash)
202            }
203            VerificationError::MissingSubspaceJustification => Error::MissingSubspaceJustification,
204            VerificationError::InvalidSubspaceJustification(error) => {
205                Error::InvalidSubspaceJustification(error)
206            }
207            VerificationError::InvalidSubspaceJustificationContents => {
208                Error::InvalidSubspaceJustificationContents
209            }
210            VerificationError::InvalidProofOfTime => Error::InvalidProofOfTime,
211            VerificationError::VerificationError(slot, error) => match error {
212                VerificationPrimitiveError::InvalidPieceOffset {
213                    piece_offset,
214                    max_pieces_in_sector,
215                } => Error::InvalidPieceOffset {
216                    slot,
217                    piece_offset,
218                    max_pieces_in_sector,
219                },
220                VerificationPrimitiveError::InvalidPiece => Error::InvalidPiece(slot),
221                VerificationPrimitiveError::OutsideSolutionRange {
222                    half_solution_range,
223                    solution_distance,
224                } => Error::OutsideOfSolutionRange {
225                    slot,
226                    half_solution_range,
227                    solution_distance,
228                },
229                VerificationPrimitiveError::InvalidProofOfSpace => Error::InvalidProofOfSpace,
230                VerificationPrimitiveError::InvalidAuditChunkOffset => {
231                    Error::InvalidAuditChunkOffset
232                }
233                VerificationPrimitiveError::InvalidChunk(error) => Error::InvalidChunk(error),
234                VerificationPrimitiveError::InvalidChunkWitness => Error::InvalidChunkWitness,
235                VerificationPrimitiveError::SectorExpired {
236                    expiration_history_size,
237                    current_history_size,
238                } => Error::SectorExpired {
239                    expiration_history_size,
240                    current_history_size,
241                },
242                VerificationPrimitiveError::InvalidHistorySize => Error::InvalidHistorySize,
243            },
244        }
245    }
246}
247
248impl<Header> From<Error<Header>> for String
249where
250    Header: HeaderT,
251{
252    #[inline]
253    fn from(error: Error<Header>) -> String {
254        error.to_string()
255    }
256}
257
258/// A block-import handler for Subspace.
259pub struct SubspaceBlockImport<PosTable, Block, Client, I, CIDP, AS>
260where
261    Block: BlockT,
262{
263    inner: I,
264    client: Arc<Client>,
265    subspace_link: SubspaceLink<Block>,
266    create_inherent_data_providers: CIDP,
267    segment_headers_store: SegmentHeadersStore<AS>,
268    pot_verifier: PotVerifier,
269    _pos_table: PhantomData<PosTable>,
270}
271
272impl<PosTable, Block, I, Client, CIDP, AS> Clone
273    for SubspaceBlockImport<PosTable, Block, Client, I, CIDP, AS>
274where
275    Block: BlockT,
276    I: Clone,
277    CIDP: Clone,
278{
279    fn clone(&self) -> Self {
280        SubspaceBlockImport {
281            inner: self.inner.clone(),
282            client: self.client.clone(),
283            subspace_link: self.subspace_link.clone(),
284            create_inherent_data_providers: self.create_inherent_data_providers.clone(),
285            segment_headers_store: self.segment_headers_store.clone(),
286            pot_verifier: self.pot_verifier.clone(),
287            _pos_table: PhantomData,
288        }
289    }
290}
291
292impl<PosTable, Block, Client, I, CIDP, AS> SubspaceBlockImport<PosTable, Block, Client, I, CIDP, AS>
293where
294    PosTable: Table,
295    Block: BlockT,
296    Client: ProvideRuntimeApi<Block> + BlockBackend<Block> + HeaderBackend<Block> + AuxStore,
297    Client::Api: BlockBuilderApi<Block> + SubspaceApi<Block, PublicKey> + ApiExt<Block>,
298    CIDP: CreateInherentDataProviders<Block, ()> + Send + Sync + 'static,
299    AS: AuxStore + Send + Sync + 'static,
300    BlockNumber: From<NumberFor<Block>>,
301{
302    /// Produce a Subspace block-import object to be used later on in the construction of an import-queue.
303    pub fn new(
304        client: Arc<Client>,
305        block_import: I,
306        subspace_link: SubspaceLink<Block>,
307        create_inherent_data_providers: CIDP,
308        segment_headers_store: SegmentHeadersStore<AS>,
309        pot_verifier: PotVerifier,
310    ) -> Self {
311        Self {
312            client,
313            inner: block_import,
314            subspace_link,
315            create_inherent_data_providers,
316            segment_headers_store,
317            pot_verifier,
318            _pos_table: PhantomData,
319        }
320    }
321
322    async fn block_import_verification(
323        &self,
324        block_hash: Block::Hash,
325        header: Block::Header,
326        extrinsics: Option<Vec<Block::Extrinsic>>,
327        root_plot_public_key: &Option<PublicKey>,
328        subspace_digest_items: &SubspaceDigestItems<PublicKey>,
329        justifications: &Option<Justifications>,
330    ) -> Result<(), Error<Block::Header>> {
331        let block_number = *header.number();
332        let parent_hash = *header.parent_hash();
333
334        let pre_digest = &subspace_digest_items.pre_digest;
335        if let Some(root_plot_public_key) = root_plot_public_key {
336            if &pre_digest.solution().public_key != root_plot_public_key {
337                // Only root plot public key is allowed.
338                return Err(Error::OnlyRootPlotPublicKeyAllowed);
339            }
340        }
341
342        let parent_header = self
343            .client
344            .header(parent_hash)?
345            .ok_or(Error::ParentUnavailable(parent_hash, block_hash))?;
346
347        let parent_slot = extract_pre_digest(&parent_header).map(|d| d.slot())?;
348
349        // Make sure that slot number is strictly increasing
350        if pre_digest.slot() <= parent_slot {
351            return Err(Error::SlotMustIncrease(parent_slot, pre_digest.slot()));
352        }
353
354        let parent_subspace_digest_items = if block_number.is_one() {
355            None
356        } else {
357            Some(extract_subspace_digest_items::<_, PublicKey>(
358                &parent_header,
359            )?)
360        };
361
362        let correct_solution_range = if block_number.is_one() {
363            slot_worker::extract_solution_ranges_for_block(self.client.as_ref(), parent_hash)?.0
364        } else {
365            let parent_subspace_digest_items = parent_subspace_digest_items
366                .as_ref()
367                .expect("Always Some for non-first block; qed");
368
369            match parent_subspace_digest_items.next_solution_range {
370                Some(solution_range) => solution_range,
371                None => parent_subspace_digest_items.solution_range,
372            }
373        };
374
375        if subspace_digest_items.solution_range != correct_solution_range {
376            return Err(Error::InvalidSolutionRange(block_hash));
377        }
378
379        let chain_constants = self.subspace_link.chain_constants();
380
381        // For PoT justifications we only need to check the seed and number of checkpoints, the rest
382        // was already checked during stateless block verification.
383        {
384            let Some(subspace_justification) = justifications
385                .as_ref()
386                .and_then(|justifications| {
387                    justifications
388                        .iter()
389                        .find_map(SubspaceJustification::try_from_justification)
390                })
391                .transpose()
392                .map_err(Error::InvalidSubspaceJustification)?
393            else {
394                return Err(Error::MissingSubspaceJustification);
395            };
396
397            let SubspaceJustification::PotCheckpoints { seed, checkpoints } =
398                subspace_justification;
399
400            let future_slot = pre_digest.slot() + chain_constants.block_authoring_delay();
401
402            if block_number.is_one() {
403                // In case of first block seed must match genesis seed
404                if seed != self.pot_verifier.genesis_seed() {
405                    return Err(Error::InvalidSubspaceJustificationContents);
406                }
407
408                // Number of checkpoints must match future slot number
409                if checkpoints.len() as u64 != *future_slot {
410                    return Err(Error::InvalidSubspaceJustificationContents);
411                }
412            } else {
413                let parent_subspace_digest_items = parent_subspace_digest_items
414                    .as_ref()
415                    .expect("Always Some for non-first block; qed");
416
417                let parent_future_slot = parent_slot + chain_constants.block_authoring_delay();
418
419                let correct_input_parameters = PotNextSlotInput::derive(
420                    subspace_digest_items.pot_slot_iterations,
421                    parent_future_slot,
422                    parent_subspace_digest_items
423                        .pre_digest
424                        .pot_info()
425                        .future_proof_of_time(),
426                    &subspace_digest_items.pot_parameters_change,
427                );
428
429                if seed != correct_input_parameters.seed {
430                    return Err(Error::InvalidSubspaceJustificationContents);
431                }
432
433                // Number of checkpoints must match number of proofs that were not yet seen on chain
434                if checkpoints.len() as u64 != (*future_slot - *parent_future_slot) {
435                    return Err(Error::InvalidSubspaceJustificationContents);
436                }
437            }
438        }
439
440        let sector_id = SectorId::new(
441            pre_digest.solution().public_key.hash(),
442            pre_digest.solution().sector_index,
443            pre_digest.solution().history_size,
444        );
445
446        let max_pieces_in_sector = self
447            .client
448            .runtime_api()
449            .max_pieces_in_sector(parent_hash)?;
450        let piece_index = sector_id.derive_piece_index(
451            pre_digest.solution().piece_offset,
452            pre_digest.solution().history_size,
453            max_pieces_in_sector,
454            chain_constants.recent_segments(),
455            chain_constants.recent_history_fraction(),
456        );
457        let segment_index = piece_index.segment_index();
458
459        let segment_commitment = self
460            .segment_headers_store
461            .get_segment_header(segment_index)
462            .map(|segment_header| segment_header.segment_commitment())
463            .ok_or(Error::SegmentCommitmentNotFound(segment_index))?;
464
465        let sector_expiration_check_segment_commitment = self
466            .segment_headers_store
467            .get_segment_header(
468                subspace_digest_items
469                    .pre_digest
470                    .solution()
471                    .history_size
472                    .sector_expiration_check(chain_constants.min_sector_lifetime())
473                    .ok_or(Error::InvalidHistorySize)?
474                    .segment_index(),
475            )
476            .map(|segment_header| segment_header.segment_commitment());
477
478        // Piece is not checked during initial block verification because it requires access to
479        // segment header and runtime, check it now.
480        subspace_verification::verify_solution::<PosTable, _>(
481            pre_digest.solution(),
482            // Slot was already checked during initial block verification
483            pre_digest.slot().into(),
484            &VerifySolutionParams {
485                proof_of_time: subspace_digest_items.pre_digest.pot_info().proof_of_time(),
486                solution_range: subspace_digest_items.solution_range,
487                piece_check_params: Some(PieceCheckParams {
488                    max_pieces_in_sector,
489                    segment_commitment,
490                    recent_segments: chain_constants.recent_segments(),
491                    recent_history_fraction: chain_constants.recent_history_fraction(),
492                    min_sector_lifetime: chain_constants.min_sector_lifetime(),
493                    current_history_size: self.client.runtime_api().history_size(parent_hash)?,
494                    sector_expiration_check_segment_commitment,
495                }),
496            },
497            &self.subspace_link.kzg,
498        )
499        .map_err(|error| VerificationError::VerificationError(pre_digest.slot(), error))?;
500
501        // If the body is passed through, we need to use the runtime to check that the
502        // internally-set timestamp in the inherents actually matches the slot set in the seal
503        // and segment headers in the inherents are set correctly.
504        if let Some(extrinsics) = extrinsics {
505            let create_inherent_data_providers = self
506                .create_inherent_data_providers
507                .create_inherent_data_providers(parent_hash, ())
508                .await
509                .map_err(|error| Error::Client(sp_blockchain::Error::from(error)))?;
510
511            let inherent_data = create_inherent_data_providers
512                .create_inherent_data()
513                .await
514                .map_err(Error::CreateInherents)?;
515
516            let inherent_res = self.client.runtime_api().check_inherents(
517                parent_hash,
518                Block::new(header, extrinsics),
519                inherent_data,
520            )?;
521
522            if !inherent_res.ok() {
523                for (i, e) in inherent_res.into_errors() {
524                    match create_inherent_data_providers
525                        .try_handle_error(&i, &e)
526                        .await
527                    {
528                        Some(res) => res.map_err(Error::CheckInherents)?,
529                        None => return Err(Error::CheckInherentsUnhandled(i)),
530                    }
531                }
532            }
533        }
534
535        Ok(())
536    }
537}
538
539#[async_trait::async_trait]
540impl<PosTable, Block, Client, Inner, CIDP, AS> BlockImport<Block>
541    for SubspaceBlockImport<PosTable, Block, Client, Inner, CIDP, AS>
542where
543    PosTable: Table,
544    Block: BlockT,
545    Inner: BlockImport<Block, Error = sp_consensus::Error> + Send + Sync,
546    Client: ProvideRuntimeApi<Block>
547        + BlockBackend<Block>
548        + HeaderBackend<Block>
549        + AuxStore
550        + Send
551        + Sync,
552    Client::Api: BlockBuilderApi<Block> + SubspaceApi<Block, PublicKey> + ApiExt<Block>,
553    CIDP: CreateInherentDataProviders<Block, ()> + Send + Sync + 'static,
554    AS: AuxStore + Send + Sync + 'static,
555    BlockNumber: From<NumberFor<Block>>,
556{
557    type Error = Error<Block::Header>;
558
559    async fn import_block(
560        &self,
561        mut block: BlockImportParams<Block>,
562    ) -> Result<ImportResult, Self::Error> {
563        let block_hash = block.post_hash();
564        let block_number = *block.header.number();
565
566        // Early exit if block already in chain
567        match self.client.status(block_hash)? {
568            sp_blockchain::BlockStatus::InChain => {
569                block.fork_choice = Some(ForkChoiceStrategy::Custom(false));
570                return self
571                    .inner
572                    .import_block(block)
573                    .await
574                    .map_err(Error::InnerBlockImportError);
575            }
576            sp_blockchain::BlockStatus::Unknown => {}
577        }
578
579        let subspace_digest_items = extract_subspace_digest_items(&block.header)?;
580
581        // Only do import verification if we do not have the state already. If state needs to be
582        // applied this means verification and execution already happened before and doesn't need to
583        // be done again here (often can't because parent block would be missing in special sync
584        // modes).
585        if !matches!(block.state_action, StateAction::ApplyChanges(_)) {
586            let root_plot_public_key = self
587                .client
588                .runtime_api()
589                .root_plot_public_key(*block.header.parent_hash())?;
590
591            self.block_import_verification(
592                block_hash,
593                block.header.clone(),
594                block.body.clone(),
595                &root_plot_public_key,
596                &subspace_digest_items,
597                &block.justifications,
598            )
599            .await?;
600        }
601
602        let parent_weight = if block_number.is_one() {
603            0
604        } else {
605            // Parent block weight might be missing in special sync modes where block is imported in
606            // the middle of the blockchain history directly
607            aux_schema::load_block_weight(self.client.as_ref(), block.header.parent_hash())?
608                .unwrap_or_default()
609        };
610
611        let added_weight = calculate_block_weight(subspace_digest_items.solution_range);
612        let total_weight = parent_weight + added_weight;
613
614        aux_schema::write_block_weight(block_hash, total_weight, |values| {
615            block
616                .auxiliary
617                .extend(values.iter().map(|(k, v)| (k.to_vec(), Some(v.to_vec()))))
618        });
619
620        for (&segment_index, segment_commitment) in &subspace_digest_items.segment_commitments {
621            let found_segment_commitment = self
622                .segment_headers_store
623                .get_segment_header(segment_index)
624                .ok_or_else(|| Error::SegmentHeaderNotFound(segment_index))?
625                .segment_commitment();
626
627            if &found_segment_commitment != segment_commitment {
628                warn!(
629                    "Different segment commitment for segment index {} was found in storage, \
630                    likely fork below archiving point. expected {:?}, found {:?}",
631                    segment_index, segment_commitment, found_segment_commitment
632                );
633                return Err(Error::DifferentSegmentCommitment(segment_index));
634            }
635        }
636
637        // The fork choice rule is that we pick the heaviest chain (i.e. smallest solution range),
638        // if there's a tie we go with the longest chain
639        let fork_choice = {
640            let info = self.client.info();
641
642            let last_best_weight = if &info.best_hash == block.header.parent_hash() {
643                // the parent=genesis case is already covered for loading parent weight, so we don't
644                // need to cover again here
645                parent_weight
646            } else {
647                // Best block weight might be missing in special sync modes where block is imported
648                // in the middle of the blockchain history right after genesis
649                aux_schema::load_block_weight(&*self.client, info.best_hash)?.unwrap_or_default()
650            };
651
652            ForkChoiceStrategy::Custom(total_weight > last_best_weight)
653        };
654        block.fork_choice = Some(fork_choice);
655
656        let (acknowledgement_sender, mut acknowledgement_receiver) = mpsc::channel(0);
657
658        self.subspace_link
659            .block_importing_notification_sender
660            .notify(move || BlockImportingNotification {
661                block_number,
662                acknowledgement_sender,
663            });
664
665        while acknowledgement_receiver.next().await.is_some() {
666            // Wait for all the acknowledgements to finish.
667        }
668
669        self.inner
670            .import_block(block)
671            .await
672            .map_err(Error::InnerBlockImportError)
673    }
674
675    async fn check_block(
676        &self,
677        block: BlockCheckParams<Block>,
678    ) -> Result<ImportResult, Self::Error> {
679        self.inner.check_block(block).await.map_err(Into::into)
680    }
681}