1use futures::lock::Mutex;
17use rand::prelude::*;
18use rayon::prelude::*;
19use sc_client_api::backend::AuxStore;
20use sc_consensus::block_import::BlockImportParams;
21use sc_consensus::import_queue::Verifier;
22use sc_consensus_slots::check_equivocation;
23use sc_proof_of_time::verifier::PotVerifier;
24use sc_telemetry::{telemetry, TelemetryHandle, CONSENSUS_TRACE};
25use schnorrkel::context::SigningContext;
26use sp_api::ProvideRuntimeApi;
27use sp_block_builder::BlockBuilder as BlockBuilderApi;
28use sp_blockchain::HeaderBackend;
29use sp_consensus::BlockOrigin;
30use sp_consensus_slots::Slot;
31use sp_consensus_subspace::digests::{
32 extract_subspace_digest_items, CompatibleDigestItem, PreDigest, SubspaceDigestItems,
33};
34use sp_consensus_subspace::{ChainConstants, PotNextSlotInput, SubspaceApi, SubspaceJustification};
35use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor};
36use sp_runtime::{DigestItem, Justifications};
37use std::iter;
38use std::marker::PhantomData;
39use std::num::NonZeroUsize;
40use std::sync::atomic::{AtomicU32, Ordering};
41use std::sync::Arc;
42use std::thread::available_parallelism;
43use subspace_core_primitives::{BlockNumber, PublicKey};
44use subspace_kzg::Kzg;
45use subspace_proof_of_space::Table;
46use subspace_verification::{check_reward_signature, verify_solution, VerifySolutionParams};
47use tokio::runtime::Handle;
48use tracing::{debug, info, trace, warn};
49
50#[derive(Debug, Eq, PartialEq, thiserror::Error)]
52pub enum VerificationError<Header: HeaderT> {
53 #[error("Header {0:?} has a bad seal")]
55 HeaderBadSeal(Header::Hash),
56 #[error("Header {0:?} is unsealed")]
58 HeaderUnsealed(Header::Hash),
59 #[error("Bad reward signature on {0:?}")]
61 BadRewardSignature(Header::Hash),
62 #[error("Missing Subspace justification")]
64 MissingSubspaceJustification,
65 #[error("Invalid Subspace justification: {0}")]
67 InvalidSubspaceJustification(parity_scale_codec::Error),
68 #[error("Invalid Subspace justification contents")]
70 InvalidSubspaceJustificationContents,
71 #[error("Invalid proof of time")]
73 InvalidProofOfTime,
74 #[error("Verification error on slot {0:?}: {1:?}")]
76 VerificationError(Slot, subspace_verification::Error),
77}
78
79struct CheckedHeader<H> {
81 pre_header: H,
86 pre_digest: PreDigest<PublicKey>,
88 seal: DigestItem,
90}
91
92struct VerificationParams<'a, Header>
94where
95 Header: HeaderT + 'a,
96{
97 header: Header,
99 verify_solution_params: &'a VerifySolutionParams,
101}
102
103pub struct SubspaceVerifierOptions<Client> {
105 pub client: Arc<Client>,
107 pub chain_constants: ChainConstants,
109 pub kzg: Kzg,
111 pub telemetry: Option<TelemetryHandle>,
113 pub reward_signing_context: SigningContext,
115 pub sync_target_block_number: Arc<AtomicU32>,
117 pub is_authoring_blocks: bool,
119 pub pot_verifier: PotVerifier,
121}
122
123struct Inner<PosTable, Block, Client>
125where
126 Block: BlockT,
127{
128 client: Arc<Client>,
129 kzg: Kzg,
130 telemetry: Option<TelemetryHandle>,
131 chain_constants: ChainConstants,
132 reward_signing_context: SigningContext,
133 sync_target_block_number: Arc<AtomicU32>,
134 is_authoring_blocks: bool,
135 pot_verifier: PotVerifier,
136 equivocation_mutex: Mutex<()>,
137 _pos_table: PhantomData<PosTable>,
138 _block: PhantomData<Block>,
139}
140
141impl<PosTable, Block, Client> Inner<PosTable, Block, Client>
142where
143 PosTable: Table,
144 Block: BlockT,
145 BlockNumber: From<NumberFor<Block>>,
146 Client: HeaderBackend<Block> + ProvideRuntimeApi<Block> + AuxStore + 'static,
147 Client::Api: BlockBuilderApi<Block> + SubspaceApi<Block, PublicKey>,
148{
149 fn new(options: SubspaceVerifierOptions<Client>) -> Self {
151 let SubspaceVerifierOptions {
152 client,
153 chain_constants,
154 kzg,
155 telemetry,
156 reward_signing_context,
157 sync_target_block_number,
158 is_authoring_blocks,
159 pot_verifier,
160 } = options;
161
162 Self {
163 client,
164 kzg,
165 telemetry,
166 chain_constants,
167 reward_signing_context,
168 sync_target_block_number,
169 is_authoring_blocks,
170 pot_verifier,
171 equivocation_mutex: Mutex::default(),
172 _pos_table: Default::default(),
173 _block: Default::default(),
174 }
175 }
176
177 fn full_pot_verification(&self, block_number: NumberFor<Block>) -> bool {
179 let sync_target_block_number: BlockNumber =
180 self.sync_target_block_number.load(Ordering::Relaxed);
181 let Some(diff) = sync_target_block_number.checked_sub(BlockNumber::from(block_number))
182 else {
183 return true;
184 };
185
186 let sample_size = match diff {
187 ..=1_581 => {
188 return true;
189 }
190 1_582..=6_234 => 1_581,
191 6_235..=63_240 => 3_162 * (diff - 3_162) / (diff - 1),
192 63_241..=3_162_000 => 3_162,
193 _ => diff / 1_000,
194 };
195
196 let n = thread_rng().gen_range(0..=diff);
197
198 n < sample_size
199 }
200
201 fn check_header(
213 &self,
214 params: VerificationParams<'_, Block::Header>,
215 subspace_digest_items: SubspaceDigestItems<PublicKey>,
216 full_pot_verification: bool,
217 justifications: &Option<Justifications>,
218 ) -> Result<CheckedHeader<Block::Header>, VerificationError<Block::Header>> {
219 let VerificationParams {
220 mut header,
221 verify_solution_params,
222 } = params;
223
224 let pre_digest = subspace_digest_items.pre_digest;
225 let slot = pre_digest.slot();
226
227 let seal = header
228 .digest_mut()
229 .pop()
230 .ok_or_else(|| VerificationError::HeaderUnsealed(header.hash()))?;
231
232 let signature = seal
233 .as_subspace_seal()
234 .ok_or_else(|| VerificationError::HeaderBadSeal(header.hash()))?;
235
236 let pre_hash = header.hash();
238
239 {
243 let Some(subspace_justification) = justifications
244 .as_ref()
245 .and_then(|justifications| {
246 justifications
247 .iter()
248 .find_map(SubspaceJustification::try_from_justification)
249 })
250 .transpose()
251 .map_err(VerificationError::InvalidSubspaceJustification)?
252 else {
253 return Err(VerificationError::MissingSubspaceJustification);
254 };
255
256 let SubspaceJustification::PotCheckpoints { seed, checkpoints } =
257 subspace_justification;
258
259 if checkpoints.last().map(|checkpoints| checkpoints.output())
262 != Some(pre_digest.pot_info().future_proof_of_time())
263 {
264 return Err(VerificationError::InvalidSubspaceJustificationContents);
265 }
266
267 let future_slot = slot + self.chain_constants.block_authoring_delay();
268 let first_slot_to_check = Slot::from(
269 future_slot
270 .checked_sub(checkpoints.len() as u64 - 1)
271 .ok_or(VerificationError::InvalidProofOfTime)?,
272 );
273 let slot_iterations = subspace_digest_items
274 .pot_parameters_change
275 .as_ref()
276 .and_then(|parameters_change| {
277 (parameters_change.slot <= first_slot_to_check)
278 .then_some(parameters_change.slot_iterations)
279 })
280 .unwrap_or(subspace_digest_items.pot_slot_iterations);
281
282 let mut pot_input = PotNextSlotInput {
283 slot: first_slot_to_check,
284 slot_iterations,
285 seed,
286 };
287 let checkpoints_verification_input = iter::once((
289 pot_input,
290 *checkpoints
291 .first()
292 .expect("Not empty, contents was checked above; qed"),
293 ));
294 let checkpoints_verification_input = checkpoints_verification_input
295 .chain(checkpoints.windows(2).map(|checkpoints_pair| {
296 pot_input = PotNextSlotInput::derive(
297 pot_input.slot_iterations,
298 pot_input.slot,
299 checkpoints_pair[0].output(),
300 &subspace_digest_items.pot_parameters_change,
301 );
302
303 (pot_input, checkpoints_pair[1])
304 }))
305 .collect::<Vec<_>>();
306
307 let pot_verifier = &self.pot_verifier;
310 checkpoints_verification_input
311 .into_par_iter()
312 .find_map_first(|(pot_input, checkpoints)| {
313 if full_pot_verification {
314 if !pot_verifier.verify_checkpoints(
316 pot_input.seed,
317 pot_input.slot_iterations,
318 &checkpoints,
319 ) {
320 return Some(VerificationError::InvalidProofOfTime);
321 }
322 } else {
323 pot_verifier.inject_verified_checkpoints(
326 pot_input.seed,
327 pot_input.slot_iterations,
328 checkpoints,
329 );
330 }
331
332 None
334 })
335 .map_or(Ok(()), Err)?;
336 }
337
338 if check_reward_signature(
340 pre_hash.as_ref(),
341 &signature,
342 &pre_digest.solution().public_key,
343 &self.reward_signing_context,
344 )
345 .is_err()
346 {
347 return Err(VerificationError::BadRewardSignature(pre_hash));
348 }
349
350 verify_solution::<PosTable, _>(
352 pre_digest.solution(),
353 slot.into(),
354 verify_solution_params,
355 &self.kzg,
356 )
357 .map_err(|error| VerificationError::VerificationError(slot, error))?;
358
359 Ok(CheckedHeader {
360 pre_header: header,
361 pre_digest,
362 seal,
363 })
364 }
365
366 async fn check_and_report_equivocation(
367 &self,
368 slot_now: Slot,
369 slot: Slot,
370 header: &Block::Header,
371 author: &PublicKey,
372 origin: &BlockOrigin,
373 ) -> Result<(), String> {
374 if *origin == BlockOrigin::NetworkInitialSync {
377 return Ok(());
378 }
379
380 let _guard = self.equivocation_mutex.lock().await;
383
384 let equivocation_proof =
386 match check_equivocation(&*self.client, slot_now, slot, header, author)
387 .map_err(|error| error.to_string())?
388 {
389 Some(proof) => proof,
390 None => return Ok(()),
391 };
392
393 info!(
394 "Slot author {:?} is equivocating at slot {} with headers {:?} and {:?}",
395 author,
396 slot,
397 equivocation_proof.first_header.hash(),
398 equivocation_proof.second_header.hash(),
399 );
400
401 if self.is_authoring_blocks {
402 } else {
404 info!("Not submitting equivocation report because node is not authoring blocks");
405 }
406
407 Ok(())
408 }
409
410 async fn verify(
411 &self,
412 mut block: BlockImportParams<Block>,
413 ) -> Result<BlockImportParams<Block>, String> {
414 trace!(
415 origin = ?block.origin,
416 header = ?block.header,
417 justifications = ?block.justifications,
418 body = ?block.body,
419 "Verifying",
420 );
421
422 let best_number = self.client.info().best_number;
423 if *block.header.number() + self.chain_constants.confirmation_depth_k().into() < best_number
425 && matches!(block.origin, BlockOrigin::NetworkBroadcast)
426 {
427 debug!(
428 header = ?block.header,
429 %best_number,
430 "Rejecting block below archiving point"
431 );
432
433 return Err(format!(
434 "Rejecting block #{} below archiving point",
435 block.header.number()
436 ));
437 }
438
439 let hash = block.header.hash();
440
441 debug!(
442 "We have {:?} logs in this header",
443 block.header.digest().logs().len()
444 );
445
446 let subspace_digest_items =
447 extract_subspace_digest_items::<Block::Header, PublicKey>(&block.header)?;
448
449 let full_pot_verification = self.full_pot_verification(*block.header.number());
450
451 let checked_header = self
459 .check_header(
460 VerificationParams {
461 header: block.header.clone(),
462 verify_solution_params: &VerifySolutionParams {
463 proof_of_time: subspace_digest_items.pre_digest.pot_info().proof_of_time(),
464 solution_range: subspace_digest_items.solution_range,
465 piece_check_params: None,
466 },
467 },
468 subspace_digest_items,
469 full_pot_verification,
470 &block.justifications,
471 )
472 .map_err(|error| error.to_string())?;
473
474 let CheckedHeader {
475 pre_header,
476 pre_digest,
477 seal,
478 } = checked_header;
479
480 let slot = pre_digest.slot();
481 let diff_in_blocks = self
484 .sync_target_block_number
485 .load(Ordering::Relaxed)
486 .saturating_sub(BlockNumber::from(*pre_header.number()));
487 let slot_now = if diff_in_blocks > 0 {
488 slot + Slot::from(
489 u64::from(diff_in_blocks) * self.chain_constants.slot_probability().1
490 / self.chain_constants.slot_probability().0,
491 )
492 } else {
493 slot
494 };
495
496 if let Err(error) = self
500 .check_and_report_equivocation(
501 slot_now,
502 slot,
503 &block.header,
504 &pre_digest.solution().public_key,
505 &block.origin,
506 )
507 .await
508 {
509 warn!(
510 %error,
511 "Error checking/reporting Subspace equivocation"
512 );
513 }
514
515 trace!(?pre_header, "Checked header; importing");
516 telemetry!(
517 self.telemetry;
518 CONSENSUS_TRACE;
519 "subspace.checked_and_importing";
520 "pre_header" => ?pre_header,
521 );
522
523 block.header = pre_header;
524 block.post_digests.push(seal);
525 block.post_hash = Some(hash);
526
527 Ok(block)
528 }
529}
530
531pub struct SubspaceVerifier<PosTable, Block, Client>
533where
534 Block: BlockT,
535{
536 inner: Arc<Inner<PosTable, Block, Client>>,
537}
538
539impl<PosTable, Block, Client> SubspaceVerifier<PosTable, Block, Client>
540where
541 PosTable: Table,
542 Block: BlockT,
543 BlockNumber: From<NumberFor<Block>>,
544 Client: HeaderBackend<Block> + ProvideRuntimeApi<Block> + AuxStore + 'static,
545 Client::Api: BlockBuilderApi<Block> + SubspaceApi<Block, PublicKey>,
546{
547 pub fn new(options: SubspaceVerifierOptions<Client>) -> Self {
549 Self {
550 inner: Arc::new(Inner::new(options)),
551 }
552 }
553}
554
555#[async_trait::async_trait]
556impl<PosTable, Block, Client> Verifier<Block> for SubspaceVerifier<PosTable, Block, Client>
557where
558 PosTable: Table,
559 Block: BlockT,
560 BlockNumber: From<NumberFor<Block>>,
561 Client: HeaderBackend<Block> + ProvideRuntimeApi<Block> + AuxStore + 'static,
562 Client::Api: BlockBuilderApi<Block> + SubspaceApi<Block, PublicKey>,
563{
564 fn verification_concurrency(&self) -> NonZeroUsize {
565 available_parallelism().unwrap_or(NonZeroUsize::new(1).expect("Not zero; qed"))
566 }
567
568 async fn verify(
569 &self,
570 block: BlockImportParams<Block>,
571 ) -> Result<BlockImportParams<Block>, String> {
572 let inner = Arc::clone(&self.inner);
573 tokio::task::spawn_blocking(move || Handle::current().block_on(inner.verify(block)))
574 .await
575 .map_err(|error| format!("Failed to join block verification task: {error}"))?
576 }
577}