pallet_subspace/
lib.rs

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