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