1use crate::archiver::SegmentHeadersStore;
15use crate::verifier::VerificationError;
16use crate::{SubspaceLink, aux_schema, slot_worker};
17use futures::StreamExt;
18use futures::channel::mpsc;
19use sc_client_api::BlockBackend;
20use sc_client_api::backend::AuxStore;
21use sc_consensus::StateAction;
22use sc_consensus::block_import::{
23 BlockCheckParams, BlockImport, BlockImportParams, ForkChoiceStrategy, ImportResult,
24};
25use sc_proof_of_time::source::pot_next_slot_input;
26use sc_proof_of_time::verifier::PotVerifier;
27use sp_api::{ApiError, ApiExt, ProvideRuntimeApi};
28use sp_block_builder::BlockBuilder as BlockBuilderApi;
29use sp_blockchain::HeaderBackend;
30use sp_consensus_slots::Slot;
31use sp_consensus_subspace::digests::{
32 SubspaceDigestItems, extract_pre_digest, extract_subspace_digest_items,
33};
34use sp_consensus_subspace::{PotNextSlotInput, SubspaceApi, SubspaceJustification};
35use sp_inherents::{CreateInherentDataProviders, InherentDataProvider};
36use sp_runtime::Justifications;
37use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor, One};
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#[derive(Debug, Clone)]
53pub struct BlockImportingNotification<Block>
54where
55 Block: BlockT,
56{
57 pub block_number: NumberFor<Block>,
59 pub acknowledgement_sender: mpsc::Sender<()>,
62}
63use subspace_verification::Error as VerificationPrimitiveError;
64
65#[derive(Debug, thiserror::Error)]
67pub enum Error<Header: HeaderT> {
68 #[error("Inner block import error: {0}")]
70 InnerBlockImportError(#[from] sp_consensus::Error),
71 #[error("Digest item error: {0}")]
73 DigestItemError(#[from] sp_consensus_subspace::digests::Error),
74 #[error("Parent ({0}) of {1} unavailable. Cannot import")]
76 ParentUnavailable(Header::Hash, Header::Hash),
77 #[error("Genesis block unavailable. Cannot import")]
79 GenesisUnavailable,
80 #[error("Slot number must increase: parent slot: {0}, this slot: {1}")]
82 SlotMustIncrease(Slot, Slot),
83 #[error("Header {0:?} has a bad seal")]
85 HeaderBadSeal(Header::Hash),
86 #[error("Header {0:?} is unsealed")]
88 HeaderUnsealed(Header::Hash),
89 #[error("Bad reward signature on {0:?}")]
91 BadRewardSignature(Header::Hash),
92 #[error("Missing Subspace justification")]
94 MissingSubspaceJustification,
95 #[error("Invalid Subspace justification: {0}")]
97 InvalidSubspaceJustification(parity_scale_codec::Error),
98 #[error("Invalid Subspace justification contents")]
100 InvalidSubspaceJustificationContents,
101 #[error("Invalid proof of time")]
103 InvalidProofOfTime,
104 #[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 slot: Slot,
112 half_solution_range: SolutionRange,
114 solution_distance: SolutionRange,
116 },
117 #[error("Invalid proof of space")]
119 InvalidProofOfSpace,
120 #[error("Invalid audit chunk offset")]
122 InvalidAuditChunkOffset,
123 #[error("Invalid chunk: {0}")]
125 InvalidChunk(String),
126 #[error("Invalid chunk witness")]
128 InvalidChunkWitness,
129 #[error("Piece verification failed")]
131 InvalidPieceOffset {
132 slot: Slot,
134 piece_offset: u16,
136 max_pieces_in_sector: u16,
138 },
139 #[error("History size {solution} is in the future, current is {current}")]
141 FutureHistorySize {
142 current: HistorySize,
144 solution: HistorySize,
146 },
147 #[error("Piece verification failed for slot {0}")]
149 InvalidPiece(Slot),
150 #[error("Invalid solution range for block {0}")]
152 InvalidSolutionRange(Header::Hash),
153 #[error("Invalid set of segment headers")]
155 InvalidSetOfSegmentHeaders,
156 #[error("Stored segment header extrinsic was not found: {0:?}")]
158 SegmentHeadersExtrinsicNotFound(Vec<SegmentHeader>),
159 #[error("Segment header for index {0} not found")]
161 SegmentHeaderNotFound(SegmentIndex),
162 #[error(
164 "Different segment commitment for segment index {0} was found in storage, likely fork \
165 below archiving point"
166 )]
167 DifferentSegmentCommitment(SegmentIndex),
168 #[error("Segment commitment for segment index {0} not found")]
170 SegmentCommitmentNotFound(SegmentIndex),
171 #[error("Sector expired")]
173 SectorExpired {
174 expiration_history_size: HistorySize,
176 current_history_size: HistorySize,
178 },
179 #[error("Invalid history size")]
181 InvalidHistorySize,
182 #[error("Only root plot public key is allowed")]
184 OnlyRootPlotPublicKeyAllowed,
185 #[error("Checking inherents failed: {0}")]
187 CheckInherents(sp_inherents::Error),
188 #[error("Checking inherents unhandled error: {}", String::from_utf8_lossy(.0))]
190 CheckInherentsUnhandled(sp_inherents::InherentIdentifier),
191 #[error("Creating inherents failed: {0}")]
193 CreateInherents(sp_inherents::Error),
194 #[error(transparent)]
196 Client(#[from] sp_blockchain::Error),
197 #[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
272pub 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 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 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 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 {
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 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 if seed != self.pot_verifier.genesis_seed() {
443 return Err(Error::InvalidSubspaceJustificationContents);
444 }
445
446 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 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 subspace_verification::verify_solution::<PosTable, _>(
519 pre_digest.solution(),
520 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 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 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 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 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 let fork_choice = {
678 let info = self.client.info();
679
680 let last_best_weight = if &info.best_hash == block.header.parent_hash() {
681 parent_weight
684 } else {
685 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 }
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 .unwrap_or(1);
725
726 if subspace_api_version < 2 {
727 return Ok(result);
728 }
729
730 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}