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
65pub trait EraChangeTrigger {
67 fn trigger<T: Config>(block_number: BlockNumberFor<T>);
70}
71
72pub 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#[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
99pub 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 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 #[derive(Debug, Encode, Decode, TypeInfo)]
171 pub(super) struct SolutionRangeOverride {
172 pub(super) solution_range: SolutionRange,
174 pub(super) voting_solution_range: SolutionRange,
176 }
177
178 #[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 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
188
189 type SubspaceOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = ()>;
191
192 #[pallet::constant]
197 type BlockAuthoringDelay: Get<Slot>;
198
199 #[pallet::constant]
201 type PotEntropyInjectionInterval: Get<BlockNumberFor<Self>>;
202
203 #[pallet::constant]
205 type PotEntropyInjectionLookbackDepth: Get<u8>;
206
207 #[pallet::constant]
209 type PotEntropyInjectionDelay: Get<Slot>;
210
211 #[pallet::constant]
215 type EraDuration: Get<BlockNumberFor<Self>>;
216
217 #[pallet::constant]
219 type InitialSolutionRange: Get<SolutionRange>;
220
221 #[pallet::constant]
227 type SlotProbability: Get<(u64, u64)>;
228
229 #[pallet::constant]
232 type ConfirmationDepthK: Get<BlockNumberFor<Self>>;
233
234 #[pallet::constant]
236 type RecentSegments: Get<HistorySize>;
237
238 #[pallet::constant]
240 type RecentHistoryFraction: Get<(HistorySize, HistorySize)>;
241
242 #[pallet::constant]
244 type MinSectorLifetime: Get<HistorySize>;
245
246 #[pallet::constant]
250 type ExpectedVotesPerBlock: Get<u32>;
251
252 #[pallet::constant]
254 type MaxPiecesInSector: Get<u16>;
255
256 type ShouldAdjustSolutionRange: Get<bool>;
257 type EraChangeTrigger: EraChangeTrigger;
262
263 type WeightInfo: WeightInfo;
265
266 #[pallet::constant]
268 type BlockSlotCount: Get<u32>;
269
270 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 #[default]
279 Anyone,
280 FirstFarmer,
283 RootFarmer(PublicKey),
285 }
286
287 #[derive(Debug, Copy, Clone, Encode, Decode, TypeInfo)]
288 pub(super) struct PotEntropyValue {
289 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 pub(super) update: Option<PotSlotIterationsUpdate>,
299 }
300
301 #[derive(Debug, Copy, Clone, Encode, Decode, TypeInfo, PartialEq)]
302 pub(super) struct PotSlotIterationsUpdate {
303 pub(super) target_slot: Option<Slot>,
305 pub(super) slot_iterations: NonZeroU32,
306 }
307
308 #[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 Height(BlockNumber),
316 SolutionRange(u64),
318 Manually,
320 }
321
322 #[pallet::genesis_config]
323 pub struct GenesisConfig<T>
324 where
325 T: Config,
326 {
327 pub enable_rewards_at: EnableRewardsAt<BlockNumberFor<T>>,
329 pub allow_authoring_by: AllowAuthoringBy,
331 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 }
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 #[pallet::event]
390 #[pallet::generate_deposit(pub (super) fn deposit_event)]
391 pub enum Event<T: Config> {
392 SegmentHeaderStored { segment_header: SegmentHeader },
394 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 SolutionRangeAdjustmentAlreadyEnabled,
410 NotMultipleOfCheckpoints,
412 PotSlotIterationsMustIncrease,
414 PotSlotIterationsUpdateAlreadyScheduled,
416 }
417
418 #[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 #[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 #[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 #[pallet::storage]
442 pub(super) type NextSolutionRangeOverride<T> = StorageValue<_, SolutionRangeOverride>;
443
444 #[pallet::storage]
446 pub(super) type EraStartSlot<T> = StorageValue<_, Slot>;
447
448 #[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 #[pallet::storage]
463 pub(super) type DidProcessSegmentHeaders<T: Config> = StorageValue<_, bool, ValueQuery>;
464
465 #[pallet::storage]
467 pub(super) type ParentVoteVerificationData<T> = StorageValue<_, VoteVerificationData>;
468
469 #[pallet::storage]
471 pub(super) type ParentBlockAuthorInfo<T> =
472 StorageValue<_, (PublicKey, SectorIndex, PieceOffset, ScalarBytes, Slot)>;
473
474 #[pallet::storage]
476 pub(super) type EnableRewards<T: Config> = StorageValue<_, BlockNumberFor<T>>;
477
478 #[pallet::storage]
480 pub(super) type EnableRewardsBelowSolutionRange<T: Config> = StorageValue<_, u64>;
481
482 #[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 #[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 #[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 #[pallet::storage]
519 pub(super) type PotSlotIterations<T> = StorageValue<_, PotSlotIterationsValue>;
520
521 #[pallet::storage]
524 pub(super) type PotEntropy<T: Config> =
525 StorageValue<_, BTreeMap<BlockNumberFor<T>, PotEntropyValue>, ValueQuery>;
526
527 #[pallet::storage]
530 pub type BlockRandomness<T> = StorageValue<_, Randomness>;
531
532 #[pallet::storage]
534 pub(super) type AllowAuthoringByAnyone<T> = StorageValue<_, bool, ValueQuery>;
535
536 #[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 #[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 #[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 #[pallet::call_index(2)]
594 #[pallet::weight((< T as Config >::WeightInfo::vote(), DispatchClass::Operational))]
595 #[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 #[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 #[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 frame_system::Pallet::<T>::deposit_log(DigestItem::root_plot_public_key_update(None));
629
630 Ok(())
631 }
632
633 #[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 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 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 pub fn history_size() -> HistorySize {
757 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 fn should_era_change(block_number: BlockNumberFor<T>) -> bool {
765 block_number % T::EraDuration::get() == Zero::zero()
766 }
767
768 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 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 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 CurrentBlockAuthorInfo::<T>::take();
841 let farmer_public_key = pre_digest.solution().public_key;
842
843 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 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 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 BlockRandomness::<T>::put(block_randomness);
908
909 frame_system::Pallet::<T>::deposit_log(DigestItem::solution_range(
911 SolutionRanges::<T>::get().current,
912 ));
913
914 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 frame_system::Pallet::<T>::deposit_log(DigestItem::pot_slot_iterations(
922 pot_slot_iterations.slot_iterations,
923 ));
924
925 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 entropy.insert(
959 block_number,
960 PotEntropyValue {
961 target_slot: None,
962 entropy: current_block_entropy,
963 },
964 );
965
966 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 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 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 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 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 if let Some(next_solution_range) = SolutionRanges::<T>::get().next {
1036 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 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_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 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 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 }
1156 }
1157
1158 Ok(())
1159 }
1160
1161 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 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 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 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 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
1251impl<T: Config> Pallet<T> {
1256 fn validate_segment_header(
1257 source: TransactionSource,
1258 segment_headers: &[SegmentHeader],
1259 ) -> TransactionValidity {
1260 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 .priority(TransactionPriority::MAX)
1275 .longevity(0)
1278 .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 .priority(TransactionPriority::MAX)
1297 .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 Ok(T::ExtensionWeightInfo::vote_with_equivocation())
1312 }
1313 Err(error) => Err(error.into()),
1314 }
1315 }
1316}
1317
1318fn 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 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 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 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 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 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 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 !pre_dispatch,
1572 ) {
1573 debug!("Invalid proof of time");
1574
1575 return Err(CheckVoteError::InvalidProofOfTime);
1576 }
1577
1578 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 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 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 return Err(CheckVoteError::DuplicateVote);
1636 }
1637 }
1638
1639 if pre_dispatch {
1640 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 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 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 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 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 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 if segment_index != last_segment_index + SegmentIndex::ONE {
1727 return Err(InvalidTransaction::BadMandatory.into());
1728 }
1729
1730 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 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 if let Some(height) = EnableRewards::<T>::get()
1772 && frame_system::Pallet::<T>::current_block_number() >= height
1773 {
1774 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}