Skip to main content

pallet_subspace/
lib.rs

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