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, DecodeWithMemTracking, 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::{
24 AllocateAndReturnByCodec, PassFatPointerAndDecode, PassPointerAndReadCopy,
25};
26use sp_runtime_interface::runtime_interface;
27use sp_std::num::NonZeroU32;
28use sp_weights::Weight;
29use subspace_core_primitives::hashes::Blake3Hash;
30use subspace_core_primitives::pot::{PotCheckpoints, PotOutput, PotSeed};
31use subspace_core_primitives::segments::{
32 HistorySize, SegmentCommitment, SegmentHeader, SegmentIndex,
33};
34use subspace_core_primitives::solutions::{RewardSignature, Solution, SolutionRange};
35use subspace_core_primitives::{BlockHash, BlockNumber, PublicKey, SlotNumber};
36#[cfg(feature = "std")]
37use subspace_kzg::Kzg;
38#[cfg(feature = "std")]
39use subspace_proof_of_space::PosTableType;
40#[cfg(feature = "std")]
41use subspace_proof_of_space::Table;
42#[cfg(feature = "std")]
43use subspace_proof_of_space::shim::ShimTable;
44use subspace_verification::VerifySolutionParams;
45
46const SUBSPACE_ENGINE_ID: ConsensusEngineId = *b"SUB_";
48
49#[derive(Debug, Clone, Encode, Decode, TypeInfo)]
51pub enum SubspaceJustification {
52 #[codec(index = 0)]
54 PotCheckpoints {
55 seed: PotSeed,
57 checkpoints: Vec<PotCheckpoints>,
60 },
61}
62
63impl From<SubspaceJustification> for Justification {
64 #[inline]
65 fn from(justification: SubspaceJustification) -> Self {
66 (SUBSPACE_ENGINE_ID, justification.encode())
67 }
68}
69
70impl SubspaceJustification {
71 pub fn try_from_justification(
75 (consensus_engine_id, encoded_justification): &Justification,
76 ) -> Option<Result<Self, parity_scale_codec::Error>> {
77 (*consensus_engine_id == SUBSPACE_ENGINE_ID)
78 .then(|| Self::decode(&mut encoded_justification.as_slice()))
79 }
80
81 pub fn must_be_archived(&self) -> bool {
83 match self {
84 SubspaceJustification::PotCheckpoints { .. } => true,
85 }
86 }
87}
88
89#[derive(Debug, Copy, Clone, PartialEq, Eq, Decode, Encode, TypeInfo, MaxEncodedLen)]
91pub struct PotNextSlotInput {
92 pub slot: Slot,
94 pub slot_iterations: NonZeroU32,
96 pub seed: PotSeed,
98}
99
100impl PotNextSlotInput {
101 pub fn derive(
107 base_slot_iterations: NonZeroU32,
108 parent_slot: Slot,
109 parent_output: PotOutput,
110 pot_parameters_change: &Option<PotParametersChange>,
111 ) -> Self {
112 let next_slot = parent_slot + Slot::from(1);
113 let slot_iterations;
114 let seed;
115
116 if let Some(parameters_change) = pot_parameters_change
118 && parameters_change.slot <= next_slot
119 {
120 slot_iterations = parameters_change.slot_iterations;
121 if parameters_change.slot == next_slot {
123 seed = parent_output.seed_with_entropy(¶meters_change.entropy);
124 } else {
125 seed = parent_output.seed();
126 }
127 } else {
128 slot_iterations = base_slot_iterations;
129 seed = parent_output.seed();
130 }
131
132 PotNextSlotInput {
133 slot: next_slot,
134 slot_iterations,
135 seed,
136 }
137 }
138}
139
140#[derive(Debug, Copy, Clone, PartialEq, Eq, Decode, Encode, TypeInfo, MaxEncodedLen)]
142pub struct PotParametersChange {
143 pub slot: Slot,
145 pub slot_iterations: NonZeroU32,
147 pub entropy: Blake3Hash,
149}
150
151#[derive(Debug, Decode, Encode, Clone, PartialEq, Eq)]
153enum ConsensusLog {
154 #[codec(index = 0)]
157 PotSlotIterations(NonZeroU32),
158 #[codec(index = 1)]
160 SolutionRange(SolutionRange),
161 #[codec(index = 2)]
163 PotParametersChange(PotParametersChange),
164 #[codec(index = 3)]
166 NextSolutionRange(SolutionRange),
167 #[codec(index = 4)]
169 SegmentCommitment((SegmentIndex, SegmentCommitment)),
170 #[codec(index = 5)]
172 EnableSolutionRangeAdjustmentAndOverride(Option<SolutionRange>),
173 #[codec(index = 6)]
175 RootPlotPublicKeyUpdate(Option<PublicKey>),
176}
177
178#[derive(Debug, Clone, Eq, PartialEq, Encode, Decode, TypeInfo, DecodeWithMemTracking)]
180pub enum Vote<Number, Hash, RewardAddress> {
181 V0 {
183 height: Number,
187 parent_hash: Hash,
189 slot: Slot,
191 solution: Solution<RewardAddress>,
193 proof_of_time: PotOutput,
195 future_proof_of_time: PotOutput,
197 },
198}
199
200impl<Number, Hash, RewardAddress> Vote<Number, Hash, RewardAddress>
201where
202 Number: Encode,
203 Hash: Encode,
204 RewardAddress: Encode,
205{
206 pub fn solution(&self) -> &Solution<RewardAddress> {
208 let Self::V0 { solution, .. } = self;
209 solution
210 }
211
212 pub fn slot(&self) -> &Slot {
214 let Self::V0 { slot, .. } = self;
215 slot
216 }
217
218 pub fn hash(&self) -> H256 {
220 hashing::blake2_256(&self.encode()).into()
221 }
222}
223
224#[derive(Debug, Clone, Eq, PartialEq, Encode, Decode, TypeInfo, DecodeWithMemTracking)]
226pub struct SignedVote<Number, Hash, RewardAddress> {
227 pub vote: Vote<Number, Hash, RewardAddress>,
229 pub signature: RewardSignature,
231}
232
233#[derive(Decode, Encode, MaxEncodedLen, PartialEq, Eq, Clone, Copy, Debug, TypeInfo)]
235pub struct SolutionRanges {
236 pub current: u64,
238 pub next: Option<u64>,
240 pub voting_current: u64,
242 pub voting_next: Option<u64>,
244}
245
246impl Default for SolutionRanges {
247 #[inline]
248 fn default() -> Self {
249 Self {
250 current: u64::MAX,
251 next: None,
252 voting_current: u64::MAX,
253 voting_next: None,
254 }
255 }
256}
257
258#[derive(Debug, Encode, Decode, PartialEq, Eq, Clone, Copy, TypeInfo)]
260pub enum ChainConstants {
261 #[codec(index = 0)]
263 V0 {
264 confirmation_depth_k: BlockNumber,
266 block_authoring_delay: Slot,
268 era_duration: BlockNumber,
270 slot_probability: (u64, u64),
272 slot_duration: SlotDuration,
274 recent_segments: HistorySize,
276 recent_history_fraction: (HistorySize, HistorySize),
278 min_sector_lifetime: HistorySize,
280 },
281}
282
283impl ChainConstants {
284 pub fn confirmation_depth_k(&self) -> BlockNumber {
286 let Self::V0 {
287 confirmation_depth_k,
288 ..
289 } = self;
290 *confirmation_depth_k
291 }
292
293 pub fn era_duration(&self) -> BlockNumber {
295 let Self::V0 { era_duration, .. } = self;
296 *era_duration
297 }
298
299 pub fn block_authoring_delay(&self) -> Slot {
301 let Self::V0 {
302 block_authoring_delay,
303 ..
304 } = self;
305 *block_authoring_delay
306 }
307
308 pub fn slot_probability(&self) -> (u64, u64) {
310 let Self::V0 {
311 slot_probability, ..
312 } = self;
313 *slot_probability
314 }
315
316 pub fn slot_duration(&self) -> SlotDuration {
318 let Self::V0 { slot_duration, .. } = self;
319 *slot_duration
320 }
321
322 pub fn recent_segments(&self) -> HistorySize {
324 let Self::V0 {
325 recent_segments, ..
326 } = self;
327 *recent_segments
328 }
329
330 pub fn recent_history_fraction(&self) -> (HistorySize, HistorySize) {
332 let Self::V0 {
333 recent_history_fraction,
334 ..
335 } = self;
336 *recent_history_fraction
337 }
338
339 pub fn min_sector_lifetime(&self) -> HistorySize {
341 let Self::V0 {
342 min_sector_lifetime,
343 ..
344 } = self;
345 *min_sector_lifetime
346 }
347}
348
349#[derive(Debug, Encode, Decode)]
351pub struct WrappedSolution(Solution<()>);
352
353impl<RewardAddress> From<&Solution<RewardAddress>> for WrappedSolution {
354 #[inline]
355 fn from(solution: &Solution<RewardAddress>) -> Self {
356 Self(Solution {
357 public_key: solution.public_key,
358 reward_address: (),
359 sector_index: solution.sector_index,
360 history_size: solution.history_size,
361 piece_offset: solution.piece_offset,
362 record_commitment: solution.record_commitment,
363 record_witness: solution.record_witness,
364 chunk: solution.chunk,
365 chunk_witness: solution.chunk_witness,
366 proof_of_space: solution.proof_of_space,
367 })
368 }
369}
370
371#[derive(Debug, Encode, Decode)]
373pub struct WrappedVerifySolutionParams<'a>(Cow<'a, VerifySolutionParams>);
374
375impl<'a> From<&'a VerifySolutionParams> for WrappedVerifySolutionParams<'a> {
376 #[inline]
377 fn from(value: &'a VerifySolutionParams) -> Self {
378 Self(Cow::Borrowed(value))
379 }
380}
381
382#[derive(Debug, Encode, Decode)]
384pub struct WrappedPotOutput(PotOutput);
385
386impl From<PotOutput> for WrappedPotOutput {
387 #[inline]
388 fn from(value: PotOutput) -> Self {
389 Self(value)
390 }
391}
392
393#[cfg(feature = "std")]
394sp_externalities::decl_extension! {
395 pub struct KzgExtension(Kzg);
397}
398
399#[cfg(feature = "std")]
400impl KzgExtension {
401 pub fn new(kzg: Kzg) -> Self {
403 Self(kzg)
404 }
405}
406
407#[cfg(feature = "std")]
408sp_externalities::decl_extension! {
409 pub struct PosExtension(PosTableType);
411}
412
413#[cfg(feature = "std")]
414impl PosExtension {
415 pub fn new<PosTable>() -> Self
417 where
418 PosTable: Table,
419 {
420 Self(PosTable::TABLE_TYPE)
421 }
422}
423
424#[cfg(feature = "std")]
425sp_externalities::decl_extension! {
426 pub struct PotExtension(Box<dyn (Fn(BlockHash, SlotNumber, PotOutput, bool) -> bool) + Send + Sync>);
428}
429
430#[cfg(feature = "std")]
431impl PotExtension {
432 pub fn new(
434 verifier: Box<dyn (Fn(BlockHash, SlotNumber, PotOutput, bool) -> bool) + Send + Sync>,
435 ) -> Self {
436 Self(verifier)
437 }
438}
439
440#[runtime_interface]
442pub trait Consensus {
443 fn verify_solution(
446 &mut self,
447 solution: PassFatPointerAndDecode<WrappedSolution>,
448 slot: SlotNumber,
449 params: PassFatPointerAndDecode<WrappedVerifySolutionParams<'_>>,
450 ) -> AllocateAndReturnByCodec<Result<SolutionRange, String>> {
451 #[cfg(feature = "runtime-benchmarks")]
455 {
456 subspace_verification::verify_solution::<ShimTable, _>(
457 &solution.0,
458 slot,
459 ¶ms.0,
460 kzg_instance(),
461 )
462 .map_err(|error| error.to_string())
463 }
464
465 #[cfg(not(feature = "runtime-benchmarks"))]
466 {
467 use sp_externalities::ExternalitiesExt;
468 use subspace_proof_of_space::PosTableType;
469 #[cfg(feature = "std")]
470 use subspace_proof_of_space::chia::ChiaTable;
471
472 let pos_table_type = self
473 .extension::<PosExtension>()
474 .expect("No `PosExtension` associated for the current context!")
475 .0;
476
477 let kzg = &self
478 .extension::<KzgExtension>()
479 .expect("No `KzgExtension` associated for the current context!")
480 .0;
481
482 match pos_table_type {
483 PosTableType::Chia => subspace_verification::verify_solution::<ChiaTable, _>(
484 &solution.0,
485 slot,
486 ¶ms.0,
487 kzg,
488 )
489 .map_err(|error| error.to_string()),
490 PosTableType::Shim => subspace_verification::verify_solution::<ShimTable, _>(
491 &solution.0,
492 slot,
493 ¶ms.0,
494 kzg,
495 )
496 .map_err(|error| error.to_string()),
497 }
498 }
499 }
500
501 fn is_proof_of_time_valid(
504 &mut self,
505 parent_hash: PassPointerAndReadCopy<BlockHash, 32>,
506 slot: SlotNumber,
507 proof_of_time: PassFatPointerAndDecode<WrappedPotOutput>,
508 quick_verification: bool,
509 ) -> bool {
510 #[cfg(feature = "runtime-benchmarks")]
514 {
515 let _ = (slot, parent_hash, proof_of_time, quick_verification);
516 true
517 }
518
519 #[cfg(not(feature = "runtime-benchmarks"))]
520 {
521 use sp_externalities::ExternalitiesExt;
522
523 let verifier = &self
524 .extension::<PotExtension>()
525 .expect("No `PotExtension` associated for the current context!")
526 .0;
527
528 verifier(parent_hash, slot, proof_of_time.0, quick_verification)
529 }
530 }
531}
532
533#[derive(Debug, Clone, Encode, Decode, TypeInfo, MaxEncodedLen)]
535pub enum PotParameters {
536 V0 {
538 slot_iterations: NonZeroU32,
541 next_change: Option<PotParametersChange>,
543 },
544}
545
546impl PotParameters {
547 pub fn slot_iterations(&self) -> NonZeroU32 {
550 let Self::V0 {
551 slot_iterations, ..
552 } = self;
553
554 *slot_iterations
555 }
556
557 pub fn next_parameters_change(&self) -> Option<PotParametersChange> {
559 let Self::V0 { next_change, .. } = self;
560
561 *next_change
562 }
563}
564
565sp_api::decl_runtime_apis! {
566 #[api_version(2)]
568 pub trait SubspaceApi<RewardAddress: Encode + Decode> {
569 fn pot_parameters() -> PotParameters;
571
572 fn solution_ranges() -> SolutionRanges;
574
575 fn submit_vote_extrinsic(
578 signed_vote: SignedVote<
579 NumberFor<Block>,
580 Block::Hash,
581 RewardAddress,
582 >,
583 );
584
585 fn history_size() -> HistorySize;
587
588 fn max_pieces_in_sector() -> u16;
590
591 fn segment_commitment(segment_index: SegmentIndex) -> Option<SegmentCommitment>;
593
594 fn extract_segment_headers(ext: &Block::Extrinsic) -> Option<Vec<SegmentHeader >>;
596
597 fn is_inherent(ext: &Block::Extrinsic) -> bool;
599
600 fn root_plot_public_key() -> Option<PublicKey>;
602
603 fn should_adjust_solution_range() -> bool;
605
606 fn chain_constants() -> ChainConstants;
608
609 fn block_weight() -> Weight;
612 }
613}
614
615#[cfg(all(feature = "std", feature = "runtime-benchmarks"))]
616fn kzg_instance() -> &'static Kzg {
617 use std::sync::OnceLock;
618 static KZG: OnceLock<Kzg> = OnceLock::new();
619
620 KZG.get_or_init(Kzg::new)
621}