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