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("Piece verification failed for slot {0}")]
139 InvalidPiece(Slot),
140 #[error("Invalid solution range for block {0}")]
142 InvalidSolutionRange(Header::Hash),
143 #[error("Invalid set of segment headers")]
145 InvalidSetOfSegmentHeaders,
146 #[error("Stored segment header extrinsic was not found: {0:?}")]
148 SegmentHeadersExtrinsicNotFound(Vec<SegmentHeader>),
149 #[error("Segment header for index {0} not found")]
151 SegmentHeaderNotFound(SegmentIndex),
152 #[error(
154 "Different segment commitment for segment index {0} was found in storage, likely fork \
155 below archiving point"
156 )]
157 DifferentSegmentCommitment(SegmentIndex),
158 #[error("Segment commitment for segment index {0} not found")]
160 SegmentCommitmentNotFound(SegmentIndex),
161 #[error("Sector expired")]
163 SectorExpired {
164 expiration_history_size: HistorySize,
166 current_history_size: HistorySize,
168 },
169 #[error("Invalid history size")]
171 InvalidHistorySize,
172 #[error("Only root plot public key is allowed")]
174 OnlyRootPlotPublicKeyAllowed,
175 #[error("Checking inherents failed: {0}")]
177 CheckInherents(sp_inherents::Error),
178 #[error("Checking inherents unhandled error: {}", String::from_utf8_lossy(.0))]
180 CheckInherentsUnhandled(sp_inherents::InherentIdentifier),
181 #[error("Creating inherents failed: {0}")]
183 CreateInherents(sp_inherents::Error),
184 #[error(transparent)]
186 Client(#[from] sp_blockchain::Error),
187 #[error(transparent)]
189 RuntimeApi(#[from] ApiError),
190}
191
192impl<Header> From<VerificationError<Header>> for Error<Header>
193where
194 Header: HeaderT,
195{
196 #[inline]
197 fn from(error: VerificationError<Header>) -> Self {
198 match error {
199 VerificationError::HeaderBadSeal(block_hash) => Error::HeaderBadSeal(block_hash),
200 VerificationError::HeaderUnsealed(block_hash) => Error::HeaderUnsealed(block_hash),
201 VerificationError::BadRewardSignature(block_hash) => {
202 Error::BadRewardSignature(block_hash)
203 }
204 VerificationError::MissingSubspaceJustification => Error::MissingSubspaceJustification,
205 VerificationError::InvalidSubspaceJustification(error) => {
206 Error::InvalidSubspaceJustification(error)
207 }
208 VerificationError::InvalidSubspaceJustificationContents => {
209 Error::InvalidSubspaceJustificationContents
210 }
211 VerificationError::InvalidProofOfTime => Error::InvalidProofOfTime,
212 VerificationError::VerificationError(slot, error) => match error {
213 VerificationPrimitiveError::InvalidPieceOffset {
214 piece_offset,
215 max_pieces_in_sector,
216 } => Error::InvalidPieceOffset {
217 slot,
218 piece_offset,
219 max_pieces_in_sector,
220 },
221 VerificationPrimitiveError::InvalidPiece => Error::InvalidPiece(slot),
222 VerificationPrimitiveError::OutsideSolutionRange {
223 half_solution_range,
224 solution_distance,
225 } => Error::OutsideOfSolutionRange {
226 slot,
227 half_solution_range,
228 solution_distance,
229 },
230 VerificationPrimitiveError::InvalidProofOfSpace => Error::InvalidProofOfSpace,
231 VerificationPrimitiveError::InvalidAuditChunkOffset => {
232 Error::InvalidAuditChunkOffset
233 }
234 VerificationPrimitiveError::InvalidChunk(error) => Error::InvalidChunk(error),
235 VerificationPrimitiveError::InvalidChunkWitness => Error::InvalidChunkWitness,
236 VerificationPrimitiveError::SectorExpired {
237 expiration_history_size,
238 current_history_size,
239 } => Error::SectorExpired {
240 expiration_history_size,
241 current_history_size,
242 },
243 VerificationPrimitiveError::InvalidHistorySize => Error::InvalidHistorySize,
244 },
245 }
246 }
247}
248
249impl<Header> From<Error<Header>> for String
250where
251 Header: HeaderT,
252{
253 #[inline]
254 fn from(error: Error<Header>) -> String {
255 error.to_string()
256 }
257}
258
259pub struct SubspaceBlockImport<PosTable, Block, Client, I, CIDP, AS>
261where
262 Block: BlockT,
263{
264 inner: I,
265 client: Arc<Client>,
266 subspace_link: SubspaceLink<Block>,
267 create_inherent_data_providers: CIDP,
268 segment_headers_store: SegmentHeadersStore<AS>,
269 pot_verifier: PotVerifier,
270 _pos_table: PhantomData<PosTable>,
271}
272
273impl<PosTable, Block, I, Client, CIDP, AS> Clone
274 for SubspaceBlockImport<PosTable, Block, Client, I, CIDP, AS>
275where
276 Block: BlockT,
277 I: Clone,
278 CIDP: Clone,
279{
280 fn clone(&self) -> Self {
281 SubspaceBlockImport {
282 inner: self.inner.clone(),
283 client: self.client.clone(),
284 subspace_link: self.subspace_link.clone(),
285 create_inherent_data_providers: self.create_inherent_data_providers.clone(),
286 segment_headers_store: self.segment_headers_store.clone(),
287 pot_verifier: self.pot_verifier.clone(),
288 _pos_table: PhantomData,
289 }
290 }
291}
292
293impl<PosTable, Block, Client, I, CIDP, AS> SubspaceBlockImport<PosTable, Block, Client, I, CIDP, AS>
294where
295 PosTable: Table,
296 Block: BlockT,
297 Client: ProvideRuntimeApi<Block> + BlockBackend<Block> + HeaderBackend<Block> + AuxStore,
298 Client::Api: BlockBuilderApi<Block> + SubspaceApi<Block, PublicKey> + ApiExt<Block>,
299 CIDP: CreateInherentDataProviders<Block, ()> + Send + Sync + 'static,
300 AS: AuxStore + Send + Sync + 'static,
301 BlockNumber: From<NumberFor<Block>>,
302{
303 pub fn new(
305 client: Arc<Client>,
306 block_import: I,
307 subspace_link: SubspaceLink<Block>,
308 create_inherent_data_providers: CIDP,
309 segment_headers_store: SegmentHeadersStore<AS>,
310 pot_verifier: PotVerifier,
311 ) -> Self {
312 Self {
313 client,
314 inner: block_import,
315 subspace_link,
316 create_inherent_data_providers,
317 segment_headers_store,
318 pot_verifier,
319 _pos_table: PhantomData,
320 }
321 }
322
323 async fn block_import_verification(
324 &self,
325 block_hash: Block::Hash,
326 header: Block::Header,
327 extrinsics: Option<Vec<Block::Extrinsic>>,
328 root_plot_public_key: &Option<PublicKey>,
329 subspace_digest_items: &SubspaceDigestItems<PublicKey>,
330 justifications: &Option<Justifications>,
331 ) -> Result<(), Error<Block::Header>> {
332 let block_number = *header.number();
333 let parent_hash = *header.parent_hash();
334
335 let pre_digest = &subspace_digest_items.pre_digest;
336 if let Some(root_plot_public_key) = root_plot_public_key
337 && &pre_digest.solution().public_key != root_plot_public_key
338 {
339 return Err(Error::OnlyRootPlotPublicKeyAllowed);
341 }
342
343 let parent_header = self
344 .client
345 .header(parent_hash)?
346 .ok_or(Error::ParentUnavailable(parent_hash, block_hash))?;
347
348 let parent_pre_digest = extract_pre_digest(&parent_header)?;
349 let parent_slot = parent_pre_digest.slot();
350
351 if pre_digest.slot() <= parent_slot {
353 return Err(Error::SlotMustIncrease(parent_slot, pre_digest.slot()));
354 }
355
356 let parent_subspace_digest_items = if block_number.is_one() {
357 None
358 } else {
359 Some(extract_subspace_digest_items::<_, PublicKey>(
360 &parent_header,
361 )?)
362 };
363
364 let correct_solution_range = if block_number.is_one() {
365 slot_worker::extract_solution_ranges_for_block(self.client.as_ref(), parent_hash)?.0
366 } else {
367 let parent_subspace_digest_items = parent_subspace_digest_items
368 .as_ref()
369 .expect("Always Some for non-first block; qed");
370
371 match parent_subspace_digest_items.next_solution_range {
372 Some(solution_range) => solution_range,
373 None => parent_subspace_digest_items.solution_range,
374 }
375 };
376
377 if subspace_digest_items.solution_range != correct_solution_range {
378 return Err(Error::InvalidSolutionRange(block_hash));
379 }
380
381 let chain_constants = self.subspace_link.chain_constants();
382
383 {
386 let runtime_api = self.client.runtime_api();
387 let parent_pot_parameters = runtime_api
388 .pot_parameters(parent_hash)
389 .map_err(|_| Error::ParentUnavailable(parent_hash, block_hash))?;
390
391 let pot_input = pot_next_slot_input::<Block>(
392 parent_header.number(),
393 parent_slot,
394 &parent_pot_parameters,
395 self.pot_verifier.genesis_seed(),
396 parent_pre_digest.pot_info().proof_of_time(),
397 );
398
399 if !self.pot_verifier.is_output_valid(
401 pot_input,
402 pre_digest.slot() - parent_slot,
403 pre_digest.pot_info().proof_of_time(),
404 parent_pot_parameters.next_parameters_change(),
405 ) {
406 return Err(Error::InvalidProofOfTime);
407 }
408
409 let Some(subspace_justification) = justifications
410 .as_ref()
411 .and_then(|justifications| {
412 justifications
413 .iter()
414 .find_map(SubspaceJustification::try_from_justification)
415 })
416 .transpose()
417 .map_err(Error::InvalidSubspaceJustification)?
418 else {
419 return Err(Error::MissingSubspaceJustification);
420 };
421
422 let SubspaceJustification::PotCheckpoints { seed, checkpoints } =
423 subspace_justification;
424
425 let future_slot = pre_digest.slot() + chain_constants.block_authoring_delay();
426
427 if block_number.is_one() {
428 if seed != self.pot_verifier.genesis_seed() {
430 return Err(Error::InvalidSubspaceJustificationContents);
431 }
432
433 if checkpoints.len() as u64 != *future_slot {
435 return Err(Error::InvalidSubspaceJustificationContents);
436 }
437 } else {
438 let parent_subspace_digest_items = parent_subspace_digest_items
439 .as_ref()
440 .expect("Always Some for non-first block; qed");
441
442 let parent_future_slot = parent_slot + chain_constants.block_authoring_delay();
443
444 let correct_input_parameters = PotNextSlotInput::derive(
445 subspace_digest_items.pot_slot_iterations,
446 parent_future_slot,
447 parent_subspace_digest_items
448 .pre_digest
449 .pot_info()
450 .future_proof_of_time(),
451 &subspace_digest_items.pot_parameters_change,
452 );
453
454 if seed != correct_input_parameters.seed {
455 return Err(Error::InvalidSubspaceJustificationContents);
456 }
457
458 if checkpoints.len() as u64 != (*future_slot - *parent_future_slot) {
460 return Err(Error::InvalidSubspaceJustificationContents);
461 }
462 }
463 }
464
465 let sector_id = SectorId::new(
466 pre_digest.solution().public_key.hash(),
467 pre_digest.solution().sector_index,
468 pre_digest.solution().history_size,
469 );
470
471 let max_pieces_in_sector = self
472 .client
473 .runtime_api()
474 .max_pieces_in_sector(parent_hash)?;
475 let piece_index = sector_id.derive_piece_index(
476 pre_digest.solution().piece_offset,
477 pre_digest.solution().history_size,
478 max_pieces_in_sector,
479 chain_constants.recent_segments(),
480 chain_constants.recent_history_fraction(),
481 );
482 let segment_index = piece_index.segment_index();
483
484 let segment_commitment = self
485 .segment_headers_store
486 .get_segment_header(segment_index)
487 .map(|segment_header| segment_header.segment_commitment())
488 .ok_or(Error::SegmentCommitmentNotFound(segment_index))?;
489
490 let sector_expiration_check_segment_commitment = self
491 .segment_headers_store
492 .get_segment_header(
493 subspace_digest_items
494 .pre_digest
495 .solution()
496 .history_size
497 .sector_expiration_check(chain_constants.min_sector_lifetime())
498 .ok_or(Error::InvalidHistorySize)?
499 .segment_index(),
500 )
501 .map(|segment_header| segment_header.segment_commitment());
502
503 subspace_verification::verify_solution::<PosTable, _>(
506 pre_digest.solution(),
507 pre_digest.slot().into(),
509 &VerifySolutionParams {
510 proof_of_time: subspace_digest_items.pre_digest.pot_info().proof_of_time(),
511 solution_range: subspace_digest_items.solution_range,
512 piece_check_params: Some(PieceCheckParams {
513 max_pieces_in_sector,
514 segment_commitment,
515 recent_segments: chain_constants.recent_segments(),
516 recent_history_fraction: chain_constants.recent_history_fraction(),
517 min_sector_lifetime: chain_constants.min_sector_lifetime(),
518 current_history_size: self.client.runtime_api().history_size(parent_hash)?,
519 sector_expiration_check_segment_commitment,
520 }),
521 },
522 &self.subspace_link.kzg,
523 )
524 .map_err(|error| VerificationError::VerificationError(pre_digest.slot(), error))?;
525
526 if let Some(extrinsics) = extrinsics {
530 let create_inherent_data_providers = self
531 .create_inherent_data_providers
532 .create_inherent_data_providers(parent_hash, ())
533 .await
534 .map_err(|error| Error::Client(sp_blockchain::Error::from(error)))?;
535
536 let inherent_data = create_inherent_data_providers
537 .create_inherent_data()
538 .await
539 .map_err(Error::CreateInherents)?;
540
541 let inherent_res = self.client.runtime_api().check_inherents(
542 parent_hash,
543 Block::new(header, extrinsics),
544 inherent_data,
545 )?;
546
547 if !inherent_res.ok() {
548 for (i, e) in inherent_res.into_errors() {
549 match create_inherent_data_providers
550 .try_handle_error(&i, &e)
551 .await
552 {
553 Some(res) => res.map_err(Error::CheckInherents)?,
554 None => return Err(Error::CheckInherentsUnhandled(i)),
555 }
556 }
557 }
558 }
559
560 Ok(())
561 }
562}
563
564#[async_trait::async_trait]
565impl<PosTable, Block, Client, Inner, CIDP, AS> BlockImport<Block>
566 for SubspaceBlockImport<PosTable, Block, Client, Inner, CIDP, AS>
567where
568 PosTable: Table,
569 Block: BlockT,
570 Inner: BlockImport<Block, Error = sp_consensus::Error> + Send + Sync,
571 Client: ProvideRuntimeApi<Block>
572 + BlockBackend<Block>
573 + HeaderBackend<Block>
574 + AuxStore
575 + Send
576 + Sync,
577 Client::Api: BlockBuilderApi<Block> + SubspaceApi<Block, PublicKey> + ApiExt<Block>,
578 CIDP: CreateInherentDataProviders<Block, ()> + Send + Sync + 'static,
579 AS: AuxStore + Send + Sync + 'static,
580 BlockNumber: From<NumberFor<Block>>,
581{
582 type Error = Error<Block::Header>;
583
584 async fn import_block(
585 &self,
586 mut block: BlockImportParams<Block>,
587 ) -> Result<ImportResult, Self::Error> {
588 let block_hash = block.post_hash();
589 let block_number = *block.header.number();
590
591 match self.client.status(block_hash)? {
593 sp_blockchain::BlockStatus::InChain => {
594 block.fork_choice = Some(ForkChoiceStrategy::Custom(false));
595 return self
596 .inner
597 .import_block(block)
598 .await
599 .map_err(Error::InnerBlockImportError);
600 }
601 sp_blockchain::BlockStatus::Unknown => {}
602 }
603
604 let subspace_digest_items = extract_subspace_digest_items(&block.header)?;
605
606 if !matches!(block.state_action, StateAction::ApplyChanges(_)) {
611 let root_plot_public_key = self
612 .client
613 .runtime_api()
614 .root_plot_public_key(*block.header.parent_hash())?;
615
616 self.block_import_verification(
617 block_hash,
618 block.header.clone(),
619 block.body.clone(),
620 &root_plot_public_key,
621 &subspace_digest_items,
622 &block.justifications,
623 )
624 .await?;
625 }
626
627 let parent_weight = if block_number.is_one() {
628 0
629 } else {
630 aux_schema::load_block_weight(self.client.as_ref(), block.header.parent_hash())?
633 .unwrap_or_default()
634 };
635
636 let added_weight = calculate_block_weight(subspace_digest_items.solution_range);
637 let total_weight = parent_weight.saturating_add(added_weight);
638
639 aux_schema::write_block_weight(block_hash, total_weight, |values| {
640 block
641 .auxiliary
642 .extend(values.iter().map(|(k, v)| (k.to_vec(), Some(v.to_vec()))))
643 });
644
645 for (&segment_index, segment_commitment) in &subspace_digest_items.segment_commitments {
646 let found_segment_commitment = self
647 .segment_headers_store
648 .get_segment_header(segment_index)
649 .ok_or_else(|| Error::SegmentHeaderNotFound(segment_index))?
650 .segment_commitment();
651
652 if &found_segment_commitment != segment_commitment {
653 warn!(
654 "Different segment commitment for segment index {} was found in storage, \
655 likely fork below archiving point. expected {:?}, found {:?}",
656 segment_index, segment_commitment, found_segment_commitment
657 );
658 return Err(Error::DifferentSegmentCommitment(segment_index));
659 }
660 }
661
662 let fork_choice = {
665 let info = self.client.info();
666
667 let last_best_weight = if &info.best_hash == block.header.parent_hash() {
668 parent_weight
671 } else {
672 aux_schema::load_block_weight(&*self.client, info.best_hash)?.unwrap_or_default()
675 };
676
677 ForkChoiceStrategy::Custom(total_weight > last_best_weight)
678 };
679 block.fork_choice = Some(fork_choice);
680
681 let (acknowledgement_sender, mut acknowledgement_receiver) = mpsc::channel(0);
682
683 self.subspace_link
684 .block_importing_notification_sender
685 .notify(move || BlockImportingNotification {
686 block_number,
687 acknowledgement_sender,
688 });
689
690 while acknowledgement_receiver.next().await.is_some() {
691 }
693
694 self.inner
695 .import_block(block)
696 .await
697 .map_err(Error::InnerBlockImportError)
698 }
699
700 async fn check_block(
701 &self,
702 block: BlockCheckParams<Block>,
703 ) -> Result<ImportResult, Self::Error> {
704 self.inner.check_block(block).await.map_err(Into::into)
705 }
706}