pallet_subspace/
lib.rs

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