Skip to main content

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