1#![forbid(unsafe_code, missing_docs)]
4#![cfg_attr(not(feature = "std"), no_std)]
5
6extern crate alloc;
7
8pub mod digests;
9pub mod inherents;
10
11use alloc::borrow::Cow;
12#[cfg(not(feature = "std"))]
13use alloc::string::String;
14#[cfg(not(feature = "std"))]
15use alloc::vec::Vec;
16use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
17use scale_info::TypeInfo;
18use sp_consensus_slots::{Slot, SlotDuration};
19use sp_core::H256;
20use sp_io::hashing;
21use sp_runtime::traits::NumberFor;
22use sp_runtime::{ConsensusEngineId, Justification};
23use sp_runtime_interface::pass_by::PassBy;
24use sp_runtime_interface::{pass_by, runtime_interface};
25use sp_std::num::NonZeroU32;
26use subspace_core_primitives::hashes::Blake3Hash;
27use subspace_core_primitives::pot::{PotCheckpoints, PotOutput, PotSeed};
28use subspace_core_primitives::segments::{
29 HistorySize, SegmentCommitment, SegmentHeader, SegmentIndex,
30};
31use subspace_core_primitives::solutions::{RewardSignature, Solution, SolutionRange};
32use subspace_core_primitives::{BlockHash, BlockNumber, PublicKey, SlotNumber};
33#[cfg(feature = "std")]
34use subspace_kzg::Kzg;
35#[cfg(feature = "std")]
36use subspace_proof_of_space::PosTableType;
37#[cfg(feature = "std")]
38use subspace_proof_of_space::Table;
39#[cfg(feature = "std")]
40use subspace_proof_of_space::shim::ShimTable;
41use subspace_verification::VerifySolutionParams;
42
43const SUBSPACE_ENGINE_ID: ConsensusEngineId = *b"SUB_";
45
46#[derive(Debug, Clone, Encode, Decode, TypeInfo)]
48pub enum SubspaceJustification {
49 #[codec(index = 0)]
51 PotCheckpoints {
52 seed: PotSeed,
54 checkpoints: Vec<PotCheckpoints>,
57 },
58}
59
60impl From<SubspaceJustification> for Justification {
61 #[inline]
62 fn from(justification: SubspaceJustification) -> Self {
63 (SUBSPACE_ENGINE_ID, justification.encode())
64 }
65}
66
67impl SubspaceJustification {
68 pub fn try_from_justification(
72 (consensus_engine_id, encoded_justification): &Justification,
73 ) -> Option<Result<Self, parity_scale_codec::Error>> {
74 (*consensus_engine_id == SUBSPACE_ENGINE_ID)
75 .then(|| Self::decode(&mut encoded_justification.as_slice()))
76 }
77
78 pub fn must_be_archived(&self) -> bool {
80 match self {
81 SubspaceJustification::PotCheckpoints { .. } => true,
82 }
83 }
84}
85
86#[derive(Debug, Copy, Clone, PartialEq, Eq, Decode, Encode, TypeInfo, MaxEncodedLen)]
88pub struct PotNextSlotInput {
89 pub slot: Slot,
91 pub slot_iterations: NonZeroU32,
93 pub seed: PotSeed,
95}
96
97impl PotNextSlotInput {
98 pub fn derive(
104 base_slot_iterations: NonZeroU32,
105 parent_slot: Slot,
106 parent_output: PotOutput,
107 pot_parameters_change: &Option<PotParametersChange>,
108 ) -> Self {
109 let next_slot = parent_slot + Slot::from(1);
110 let slot_iterations;
111 let seed;
112
113 if let Some(parameters_change) = pot_parameters_change
115 && parameters_change.slot <= next_slot
116 {
117 slot_iterations = parameters_change.slot_iterations;
118 if parameters_change.slot == next_slot {
120 seed = parent_output.seed_with_entropy(¶meters_change.entropy);
121 } else {
122 seed = parent_output.seed();
123 }
124 } else {
125 slot_iterations = base_slot_iterations;
126 seed = parent_output.seed();
127 }
128
129 PotNextSlotInput {
130 slot: next_slot,
131 slot_iterations,
132 seed,
133 }
134 }
135}
136
137#[derive(Debug, Copy, Clone, PartialEq, Eq, Decode, Encode, TypeInfo, MaxEncodedLen)]
139pub struct PotParametersChange {
140 pub slot: Slot,
142 pub slot_iterations: NonZeroU32,
144 pub entropy: Blake3Hash,
146}
147
148#[derive(Debug, Decode, Encode, Clone, PartialEq, Eq)]
150enum ConsensusLog {
151 #[codec(index = 0)]
154 PotSlotIterations(NonZeroU32),
155 #[codec(index = 1)]
157 SolutionRange(SolutionRange),
158 #[codec(index = 2)]
160 PotParametersChange(PotParametersChange),
161 #[codec(index = 3)]
163 NextSolutionRange(SolutionRange),
164 #[codec(index = 4)]
166 SegmentCommitment((SegmentIndex, SegmentCommitment)),
167 #[codec(index = 5)]
169 EnableSolutionRangeAdjustmentAndOverride(Option<SolutionRange>),
170 #[codec(index = 6)]
172 RootPlotPublicKeyUpdate(Option<PublicKey>),
173}
174
175#[derive(Debug, Clone, Eq, PartialEq, Encode, Decode, TypeInfo)]
177pub enum Vote<Number, Hash, RewardAddress> {
178 V0 {
180 height: Number,
184 parent_hash: Hash,
186 slot: Slot,
188 solution: Solution<RewardAddress>,
190 proof_of_time: PotOutput,
192 future_proof_of_time: PotOutput,
194 },
195}
196
197impl<Number, Hash, RewardAddress> Vote<Number, Hash, RewardAddress>
198where
199 Number: Encode,
200 Hash: Encode,
201 RewardAddress: Encode,
202{
203 pub fn solution(&self) -> &Solution<RewardAddress> {
205 let Self::V0 { solution, .. } = self;
206 solution
207 }
208
209 pub fn slot(&self) -> &Slot {
211 let Self::V0 { slot, .. } = self;
212 slot
213 }
214
215 pub fn hash(&self) -> H256 {
217 hashing::blake2_256(&self.encode()).into()
218 }
219}
220
221#[derive(Debug, Clone, Eq, PartialEq, Encode, Decode, TypeInfo)]
223pub struct SignedVote<Number, Hash, RewardAddress> {
224 pub vote: Vote<Number, Hash, RewardAddress>,
226 pub signature: RewardSignature,
228}
229
230#[derive(Decode, Encode, MaxEncodedLen, PartialEq, Eq, Clone, Copy, Debug, TypeInfo)]
232pub struct SolutionRanges {
233 pub current: u64,
235 pub next: Option<u64>,
237 pub voting_current: u64,
239 pub voting_next: Option<u64>,
241}
242
243impl Default for SolutionRanges {
244 #[inline]
245 fn default() -> Self {
246 Self {
247 current: u64::MAX,
248 next: None,
249 voting_current: u64::MAX,
250 voting_next: None,
251 }
252 }
253}
254
255#[derive(Debug, Encode, Decode, PartialEq, Eq, Clone, Copy, TypeInfo)]
257pub enum ChainConstants {
258 #[codec(index = 0)]
260 V0 {
261 confirmation_depth_k: BlockNumber,
263 block_authoring_delay: Slot,
265 era_duration: BlockNumber,
267 slot_probability: (u64, u64),
269 slot_duration: SlotDuration,
271 recent_segments: HistorySize,
273 recent_history_fraction: (HistorySize, HistorySize),
275 min_sector_lifetime: HistorySize,
277 },
278}
279
280impl ChainConstants {
281 pub fn confirmation_depth_k(&self) -> BlockNumber {
283 let Self::V0 {
284 confirmation_depth_k,
285 ..
286 } = self;
287 *confirmation_depth_k
288 }
289
290 pub fn era_duration(&self) -> BlockNumber {
292 let Self::V0 { era_duration, .. } = self;
293 *era_duration
294 }
295
296 pub fn block_authoring_delay(&self) -> Slot {
298 let Self::V0 {
299 block_authoring_delay,
300 ..
301 } = self;
302 *block_authoring_delay
303 }
304
305 pub fn slot_probability(&self) -> (u64, u64) {
307 let Self::V0 {
308 slot_probability, ..
309 } = self;
310 *slot_probability
311 }
312
313 pub fn slot_duration(&self) -> SlotDuration {
315 let Self::V0 { slot_duration, .. } = self;
316 *slot_duration
317 }
318
319 pub fn recent_segments(&self) -> HistorySize {
321 let Self::V0 {
322 recent_segments, ..
323 } = self;
324 *recent_segments
325 }
326
327 pub fn recent_history_fraction(&self) -> (HistorySize, HistorySize) {
329 let Self::V0 {
330 recent_history_fraction,
331 ..
332 } = self;
333 *recent_history_fraction
334 }
335
336 pub fn min_sector_lifetime(&self) -> HistorySize {
338 let Self::V0 {
339 min_sector_lifetime,
340 ..
341 } = self;
342 *min_sector_lifetime
343 }
344}
345
346#[derive(Debug, Encode, Decode)]
348pub struct WrappedSolution(Solution<()>);
349
350impl<RewardAddress> From<&Solution<RewardAddress>> for WrappedSolution {
351 #[inline]
352 fn from(solution: &Solution<RewardAddress>) -> Self {
353 Self(Solution {
354 public_key: solution.public_key,
355 reward_address: (),
356 sector_index: solution.sector_index,
357 history_size: solution.history_size,
358 piece_offset: solution.piece_offset,
359 record_commitment: solution.record_commitment,
360 record_witness: solution.record_witness,
361 chunk: solution.chunk,
362 chunk_witness: solution.chunk_witness,
363 proof_of_space: solution.proof_of_space,
364 })
365 }
366}
367
368impl PassBy for WrappedSolution {
369 type PassBy = pass_by::Codec<Self>;
370}
371
372#[derive(Debug, Encode, Decode)]
374pub struct WrappedVerifySolutionParams<'a>(Cow<'a, VerifySolutionParams>);
375
376impl<'a> From<&'a VerifySolutionParams> for WrappedVerifySolutionParams<'a> {
377 #[inline]
378 fn from(value: &'a VerifySolutionParams) -> Self {
379 Self(Cow::Borrowed(value))
380 }
381}
382
383impl PassBy for WrappedVerifySolutionParams<'_> {
384 type PassBy = pass_by::Codec<Self>;
385}
386
387#[derive(Debug, Encode, Decode)]
389pub struct WrappedPotOutput(PotOutput);
390
391impl From<PotOutput> for WrappedPotOutput {
392 #[inline]
393 fn from(value: PotOutput) -> Self {
394 Self(value)
395 }
396}
397
398impl PassBy for WrappedPotOutput {
399 type PassBy = pass_by::Codec<Self>;
400}
401
402#[cfg(feature = "std")]
403sp_externalities::decl_extension! {
404 pub struct KzgExtension(Kzg);
406}
407
408#[cfg(feature = "std")]
409impl KzgExtension {
410 pub fn new(kzg: Kzg) -> Self {
412 Self(kzg)
413 }
414}
415
416#[cfg(feature = "std")]
417sp_externalities::decl_extension! {
418 pub struct PosExtension(PosTableType);
420}
421
422#[cfg(feature = "std")]
423impl PosExtension {
424 pub fn new<PosTable>() -> Self
426 where
427 PosTable: Table,
428 {
429 Self(PosTable::TABLE_TYPE)
430 }
431}
432
433#[cfg(feature = "std")]
434sp_externalities::decl_extension! {
435 pub struct PotExtension(Box<dyn (Fn(BlockHash, SlotNumber, PotOutput, bool) -> bool) + Send + Sync>);
437}
438
439#[cfg(feature = "std")]
440impl PotExtension {
441 pub fn new(
443 verifier: Box<dyn (Fn(BlockHash, SlotNumber, PotOutput, bool) -> bool) + Send + Sync>,
444 ) -> Self {
445 Self(verifier)
446 }
447}
448
449#[runtime_interface]
451pub trait Consensus {
452 fn verify_solution(
455 &mut self,
456 solution: WrappedSolution,
457 slot: SlotNumber,
458 params: WrappedVerifySolutionParams<'_>,
459 ) -> Result<SolutionRange, String> {
460 #[cfg(feature = "runtime-benchmarks")]
464 {
465 subspace_verification::verify_solution::<ShimTable, _>(
466 &solution.0,
467 slot,
468 ¶ms.0,
469 kzg_instance(),
470 )
471 .map_err(|error| error.to_string())
472 }
473
474 #[cfg(not(feature = "runtime-benchmarks"))]
475 {
476 use sp_externalities::ExternalitiesExt;
477 use subspace_proof_of_space::PosTableType;
478 #[cfg(feature = "std")]
479 use subspace_proof_of_space::chia::ChiaTable;
480
481 let pos_table_type = self
482 .extension::<PosExtension>()
483 .expect("No `PosExtension` associated for the current context!")
484 .0;
485
486 let kzg = &self
487 .extension::<KzgExtension>()
488 .expect("No `KzgExtension` associated for the current context!")
489 .0;
490
491 match pos_table_type {
492 PosTableType::Chia => subspace_verification::verify_solution::<ChiaTable, _>(
493 &solution.0,
494 slot,
495 ¶ms.0,
496 kzg,
497 )
498 .map_err(|error| error.to_string()),
499 PosTableType::Shim => subspace_verification::verify_solution::<ShimTable, _>(
500 &solution.0,
501 slot,
502 ¶ms.0,
503 kzg,
504 )
505 .map_err(|error| error.to_string()),
506 }
507 }
508 }
509
510 fn is_proof_of_time_valid(
513 &mut self,
514 parent_hash: BlockHash,
515 slot: SlotNumber,
516 proof_of_time: WrappedPotOutput,
517 quick_verification: bool,
518 ) -> bool {
519 #[cfg(feature = "runtime-benchmarks")]
523 {
524 let _ = (slot, parent_hash, proof_of_time, quick_verification);
525 true
526 }
527
528 #[cfg(not(feature = "runtime-benchmarks"))]
529 {
530 use sp_externalities::ExternalitiesExt;
531
532 let verifier = &self
533 .extension::<PotExtension>()
534 .expect("No `PotExtension` associated for the current context!")
535 .0;
536
537 verifier(parent_hash, slot, proof_of_time.0, quick_verification)
538 }
539 }
540}
541
542#[derive(Debug, Clone, Encode, Decode, TypeInfo, MaxEncodedLen)]
544pub enum PotParameters {
545 V0 {
547 slot_iterations: NonZeroU32,
550 next_change: Option<PotParametersChange>,
552 },
553}
554
555impl PotParameters {
556 pub fn slot_iterations(&self) -> NonZeroU32 {
559 let Self::V0 {
560 slot_iterations, ..
561 } = self;
562
563 *slot_iterations
564 }
565
566 pub fn next_parameters_change(&self) -> Option<PotParametersChange> {
568 let Self::V0 { next_change, .. } = self;
569
570 *next_change
571 }
572}
573
574sp_api::decl_runtime_apis! {
575 pub trait SubspaceApi<RewardAddress: Encode + Decode> {
577 fn pot_parameters() -> PotParameters;
579
580 fn solution_ranges() -> SolutionRanges;
582
583 fn submit_vote_extrinsic(
586 signed_vote: SignedVote<
587 NumberFor<Block>,
588 Block::Hash,
589 RewardAddress,
590 >,
591 );
592
593 fn history_size() -> HistorySize;
595
596 fn max_pieces_in_sector() -> u16;
598
599 fn segment_commitment(segment_index: SegmentIndex) -> Option<SegmentCommitment>;
601
602 fn extract_segment_headers(ext: &Block::Extrinsic) -> Option<Vec<SegmentHeader >>;
604
605 fn is_inherent(ext: &Block::Extrinsic) -> bool;
607
608 fn root_plot_public_key() -> Option<PublicKey>;
610
611 fn should_adjust_solution_range() -> bool;
613
614 fn chain_constants() -> ChainConstants;
616 }
617}
618
619#[cfg(all(feature = "std", feature = "runtime-benchmarks"))]
620fn kzg_instance() -> &'static Kzg {
621 use std::sync::OnceLock;
622 static KZG: OnceLock<Kzg> = OnceLock::new();
623
624 KZG.get_or_init(Kzg::new)
625}