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 std::marker::PhantomData;
39use std::sync::Arc;
40use subspace_core_primitives::sectors::SectorId;
41use subspace_core_primitives::segments::{HistorySize, SegmentHeader, SegmentIndex};
42use subspace_core_primitives::solutions::SolutionRange;
43use subspace_core_primitives::{BlockNumber, PublicKey};
44use subspace_proof_of_space::Table;
45use subspace_verification::{PieceCheckParams, VerifySolutionParams, calculate_block_weight};
46use tracing::warn;
47
48#[derive(Debug, Clone)]
51pub struct BlockImportingNotification<Block>
52where
53 Block: BlockT,
54{
55 pub block_number: NumberFor<Block>,
57 pub acknowledgement_sender: mpsc::Sender<()>,
60}
61use subspace_verification::Error as VerificationPrimitiveError;
62
63#[derive(Debug, thiserror::Error)]
65pub enum Error<Header: HeaderT> {
66 #[error("Inner block import error: {0}")]
68 InnerBlockImportError(#[from] sp_consensus::Error),
69 #[error("Digest item error: {0}")]
71 DigestItemError(#[from] sp_consensus_subspace::digests::Error),
72 #[error("Parent ({0}) of {1} unavailable. Cannot import")]
74 ParentUnavailable(Header::Hash, Header::Hash),
75 #[error("Genesis block unavailable. Cannot import")]
77 GenesisUnavailable,
78 #[error("Slot number must increase: parent slot: {0}, this slot: {1}")]
80 SlotMustIncrease(Slot, Slot),
81 #[error("Header {0:?} has a bad seal")]
83 HeaderBadSeal(Header::Hash),
84 #[error("Header {0:?} is unsealed")]
86 HeaderUnsealed(Header::Hash),
87 #[error("Bad reward signature on {0:?}")]
89 BadRewardSignature(Header::Hash),
90 #[error("Missing Subspace justification")]
92 MissingSubspaceJustification,
93 #[error("Invalid Subspace justification: {0}")]
95 InvalidSubspaceJustification(parity_scale_codec::Error),
96 #[error("Invalid Subspace justification contents")]
98 InvalidSubspaceJustificationContents,
99 #[error("Invalid proof of time")]
101 InvalidProofOfTime,
102 #[error(
104 "Solution distance {solution_distance} is outside of solution range \
105 {half_solution_range} (half of actual solution range) for slot {slot}"
106 )]
107 OutsideOfSolutionRange {
108 slot: Slot,
110 half_solution_range: SolutionRange,
112 solution_distance: SolutionRange,
114 },
115 #[error("Invalid proof of space")]
117 InvalidProofOfSpace,
118 #[error("Invalid audit chunk offset")]
120 InvalidAuditChunkOffset,
121 #[error("Invalid chunk: {0}")]
123 InvalidChunk(String),
124 #[error("Invalid chunk witness")]
126 InvalidChunkWitness,
127 #[error("Piece verification failed")]
129 InvalidPieceOffset {
130 slot: Slot,
132 piece_offset: u16,
134 max_pieces_in_sector: u16,
136 },
137 #[error("History size {solution} is in the future, current is {current}")]
139 FutureHistorySize {
140 current: HistorySize,
142 solution: HistorySize,
144 },
145 #[error("Piece verification failed for slot {0}")]
147 InvalidPiece(Slot),
148 #[error("Invalid solution range for block {0}")]
150 InvalidSolutionRange(Header::Hash),
151 #[error("Invalid set of segment headers")]
153 InvalidSetOfSegmentHeaders,
154 #[error("Stored segment header extrinsic was not found: {0:?}")]
156 SegmentHeadersExtrinsicNotFound(Vec<SegmentHeader>),
157 #[error("Segment header for index {0} not found")]
159 SegmentHeaderNotFound(SegmentIndex),
160 #[error(
162 "Different segment commitment for segment index {0} was found in storage, likely fork \
163 below archiving point"
164 )]
165 DifferentSegmentCommitment(SegmentIndex),
166 #[error("Segment commitment for segment index {0} not found")]
168 SegmentCommitmentNotFound(SegmentIndex),
169 #[error("Sector expired")]
171 SectorExpired {
172 expiration_history_size: HistorySize,
174 current_history_size: HistorySize,
176 },
177 #[error("Invalid history size")]
179 InvalidHistorySize,
180 #[error("Only root plot public key is allowed")]
182 OnlyRootPlotPublicKeyAllowed,
183 #[error("Checking inherents failed: {0}")]
185 CheckInherents(sp_inherents::Error),
186 #[error("Checking inherents unhandled error: {}", String::from_utf8_lossy(.0))]
188 CheckInherentsUnhandled(sp_inherents::InherentIdentifier),
189 #[error("Creating inherents failed: {0}")]
191 CreateInherents(sp_inherents::Error),
192 #[error(transparent)]
194 Client(#[from] sp_blockchain::Error),
195 #[error(transparent)]
197 RuntimeApi(#[from] ApiError),
198}
199
200impl<Header> From<VerificationError<Header>> for Error<Header>
201where
202 Header: HeaderT,
203{
204 #[inline]
205 fn from(error: VerificationError<Header>) -> Self {
206 match error {
207 VerificationError::HeaderBadSeal(block_hash) => Error::HeaderBadSeal(block_hash),
208 VerificationError::HeaderUnsealed(block_hash) => Error::HeaderUnsealed(block_hash),
209 VerificationError::BadRewardSignature(block_hash) => {
210 Error::BadRewardSignature(block_hash)
211 }
212 VerificationError::MissingSubspaceJustification => Error::MissingSubspaceJustification,
213 VerificationError::InvalidSubspaceJustification(error) => {
214 Error::InvalidSubspaceJustification(error)
215 }
216 VerificationError::InvalidSubspaceJustificationContents => {
217 Error::InvalidSubspaceJustificationContents
218 }
219 VerificationError::InvalidProofOfTime => Error::InvalidProofOfTime,
220 VerificationError::VerificationError(slot, error) => match error {
221 VerificationPrimitiveError::InvalidPieceOffset {
222 piece_offset,
223 max_pieces_in_sector,
224 } => Error::InvalidPieceOffset {
225 slot,
226 piece_offset,
227 max_pieces_in_sector,
228 },
229 VerificationPrimitiveError::FutureHistorySize { current, solution } => {
230 Error::FutureHistorySize { current, solution }
231 }
232 VerificationPrimitiveError::InvalidPiece => Error::InvalidPiece(slot),
233 VerificationPrimitiveError::OutsideSolutionRange {
234 half_solution_range,
235 solution_distance,
236 } => Error::OutsideOfSolutionRange {
237 slot,
238 half_solution_range,
239 solution_distance,
240 },
241 VerificationPrimitiveError::InvalidProofOfSpace => Error::InvalidProofOfSpace,
242 VerificationPrimitiveError::InvalidAuditChunkOffset => {
243 Error::InvalidAuditChunkOffset
244 }
245 VerificationPrimitiveError::InvalidChunk(error) => Error::InvalidChunk(error),
246 VerificationPrimitiveError::InvalidChunkWitness => Error::InvalidChunkWitness,
247 VerificationPrimitiveError::SectorExpired {
248 expiration_history_size,
249 current_history_size,
250 } => Error::SectorExpired {
251 expiration_history_size,
252 current_history_size,
253 },
254 VerificationPrimitiveError::InvalidHistorySize => Error::InvalidHistorySize,
255 },
256 }
257 }
258}
259
260impl<Header> From<Error<Header>> for String
261where
262 Header: HeaderT,
263{
264 #[inline]
265 fn from(error: Error<Header>) -> String {
266 error.to_string()
267 }
268}
269
270pub struct SubspaceBlockImport<PosTable, Block, Client, I, CIDP, AS>
272where
273 Block: BlockT,
274{
275 inner: I,
276 client: Arc<Client>,
277 subspace_link: SubspaceLink<Block>,
278 create_inherent_data_providers: CIDP,
279 segment_headers_store: SegmentHeadersStore<AS>,
280 pot_verifier: PotVerifier,
281 _pos_table: PhantomData<PosTable>,
282}
283
284impl<PosTable, Block, I, Client, CIDP, AS> Clone
285 for SubspaceBlockImport<PosTable, Block, Client, I, CIDP, AS>
286where
287 Block: BlockT,
288 I: Clone,
289 CIDP: Clone,
290{
291 fn clone(&self) -> Self {
292 SubspaceBlockImport {
293 inner: self.inner.clone(),
294 client: self.client.clone(),
295 subspace_link: self.subspace_link.clone(),
296 create_inherent_data_providers: self.create_inherent_data_providers.clone(),
297 segment_headers_store: self.segment_headers_store.clone(),
298 pot_verifier: self.pot_verifier.clone(),
299 _pos_table: PhantomData,
300 }
301 }
302}
303
304impl<PosTable, Block, Client, I, CIDP, AS> SubspaceBlockImport<PosTable, Block, Client, I, CIDP, AS>
305where
306 PosTable: Table,
307 Block: BlockT,
308 Client: ProvideRuntimeApi<Block> + BlockBackend<Block> + HeaderBackend<Block> + AuxStore,
309 Client::Api: BlockBuilderApi<Block> + SubspaceApi<Block, PublicKey> + ApiExt<Block>,
310 CIDP: CreateInherentDataProviders<Block, ()> + Send + Sync + 'static,
311 AS: AuxStore + Send + Sync + 'static,
312 BlockNumber: From<NumberFor<Block>>,
313{
314 pub fn new(
316 client: Arc<Client>,
317 block_import: I,
318 subspace_link: SubspaceLink<Block>,
319 create_inherent_data_providers: CIDP,
320 segment_headers_store: SegmentHeadersStore<AS>,
321 pot_verifier: PotVerifier,
322 ) -> Self {
323 Self {
324 client,
325 inner: block_import,
326 subspace_link,
327 create_inherent_data_providers,
328 segment_headers_store,
329 pot_verifier,
330 _pos_table: PhantomData,
331 }
332 }
333
334 async fn block_import_verification(
335 &self,
336 block_hash: Block::Hash,
337 header: Block::Header,
338 extrinsics: Option<Vec<Block::Extrinsic>>,
339 root_plot_public_key: &Option<PublicKey>,
340 subspace_digest_items: &SubspaceDigestItems<PublicKey>,
341 justifications: &Option<Justifications>,
342 ) -> Result<(), Error<Block::Header>> {
343 let block_number = *header.number();
344 let parent_hash = *header.parent_hash();
345
346 let pre_digest = &subspace_digest_items.pre_digest;
347 if let Some(root_plot_public_key) = root_plot_public_key
348 && &pre_digest.solution().public_key != root_plot_public_key
349 {
350 return Err(Error::OnlyRootPlotPublicKeyAllowed);
352 }
353
354 let parent_header = self
355 .client
356 .header(parent_hash)?
357 .ok_or(Error::ParentUnavailable(parent_hash, block_hash))?;
358
359 let parent_pre_digest = extract_pre_digest(&parent_header)?;
360 let parent_slot = parent_pre_digest.slot();
361
362 if pre_digest.slot() <= parent_slot {
364 return Err(Error::SlotMustIncrease(parent_slot, pre_digest.slot()));
365 }
366
367 let parent_subspace_digest_items = if block_number.is_one() {
368 None
369 } else {
370 Some(extract_subspace_digest_items::<_, PublicKey>(
371 &parent_header,
372 )?)
373 };
374
375 let correct_solution_range = if block_number.is_one() {
376 slot_worker::extract_solution_ranges_for_block(self.client.as_ref(), parent_hash)?.0
377 } else {
378 let parent_subspace_digest_items = parent_subspace_digest_items
379 .as_ref()
380 .expect("Always Some for non-first block; qed");
381
382 match parent_subspace_digest_items.next_solution_range {
383 Some(solution_range) => solution_range,
384 None => parent_subspace_digest_items.solution_range,
385 }
386 };
387
388 if subspace_digest_items.solution_range != correct_solution_range {
389 return Err(Error::InvalidSolutionRange(block_hash));
390 }
391
392 let chain_constants = self.subspace_link.chain_constants();
393
394 {
397 let runtime_api = self.client.runtime_api();
398 let parent_pot_parameters = runtime_api
399 .pot_parameters(parent_hash)
400 .map_err(|_| Error::ParentUnavailable(parent_hash, block_hash))?;
401
402 let pot_input = pot_next_slot_input::<Block>(
403 parent_header.number(),
404 parent_slot,
405 &parent_pot_parameters,
406 self.pot_verifier.genesis_seed(),
407 parent_pre_digest.pot_info().proof_of_time(),
408 );
409
410 if !self.pot_verifier.is_output_valid(
412 pot_input,
413 pre_digest.slot() - parent_slot,
414 pre_digest.pot_info().proof_of_time(),
415 parent_pot_parameters.next_parameters_change(),
416 ) {
417 return Err(Error::InvalidProofOfTime);
418 }
419
420 let Some(subspace_justification) = justifications
421 .as_ref()
422 .and_then(|justifications| {
423 justifications
424 .iter()
425 .find_map(SubspaceJustification::try_from_justification)
426 })
427 .transpose()
428 .map_err(Error::InvalidSubspaceJustification)?
429 else {
430 return Err(Error::MissingSubspaceJustification);
431 };
432
433 let SubspaceJustification::PotCheckpoints { seed, checkpoints } =
434 subspace_justification;
435
436 let future_slot = pre_digest.slot() + chain_constants.block_authoring_delay();
437
438 if block_number.is_one() {
439 if seed != self.pot_verifier.genesis_seed() {
441 return Err(Error::InvalidSubspaceJustificationContents);
442 }
443
444 if checkpoints.len() as u64 != *future_slot {
446 return Err(Error::InvalidSubspaceJustificationContents);
447 }
448 } else {
449 let parent_subspace_digest_items = parent_subspace_digest_items
450 .as_ref()
451 .expect("Always Some for non-first block; qed");
452
453 let parent_future_slot = parent_slot + chain_constants.block_authoring_delay();
454
455 let correct_input_parameters = PotNextSlotInput::derive(
456 subspace_digest_items.pot_slot_iterations,
457 parent_future_slot,
458 parent_subspace_digest_items
459 .pre_digest
460 .pot_info()
461 .future_proof_of_time(),
462 &subspace_digest_items.pot_parameters_change,
463 );
464
465 if seed != correct_input_parameters.seed {
466 return Err(Error::InvalidSubspaceJustificationContents);
467 }
468
469 if checkpoints.len() as u64 != (*future_slot - *parent_future_slot) {
471 return Err(Error::InvalidSubspaceJustificationContents);
472 }
473 }
474 }
475
476 let sector_id = SectorId::new(
477 pre_digest.solution().public_key.hash(),
478 pre_digest.solution().sector_index,
479 pre_digest.solution().history_size,
480 );
481
482 let max_pieces_in_sector = self
483 .client
484 .runtime_api()
485 .max_pieces_in_sector(parent_hash)?;
486 let piece_index = sector_id.derive_piece_index(
487 pre_digest.solution().piece_offset,
488 pre_digest.solution().history_size,
489 max_pieces_in_sector,
490 chain_constants.recent_segments(),
491 chain_constants.recent_history_fraction(),
492 );
493 let segment_index = piece_index.segment_index();
494
495 let segment_commitment = self
496 .segment_headers_store
497 .get_segment_header(segment_index)
498 .map(|segment_header| segment_header.segment_commitment())
499 .ok_or(Error::SegmentCommitmentNotFound(segment_index))?;
500
501 let sector_expiration_check_segment_commitment = self
502 .segment_headers_store
503 .get_segment_header(
504 subspace_digest_items
505 .pre_digest
506 .solution()
507 .history_size
508 .sector_expiration_check(chain_constants.min_sector_lifetime())
509 .ok_or(Error::InvalidHistorySize)?
510 .segment_index(),
511 )
512 .map(|segment_header| segment_header.segment_commitment());
513
514 subspace_verification::verify_solution::<PosTable, _>(
517 pre_digest.solution(),
518 pre_digest.slot().into(),
520 &VerifySolutionParams {
521 proof_of_time: subspace_digest_items.pre_digest.pot_info().proof_of_time(),
522 solution_range: subspace_digest_items.solution_range,
523 piece_check_params: Some(PieceCheckParams {
524 max_pieces_in_sector,
525 segment_commitment,
526 recent_segments: chain_constants.recent_segments(),
527 recent_history_fraction: chain_constants.recent_history_fraction(),
528 min_sector_lifetime: chain_constants.min_sector_lifetime(),
529 current_history_size: self.client.runtime_api().history_size(parent_hash)?,
530 sector_expiration_check_segment_commitment,
531 }),
532 },
533 &self.subspace_link.kzg,
534 )
535 .map_err(|error| VerificationError::VerificationError(pre_digest.slot(), error))?;
536
537 if let Some(extrinsics) = extrinsics {
541 let create_inherent_data_providers = self
542 .create_inherent_data_providers
543 .create_inherent_data_providers(parent_hash, ())
544 .await
545 .map_err(|error| Error::Client(sp_blockchain::Error::from(error)))?;
546
547 let inherent_data = create_inherent_data_providers
548 .create_inherent_data()
549 .await
550 .map_err(Error::CreateInherents)?;
551
552 let inherent_res = self.client.runtime_api().check_inherents(
553 parent_hash,
554 Block::new(header, extrinsics),
555 inherent_data,
556 )?;
557
558 if !inherent_res.ok() {
559 for (i, e) in inherent_res.into_errors() {
560 match create_inherent_data_providers
561 .try_handle_error(&i, &e)
562 .await
563 {
564 Some(res) => res.map_err(Error::CheckInherents)?,
565 None => return Err(Error::CheckInherentsUnhandled(i)),
566 }
567 }
568 }
569 }
570
571 Ok(())
572 }
573}
574
575#[async_trait::async_trait]
576impl<PosTable, Block, Client, Inner, CIDP, AS> BlockImport<Block>
577 for SubspaceBlockImport<PosTable, Block, Client, Inner, CIDP, AS>
578where
579 PosTable: Table,
580 Block: BlockT,
581 Inner: BlockImport<Block, Error = sp_consensus::Error> + Send + Sync,
582 Client: ProvideRuntimeApi<Block>
583 + BlockBackend<Block>
584 + HeaderBackend<Block>
585 + AuxStore
586 + Send
587 + Sync,
588 Client::Api: BlockBuilderApi<Block> + SubspaceApi<Block, PublicKey> + ApiExt<Block>,
589 CIDP: CreateInherentDataProviders<Block, ()> + Send + Sync + 'static,
590 AS: AuxStore + Send + Sync + 'static,
591 BlockNumber: From<NumberFor<Block>>,
592{
593 type Error = Error<Block::Header>;
594
595 async fn import_block(
596 &self,
597 mut block: BlockImportParams<Block>,
598 ) -> Result<ImportResult, Self::Error> {
599 let block_hash = block.post_hash();
600 let block_number = *block.header.number();
601
602 match self.client.status(block_hash)? {
604 sp_blockchain::BlockStatus::InChain => {
605 block.fork_choice = Some(ForkChoiceStrategy::Custom(false));
606 return self
607 .inner
608 .import_block(block)
609 .await
610 .map_err(Error::InnerBlockImportError);
611 }
612 sp_blockchain::BlockStatus::Unknown => {}
613 }
614
615 let subspace_digest_items = extract_subspace_digest_items(&block.header)?;
616
617 if !matches!(block.state_action, StateAction::ApplyChanges(_)) {
622 let root_plot_public_key = self
623 .client
624 .runtime_api()
625 .root_plot_public_key(*block.header.parent_hash())?;
626
627 self.block_import_verification(
628 block_hash,
629 block.header.clone(),
630 block.body.clone(),
631 &root_plot_public_key,
632 &subspace_digest_items,
633 &block.justifications,
634 )
635 .await?;
636 }
637
638 let parent_weight = if block_number.is_one() {
639 0
640 } else {
641 aux_schema::load_block_weight(self.client.as_ref(), block.header.parent_hash())?
644 .unwrap_or_default()
645 };
646
647 let added_weight = calculate_block_weight(subspace_digest_items.solution_range);
648 let total_weight = parent_weight.saturating_add(added_weight);
649
650 aux_schema::write_block_weight(block_hash, total_weight, |values| {
651 block
652 .auxiliary
653 .extend(values.iter().map(|(k, v)| (k.to_vec(), Some(v.to_vec()))))
654 });
655
656 for (&segment_index, segment_commitment) in &subspace_digest_items.segment_commitments {
657 let found_segment_commitment = self
658 .segment_headers_store
659 .get_segment_header(segment_index)
660 .ok_or_else(|| Error::SegmentHeaderNotFound(segment_index))?
661 .segment_commitment();
662
663 if &found_segment_commitment != segment_commitment {
664 warn!(
665 "Different segment commitment for segment index {} was found in storage, \
666 likely fork below archiving point. expected {:?}, found {:?}",
667 segment_index, segment_commitment, found_segment_commitment
668 );
669 return Err(Error::DifferentSegmentCommitment(segment_index));
670 }
671 }
672
673 let fork_choice = {
676 let info = self.client.info();
677
678 let last_best_weight = if &info.best_hash == block.header.parent_hash() {
679 parent_weight
682 } else {
683 aux_schema::load_block_weight(&*self.client, info.best_hash)?.unwrap_or_default()
686 };
687
688 ForkChoiceStrategy::Custom(total_weight > last_best_weight)
689 };
690 block.fork_choice = Some(fork_choice);
691
692 let (acknowledgement_sender, mut acknowledgement_receiver) = mpsc::channel(0);
693
694 self.subspace_link
695 .block_importing_notification_sender
696 .notify(move || BlockImportingNotification {
697 block_number,
698 acknowledgement_sender,
699 });
700
701 while acknowledgement_receiver.next().await.is_some() {
702 }
704
705 self.inner
706 .import_block(block)
707 .await
708 .map_err(Error::InnerBlockImportError)
709 }
710
711 async fn check_block(
712 &self,
713 block: BlockCheckParams<Block>,
714 ) -> Result<ImportResult, Self::Error> {
715 self.inner.check_block(block).await.map_err(Into::into)
716 }
717}