sp_consensus_subspace/
lib.rs

1//! Primitives for Subspace consensus.
2
3#![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
44/// The `ConsensusEngineId` of Subspace.
45const SUBSPACE_ENGINE_ID: ConsensusEngineId = *b"SUB_";
46
47/// Subspace justification
48#[derive(Debug, Clone, Encode, Decode, TypeInfo)]
49pub enum SubspaceJustification {
50    /// Proof of time checkpoints that were not seen before
51    #[codec(index = 0)]
52    PotCheckpoints {
53        /// Proof of time seed, the input for computing checkpoints
54        seed: PotSeed,
55        /// Proof of time checkpoints from after future proof of parent block to current block's
56        /// future proof (inclusive)
57        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    /// Try to decode Subspace justification from generic justification.
70    ///
71    /// `None` means this is not a Subspace justification.
72    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    /// Returns `true` if justification must be archived, implies that it is canonical
80    pub fn must_be_archived(&self) -> bool {
81        match self {
82            SubspaceJustification::PotCheckpoints { .. } => true,
83        }
84    }
85}
86
87/// Next slot input for proof of time evaluation
88#[derive(Debug, Copy, Clone, PartialEq, Eq, Decode, Encode, TypeInfo, MaxEncodedLen)]
89pub struct PotNextSlotInput {
90    /// Slot
91    pub slot: Slot,
92    /// Slot iterations for this slot
93    pub slot_iterations: NonZeroU32,
94    /// Seed for this slot
95    pub seed: PotSeed,
96}
97
98impl PotNextSlotInput {
99    /// Derive next slot input while taking parameters change into account.
100    ///
101    /// NOTE: `base_slot_iterations` doesn't have to be parent block, just something that is after
102    /// prior parameters change (if any) took effect, in most cases this value corresponds to parent
103    /// block's slot.
104    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        // The change to number of iterations might have happened before `next_slot`
115        if let Some(parameters_change) = pot_parameters_change
116            && parameters_change.slot <= next_slot
117        {
118            slot_iterations = parameters_change.slot_iterations;
119            // Only if entropy injection happens exactly on next slot we need to mix it in
120            if parameters_change.slot == next_slot {
121                seed = parent_output.seed_with_entropy(&parameters_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/// Change of parameters to apply to PoT chain
139#[derive(Debug, Copy, Clone, PartialEq, Eq, Decode, Encode, TypeInfo, MaxEncodedLen)]
140pub struct PotParametersChange {
141    /// At which slot change of parameters takes effect
142    pub slot: Slot,
143    /// New number of slot iterations
144    pub slot_iterations: NonZeroU32,
145    /// Entropy that should be injected at this time
146    pub entropy: Blake3Hash,
147}
148
149/// An consensus log item for Subspace.
150#[derive(Debug, Decode, Encode, Clone, PartialEq, Eq)]
151enum ConsensusLog {
152    /// Number of iterations for proof of time per slot, corresponds to slot that directly follows
153    /// parent block's slot and can change before slot for which block is produced.
154    #[codec(index = 0)]
155    PotSlotIterations(NonZeroU32),
156    /// Solution range for this block/era.
157    #[codec(index = 1)]
158    SolutionRange(SolutionRange),
159    /// Change of parameters to apply to PoT chain.
160    #[codec(index = 2)]
161    PotParametersChange(PotParametersChange),
162    /// Solution range for next block/era.
163    #[codec(index = 3)]
164    NextSolutionRange(SolutionRange),
165    /// Segment commitments.
166    #[codec(index = 4)]
167    SegmentCommitment((SegmentIndex, SegmentCommitment)),
168    /// Enable Solution range adjustment and Override Solution Range.
169    #[codec(index = 5)]
170    EnableSolutionRangeAdjustmentAndOverride(Option<SolutionRange>),
171    /// Root plot public key was updated.
172    #[codec(index = 6)]
173    RootPlotPublicKeyUpdate(Option<PublicKey>),
174}
175
176/// Farmer vote.
177#[derive(Debug, Clone, Eq, PartialEq, Encode, Decode, TypeInfo)]
178pub enum Vote<Number, Hash, RewardAddress> {
179    /// V0 of the farmer vote.
180    V0 {
181        /// Height at which vote was created.
182        ///
183        /// Equivalent to block number, but this is not a block.
184        height: Number,
185        /// Hash of the block on top of which vote was created.
186        parent_hash: Hash,
187        /// Slot at which vote was created.
188        slot: Slot,
189        /// Solution (includes PoR).
190        solution: Solution<RewardAddress>,
191        /// Proof of time for this slot
192        proof_of_time: PotOutput,
193        /// Future proof of time
194        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    /// Solution contained within.
205    pub fn solution(&self) -> &Solution<RewardAddress> {
206        let Self::V0 { solution, .. } = self;
207        solution
208    }
209
210    /// Slot at which vote was created.
211    pub fn slot(&self) -> &Slot {
212        let Self::V0 { slot, .. } = self;
213        slot
214    }
215
216    /// Hash of the vote, used for signing and verifying signature.
217    pub fn hash(&self) -> H256 {
218        hashing::blake2_256(&self.encode()).into()
219    }
220}
221
222/// Signed farmer vote.
223#[derive(Debug, Clone, Eq, PartialEq, Encode, Decode, TypeInfo)]
224pub struct SignedVote<Number, Hash, RewardAddress> {
225    /// Farmer vote.
226    pub vote: Vote<Number, Hash, RewardAddress>,
227    /// Signature.
228    pub signature: RewardSignature,
229}
230
231/// Subspace solution ranges used for challenges.
232#[derive(Decode, Encode, MaxEncodedLen, PartialEq, Eq, Clone, Copy, Debug, TypeInfo)]
233pub struct SolutionRanges {
234    /// Solution range in current block/era.
235    pub current: u64,
236    /// Solution range that will be used in the next block/era.
237    pub next: Option<u64>,
238    /// Voting solution range in current block/era.
239    pub voting_current: u64,
240    /// Voting solution range that will be used in the next block/era.
241    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/// Subspace blockchain constants.
257#[derive(Debug, Encode, Decode, PartialEq, Eq, Clone, Copy, TypeInfo)]
258pub enum ChainConstants {
259    /// V0 of the chain constants.
260    #[codec(index = 0)]
261    V0 {
262        /// Depth `K` after which a block enters the recorded history.
263        confirmation_depth_k: BlockNumber,
264        /// Number of slots between slot arrival and when corresponding block can be produced.
265        block_authoring_delay: Slot,
266        /// Era duration in blocks.
267        era_duration: BlockNumber,
268        /// Slot probability.
269        slot_probability: (u64, u64),
270        /// The slot duration in milliseconds.
271        slot_duration: SlotDuration,
272        /// Number of latest archived segments that are considered "recent history".
273        recent_segments: HistorySize,
274        /// Fraction of pieces from the "recent history" (`recent_segments`) in each sector.
275        recent_history_fraction: (HistorySize, HistorySize),
276        /// Minimum lifetime of a plotted sector, measured in archived segment.
277        min_sector_lifetime: HistorySize,
278    },
279}
280
281impl ChainConstants {
282    /// Depth `K` after which a block enters the recorded history.
283    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    /// Era duration in blocks.
292    pub fn era_duration(&self) -> BlockNumber {
293        let Self::V0 { era_duration, .. } = self;
294        *era_duration
295    }
296
297    /// Number of slots between slot arrival and when corresponding block can be produced.
298    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    /// Slot probability.
307    pub fn slot_probability(&self) -> (u64, u64) {
308        let Self::V0 {
309            slot_probability, ..
310        } = self;
311        *slot_probability
312    }
313
314    /// The slot duration in milliseconds.
315    pub fn slot_duration(&self) -> SlotDuration {
316        let Self::V0 { slot_duration, .. } = self;
317        *slot_duration
318    }
319
320    /// Number of latest archived segments that are considered "recent history".
321    pub fn recent_segments(&self) -> HistorySize {
322        let Self::V0 {
323            recent_segments, ..
324        } = self;
325        *recent_segments
326    }
327
328    /// Fraction of pieces from the "recent history" (`recent_segments`) in each sector.
329    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    /// Minimum lifetime of a plotted sector, measured in archived segment.
338    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/// Wrapped solution for the purposes of runtime interface.
348#[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/// Wrapped solution verification parameters for the purposes of runtime interface.
374#[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/// Wrapped proof of time output for the purposes of runtime interface.
389#[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    /// A KZG extension.
406    pub struct KzgExtension(Kzg);
407}
408
409#[cfg(feature = "std")]
410impl KzgExtension {
411    /// Create new instance.
412    pub fn new(kzg: Kzg) -> Self {
413        Self(kzg)
414    }
415}
416
417#[cfg(feature = "std")]
418sp_externalities::decl_extension! {
419    /// A Poof of space extension.
420    pub struct PosExtension(PosTableType);
421}
422
423#[cfg(feature = "std")]
424impl PosExtension {
425    /// Create new instance.
426    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    /// A Poof of time extension.
437    pub struct PotExtension(Box<dyn (Fn(BlockHash, SlotNumber, PotOutput, bool) -> bool) + Send + Sync>);
438}
439
440#[cfg(feature = "std")]
441impl PotExtension {
442    /// Create new instance.
443    pub fn new(
444        verifier: Box<dyn (Fn(BlockHash, SlotNumber, PotOutput, bool) -> bool) + Send + Sync>,
445    ) -> Self {
446        Self(verifier)
447    }
448}
449
450/// Consensus-related runtime interface
451#[runtime_interface]
452pub trait Consensus {
453    /// Verify whether solution is valid, returns solution distance that is `<= solution_range/2` on
454    /// success.
455    fn verify_solution(
456        &mut self,
457        solution: WrappedSolution,
458        slot: SlotNumber,
459        params: WrappedVerifySolutionParams<'_>,
460    ) -> Result<SolutionRange, String> {
461        // TODO: we need to conditional compile for benchmarks here since
462        //  benchmark externalities does not provide custom extensions.
463        //  Remove this once the issue is resolved: https://github.com/paritytech/polkadot-sdk/issues/137
464        #[cfg(feature = "runtime-benchmarks")]
465        {
466            subspace_verification::verify_solution::<ShimTable, _>(
467                &solution.0,
468                slot,
469                &params.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                    &params.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                    &params.0,
504                    kzg,
505                )
506                .map_err(|error| error.to_string()),
507            }
508        }
509    }
510
511    /// Verify whether `proof_of_time` is valid at specified `slot` if built on top of `parent_hash`
512    /// fork of the chain.
513    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        // TODO: we need to conditional compile for benchmarks here since
521        //  benchmark externalities does not provide custom extensions.
522        //  Remove this once the issue is resolved: https://github.com/paritytech/polkadot-sdk/issues/137
523        #[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/// Proof of time parameters
544#[derive(Debug, Clone, Encode, Decode, TypeInfo, MaxEncodedLen)]
545pub enum PotParameters {
546    /// Initial version of the parameters
547    V0 {
548        /// Number of iterations for proof of time per slot, corresponds to slot that directly
549        /// follows parent block's slot and can change before slot for which block is produced
550        slot_iterations: NonZeroU32,
551        /// Optional next scheduled change of parameters
552        next_change: Option<PotParametersChange>,
553    },
554}
555
556impl PotParameters {
557    /// Number of iterations for proof of time per slot, corresponds to slot that directly follows
558    /// parent block's slot and can change before slot for which block is produced
559    pub fn slot_iterations(&self) -> NonZeroU32 {
560        let Self::V0 {
561            slot_iterations, ..
562        } = self;
563
564        *slot_iterations
565    }
566
567    /// Get next proof of time parameters change if any
568    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    /// API necessary for block authorship with Subspace.
577    pub trait SubspaceApi<RewardAddress: Encode + Decode> {
578        /// Proof of time parameters
579        fn pot_parameters() -> PotParameters;
580
581        /// Solution ranges.
582        fn solution_ranges() -> SolutionRanges;
583
584        /// Submit farmer vote that is essentially a header with bigger solution range than
585        /// acceptable for block authoring. Only useful in an offchain context.
586        fn submit_vote_extrinsic(
587            signed_vote: SignedVote<
588                NumberFor<Block>,
589                Block::Hash,
590                RewardAddress,
591            >,
592        );
593
594        /// Size of the blockchain history
595        fn history_size() -> HistorySize;
596
597        /// How many pieces one sector is supposed to contain (max)
598        fn max_pieces_in_sector() -> u16;
599
600        /// Get the segment commitment of records for specified segment index
601        fn segment_commitment(segment_index: SegmentIndex) -> Option<SegmentCommitment>;
602
603        /// Returns `Vec<SegmentHeader>` if a given extrinsic has them.
604        fn extract_segment_headers(ext: &Block::Extrinsic) -> Option<Vec<SegmentHeader >>;
605
606        /// Checks if the extrinsic is an inherent.
607        fn is_inherent(ext: &Block::Extrinsic) -> bool;
608
609        /// Returns root plot public key in case block authoring is restricted.
610        fn root_plot_public_key() -> Option<PublicKey>;
611
612        /// Whether solution range adjustment is enabled.
613        fn should_adjust_solution_range() -> bool;
614
615        /// Get Subspace blockchain constants
616        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}