Skip to main content

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, 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
50/// Maximum number of canonical heights to tombstone per block import.
51const BLOCK_WEIGHT_SWEEP_BATCH: u32 = 10_000;
52
53/// Notification with number of the block that is about to be imported and acknowledgement sender
54/// that can be used to pause block production if desired.
55#[derive(Debug, Clone)]
56pub struct BlockImportingNotification<Block>
57where
58    Block: BlockT,
59{
60    /// Block number
61    pub block_number: NumberFor<Block>,
62    /// Sender for pausing the block import when operator is not fast enough to process
63    /// the consensus block.
64    pub acknowledgement_sender: mpsc::Sender<()>,
65}
66use subspace_verification::Error as VerificationPrimitiveError;
67
68/// Errors encountered by the Subspace authorship task.
69#[derive(Debug, thiserror::Error)]
70pub enum Error<Header: HeaderT> {
71    /// Inner block import error
72    #[error("Inner block import error: {0}")]
73    InnerBlockImportError(#[from] sp_consensus::Error),
74    /// Error during digest item extraction
75    #[error("Digest item error: {0}")]
76    DigestItemError(#[from] sp_consensus_subspace::digests::Error),
77    /// Parent unavailable. Cannot import
78    #[error("Parent ({0}) of {1} unavailable. Cannot import")]
79    ParentUnavailable(Header::Hash, Header::Hash),
80    /// Genesis block unavailable. Cannot import
81    #[error("Genesis block unavailable. Cannot import")]
82    GenesisUnavailable,
83    /// Slot number must increase
84    #[error("Slot number must increase: parent slot: {0}, this slot: {1}")]
85    SlotMustIncrease(Slot, Slot),
86    /// Header has a bad seal
87    #[error("Header {0:?} has a bad seal")]
88    HeaderBadSeal(Header::Hash),
89    /// Header is unsealed
90    #[error("Header {0:?} is unsealed")]
91    HeaderUnsealed(Header::Hash),
92    /// Bad reward signature
93    #[error("Bad reward signature on {0:?}")]
94    BadRewardSignature(Header::Hash),
95    /// Missing Subspace justification
96    #[error("Missing Subspace justification")]
97    MissingSubspaceJustification,
98    /// Invalid Subspace justification
99    #[error("Invalid Subspace justification: {0}")]
100    InvalidSubspaceJustification(parity_scale_codec::Error),
101    /// Invalid Subspace justification contents
102    #[error("Invalid Subspace justification contents")]
103    InvalidSubspaceJustificationContents,
104    /// Invalid proof of time
105    #[error("Invalid proof of time")]
106    InvalidProofOfTime,
107    /// Solution is outside of solution range
108    #[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        /// Time slot
114        slot: Slot,
115        /// Half of solution range
116        half_solution_range: SolutionRange,
117        /// Solution distance
118        solution_distance: SolutionRange,
119    },
120    /// Invalid proof of space
121    #[error("Invalid proof of space")]
122    InvalidProofOfSpace,
123    /// Invalid audit chunk offset
124    #[error("Invalid audit chunk offset")]
125    InvalidAuditChunkOffset,
126    /// Invalid chunk
127    #[error("Invalid chunk: {0}")]
128    InvalidChunk(String),
129    /// Invalid chunk witness
130    #[error("Invalid chunk witness")]
131    InvalidChunkWitness,
132    /// Piece verification failed
133    #[error("Piece verification failed")]
134    InvalidPieceOffset {
135        /// Time slot
136        slot: Slot,
137        /// Index of the piece that failed verification
138        piece_offset: u16,
139        /// How many pieces one sector is supposed to contain (max)
140        max_pieces_in_sector: u16,
141    },
142    /// History size is in the future
143    #[error("History size {solution} is in the future, current is {current}")]
144    FutureHistorySize {
145        /// Current history size
146        current: HistorySize,
147        /// History size solution was created for
148        solution: HistorySize,
149    },
150    /// Piece verification failed
151    #[error("Piece verification failed for slot {0}")]
152    InvalidPiece(Slot),
153    /// Block has invalid associated solution range
154    #[error("Invalid solution range for block {0}")]
155    InvalidSolutionRange(Header::Hash),
156    /// Invalid set of segment headers
157    #[error("Invalid set of segment headers")]
158    InvalidSetOfSegmentHeaders,
159    /// Stored segment header extrinsic was not found
160    #[error("Stored segment header extrinsic was not found: {0:?}")]
161    SegmentHeadersExtrinsicNotFound(Vec<SegmentHeader>),
162    /// Segment header not found
163    #[error("Segment header for index {0} not found")]
164    SegmentHeaderNotFound(SegmentIndex),
165    /// Different segment commitment found
166    #[error(
167        "Different segment commitment for segment index {0} was found in storage, likely fork \
168        below archiving point"
169    )]
170    DifferentSegmentCommitment(SegmentIndex),
171    /// Segment commitment not found
172    #[error("Segment commitment for segment index {0} not found")]
173    SegmentCommitmentNotFound(SegmentIndex),
174    /// Sector expired
175    #[error("Sector expired")]
176    SectorExpired {
177        /// Expiration history size
178        expiration_history_size: HistorySize,
179        /// Current history size
180        current_history_size: HistorySize,
181    },
182    /// Invalid history size
183    #[error("Invalid history size")]
184    InvalidHistorySize,
185    /// Only root plot public key is allowed
186    #[error("Only root plot public key is allowed")]
187    OnlyRootPlotPublicKeyAllowed,
188    /// Check inherents error
189    #[error("Checking inherents failed: {0}")]
190    CheckInherents(sp_inherents::Error),
191    /// Unhandled check inherents error
192    #[error("Checking inherents unhandled error: {}", String::from_utf8_lossy(.0))]
193    CheckInherentsUnhandled(sp_inherents::InherentIdentifier),
194    /// Create inherents error.
195    #[error("Creating inherents failed: {0}")]
196    CreateInherents(sp_inherents::Error),
197    /// Client error
198    #[error(transparent)]
199    Client(#[from] sp_blockchain::Error),
200    /// Runtime Api error.
201    #[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
275/// A block-import handler for Subspace.
276pub 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    /// Produce a Subspace block-import object to be used later on in the construction of an import-queue.
320    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            // Only root plot public key is allowed.
356            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        // Make sure that slot number is strictly increasing
368        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        // For PoT justifications we only need to check the seed and number of checkpoints, the rest
400        // was already checked during stateless block verification.
401        {
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            // Ensure proof of time is valid according to parent block
416            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                // In case of first block seed must match genesis seed
445                if seed != self.pot_verifier.genesis_seed() {
446                    return Err(Error::InvalidSubspaceJustificationContents);
447                }
448
449                // Number of checkpoints must match future slot number
450                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                // Number of checkpoints must match number of proofs that were not yet seen on chain
475                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        // Piece is not checked during initial block verification because it requires access to
520        // segment header and runtime, check it now.
521        subspace_verification::verify_solution::<PosTable, _>(
522            pre_digest.solution(),
523            // Slot was already checked during initial block verification
524            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 the body is passed through, we need to use the runtime to check that the
543        // internally-set timestamp in the inherents actually matches the slot set in the seal
544        // and segment headers in the inherents are set correctly.
545        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        // Early exit if the block is already in the chain, and never treat it as the best block.
608        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        // Only do import verification if we do not have the state already. If state needs to be
623        // applied this means verification and execution already happened before and doesn't need to
624        // be done again here (often can't because parent block would be missing in special sync
625        // modes).
626        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        // Find the solution range weight of the chain with the parent block at its tip.
644        let parent_weight = if block_number.is_one() {
645            // The genesis block is given a zero fork weight.
646            0
647        } else {
648            // Parent block fork weight might be missing in special sync modes where the block is
649            // imported in the middle of the blockchain history directly. For forks off the same
650            // parent, this doesn't change the comparison outcome.
651            //
652            // For forks off different parents, this only changes the outcome if the fork is over an
653            // era transition. (Solution ranges are fixed within the same era.) In this case, the
654            // rest of the connected nodes will quickly converge, because they have weights starting
655            // further back. This convergence will overwhelm the inconsistent fork choices of any
656            // nodes that are currently snap syncing.
657            aux_schema::load_block_weight(self.client.as_ref(), block.header.parent_hash())?
658                .unwrap_or_default()
659        };
660
661        // We prioritise narrower (numerically smaller) solution ranges, using an inverse
662        // calculation.
663        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        // The fork choice rule is the largest fork solution range weight.
706        //
707        // This almost always prioritises:
708        // - the longest chain (the largest number of solutions), and if there is a tie
709        // - the strictest solutions (the numerically smallest solution ranges).
710        //
711        // If these totals are equal:
712        // - each node keeps the block it already chose (the one that it processed first).
713        //
714        // If there is no previous best block, or the old best block is missing a weight, or has a
715        // zero weight:
716        // - the new block is chosen as the best block, as long as it has a non-zero weight.
717        // (The only blocks with zero weights are in the test runtime.)
718        //
719        // Solution ranges only change at the end of each era, where different block times can make
720        // the range in each fork different. This can lead to some edge cases:
721        // - one fork accepts a solution as within its range, but another with a narrower range
722        //   does not, or
723        // - a fork with a narrower range outweighs a fork with a wider range, leading to a reorg
724        //   to a fork with fewer blocks.
725        //
726        // But these will be resolved with very high probability after a few blocks, assuming the
727        // network is well-connected.
728        let fork_choice = {
729            let info = self.client.info();
730
731            let last_best_weight = if &info.best_hash == block.header.parent_hash() {
732                // The "parent is genesis" case is already covered when loading parent weight, so we don't
733                // need to cover it again here.
734                parent_weight
735            } else {
736                // The best block weight might be missing in special sync modes where the block is
737                // imported in the middle of the blockchain history, right after importing genesis.
738                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            // Wait for all the acknowledgements to finish.
756        }
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            // It is safe to return a default version of 1, since there will always be version 1.
775            .unwrap_or(1);
776
777        if subspace_api_version < 2 {
778            return Ok(result);
779        }
780
781        // this is the actual reference execution time for the given block weight.
782        // but we need add some buffer here to allow for block import processing
783        // apart from the actual execution. A 200ms should be good enough.
784        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}