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