1#![forbid(unsafe_code, missing_docs)]
4#![cfg_attr(not(feature = "std"), no_std)]
5#![feature(let_chains)]
6
7extern crate alloc;
8
9pub mod digests;
10pub mod inherents;
11
12use alloc::borrow::Cow;
13#[cfg(not(feature = "std"))]
14use alloc::string::String;
15#[cfg(not(feature = "std"))]
16use alloc::vec::Vec;
17use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
18use scale_info::TypeInfo;
19use sp_consensus_slots::{Slot, SlotDuration};
20use sp_core::H256;
21use sp_io::hashing;
22use sp_runtime::traits::NumberFor;
23use sp_runtime::{ConsensusEngineId, Justification};
24use sp_runtime_interface::pass_by::PassBy;
25use sp_runtime_interface::{pass_by, runtime_interface};
26use sp_std::num::NonZeroU32;
27use subspace_core_primitives::hashes::Blake3Hash;
28use subspace_core_primitives::pot::{PotCheckpoints, PotOutput, PotSeed};
29use subspace_core_primitives::segments::{
30 HistorySize, SegmentCommitment, SegmentHeader, SegmentIndex,
31};
32use subspace_core_primitives::solutions::{RewardSignature, Solution, SolutionRange};
33use subspace_core_primitives::{BlockHash, BlockNumber, PublicKey, SlotNumber};
34#[cfg(feature = "std")]
35use subspace_kzg::Kzg;
36#[cfg(feature = "std")]
37use subspace_proof_of_space::shim::ShimTable;
38#[cfg(feature = "std")]
39use subspace_proof_of_space::PosTableType;
40#[cfg(feature = "std")]
41use subspace_proof_of_space::Table;
42use subspace_verification::VerifySolutionParams;
43
44const SUBSPACE_ENGINE_ID: ConsensusEngineId = *b"SUB_";
46
47#[derive(Debug, Clone, Encode, Decode, TypeInfo)]
49pub enum SubspaceJustification {
50 #[codec(index = 0)]
52 PotCheckpoints {
53 seed: PotSeed,
55 checkpoints: Vec<PotCheckpoints>,
58 },
59}
60
61impl From<SubspaceJustification> for Justification {
62 #[inline]
63 fn from(justification: SubspaceJustification) -> Self {
64 (SUBSPACE_ENGINE_ID, justification.encode())
65 }
66}
67
68impl SubspaceJustification {
69 pub fn try_from_justification(
73 (consensus_engine_id, encoded_justification): &Justification,
74 ) -> Option<Result<Self, parity_scale_codec::Error>> {
75 (*consensus_engine_id == SUBSPACE_ENGINE_ID)
76 .then(|| Self::decode(&mut encoded_justification.as_slice()))
77 }
78
79 pub fn must_be_archived(&self) -> bool {
81 match self {
82 SubspaceJustification::PotCheckpoints { .. } => true,
83 }
84 }
85}
86
87#[derive(Debug, Copy, Clone, PartialEq, Eq, Decode, Encode, TypeInfo, MaxEncodedLen)]
89pub struct PotNextSlotInput {
90 pub slot: Slot,
92 pub slot_iterations: NonZeroU32,
94 pub seed: PotSeed,
96}
97
98impl PotNextSlotInput {
99 pub fn derive(
105 base_slot_iterations: NonZeroU32,
106 parent_slot: Slot,
107 parent_output: PotOutput,
108 pot_parameters_change: &Option<PotParametersChange>,
109 ) -> Self {
110 let next_slot = parent_slot + Slot::from(1);
111 let slot_iterations;
112 let seed;
113
114 if let Some(parameters_change) = pot_parameters_change
116 && parameters_change.slot <= next_slot
117 {
118 slot_iterations = parameters_change.slot_iterations;
119 if parameters_change.slot == next_slot {
121 seed = parent_output.seed_with_entropy(¶meters_change.entropy);
122 } else {
123 seed = parent_output.seed();
124 }
125 } else {
126 slot_iterations = base_slot_iterations;
127 seed = parent_output.seed();
128 }
129
130 PotNextSlotInput {
131 slot: next_slot,
132 slot_iterations,
133 seed,
134 }
135 }
136}
137
138#[derive(Debug, Copy, Clone, PartialEq, Eq, Decode, Encode, TypeInfo, MaxEncodedLen)]
140pub struct PotParametersChange {
141 pub slot: Slot,
143 pub slot_iterations: NonZeroU32,
145 pub entropy: Blake3Hash,
147}
148
149#[derive(Debug, Decode, Encode, Clone, PartialEq, Eq)]
151enum ConsensusLog {
152 #[codec(index = 0)]
155 PotSlotIterations(NonZeroU32),
156 #[codec(index = 1)]
158 SolutionRange(SolutionRange),
159 #[codec(index = 2)]
161 PotParametersChange(PotParametersChange),
162 #[codec(index = 3)]
164 NextSolutionRange(SolutionRange),
165 #[codec(index = 4)]
167 SegmentCommitment((SegmentIndex, SegmentCommitment)),
168 #[codec(index = 5)]
170 EnableSolutionRangeAdjustmentAndOverride(Option<SolutionRange>),
171 #[codec(index = 6)]
173 RootPlotPublicKeyUpdate(Option<PublicKey>),
174}
175
176#[derive(Debug, Clone, Eq, PartialEq, Encode, Decode, TypeInfo)]
178pub enum Vote<Number, Hash, RewardAddress> {
179 V0 {
181 height: Number,
185 parent_hash: Hash,
187 slot: Slot,
189 solution: Solution<RewardAddress>,
191 proof_of_time: PotOutput,
193 future_proof_of_time: PotOutput,
195 },
196}
197
198impl<Number, Hash, RewardAddress> Vote<Number, Hash, RewardAddress>
199where
200 Number: Encode,
201 Hash: Encode,
202 RewardAddress: Encode,
203{
204 pub fn solution(&self) -> &Solution<RewardAddress> {
206 let Self::V0 { solution, .. } = self;
207 solution
208 }
209
210 pub fn slot(&self) -> &Slot {
212 let Self::V0 { slot, .. } = self;
213 slot
214 }
215
216 pub fn hash(&self) -> H256 {
218 hashing::blake2_256(&self.encode()).into()
219 }
220}
221
222#[derive(Debug, Clone, Eq, PartialEq, Encode, Decode, TypeInfo)]
224pub struct SignedVote<Number, Hash, RewardAddress> {
225 pub vote: Vote<Number, Hash, RewardAddress>,
227 pub signature: RewardSignature,
229}
230
231#[derive(Decode, Encode, MaxEncodedLen, PartialEq, Eq, Clone, Copy, Debug, TypeInfo)]
233pub struct SolutionRanges {
234 pub current: u64,
236 pub next: Option<u64>,
238 pub voting_current: u64,
240 pub voting_next: Option<u64>,
242}
243
244impl Default for SolutionRanges {
245 #[inline]
246 fn default() -> Self {
247 Self {
248 current: u64::MAX,
249 next: None,
250 voting_current: u64::MAX,
251 voting_next: None,
252 }
253 }
254}
255
256#[derive(Debug, Encode, Decode, PartialEq, Eq, Clone, Copy, TypeInfo)]
258pub enum ChainConstants {
259 #[codec(index = 0)]
261 V0 {
262 confirmation_depth_k: BlockNumber,
264 block_authoring_delay: Slot,
266 era_duration: BlockNumber,
268 slot_probability: (u64, u64),
270 slot_duration: SlotDuration,
272 recent_segments: HistorySize,
274 recent_history_fraction: (HistorySize, HistorySize),
276 min_sector_lifetime: HistorySize,
278 },
279}
280
281impl ChainConstants {
282 pub fn confirmation_depth_k(&self) -> BlockNumber {
284 let Self::V0 {
285 confirmation_depth_k,
286 ..
287 } = self;
288 *confirmation_depth_k
289 }
290
291 pub fn era_duration(&self) -> BlockNumber {
293 let Self::V0 { era_duration, .. } = self;
294 *era_duration
295 }
296
297 pub fn block_authoring_delay(&self) -> Slot {
299 let Self::V0 {
300 block_authoring_delay,
301 ..
302 } = self;
303 *block_authoring_delay
304 }
305
306 pub fn slot_probability(&self) -> (u64, u64) {
308 let Self::V0 {
309 slot_probability, ..
310 } = self;
311 *slot_probability
312 }
313
314 pub fn slot_duration(&self) -> SlotDuration {
316 let Self::V0 { slot_duration, .. } = self;
317 *slot_duration
318 }
319
320 pub fn recent_segments(&self) -> HistorySize {
322 let Self::V0 {
323 recent_segments, ..
324 } = self;
325 *recent_segments
326 }
327
328 pub fn recent_history_fraction(&self) -> (HistorySize, HistorySize) {
330 let Self::V0 {
331 recent_history_fraction,
332 ..
333 } = self;
334 *recent_history_fraction
335 }
336
337 pub fn min_sector_lifetime(&self) -> HistorySize {
339 let Self::V0 {
340 min_sector_lifetime,
341 ..
342 } = self;
343 *min_sector_lifetime
344 }
345}
346
347#[derive(Debug, Encode, Decode)]
349pub struct WrappedSolution(Solution<()>);
350
351impl<RewardAddress> From<&Solution<RewardAddress>> for WrappedSolution {
352 #[inline]
353 fn from(solution: &Solution<RewardAddress>) -> Self {
354 Self(Solution {
355 public_key: solution.public_key,
356 reward_address: (),
357 sector_index: solution.sector_index,
358 history_size: solution.history_size,
359 piece_offset: solution.piece_offset,
360 record_commitment: solution.record_commitment,
361 record_witness: solution.record_witness,
362 chunk: solution.chunk,
363 chunk_witness: solution.chunk_witness,
364 proof_of_space: solution.proof_of_space,
365 })
366 }
367}
368
369impl PassBy for WrappedSolution {
370 type PassBy = pass_by::Codec<Self>;
371}
372
373#[derive(Debug, Encode, Decode)]
375pub struct WrappedVerifySolutionParams<'a>(Cow<'a, VerifySolutionParams>);
376
377impl<'a> From<&'a VerifySolutionParams> for WrappedVerifySolutionParams<'a> {
378 #[inline]
379 fn from(value: &'a VerifySolutionParams) -> Self {
380 Self(Cow::Borrowed(value))
381 }
382}
383
384impl PassBy for WrappedVerifySolutionParams<'_> {
385 type PassBy = pass_by::Codec<Self>;
386}
387
388#[derive(Debug, Encode, Decode)]
390pub struct WrappedPotOutput(PotOutput);
391
392impl From<PotOutput> for WrappedPotOutput {
393 #[inline]
394 fn from(value: PotOutput) -> Self {
395 Self(value)
396 }
397}
398
399impl PassBy for WrappedPotOutput {
400 type PassBy = pass_by::Codec<Self>;
401}
402
403#[cfg(feature = "std")]
404sp_externalities::decl_extension! {
405 pub struct KzgExtension(Kzg);
407}
408
409#[cfg(feature = "std")]
410impl KzgExtension {
411 pub fn new(kzg: Kzg) -> Self {
413 Self(kzg)
414 }
415}
416
417#[cfg(feature = "std")]
418sp_externalities::decl_extension! {
419 pub struct PosExtension(PosTableType);
421}
422
423#[cfg(feature = "std")]
424impl PosExtension {
425 pub fn new<PosTable>() -> Self
427 where
428 PosTable: Table,
429 {
430 Self(PosTable::TABLE_TYPE)
431 }
432}
433
434#[cfg(feature = "std")]
435sp_externalities::decl_extension! {
436 pub struct PotExtension(Box<dyn (Fn(BlockHash, SlotNumber, PotOutput, bool) -> bool) + Send + Sync>);
438}
439
440#[cfg(feature = "std")]
441impl PotExtension {
442 pub fn new(
444 verifier: Box<dyn (Fn(BlockHash, SlotNumber, PotOutput, bool) -> bool) + Send + Sync>,
445 ) -> Self {
446 Self(verifier)
447 }
448}
449
450#[runtime_interface]
452pub trait Consensus {
453 fn verify_solution(
456 &mut self,
457 solution: WrappedSolution,
458 slot: SlotNumber,
459 params: WrappedVerifySolutionParams<'_>,
460 ) -> Result<SolutionRange, String> {
461 #[cfg(feature = "runtime-benchmarks")]
465 {
466 subspace_verification::verify_solution::<ShimTable, _>(
467 &solution.0,
468 slot,
469 ¶ms.0,
470 kzg_instance(),
471 )
472 .map_err(|error| error.to_string())
473 }
474
475 #[cfg(not(feature = "runtime-benchmarks"))]
476 {
477 use sp_externalities::ExternalitiesExt;
478 #[cfg(feature = "std")]
479 use subspace_proof_of_space::chia::ChiaTable;
480 use subspace_proof_of_space::PosTableType;
481
482 let pos_table_type = self
483 .extension::<PosExtension>()
484 .expect("No `PosExtension` associated for the current context!")
485 .0;
486
487 let kzg = &self
488 .extension::<KzgExtension>()
489 .expect("No `KzgExtension` associated for the current context!")
490 .0;
491
492 match pos_table_type {
493 PosTableType::Chia => subspace_verification::verify_solution::<ChiaTable, _>(
494 &solution.0,
495 slot,
496 ¶ms.0,
497 kzg,
498 )
499 .map_err(|error| error.to_string()),
500 PosTableType::Shim => subspace_verification::verify_solution::<ShimTable, _>(
501 &solution.0,
502 slot,
503 ¶ms.0,
504 kzg,
505 )
506 .map_err(|error| error.to_string()),
507 }
508 }
509 }
510
511 fn is_proof_of_time_valid(
514 &mut self,
515 parent_hash: BlockHash,
516 slot: SlotNumber,
517 proof_of_time: WrappedPotOutput,
518 quick_verification: bool,
519 ) -> bool {
520 #[cfg(feature = "runtime-benchmarks")]
524 {
525 let _ = (slot, parent_hash, proof_of_time, quick_verification);
526 true
527 }
528
529 #[cfg(not(feature = "runtime-benchmarks"))]
530 {
531 use sp_externalities::ExternalitiesExt;
532
533 let verifier = &self
534 .extension::<PotExtension>()
535 .expect("No `PotExtension` associated for the current context!")
536 .0;
537
538 verifier(parent_hash, slot, proof_of_time.0, quick_verification)
539 }
540 }
541}
542
543#[derive(Debug, Clone, Encode, Decode, TypeInfo, MaxEncodedLen)]
545pub enum PotParameters {
546 V0 {
548 slot_iterations: NonZeroU32,
551 next_change: Option<PotParametersChange>,
553 },
554}
555
556impl PotParameters {
557 pub fn slot_iterations(&self) -> NonZeroU32 {
560 let Self::V0 {
561 slot_iterations, ..
562 } = self;
563
564 *slot_iterations
565 }
566
567 pub fn next_parameters_change(&self) -> Option<PotParametersChange> {
569 let Self::V0 { next_change, .. } = self;
570
571 *next_change
572 }
573}
574
575sp_api::decl_runtime_apis! {
576 pub trait SubspaceApi<RewardAddress: Encode + Decode> {
578 fn pot_parameters() -> PotParameters;
580
581 fn solution_ranges() -> SolutionRanges;
583
584 fn submit_vote_extrinsic(
587 signed_vote: SignedVote<
588 NumberFor<Block>,
589 Block::Hash,
590 RewardAddress,
591 >,
592 );
593
594 fn history_size() -> HistorySize;
596
597 fn max_pieces_in_sector() -> u16;
599
600 fn segment_commitment(segment_index: SegmentIndex) -> Option<SegmentCommitment>;
602
603 fn extract_segment_headers(ext: &Block::Extrinsic) -> Option<Vec<SegmentHeader >>;
605
606 fn is_inherent(ext: &Block::Extrinsic) -> bool;
608
609 fn root_plot_public_key() -> Option<PublicKey>;
611
612 fn should_adjust_solution_range() -> bool;
614
615 fn chain_constants() -> ChainConstants;
617 }
618}
619
620#[cfg(all(feature = "std", feature = "runtime-benchmarks"))]
621fn kzg_instance() -> &'static Kzg {
622 use std::sync::OnceLock;
623 static KZG: OnceLock<Kzg> = OnceLock::new();
624
625 KZG.get_or_init(Kzg::new)
626}