pallet_subspace/
lib.rs

1#![doc = include_str!("../README.md")]
2#![cfg_attr(not(feature = "std"), no_std)]
3#![feature(array_chunks, assert_matches, let_chains, portable_simd)]
4#![warn(unused_must_use, unsafe_code, unused_variables)]
5
6#[cfg(not(feature = "std"))]
7extern crate alloc;
8
9#[cfg(test)]
10mod mock;
11#[cfg(test)]
12mod tests;
13
14#[cfg(feature = "runtime-benchmarks")]
15mod benchmarking;
16
17pub mod extensions;
18pub mod weights;
19
20use crate::extensions::weights::WeightInfo as ExtensionWeightInfo;
21#[cfg(not(feature = "std"))]
22use alloc::string::String;
23use core::num::NonZeroU64;
24use frame_support::dispatch::DispatchResult;
25use frame_support::pallet_prelude::{EnsureOrigin, RuntimeDebug};
26use frame_support::traits::Get;
27use frame_system::offchain::SubmitTransaction;
28use frame_system::pallet_prelude::*;
29use log::{debug, error, warn};
30pub use pallet::*;
31use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
32use scale_info::TypeInfo;
33use schnorrkel::SignatureError;
34use sp_consensus_slots::Slot;
35use sp_consensus_subspace::consensus::{is_proof_of_time_valid, verify_solution};
36use sp_consensus_subspace::digests::CompatibleDigestItem;
37use sp_consensus_subspace::{
38    PotParameters, PotParametersChange, SignedVote, Vote, WrappedPotOutput,
39};
40use sp_runtime::generic::DigestItem;
41use sp_runtime::traits::{BlockNumberProvider, CheckedSub, Hash, One, Zero};
42use sp_runtime::transaction_validity::{
43    InvalidTransaction, TransactionPriority, TransactionSource, TransactionValidity,
44    TransactionValidityError, ValidTransaction,
45};
46use sp_runtime::Weight;
47use sp_std::collections::btree_map::BTreeMap;
48use sp_std::prelude::*;
49use subspace_core_primitives::pieces::PieceOffset;
50use subspace_core_primitives::sectors::{SectorId, SectorIndex};
51use subspace_core_primitives::segments::{
52    ArchivedHistorySegment, HistorySize, SegmentHeader, SegmentIndex,
53};
54use subspace_core_primitives::solutions::{RewardSignature, SolutionRange};
55use subspace_core_primitives::{
56    BlockHash, PublicKey, ScalarBytes, SlotNumber, REWARD_SIGNING_CONTEXT,
57};
58use subspace_runtime_primitives::CreateUnsigned;
59use subspace_verification::{
60    check_reward_signature, derive_next_solution_range, derive_pot_entropy, PieceCheckParams,
61    VerifySolutionParams,
62};
63
64/// Trigger an era change, if any should take place.
65pub trait EraChangeTrigger {
66    /// Trigger an era change, if any should take place. This should be called
67    /// during every block, after initialization is done.
68    fn trigger<T: Config>(block_number: BlockNumberFor<T>);
69}
70
71/// A type signifying to Subspace that it should perform era changes with an internal trigger.
72pub struct NormalEraChange;
73
74impl EraChangeTrigger for NormalEraChange {
75    fn trigger<T: Config>(block_number: BlockNumberFor<T>) {
76        if <Pallet<T>>::should_era_change(block_number) {
77            <Pallet<T>>::enact_era_change();
78        }
79    }
80}
81
82/// Custom origin for validated unsigned extrinsics.
83#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]
84pub enum RawOrigin {
85    ValidatedUnsigned,
86}
87
88/// Ensure the subspace origin.
89pub struct EnsureSubspaceOrigin;
90impl<O: Into<Result<RawOrigin, O>> + From<RawOrigin>> EnsureOrigin<O> for EnsureSubspaceOrigin {
91    type Success = ();
92
93    fn try_origin(o: O) -> Result<Self::Success, O> {
94        o.into().map(|o| match o {
95            RawOrigin::ValidatedUnsigned => (),
96        })
97    }
98
99    #[cfg(feature = "runtime-benchmarks")]
100    fn try_successful_origin() -> Result<O, ()> {
101        Ok(O::from(RawOrigin::ValidatedUnsigned))
102    }
103}
104
105#[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, MaxEncodedLen, TypeInfo)]
106struct VoteVerificationData {
107    /// Block solution range, vote must not reach it
108    solution_range: SolutionRange,
109    vote_solution_range: SolutionRange,
110    current_slot: Slot,
111    parent_slot: Slot,
112}
113
114#[frame_support::pallet]
115pub mod pallet {
116    use super::{EraChangeTrigger, ExtensionWeightInfo, VoteVerificationData};
117    use crate::weights::WeightInfo;
118    use crate::RawOrigin;
119    use frame_support::pallet_prelude::*;
120    use frame_system::pallet_prelude::*;
121    use sp_consensus_slots::Slot;
122    use sp_consensus_subspace::digests::CompatibleDigestItem;
123    use sp_consensus_subspace::inherents::{InherentError, InherentType, INHERENT_IDENTIFIER};
124    use sp_consensus_subspace::SignedVote;
125    use sp_runtime::traits::One;
126    use sp_runtime::DigestItem;
127    use sp_std::collections::btree_map::BTreeMap;
128    use sp_std::num::NonZeroU32;
129    use sp_std::prelude::*;
130    use subspace_core_primitives::hashes::Blake3Hash;
131    use subspace_core_primitives::pieces::PieceOffset;
132    use subspace_core_primitives::pot::PotCheckpoints;
133    use subspace_core_primitives::sectors::SectorIndex;
134    use subspace_core_primitives::segments::{HistorySize, SegmentHeader, SegmentIndex};
135    use subspace_core_primitives::solutions::{RewardSignature, SolutionRange};
136    use subspace_core_primitives::{PublicKey, Randomness, ScalarBytes};
137
138    pub(super) struct InitialSolutionRanges<T: Config> {
139        _config: T,
140    }
141
142    impl<T: Config> Get<sp_consensus_subspace::SolutionRanges> for InitialSolutionRanges<T> {
143        fn get() -> sp_consensus_subspace::SolutionRanges {
144            sp_consensus_subspace::SolutionRanges {
145                current: T::InitialSolutionRange::get(),
146                next: None,
147                voting_current: if T::ShouldAdjustSolutionRange::get() {
148                    T::InitialSolutionRange::get()
149                        .saturating_mul(u64::from(T::ExpectedVotesPerBlock::get()) + 1)
150                } else {
151                    T::InitialSolutionRange::get()
152                },
153                voting_next: None,
154            }
155        }
156    }
157
158    /// Override for next solution range adjustment
159    #[derive(Debug, Encode, Decode, TypeInfo)]
160    pub(super) struct SolutionRangeOverride {
161        /// Value that should be set as solution range
162        pub(super) solution_range: SolutionRange,
163        /// Value that should be set as voting solution range
164        pub(super) voting_solution_range: SolutionRange,
165    }
166
167    /// The Subspace Pallet
168    #[pallet::pallet]
169    #[pallet::without_storage_info]
170    pub struct Pallet<T>(_);
171
172    #[pallet::config]
173    #[pallet::disable_frame_system_supertrait_check]
174    pub trait Config: frame_system::Config {
175        /// The overarching event type.
176        type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
177
178        /// Origin for subspace call.
179        type SubspaceOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = ()>;
180
181        /// Number of slots between slot arrival and when corresponding block can be produced.
182        ///
183        /// Practically this means future proof of time proof needs to be revealed this many slots
184        /// ahead before block can be authored even though solution is available before that.
185        #[pallet::constant]
186        type BlockAuthoringDelay: Get<Slot>;
187
188        /// Interval, in blocks, between blockchain entropy injection into proof of time chain.
189        #[pallet::constant]
190        type PotEntropyInjectionInterval: Get<BlockNumberFor<Self>>;
191
192        /// Interval, in entropy injection intervals, where to take entropy for injection from.
193        #[pallet::constant]
194        type PotEntropyInjectionLookbackDepth: Get<u8>;
195
196        /// Delay after block, in slots, when entropy injection takes effect.
197        #[pallet::constant]
198        type PotEntropyInjectionDelay: Get<Slot>;
199
200        /// The amount of time, in blocks, that each era should last.
201        /// NOTE: Currently it is not possible to change the era duration after
202        /// the chain has started. Attempting to do so will brick block production.
203        #[pallet::constant]
204        type EraDuration: Get<BlockNumberFor<Self>>;
205
206        /// Initial solution range used for challenges during the very first era.
207        #[pallet::constant]
208        type InitialSolutionRange: Get<SolutionRange>;
209
210        /// How often in slots slots (on average, not counting collisions) will have a block.
211        ///
212        /// Expressed as a rational where the first member of the tuple is the
213        /// numerator and the second is the denominator. The rational should
214        /// represent a value between 0 and 1.
215        #[pallet::constant]
216        type SlotProbability: Get<(u64, u64)>;
217
218        /// Depth `K` after which a block enters the recorded history (a global constant, as opposed
219        /// to the client-dependent transaction confirmation depth `k`).
220        #[pallet::constant]
221        type ConfirmationDepthK: Get<BlockNumberFor<Self>>;
222
223        /// Number of latest archived segments that are considered "recent history".
224        #[pallet::constant]
225        type RecentSegments: Get<HistorySize>;
226
227        /// Fraction of pieces from the "recent history" (`recent_segments`) in each sector.
228        #[pallet::constant]
229        type RecentHistoryFraction: Get<(HistorySize, HistorySize)>;
230
231        /// Minimum lifetime of a plotted sector, measured in archived segment.
232        #[pallet::constant]
233        type MinSectorLifetime: Get<HistorySize>;
234
235        /// Number of votes expected per block.
236        ///
237        /// This impacts solution range for votes in consensus.
238        #[pallet::constant]
239        type ExpectedVotesPerBlock: Get<u32>;
240
241        /// How many pieces one sector is supposed to contain (max)
242        #[pallet::constant]
243        type MaxPiecesInSector: Get<u16>;
244
245        type ShouldAdjustSolutionRange: Get<bool>;
246        /// Subspace requires some logic to be triggered on every block to query for whether an era
247        /// has ended and to perform the transition to the next era.
248        ///
249        /// Era is normally used to update solution range used for challenges.
250        type EraChangeTrigger: EraChangeTrigger;
251
252        /// Weight information for extrinsics in this pallet.
253        type WeightInfo: WeightInfo;
254
255        /// Maximum number of block number to block slot mappings to keep (oldest pruned first).
256        #[pallet::constant]
257        type BlockSlotCount: Get<u32>;
258
259        /// Extension weight information for the pallet's extensions.
260        type ExtensionWeightInfo: ExtensionWeightInfo;
261    }
262
263    #[derive(Debug, Default, Encode, Decode, TypeInfo)]
264    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
265    pub enum AllowAuthoringBy {
266        /// Anyone can author new blocks at genesis.
267        #[default]
268        Anyone,
269        /// Author of the first block will be able to author blocks going forward unless unlocked
270        /// for everyone.
271        FirstFarmer,
272        /// Specified root farmer is allowed to author blocks unless unlocked for everyone.
273        RootFarmer(PublicKey),
274    }
275
276    #[derive(Debug, Copy, Clone, Encode, Decode, TypeInfo)]
277    pub(super) struct PotEntropyValue {
278        /// Target slot at which entropy should be injected (when known)
279        pub(super) target_slot: Option<Slot>,
280        pub(super) entropy: Blake3Hash,
281    }
282
283    #[derive(Debug, Copy, Clone, Encode, Decode, TypeInfo, PartialEq)]
284    pub(super) struct PotSlotIterationsValue {
285        pub(super) slot_iterations: NonZeroU32,
286        /// Scheduled proof of time slot iterations update
287        pub(super) update: Option<PotSlotIterationsUpdate>,
288    }
289
290    #[derive(Debug, Copy, Clone, Encode, Decode, TypeInfo, PartialEq)]
291    pub(super) struct PotSlotIterationsUpdate {
292        /// Target slot at which entropy should be injected (when known)
293        pub(super) target_slot: Option<Slot>,
294        pub(super) slot_iterations: NonZeroU32,
295    }
296
297    /// When to enable block/vote rewards
298    #[derive(Debug, Copy, Clone, Eq, PartialEq, Encode, Decode, TypeInfo)]
299    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
300    pub enum EnableRewardsAt<BlockNumber> {
301        /// At specified height or next block if `None`
302        Height(BlockNumber),
303        /// When solution range is below specified threshold
304        SolutionRange(u64),
305        /// Manually with an explicit extrinsic
306        Manually,
307    }
308
309    #[pallet::genesis_config]
310    pub struct GenesisConfig<T>
311    where
312        T: Config,
313    {
314        /// When rewards should be enabled.
315        pub enable_rewards_at: EnableRewardsAt<BlockNumberFor<T>>,
316        /// Who can author blocks at genesis.
317        pub allow_authoring_by: AllowAuthoringBy,
318        /// Number of iterations for proof of time per slot
319        pub pot_slot_iterations: NonZeroU32,
320        #[serde(skip)]
321        pub phantom: PhantomData<T>,
322    }
323
324    impl<T> Default for GenesisConfig<T>
325    where
326        T: Config,
327    {
328        #[inline]
329        fn default() -> Self {
330            Self {
331                enable_rewards_at: EnableRewardsAt::Height(BlockNumberFor::<T>::one()),
332                allow_authoring_by: AllowAuthoringBy::Anyone,
333                pot_slot_iterations: NonZeroU32::MIN,
334                phantom: PhantomData,
335            }
336        }
337    }
338
339    #[pallet::genesis_build]
340    impl<T> BuildGenesisConfig for GenesisConfig<T>
341    where
342        T: Config,
343    {
344        fn build(&self) {
345            match self.enable_rewards_at {
346                EnableRewardsAt::Height(block_number) => {
347                    EnableRewards::<T>::put(block_number);
348                }
349                EnableRewardsAt::SolutionRange(solution_range) => {
350                    EnableRewardsBelowSolutionRange::<T>::put(solution_range);
351                }
352                EnableRewardsAt::Manually => {
353                    // Nothing to do in this case
354                }
355            }
356            match &self.allow_authoring_by {
357                AllowAuthoringBy::Anyone => {
358                    AllowAuthoringByAnyone::<T>::put(true);
359                }
360                AllowAuthoringBy::FirstFarmer => {
361                    AllowAuthoringByAnyone::<T>::put(false);
362                }
363                AllowAuthoringBy::RootFarmer(root_farmer) => {
364                    AllowAuthoringByAnyone::<T>::put(false);
365                    RootPlotPublicKey::<T>::put(root_farmer);
366                }
367            }
368            PotSlotIterations::<T>::put(PotSlotIterationsValue {
369                slot_iterations: self.pot_slot_iterations,
370                update: None,
371            });
372        }
373    }
374
375    /// Events type.
376    #[pallet::event]
377    #[pallet::generate_deposit(pub (super) fn deposit_event)]
378    pub enum Event<T: Config> {
379        /// Segment header was stored in blockchain history.
380        SegmentHeaderStored { segment_header: SegmentHeader },
381        /// Farmer vote.
382        FarmerVote {
383            public_key: PublicKey,
384            reward_address: T::AccountId,
385            height: BlockNumberFor<T>,
386            parent_hash: T::Hash,
387        },
388    }
389
390    #[pallet::origin]
391    pub type Origin = RawOrigin;
392
393    #[pallet::error]
394    pub enum Error<T> {
395        /// Solution range adjustment already enabled.
396        SolutionRangeAdjustmentAlreadyEnabled,
397        /// Iterations are not multiple of number of checkpoints times two
398        NotMultipleOfCheckpoints,
399        /// Proof of time slot iterations must increase as hardware improves
400        PotSlotIterationsMustIncrease,
401        /// Proof of time slot iterations update already scheduled
402        PotSlotIterationsUpdateAlreadyScheduled,
403    }
404
405    /// Bounded mapping from block number to slot
406    #[pallet::storage]
407    #[pallet::getter(fn block_slots)]
408    pub(super) type BlockSlots<T: Config> =
409        StorageValue<_, BoundedBTreeMap<BlockNumberFor<T>, Slot, T::BlockSlotCount>, ValueQuery>;
410
411    /// Solution ranges used for challenges.
412    #[pallet::storage]
413    #[pallet::getter(fn solution_ranges)]
414    pub(super) type SolutionRanges<T: Config> = StorageValue<
415        _,
416        sp_consensus_subspace::SolutionRanges,
417        ValueQuery,
418        InitialSolutionRanges<T>,
419    >;
420
421    /// Storage to check if the solution range is to be adjusted for next era
422    #[pallet::storage]
423    #[pallet::getter(fn should_adjust_solution_range)]
424    pub(super) type ShouldAdjustSolutionRange<T: Config> =
425        StorageValue<_, bool, ValueQuery, T::ShouldAdjustSolutionRange>;
426
427    /// Override solution range during next update
428    #[pallet::storage]
429    pub(super) type NextSolutionRangeOverride<T> = StorageValue<_, SolutionRangeOverride>;
430
431    /// Slot at which current era started.
432    #[pallet::storage]
433    pub(super) type EraStartSlot<T> = StorageValue<_, Slot>;
434
435    /// Mapping from segment index to corresponding segment commitment of contained records.
436    #[pallet::storage]
437    #[pallet::getter(fn segment_commitment)]
438    pub(super) type SegmentCommitment<T> = CountedStorageMap<
439        _,
440        Twox64Concat,
441        SegmentIndex,
442        subspace_core_primitives::segments::SegmentCommitment,
443    >;
444
445    /// Whether the segment headers inherent has been processed in this block (temporary value).
446    ///
447    /// This value is updated to `true` when processing `store_segment_headers` by a node.
448    /// It is then cleared at the end of each block execution in the `on_finalize` hook.
449    #[pallet::storage]
450    pub(super) type DidProcessSegmentHeaders<T: Config> = StorageValue<_, bool, ValueQuery>;
451
452    /// Storage of previous vote verification data, updated on each block during finalization.
453    #[pallet::storage]
454    pub(super) type ParentVoteVerificationData<T> = StorageValue<_, VoteVerificationData>;
455
456    /// Parent block author information.
457    #[pallet::storage]
458    pub(super) type ParentBlockAuthorInfo<T> =
459        StorageValue<_, (PublicKey, SectorIndex, PieceOffset, ScalarBytes, Slot)>;
460
461    /// Enable rewards since specified block number.
462    #[pallet::storage]
463    pub(super) type EnableRewards<T: Config> = StorageValue<_, BlockNumberFor<T>>;
464
465    /// Enable rewards when solution range is below this threshold.
466    #[pallet::storage]
467    pub(super) type EnableRewardsBelowSolutionRange<T: Config> = StorageValue<_, u64>;
468
469    /// Block author information
470    #[pallet::storage]
471    pub(super) type CurrentBlockAuthorInfo<T: Config> = StorageValue<
472        _,
473        (
474            PublicKey,
475            SectorIndex,
476            PieceOffset,
477            ScalarBytes,
478            Slot,
479            Option<T::AccountId>,
480        ),
481    >;
482
483    /// Voters in the parent block (set at the end of the block with current values).
484    #[pallet::storage]
485    pub(super) type ParentBlockVoters<T: Config> = StorageValue<
486        _,
487        BTreeMap<
488            (PublicKey, SectorIndex, PieceOffset, ScalarBytes, Slot),
489            (Option<T::AccountId>, RewardSignature),
490        >,
491        ValueQuery,
492    >;
493
494    /// Voters in the current block thus far
495    #[pallet::storage]
496    pub(super) type CurrentBlockVoters<T: Config> = StorageValue<
497        _,
498        BTreeMap<
499            (PublicKey, SectorIndex, PieceOffset, ScalarBytes, Slot),
500            (Option<T::AccountId>, RewardSignature),
501        >,
502    >;
503
504    /// Number of iterations for proof of time per slot with optional scheduled update
505    #[pallet::storage]
506    pub(super) type PotSlotIterations<T> = StorageValue<_, PotSlotIterationsValue>;
507
508    /// Entropy that needs to be injected into proof of time chain at specific slot associated with
509    /// block number it came from.
510    #[pallet::storage]
511    pub(super) type PotEntropy<T: Config> =
512        StorageValue<_, BTreeMap<BlockNumberFor<T>, PotEntropyValue>, ValueQuery>;
513
514    /// The current block randomness, updated at block initialization. When the proof of time feature
515    /// is enabled it derived from PoT otherwise PoR.
516    #[pallet::storage]
517    pub type BlockRandomness<T> = StorageValue<_, Randomness>;
518
519    /// Allow block authoring by anyone or just root.
520    #[pallet::storage]
521    pub(super) type AllowAuthoringByAnyone<T> = StorageValue<_, bool, ValueQuery>;
522
523    /// Root plot public key.
524    ///
525    /// Set just once to make sure no one else can author blocks until allowed for anyone.
526    #[pallet::storage]
527    #[pallet::getter(fn root_plot_public_key)]
528    pub(super) type RootPlotPublicKey<T> = StorageValue<_, PublicKey>;
529
530    #[pallet::hooks]
531    impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
532        fn on_initialize(block_number: BlockNumberFor<T>) -> Weight {
533            Self::do_initialize(block_number);
534            Weight::zero()
535        }
536
537        fn on_finalize(block_number: BlockNumberFor<T>) {
538            Self::do_finalize(block_number)
539        }
540    }
541
542    #[pallet::call]
543    impl<T: Config> Pallet<T> {
544        /// Submit new segment header to the blockchain. This is an inherent extrinsic and part of
545        /// the Subspace consensus logic.
546        #[pallet::call_index(0)]
547        #[pallet::weight((< T as Config >::WeightInfo::store_segment_headers(segment_headers.len() as u32), DispatchClass::Mandatory))]
548        pub fn store_segment_headers(
549            origin: OriginFor<T>,
550            segment_headers: Vec<SegmentHeader>,
551        ) -> DispatchResult {
552            ensure_none(origin)?;
553            Self::do_store_segment_headers(segment_headers)
554        }
555
556        /// Enable solution range adjustment after every era.
557        /// Note: No effect on the solution range for the current era
558        #[pallet::call_index(1)]
559        #[pallet::weight(< T as Config >::WeightInfo::enable_solution_range_adjustment())]
560        pub fn enable_solution_range_adjustment(
561            origin: OriginFor<T>,
562            solution_range_override: Option<u64>,
563            voting_solution_range_override: Option<u64>,
564        ) -> DispatchResult {
565            ensure_root(origin)?;
566
567            Self::do_enable_solution_range_adjustment(
568                solution_range_override,
569                voting_solution_range_override,
570            )?;
571
572            frame_system::Pallet::<T>::deposit_log(
573                DigestItem::enable_solution_range_adjustment_and_override(solution_range_override),
574            );
575
576            Ok(())
577        }
578
579        /// Farmer vote, currently only used for extra rewards to farmers.
580        #[pallet::call_index(2)]
581        #[pallet::weight((< T as Config >::WeightInfo::vote(), DispatchClass::Operational))]
582        // Suppression because the custom syntax will also generate an enum and we need enum to have
583        // boxed value.
584        #[allow(clippy::boxed_local)]
585        pub fn vote(
586            origin: OriginFor<T>,
587            signed_vote: Box<SignedVote<BlockNumberFor<T>, T::Hash, T::AccountId>>,
588        ) -> DispatchResult {
589            T::SubspaceOrigin::ensure_origin(origin)?;
590
591            Self::do_vote(*signed_vote)
592        }
593
594        /// Enable rewards for blocks and votes at specified block height.
595        #[pallet::call_index(3)]
596        #[pallet::weight(< T as Config >::WeightInfo::enable_rewards_at())]
597        pub fn enable_rewards_at(
598            origin: OriginFor<T>,
599            enable_rewards_at: EnableRewardsAt<BlockNumberFor<T>>,
600        ) -> DispatchResult {
601            ensure_root(origin)?;
602
603            Self::do_enable_rewards_at(enable_rewards_at)
604        }
605
606        /// Enable storage access for all users.
607        #[pallet::call_index(4)]
608        #[pallet::weight(< T as Config >::WeightInfo::enable_authoring_by_anyone())]
609        pub fn enable_authoring_by_anyone(origin: OriginFor<T>) -> DispatchResult {
610            ensure_root(origin)?;
611
612            AllowAuthoringByAnyone::<T>::put(true);
613            RootPlotPublicKey::<T>::take();
614            // Deposit root plot public key update such that light client can validate blocks later.
615            frame_system::Pallet::<T>::deposit_log(DigestItem::root_plot_public_key_update(None));
616
617            Ok(())
618        }
619
620        /// Update proof of time slot iterations
621        #[pallet::call_index(5)]
622        #[pallet::weight(< T as Config >::WeightInfo::set_pot_slot_iterations())]
623        pub fn set_pot_slot_iterations(
624            origin: OriginFor<T>,
625            slot_iterations: NonZeroU32,
626        ) -> DispatchResult {
627            ensure_root(origin)?;
628
629            if slot_iterations.get() % u32::from(PotCheckpoints::NUM_CHECKPOINTS.get() * 2) != 0 {
630                return Err(Error::<T>::NotMultipleOfCheckpoints.into());
631            }
632
633            let mut pot_slot_iterations =
634                PotSlotIterations::<T>::get().expect("Always initialized during genesis; qed");
635
636            if pot_slot_iterations.slot_iterations >= slot_iterations {
637                return Err(Error::<T>::PotSlotIterationsMustIncrease.into());
638            }
639
640            // Can't update if already scheduled since it will cause verification issues
641            if let Some(pot_slot_iterations_update_value) = pot_slot_iterations.update
642                && pot_slot_iterations_update_value.target_slot.is_some()
643            {
644                return Err(Error::<T>::PotSlotIterationsUpdateAlreadyScheduled.into());
645            }
646
647            pot_slot_iterations.update.replace(PotSlotIterationsUpdate {
648                // Slot will be known later when next entropy injection takes place
649                target_slot: None,
650                slot_iterations,
651            });
652
653            PotSlotIterations::<T>::put(pot_slot_iterations);
654
655            Ok(())
656        }
657    }
658
659    #[pallet::inherent]
660    impl<T: Config> ProvideInherent for Pallet<T> {
661        type Call = Call<T>;
662        type Error = InherentError;
663        const INHERENT_IDENTIFIER: InherentIdentifier = INHERENT_IDENTIFIER;
664
665        fn create_inherent(data: &InherentData) -> Option<Self::Call> {
666            let inherent_data = data
667                .get_data::<InherentType>(&INHERENT_IDENTIFIER)
668                .expect("Subspace inherent data not correctly encoded")
669                .expect("Subspace inherent data must be provided");
670
671            let segment_headers = inherent_data.segment_headers;
672            if segment_headers.is_empty() {
673                None
674            } else {
675                Some(Call::store_segment_headers { segment_headers })
676            }
677        }
678
679        fn is_inherent_required(data: &InherentData) -> Result<Option<Self::Error>, Self::Error> {
680            let inherent_data = data
681                .get_data::<InherentType>(&INHERENT_IDENTIFIER)
682                .expect("Subspace inherent data not correctly encoded")
683                .expect("Subspace inherent data must be provided");
684
685            Ok(if inherent_data.segment_headers.is_empty() {
686                None
687            } else {
688                Some(InherentError::MissingSegmentHeadersList)
689            })
690        }
691
692        fn check_inherent(call: &Self::Call, data: &InherentData) -> Result<(), Self::Error> {
693            if let Call::store_segment_headers { segment_headers } = call {
694                let inherent_data = data
695                    .get_data::<InherentType>(&INHERENT_IDENTIFIER)
696                    .expect("Subspace inherent data not correctly encoded")
697                    .expect("Subspace inherent data must be provided");
698
699                if segment_headers != &inherent_data.segment_headers {
700                    return Err(InherentError::IncorrectSegmentHeadersList {
701                        expected: inherent_data.segment_headers,
702                        actual: segment_headers.clone(),
703                    });
704                }
705            }
706
707            Ok(())
708        }
709
710        fn is_inherent(call: &Self::Call) -> bool {
711            matches!(call, Call::store_segment_headers { .. })
712        }
713    }
714
715    #[pallet::validate_unsigned]
716    impl<T: Config> ValidateUnsigned for Pallet<T> {
717        type Call = Call<T>;
718        fn validate_unsigned(source: TransactionSource, call: &Self::Call) -> TransactionValidity {
719            match call {
720                Call::store_segment_headers { segment_headers } => {
721                    Self::validate_segment_header(source, segment_headers)
722                }
723                _ => InvalidTransaction::Call.into(),
724            }
725        }
726
727        fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> {
728            match call {
729                Call::store_segment_headers { segment_headers } => {
730                    Self::pre_dispatch_segment_header(segment_headers)
731                }
732                _ => Err(InvalidTransaction::Call.into()),
733            }
734        }
735    }
736}
737
738impl<T: Config> Pallet<T> {
739    /// Total number of pieces in the blockchain
740    pub fn history_size() -> HistorySize {
741        // Chain starts with one segment plotted, even if it is not recorded in the runtime yet
742        let number_of_segments = u64::from(SegmentCommitment::<T>::count()).max(1);
743        HistorySize::from(NonZeroU64::new(number_of_segments).expect("Not zero; qed"))
744    }
745
746    /// Determine whether an era change should take place at this block.
747    /// Assumes that initialization has already taken place.
748    fn should_era_change(block_number: BlockNumberFor<T>) -> bool {
749        block_number % T::EraDuration::get() == Zero::zero()
750    }
751
752    /// DANGEROUS: Enact era change. Should be done on every block where `should_era_change` has
753    /// returned `true`, and the caller is the only caller of this function.
754    ///
755    /// This will update solution range used in consensus.
756    fn enact_era_change() {
757        let slot_probability = T::SlotProbability::get();
758
759        let current_slot = Self::current_slot();
760
761        SolutionRanges::<T>::mutate(|solution_ranges| {
762            let next_solution_range;
763            let next_voting_solution_range;
764            // Check if the solution range should be adjusted for next era.
765            if !ShouldAdjustSolutionRange::<T>::get() {
766                next_solution_range = solution_ranges.current;
767                next_voting_solution_range = solution_ranges.current;
768            } else if let Some(solution_range_override) = NextSolutionRangeOverride::<T>::take() {
769                next_solution_range = solution_range_override.solution_range;
770                next_voting_solution_range = solution_range_override.voting_solution_range;
771            } else {
772                next_solution_range = derive_next_solution_range(
773                    // If Era start slot is not found it means we have just finished the first era
774                    u64::from(EraStartSlot::<T>::get().unwrap_or_default()),
775                    u64::from(current_slot),
776                    slot_probability,
777                    solution_ranges.current,
778                    T::EraDuration::get()
779                        .try_into()
780                        .unwrap_or_else(|_| panic!("Era duration is always within u64; qed")),
781                );
782
783                next_voting_solution_range = next_solution_range
784                    .saturating_mul(u64::from(T::ExpectedVotesPerBlock::get()) + 1);
785            };
786            solution_ranges.next.replace(next_solution_range);
787            solution_ranges
788                .voting_next
789                .replace(next_voting_solution_range);
790
791            if let Some(solution_range_for_rewards) = EnableRewardsBelowSolutionRange::<T>::get() {
792                if next_solution_range <= solution_range_for_rewards {
793                    EnableRewardsBelowSolutionRange::<T>::take();
794
795                    let next_block_number =
796                        frame_system::Pallet::<T>::current_block_number() + One::one();
797                    EnableRewards::<T>::put(next_block_number);
798                }
799            }
800        });
801
802        EraStartSlot::<T>::put(current_slot);
803    }
804
805    fn do_initialize(block_number: BlockNumberFor<T>) {
806        let pre_digest = frame_system::Pallet::<T>::digest()
807            .logs
808            .iter()
809            .find_map(|s| s.as_subspace_pre_digest::<T::AccountId>())
810            .expect("Block must always have pre-digest");
811        let current_slot = pre_digest.slot();
812
813        BlockSlots::<T>::mutate(|block_slots| {
814            if let Some(to_remove) = block_number.checked_sub(&T::BlockSlotCount::get().into()) {
815                block_slots.remove(&to_remove);
816            }
817            block_slots
818                .try_insert(block_number, current_slot)
819                .expect("one entry just removed before inserting; qed");
820        });
821
822        {
823            // Remove old value
824            CurrentBlockAuthorInfo::<T>::take();
825            let farmer_public_key = pre_digest.solution().public_key;
826
827            // Optional restriction for block authoring to the root user
828            if !AllowAuthoringByAnyone::<T>::get() {
829                RootPlotPublicKey::<T>::mutate(|maybe_root_plot_public_key| {
830                    if let Some(root_plot_public_key) = maybe_root_plot_public_key {
831                        if root_plot_public_key != &farmer_public_key {
832                            panic!("Client bug, authoring must be only done by the root user");
833                        }
834                    } else {
835                        maybe_root_plot_public_key.replace(farmer_public_key);
836                        // Deposit root plot public key update such that light client can validate
837                        // blocks later.
838                        frame_system::Pallet::<T>::deposit_log(
839                            DigestItem::root_plot_public_key_update(Some(farmer_public_key)),
840                        );
841                    }
842                });
843            }
844
845            let key = (
846                farmer_public_key,
847                pre_digest.solution().sector_index,
848                pre_digest.solution().piece_offset,
849                pre_digest.solution().chunk,
850                current_slot,
851            );
852            if !ParentBlockVoters::<T>::get().contains_key(&key) {
853                let (public_key, sector_index, piece_offset, chunk, slot) = key;
854
855                CurrentBlockAuthorInfo::<T>::put((
856                    public_key,
857                    sector_index,
858                    piece_offset,
859                    chunk,
860                    slot,
861                    Some(pre_digest.solution().reward_address.clone()),
862                ));
863            }
864        }
865        CurrentBlockVoters::<T>::put(BTreeMap::<
866            (PublicKey, SectorIndex, PieceOffset, ScalarBytes, Slot),
867            (Option<T::AccountId>, RewardSignature),
868        >::default());
869
870        // If solution range was updated in previous block, set it as current.
871        if let sp_consensus_subspace::SolutionRanges {
872            next: Some(next),
873            voting_next: Some(voting_next),
874            ..
875        } = SolutionRanges::<T>::get()
876        {
877            SolutionRanges::<T>::put(sp_consensus_subspace::SolutionRanges {
878                current: next,
879                next: None,
880                voting_current: voting_next,
881                voting_next: None,
882            });
883        }
884
885        let block_randomness = pre_digest
886            .pot_info()
887            .proof_of_time()
888            .derive_global_randomness();
889
890        // Update the block randomness.
891        BlockRandomness::<T>::put(block_randomness);
892
893        // Deposit solution range data such that light client can validate blocks later.
894        frame_system::Pallet::<T>::deposit_log(DigestItem::solution_range(
895            SolutionRanges::<T>::get().current,
896        ));
897
898        // Enact era change, if necessary.
899        T::EraChangeTrigger::trigger::<T>(block_number);
900
901        {
902            let mut pot_slot_iterations =
903                PotSlotIterations::<T>::get().expect("Always initialized during genesis; qed");
904            // This is what we had after previous block
905            frame_system::Pallet::<T>::deposit_log(DigestItem::pot_slot_iterations(
906                pot_slot_iterations.slot_iterations,
907            ));
908
909            // Check PoT slot iterations update and apply it if it is time to do so, while also
910            // removing corresponding storage item
911            if let Some(update) = pot_slot_iterations.update
912                && let Some(target_slot) = update.target_slot
913                && target_slot <= current_slot
914            {
915                debug!(
916                    target: "runtime::subspace",
917                    "Applying PoT slots update, changing to {} at block #{:?}",
918                    update.slot_iterations,
919                    block_number
920                );
921                pot_slot_iterations = PotSlotIterationsValue {
922                    slot_iterations: update.slot_iterations,
923                    update: None,
924                };
925                PotSlotIterations::<T>::put(pot_slot_iterations);
926            }
927            let pot_entropy_injection_interval = T::PotEntropyInjectionInterval::get();
928            let pot_entropy_injection_delay = T::PotEntropyInjectionDelay::get();
929
930            let mut entropy = PotEntropy::<T>::get();
931            let lookback_in_blocks = pot_entropy_injection_interval
932                * BlockNumberFor::<T>::from(T::PotEntropyInjectionLookbackDepth::get());
933            let last_entropy_injection_block =
934                block_number / pot_entropy_injection_interval * pot_entropy_injection_interval;
935            let maybe_entropy_source_block_number =
936                last_entropy_injection_block.checked_sub(&lookback_in_blocks);
937
938            if (block_number % pot_entropy_injection_interval).is_zero() {
939                let current_block_entropy = derive_pot_entropy(
940                    &pre_digest.solution().chunk,
941                    pre_digest.pot_info().proof_of_time(),
942                );
943                // Collect entropy every `T::PotEntropyInjectionInterval` blocks
944                entropy.insert(
945                    block_number,
946                    PotEntropyValue {
947                        target_slot: None,
948                        entropy: current_block_entropy,
949                    },
950                );
951
952                // Update target slot for entropy injection once we know it
953                if let Some(entropy_source_block_number) = maybe_entropy_source_block_number {
954                    if let Some(entropy_value) = entropy.get_mut(&entropy_source_block_number) {
955                        let target_slot = pre_digest
956                            .slot()
957                            .saturating_add(pot_entropy_injection_delay);
958                        debug!(
959                            target: "runtime::subspace",
960                            "Pot entropy injection will happen at slot {target_slot:?}",
961                        );
962                        entropy_value.target_slot.replace(target_slot);
963
964                        // Schedule PoT slot iterations update at the same slot as entropy
965                        if let Some(update) = &mut pot_slot_iterations.update
966                            && update.target_slot.is_none()
967                        {
968                            debug!(
969                                target: "runtime::subspace",
970                                "Scheduling PoT slots update to happen at slot {target_slot:?}"
971                            );
972                            update.target_slot.replace(target_slot);
973                            PotSlotIterations::<T>::put(pot_slot_iterations);
974                        }
975                    }
976                }
977
978                PotEntropy::<T>::put(entropy.clone());
979            }
980
981            // Deposit consensus log item with parameters change in case corresponding entropy is
982            // available
983            if let Some(entropy_source_block_number) = maybe_entropy_source_block_number {
984                let maybe_entropy_value = entropy.get(&entropy_source_block_number).copied();
985                if let Some(PotEntropyValue {
986                    target_slot,
987                    entropy,
988                }) = maybe_entropy_value
989                {
990                    let target_slot = target_slot
991                        .expect("Target slot is guaranteed to be present due to logic above; qed");
992                    // Check if there was a PoT slot iterations update at the same exact slot
993                    let slot_iterations = if let Some(update) = pot_slot_iterations.update
994                        && let Some(update_target_slot) = update.target_slot
995                        && update_target_slot == target_slot
996                    {
997                        debug!(
998                            target: "runtime::subspace",
999                            "Applying PoT slots update to the next PoT parameters change"
1000                        );
1001                        update.slot_iterations
1002                    } else {
1003                        pot_slot_iterations.slot_iterations
1004                    };
1005
1006                    frame_system::Pallet::<T>::deposit_log(DigestItem::pot_parameters_change(
1007                        PotParametersChange {
1008                            slot: target_slot,
1009                            slot_iterations,
1010                            entropy,
1011                        },
1012                    ));
1013                }
1014            }
1015
1016            // Clean up old values we'll no longer need
1017            if let Some(entry) = entropy.first_entry() {
1018                if let Some(target_slot) = entry.get().target_slot
1019                    && target_slot < current_slot
1020                {
1021                    entry.remove();
1022                    PotEntropy::<T>::put(entropy);
1023                }
1024            }
1025        }
1026    }
1027
1028    fn do_finalize(_block_number: BlockNumberFor<T>) {
1029        // Deposit the next solution range in the block finalization to account for solution range override extrinsic and
1030        // era change happens in the same block.
1031        if let Some(next_solution_range) = SolutionRanges::<T>::get().next {
1032            // Deposit next solution range data such that light client can validate blocks later.
1033            frame_system::Pallet::<T>::deposit_log(DigestItem::next_solution_range(
1034                next_solution_range,
1035            ));
1036        }
1037
1038        if let Some((public_key, sector_index, piece_offset, scalar, slot, _reward_address)) =
1039            CurrentBlockAuthorInfo::<T>::get()
1040        {
1041            ParentBlockAuthorInfo::<T>::put((public_key, sector_index, piece_offset, scalar, slot));
1042        } else {
1043            ParentBlockAuthorInfo::<T>::take();
1044        }
1045
1046        ParentVoteVerificationData::<T>::put(current_vote_verification_data::<T>(true));
1047
1048        ParentBlockVoters::<T>::put(CurrentBlockVoters::<T>::get().unwrap_or_default());
1049
1050        DidProcessSegmentHeaders::<T>::take();
1051    }
1052
1053    fn do_store_segment_headers(segment_headers: Vec<SegmentHeader>) -> DispatchResult {
1054        assert!(
1055            !DidProcessSegmentHeaders::<T>::exists(),
1056            "Segment headers must be updated only once in the block"
1057        );
1058
1059        for segment_header in segment_headers {
1060            SegmentCommitment::<T>::insert(
1061                segment_header.segment_index(),
1062                segment_header.segment_commitment(),
1063            );
1064            // Deposit global randomness data such that light client can validate blocks later.
1065            frame_system::Pallet::<T>::deposit_log(DigestItem::segment_commitment(
1066                segment_header.segment_index(),
1067                segment_header.segment_commitment(),
1068            ));
1069            Self::deposit_event(Event::SegmentHeaderStored { segment_header });
1070        }
1071
1072        DidProcessSegmentHeaders::<T>::put(true);
1073        Ok(())
1074    }
1075
1076    fn do_enable_solution_range_adjustment(
1077        solution_range_override: Option<u64>,
1078        voting_solution_range_override: Option<u64>,
1079    ) -> DispatchResult {
1080        if ShouldAdjustSolutionRange::<T>::get() {
1081            return Err(Error::<T>::SolutionRangeAdjustmentAlreadyEnabled.into());
1082        }
1083
1084        ShouldAdjustSolutionRange::<T>::put(true);
1085
1086        if let Some(solution_range) = solution_range_override {
1087            let voting_solution_range = voting_solution_range_override.unwrap_or_else(|| {
1088                solution_range.saturating_mul(u64::from(T::ExpectedVotesPerBlock::get()) + 1)
1089            });
1090            SolutionRanges::<T>::mutate(|solution_ranges| {
1091                // If solution range update is already scheduled, just update values
1092                if solution_ranges.next.is_some() {
1093                    solution_ranges.next.replace(solution_range);
1094                    solution_ranges.voting_next.replace(voting_solution_range);
1095                } else {
1096                    solution_ranges.current = solution_range;
1097                    solution_ranges.voting_current = voting_solution_range;
1098
1099                    // Solution range can re-adjust very soon, make sure next re-adjustment is
1100                    // also overridden
1101                    NextSolutionRangeOverride::<T>::put(SolutionRangeOverride {
1102                        solution_range,
1103                        voting_solution_range,
1104                    });
1105                    frame_system::Pallet::<T>::deposit_log(DigestItem::next_solution_range(
1106                        solution_range,
1107                    ));
1108                }
1109            });
1110        }
1111
1112        Ok(())
1113    }
1114
1115    fn do_vote(
1116        signed_vote: SignedVote<BlockNumberFor<T>, T::Hash, T::AccountId>,
1117    ) -> DispatchResult {
1118        let Vote::V0 {
1119            height,
1120            parent_hash,
1121            solution,
1122            ..
1123        } = signed_vote.vote;
1124
1125        Self::deposit_event(Event::FarmerVote {
1126            public_key: solution.public_key,
1127            reward_address: solution.reward_address,
1128            height,
1129            parent_hash,
1130        });
1131
1132        Ok(())
1133    }
1134
1135    fn do_enable_rewards_at(
1136        enable_rewards_at: EnableRewardsAt<BlockNumberFor<T>>,
1137    ) -> DispatchResult {
1138        match enable_rewards_at {
1139            EnableRewardsAt::Height(block_number) => {
1140                // Enable rewards at a particular block height (default to the next block after
1141                // this)
1142                let next_block_number =
1143                    frame_system::Pallet::<T>::current_block_number() + One::one();
1144                EnableRewards::<T>::put(block_number.max(next_block_number));
1145            }
1146            EnableRewardsAt::SolutionRange(solution_range) => {
1147                EnableRewardsBelowSolutionRange::<T>::put(solution_range);
1148            }
1149            EnableRewardsAt::Manually => {
1150                // Nothing to do in this case
1151            }
1152        }
1153
1154        Ok(())
1155    }
1156
1157    /// Proof of time parameters
1158    pub fn pot_parameters() -> PotParameters {
1159        let block_number = frame_system::Pallet::<T>::block_number();
1160        let pot_slot_iterations =
1161            PotSlotIterations::<T>::get().expect("Always initialized during genesis; qed");
1162        let pot_entropy_injection_interval = T::PotEntropyInjectionInterval::get();
1163
1164        let entropy = PotEntropy::<T>::get();
1165        let lookback_in_blocks = pot_entropy_injection_interval
1166            * BlockNumberFor::<T>::from(T::PotEntropyInjectionLookbackDepth::get());
1167        let last_entropy_injection_block =
1168            block_number / pot_entropy_injection_interval * pot_entropy_injection_interval;
1169        let maybe_entropy_source_block_number =
1170            last_entropy_injection_block.checked_sub(&lookback_in_blocks);
1171
1172        let mut next_change = None;
1173
1174        if let Some(entropy_source_block_number) = maybe_entropy_source_block_number {
1175            let maybe_entropy_value = entropy.get(&entropy_source_block_number).copied();
1176            if let Some(PotEntropyValue {
1177                target_slot,
1178                entropy,
1179            }) = maybe_entropy_value
1180            {
1181                let target_slot = target_slot.expect(
1182                    "Always present due to identical check present in block initialization; qed",
1183                );
1184                // Check if there was a PoT slot iterations update at the same exact slot
1185                let slot_iterations = if let Some(update) = pot_slot_iterations.update
1186                    && let Some(update_target_slot) = update.target_slot
1187                    && update_target_slot == target_slot
1188                {
1189                    update.slot_iterations
1190                } else {
1191                    pot_slot_iterations.slot_iterations
1192                };
1193
1194                next_change.replace(PotParametersChange {
1195                    slot: target_slot,
1196                    slot_iterations,
1197                    entropy,
1198                });
1199            }
1200        }
1201
1202        PotParameters::V0 {
1203            slot_iterations: pot_slot_iterations.slot_iterations,
1204            next_change,
1205        }
1206    }
1207
1208    /// Current slot number
1209    pub fn current_slot() -> Slot {
1210        BlockSlots::<T>::get()
1211            .last_key_value()
1212            .map(|(_block, slot)| *slot)
1213            .unwrap_or_default()
1214    }
1215
1216    /// Size of the archived history of the blockchain in bytes
1217    pub fn archived_history_size() -> u64 {
1218        let archived_segments = SegmentCommitment::<T>::count();
1219
1220        u64::from(archived_segments) * ArchivedHistorySegment::SIZE as u64
1221    }
1222}
1223
1224impl<T> Pallet<T>
1225where
1226    T: Config + CreateUnsigned<Call<T>>,
1227{
1228    /// Submit farmer vote that is essentially a header with bigger solution range than
1229    /// acceptable for block authoring.
1230    pub fn submit_vote(signed_vote: SignedVote<BlockNumberFor<T>, T::Hash, T::AccountId>) {
1231        let call = Call::vote {
1232            signed_vote: Box::new(signed_vote),
1233        };
1234
1235        let ext = T::create_unsigned(call.into());
1236        match SubmitTransaction::<T, Call<T>>::submit_transaction(ext) {
1237            Ok(()) => {
1238                debug!(target: "runtime::subspace", "Submitted Subspace vote");
1239            }
1240            Err(()) => {
1241                error!(target: "runtime::subspace", "Error submitting Subspace vote");
1242            }
1243        }
1244    }
1245}
1246
1247/// Methods for the `ValidateUnsigned` implementation:
1248/// It restricts calls to `store_segment_header` to local calls (i.e. extrinsics generated on this
1249/// node) or that already in a block. This guarantees that only block authors can include root
1250/// blocks.
1251impl<T: Config> Pallet<T> {
1252    fn validate_segment_header(
1253        source: TransactionSource,
1254        segment_headers: &[SegmentHeader],
1255    ) -> TransactionValidity {
1256        // Discard segment header not coming from the local node
1257        if !matches!(
1258            source,
1259            TransactionSource::Local | TransactionSource::InBlock,
1260        ) {
1261            warn!(
1262                target: "runtime::subspace",
1263                "Rejecting segment header extrinsic because it is not local/in-block.",
1264            );
1265
1266            return InvalidTransaction::Call.into();
1267        }
1268
1269        check_segment_headers::<T>(segment_headers)?;
1270
1271        ValidTransaction::with_tag_prefix("SubspaceSegmentHeader")
1272            // We assign the maximum priority for any segment header.
1273            .priority(TransactionPriority::MAX)
1274            // Should be included immediately into the current block (this is an inherent
1275            // extrinsic) with no exceptions.
1276            .longevity(0)
1277            // We don't propagate this. This can never be included on a remote node.
1278            .propagate(false)
1279            .build()
1280    }
1281
1282    fn pre_dispatch_segment_header(
1283        segment_headers: &[SegmentHeader],
1284    ) -> Result<(), TransactionValidityError> {
1285        check_segment_headers::<T>(segment_headers)
1286    }
1287
1288    fn validate_vote(
1289        signed_vote: &SignedVote<BlockNumberFor<T>, T::Hash, T::AccountId>,
1290    ) -> Result<(ValidTransaction, Weight), TransactionValidityError> {
1291        check_vote::<T>(signed_vote, false)?;
1292
1293        ValidTransaction::with_tag_prefix("SubspaceVote")
1294            // We assign the maximum priority for any vote.
1295            .priority(TransactionPriority::MAX)
1296            // Should be included in the next block or block after that, but not later
1297            .longevity(2)
1298            .and_provides(signed_vote.signature)
1299            .build()
1300            .map(|validity| (validity, T::ExtensionWeightInfo::vote()))
1301    }
1302
1303    fn pre_dispatch_vote(
1304        signed_vote: &SignedVote<BlockNumberFor<T>, T::Hash, T::AccountId>,
1305    ) -> Result<Weight, TransactionValidityError> {
1306        match check_vote::<T>(signed_vote, true) {
1307            Ok(()) => Ok(T::ExtensionWeightInfo::vote()),
1308            Err(CheckVoteError::Equivocated { .. }) => {
1309                // Return Ok such that changes from this pre-dispatch are persisted
1310                Ok(T::ExtensionWeightInfo::vote_with_equivocation())
1311            }
1312            Err(error) => Err(error.into()),
1313        }
1314    }
1315}
1316
1317/// Verification data retrieval depends on whether it is called from pre_dispatch (meaning block
1318/// initialization has already happened) or from `validate_unsigned` by transaction pool (meaning
1319/// block initialization didn't happen yet).
1320fn current_vote_verification_data<T: Config>(is_block_initialized: bool) -> VoteVerificationData {
1321    let solution_ranges = SolutionRanges::<T>::get();
1322
1323    VoteVerificationData {
1324        solution_range: if is_block_initialized {
1325            solution_ranges.current
1326        } else {
1327            solution_ranges.next.unwrap_or(solution_ranges.current)
1328        },
1329        vote_solution_range: if is_block_initialized {
1330            solution_ranges.voting_current
1331        } else {
1332            solution_ranges
1333                .voting_next
1334                .unwrap_or(solution_ranges.voting_current)
1335        },
1336        current_slot: Pallet::<T>::current_slot(),
1337        parent_slot: ParentVoteVerificationData::<T>::get()
1338            .map(|parent_vote_verification_data| {
1339                if is_block_initialized {
1340                    parent_vote_verification_data.current_slot
1341                } else {
1342                    parent_vote_verification_data.parent_slot
1343                }
1344            })
1345            .unwrap_or_else(Pallet::<T>::current_slot),
1346    }
1347}
1348
1349#[derive(Debug, Eq, PartialEq)]
1350enum CheckVoteError {
1351    UnexpectedBeforeHeightTwo,
1352    HeightInTheFuture,
1353    HeightInThePast,
1354    IncorrectParentHash,
1355    SlotInTheFuture,
1356    SlotInThePast,
1357    BadRewardSignature(SignatureError),
1358    UnknownSegmentCommitment,
1359    InvalidHistorySize,
1360    InvalidSolution(String),
1361    QualityTooHigh,
1362    InvalidProofOfTime,
1363    InvalidFutureProofOfTime,
1364    DuplicateVote,
1365    Equivocated { slot: Slot, offender: PublicKey },
1366}
1367
1368impl From<CheckVoteError> for TransactionValidityError {
1369    #[inline]
1370    fn from(error: CheckVoteError) -> Self {
1371        TransactionValidityError::Invalid(match error {
1372            CheckVoteError::UnexpectedBeforeHeightTwo => InvalidTransaction::Call,
1373            CheckVoteError::HeightInTheFuture => InvalidTransaction::Future,
1374            CheckVoteError::HeightInThePast => InvalidTransaction::Stale,
1375            CheckVoteError::IncorrectParentHash => InvalidTransaction::Call,
1376            CheckVoteError::SlotInTheFuture => InvalidTransaction::Future,
1377            CheckVoteError::SlotInThePast => InvalidTransaction::Stale,
1378            CheckVoteError::BadRewardSignature(_) => InvalidTransaction::BadProof,
1379            CheckVoteError::UnknownSegmentCommitment => InvalidTransaction::Call,
1380            CheckVoteError::InvalidHistorySize => InvalidTransaction::Call,
1381            CheckVoteError::InvalidSolution(_) => InvalidTransaction::Call,
1382            CheckVoteError::QualityTooHigh => InvalidTransaction::Call,
1383            CheckVoteError::InvalidProofOfTime => InvalidTransaction::Future,
1384            CheckVoteError::InvalidFutureProofOfTime => InvalidTransaction::Call,
1385            CheckVoteError::DuplicateVote => InvalidTransaction::Call,
1386            CheckVoteError::Equivocated { .. } => InvalidTransaction::BadSigner,
1387        })
1388    }
1389}
1390
1391fn check_vote<T: Config>(
1392    signed_vote: &SignedVote<BlockNumberFor<T>, T::Hash, T::AccountId>,
1393    pre_dispatch: bool,
1394) -> Result<(), CheckVoteError> {
1395    let Vote::V0 {
1396        height,
1397        parent_hash,
1398        slot,
1399        solution,
1400        proof_of_time,
1401        future_proof_of_time,
1402    } = &signed_vote.vote;
1403    let height = *height;
1404    let slot = *slot;
1405
1406    let current_block_number = frame_system::Pallet::<T>::current_block_number();
1407
1408    if current_block_number <= One::one() || height <= One::one() {
1409        debug!(
1410            target: "runtime::subspace",
1411            "Votes are not expected at height below 2"
1412        );
1413
1414        return Err(CheckVoteError::UnexpectedBeforeHeightTwo);
1415    }
1416
1417    // Height must be either the same as in current block or smaller by one.
1418    //
1419    // Subtraction will not panic due to check above.
1420    if !(height == current_block_number || height == current_block_number - One::one()) {
1421        debug!(
1422            target: "runtime::subspace",
1423            "Vote verification error: bad height {height:?}, current block number is \
1424            {current_block_number:?}"
1425        );
1426        return Err(if height > current_block_number {
1427            CheckVoteError::HeightInTheFuture
1428        } else {
1429            CheckVoteError::HeightInThePast
1430        });
1431    }
1432
1433    // Should have parent hash from -1 (parent hash of current block) or -2 (block before that)
1434    //
1435    // Subtraction will not panic due to check above.
1436    if *parent_hash != frame_system::Pallet::<T>::block_hash(height - One::one()) {
1437        debug!(
1438            target: "runtime::subspace",
1439            "Vote verification error: parent hash {parent_hash:?}",
1440        );
1441        return Err(CheckVoteError::IncorrectParentHash);
1442    }
1443
1444    let current_vote_verification_data = current_vote_verification_data::<T>(pre_dispatch);
1445    let parent_vote_verification_data = ParentVoteVerificationData::<T>::get()
1446        .expect("Above check for block number ensures that this value is always present; qed");
1447
1448    if pre_dispatch {
1449        // New time slot is already set, whatever time slot is in the vote it must be smaller or the
1450        // same (for votes produced locally)
1451        let current_slot = current_vote_verification_data.current_slot;
1452        if slot > current_slot || (slot == current_slot && height != current_block_number) {
1453            debug!(
1454                target: "runtime::subspace",
1455                "Vote slot {slot:?} must be before current slot {current_slot:?}",
1456            );
1457            return Err(CheckVoteError::SlotInTheFuture);
1458        }
1459    }
1460
1461    let parent_slot = if pre_dispatch {
1462        // For pre-dispatch parent slot is `current_slot` in the parent vote verification data (it
1463        // was updated in current block because initialization hook was already called) if vote is
1464        // at the same height as the current block, otherwise it is one level older and
1465        // `parent_slot` from parent vote verification data needs to be taken instead
1466        if height == current_block_number {
1467            parent_vote_verification_data.current_slot
1468        } else {
1469            parent_vote_verification_data.parent_slot
1470        }
1471    } else {
1472        // Otherwise parent slot is `current_slot` in the current vote verification data (that
1473        // wasn't updated from parent block because initialization hook wasn't called yet) if vote
1474        // is at the same height as the current block, otherwise it is one level older and
1475        // `parent_slot` from current vote verification data needs to be taken instead
1476        if height == current_block_number {
1477            current_vote_verification_data.current_slot
1478        } else {
1479            current_vote_verification_data.parent_slot
1480        }
1481    };
1482
1483    if slot <= parent_slot {
1484        debug!(
1485            target: "runtime::subspace",
1486            "Vote slot {slot:?} must be after parent slot {parent_slot:?}",
1487        );
1488        return Err(CheckVoteError::SlotInThePast);
1489    }
1490
1491    if let Err(error) = check_reward_signature(
1492        signed_vote.vote.hash().as_bytes(),
1493        &signed_vote.signature,
1494        &solution.public_key,
1495        &schnorrkel::signing_context(REWARD_SIGNING_CONTEXT),
1496    ) {
1497        debug!(
1498            target: "runtime::subspace",
1499            "Vote verification error: {error:?}"
1500        );
1501        return Err(CheckVoteError::BadRewardSignature(error));
1502    }
1503
1504    let vote_verification_data = if height == current_block_number {
1505        current_vote_verification_data
1506    } else {
1507        parent_vote_verification_data
1508    };
1509
1510    let sector_id = SectorId::new(
1511        solution.public_key.hash(),
1512        solution.sector_index,
1513        solution.history_size,
1514    );
1515
1516    let recent_segments = T::RecentSegments::get();
1517    let recent_history_fraction = (
1518        T::RecentHistoryFraction::get().0,
1519        T::RecentHistoryFraction::get().1,
1520    );
1521    let segment_index = sector_id
1522        .derive_piece_index(
1523            solution.piece_offset,
1524            solution.history_size,
1525            T::MaxPiecesInSector::get(),
1526            recent_segments,
1527            recent_history_fraction,
1528        )
1529        .segment_index();
1530
1531    let segment_commitment =
1532        if let Some(segment_commitment) = Pallet::<T>::segment_commitment(segment_index) {
1533            segment_commitment
1534        } else {
1535            debug!(
1536                target: "runtime::subspace",
1537                "Vote verification error: no segment commitment for segment index {segment_index}"
1538            );
1539            return Err(CheckVoteError::UnknownSegmentCommitment);
1540        };
1541
1542    let sector_expiration_check_segment_commitment = Pallet::<T>::segment_commitment(
1543        solution
1544            .history_size
1545            .sector_expiration_check(T::MinSectorLifetime::get())
1546            .ok_or(CheckVoteError::InvalidHistorySize)?
1547            .segment_index(),
1548    );
1549
1550    match verify_solution(
1551        solution.into(),
1552        slot.into(),
1553        (&VerifySolutionParams {
1554            proof_of_time: *proof_of_time,
1555            solution_range: vote_verification_data.vote_solution_range,
1556            piece_check_params: Some(PieceCheckParams {
1557                max_pieces_in_sector: T::MaxPiecesInSector::get(),
1558                segment_commitment,
1559                recent_segments,
1560                recent_history_fraction,
1561                min_sector_lifetime: T::MinSectorLifetime::get(),
1562                current_history_size: Pallet::<T>::history_size(),
1563                sector_expiration_check_segment_commitment,
1564            }),
1565        })
1566            .into(),
1567    ) {
1568        Ok(solution_distance) => {
1569            if solution_distance <= vote_verification_data.solution_range / 2 {
1570                debug!(
1571                    target: "runtime::subspace",
1572                    "Vote quality is too high"
1573                );
1574                return Err(CheckVoteError::QualityTooHigh);
1575            }
1576        }
1577        Err(error) => {
1578            debug!(
1579                target: "runtime::subspace",
1580                "Vote verification error: {error:?}"
1581            );
1582            return Err(CheckVoteError::InvalidSolution(error));
1583        }
1584    }
1585
1586    // Cheap proof of time verification is possible here because proof of time must have already
1587    // been seen by this node due to votes requiring the same authoring delay as blocks
1588    if !is_proof_of_time_valid(
1589        BlockHash::try_from(parent_hash.as_ref())
1590            .expect("Must be able to convert to block hash type"),
1591        SlotNumber::from(slot),
1592        WrappedPotOutput::from(*proof_of_time),
1593        // Quick verification when entering transaction pool, but not when constructing the block
1594        !pre_dispatch,
1595    ) {
1596        debug!(target: "runtime::subspace", "Invalid proof of time");
1597
1598        return Err(CheckVoteError::InvalidProofOfTime);
1599    }
1600
1601    // During pre-dispatch we have already verified proofs of time up to future proof of time of
1602    // current block, which vote can't exceed, this must be possible to verify cheaply
1603    if pre_dispatch
1604        && !is_proof_of_time_valid(
1605            BlockHash::try_from(parent_hash.as_ref())
1606                .expect("Must be able to convert to block hash type"),
1607            SlotNumber::from(slot + T::BlockAuthoringDelay::get()),
1608            WrappedPotOutput::from(*future_proof_of_time),
1609            false,
1610        )
1611    {
1612        debug!(target: "runtime::subspace", "Invalid future proof of time");
1613
1614        return Err(CheckVoteError::InvalidFutureProofOfTime);
1615    }
1616
1617    let key = (
1618        solution.public_key,
1619        solution.sector_index,
1620        solution.piece_offset,
1621        solution.chunk,
1622        slot,
1623    );
1624    // Check that farmer didn't use solution from this vote yet in:
1625    // * parent block
1626    // * current block
1627    // * parent block vote
1628    // * current block vote
1629    let mut is_equivocating = ParentBlockAuthorInfo::<T>::get().as_ref() == Some(&key)
1630        || CurrentBlockAuthorInfo::<T>::get()
1631            .map(
1632                |(public_key, sector_index, piece_offset, chunk, slot, _reward_address)| {
1633                    (public_key, sector_index, piece_offset, chunk, slot)
1634                },
1635            )
1636            .as_ref()
1637            == Some(&key);
1638
1639    if !is_equivocating {
1640        if let Some((_reward_address, signature)) = ParentBlockVoters::<T>::get().get(&key) {
1641            if signature != &signed_vote.signature {
1642                is_equivocating = true;
1643            } else {
1644                // The same vote should never be included more than once
1645                return Err(CheckVoteError::DuplicateVote);
1646            }
1647        }
1648    }
1649
1650    if !is_equivocating {
1651        if let Some((_reward_address, signature)) =
1652            CurrentBlockVoters::<T>::get().unwrap_or_default().get(&key)
1653        {
1654            if signature != &signed_vote.signature {
1655                is_equivocating = true;
1656            } else {
1657                // The same vote should never be included more than once
1658                return Err(CheckVoteError::DuplicateVote);
1659            }
1660        }
1661    }
1662
1663    if pre_dispatch {
1664        // During `pre_dispatch` call put farmer into the list of reward receivers.
1665        CurrentBlockVoters::<T>::mutate(|current_reward_receivers| {
1666            current_reward_receivers
1667                .as_mut()
1668                .expect("Always set during block initialization")
1669                .insert(
1670                    key,
1671                    (
1672                        if is_equivocating {
1673                            None
1674                        } else {
1675                            Some(solution.reward_address.clone())
1676                        },
1677                        signed_vote.signature,
1678                    ),
1679                );
1680        });
1681    }
1682
1683    if is_equivocating {
1684        let offender = solution.public_key;
1685
1686        CurrentBlockAuthorInfo::<T>::mutate(|maybe_info| {
1687            if let Some((public_key, _sector_index, _piece_offset, _chunk, _slot, reward_address)) =
1688                maybe_info
1689            {
1690                if public_key == &offender {
1691                    // Revoke reward for block author
1692                    reward_address.take();
1693                }
1694            }
1695        });
1696
1697        CurrentBlockVoters::<T>::mutate(|current_reward_receivers| {
1698            if let Some(current_reward_receivers) = current_reward_receivers {
1699                for (
1700                    (public_key, _sector_index, _piece_offset, _chunk, _slot),
1701                    (reward_address, _signature),
1702                ) in current_reward_receivers.iter_mut()
1703                {
1704                    if public_key == &offender {
1705                        // Revoke reward if assigned in current block.
1706                        reward_address.take();
1707                    }
1708                }
1709            }
1710        });
1711
1712        return Err(CheckVoteError::Equivocated { slot, offender });
1713    }
1714
1715    Ok(())
1716}
1717
1718fn check_segment_headers<T: Config>(
1719    segment_headers: &[SegmentHeader],
1720) -> Result<(), TransactionValidityError> {
1721    let mut segment_headers_iter = segment_headers.iter();
1722
1723    // There should be some segment headers
1724    let first_segment_header = match segment_headers_iter.next() {
1725        Some(first_segment_header) => first_segment_header,
1726        None => {
1727            return Err(InvalidTransaction::BadMandatory.into());
1728        }
1729    };
1730
1731    // Segment in segment headers should monotonically increase
1732    if first_segment_header.segment_index() > SegmentIndex::ZERO
1733        && !SegmentCommitment::<T>::contains_key(
1734            first_segment_header.segment_index() - SegmentIndex::ONE,
1735        )
1736    {
1737        return Err(InvalidTransaction::BadMandatory.into());
1738    }
1739
1740    // Segment headers should never repeat
1741    if SegmentCommitment::<T>::contains_key(first_segment_header.segment_index()) {
1742        return Err(InvalidTransaction::BadMandatory.into());
1743    }
1744
1745    let mut last_segment_index = first_segment_header.segment_index();
1746
1747    for segment_header in segment_headers_iter {
1748        let segment_index = segment_header.segment_index();
1749
1750        // Segment in segment headers should monotonically increase
1751        if segment_index != last_segment_index + SegmentIndex::ONE {
1752            return Err(InvalidTransaction::BadMandatory.into());
1753        }
1754
1755        // Segment headers should never repeat
1756        if SegmentCommitment::<T>::contains_key(segment_index) {
1757            return Err(InvalidTransaction::BadMandatory.into());
1758        }
1759
1760        last_segment_index = segment_index;
1761    }
1762
1763    Ok(())
1764}
1765
1766impl<T: Config> subspace_runtime_primitives::RewardsEnabled for Pallet<T> {
1767    fn rewards_enabled() -> bool {
1768        if let Some(height) = EnableRewards::<T>::get() {
1769            frame_system::Pallet::<T>::current_block_number() >= height
1770        } else {
1771            false
1772        }
1773    }
1774}
1775
1776impl<T: Config> subspace_runtime_primitives::FindBlockRewardAddress<T::AccountId> for Pallet<T> {
1777    fn find_block_reward_address() -> Option<T::AccountId> {
1778        CurrentBlockAuthorInfo::<T>::get().and_then(
1779            |(_public_key, _sector_index, _piece_offset, _chunk, _slot, reward_address)| {
1780                // Rewards might be disabled, in which case no block reward
1781                if let Some(height) = EnableRewards::<T>::get() {
1782                    if frame_system::Pallet::<T>::current_block_number() >= height {
1783                        return reward_address;
1784                    }
1785                }
1786
1787                None
1788            },
1789        )
1790    }
1791}
1792
1793impl<T: Config> subspace_runtime_primitives::FindVotingRewardAddresses<T::AccountId> for Pallet<T> {
1794    fn find_voting_reward_addresses() -> Vec<T::AccountId> {
1795        // Rewards might be disabled, in which case no voting reward
1796        if let Some(height) = EnableRewards::<T>::get() {
1797            if frame_system::Pallet::<T>::current_block_number() >= height {
1798                // It is possible that this is called during initialization when current block
1799                // voters are already moved into parent block voters, handle it accordingly
1800                return CurrentBlockVoters::<T>::get()
1801                    .unwrap_or_else(ParentBlockVoters::<T>::get)
1802                    .into_values()
1803                    .filter_map(|(reward_address, _signature)| reward_address)
1804                    .collect();
1805            }
1806        }
1807
1808        Vec::new()
1809    }
1810}
1811
1812impl<T: Config> frame_support::traits::Randomness<T::Hash, BlockNumberFor<T>> for Pallet<T> {
1813    fn random(subject: &[u8]) -> (T::Hash, BlockNumberFor<T>) {
1814        let mut subject = subject.to_vec();
1815        subject.extend_from_slice(
1816            BlockRandomness::<T>::get()
1817                .expect("Block randomness is always set in block initialization; qed")
1818                .as_ref(),
1819        );
1820
1821        (
1822            T::Hashing::hash(&subject),
1823            frame_system::Pallet::<T>::current_block_number(),
1824        )
1825    }
1826
1827    fn random_seed() -> (T::Hash, BlockNumberFor<T>) {
1828        (
1829            T::Hashing::hash(
1830                BlockRandomness::<T>::get()
1831                    .expect("Block randomness is always set in block initialization; qed")
1832                    .as_ref(),
1833            ),
1834            frame_system::Pallet::<T>::current_block_number(),
1835        )
1836    }
1837}