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, 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.get() % u32::from(PotCheckpoints::NUM_CHECKPOINTS.get() * 2) != 0 {
643 return Err(Error::<T>::NotMultipleOfCheckpoints.into());
644 }
645
646 let mut pot_slot_iterations =
647 PotSlotIterations::<T>::get().expect("Always initialized during genesis; qed");
648
649 if pot_slot_iterations.slot_iterations >= slot_iterations {
650 return Err(Error::<T>::PotSlotIterationsMustIncrease.into());
651 }
652
653 if let Some(pot_slot_iterations_update_value) = pot_slot_iterations.update
655 && pot_slot_iterations_update_value.target_slot.is_some()
656 {
657 return Err(Error::<T>::PotSlotIterationsUpdateAlreadyScheduled.into());
658 }
659
660 pot_slot_iterations.update.replace(PotSlotIterationsUpdate {
661 target_slot: None,
663 slot_iterations,
664 });
665
666 PotSlotIterations::<T>::put(pot_slot_iterations);
667
668 Ok(())
669 }
670 }
671
672 #[pallet::inherent]
673 impl<T: Config> ProvideInherent for Pallet<T> {
674 type Call = Call<T>;
675 type Error = InherentError;
676 const INHERENT_IDENTIFIER: InherentIdentifier = INHERENT_IDENTIFIER;
677
678 fn create_inherent(data: &InherentData) -> Option<Self::Call> {
679 let inherent_data = data
680 .get_data::<InherentType>(&INHERENT_IDENTIFIER)
681 .expect("Subspace inherent data not correctly encoded")
682 .expect("Subspace inherent data must be provided");
683
684 let segment_headers = inherent_data.segment_headers;
685 if segment_headers.is_empty() {
686 None
687 } else {
688 Some(Call::store_segment_headers { segment_headers })
689 }
690 }
691
692 fn is_inherent_required(data: &InherentData) -> Result<Option<Self::Error>, Self::Error> {
693 let inherent_data = data
694 .get_data::<InherentType>(&INHERENT_IDENTIFIER)
695 .expect("Subspace inherent data not correctly encoded")
696 .expect("Subspace inherent data must be provided");
697
698 Ok(if inherent_data.segment_headers.is_empty() {
699 None
700 } else {
701 Some(InherentError::MissingSegmentHeadersList)
702 })
703 }
704
705 fn check_inherent(call: &Self::Call, data: &InherentData) -> Result<(), Self::Error> {
706 if let Call::store_segment_headers { segment_headers } = call {
707 let inherent_data = data
708 .get_data::<InherentType>(&INHERENT_IDENTIFIER)
709 .expect("Subspace inherent data not correctly encoded")
710 .expect("Subspace inherent data must be provided");
711
712 if segment_headers != &inherent_data.segment_headers {
713 return Err(InherentError::IncorrectSegmentHeadersList {
714 expected: inherent_data.segment_headers,
715 actual: segment_headers.clone(),
716 });
717 }
718 }
719
720 Ok(())
721 }
722
723 fn is_inherent(call: &Self::Call) -> bool {
724 matches!(call, Call::store_segment_headers { .. })
725 }
726 }
727
728 #[pallet::validate_unsigned]
729 impl<T: Config> ValidateUnsigned for Pallet<T> {
730 type Call = Call<T>;
731 fn validate_unsigned(source: TransactionSource, call: &Self::Call) -> TransactionValidity {
732 match call {
733 Call::store_segment_headers { segment_headers } => {
734 Self::validate_segment_header(source, segment_headers)
735 }
736 _ => InvalidTransaction::Call.into(),
737 }
738 }
739
740 fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> {
741 match call {
742 Call::store_segment_headers { segment_headers } => {
743 Self::pre_dispatch_segment_header(segment_headers)
744 }
745 _ => Err(InvalidTransaction::Call.into()),
746 }
747 }
748 }
749}
750
751impl<T: Config> Pallet<T> {
752 pub fn history_size() -> HistorySize {
754 let number_of_segments = u64::from(SegmentCommitment::<T>::count()).max(1);
756 HistorySize::from(NonZeroU64::new(number_of_segments).expect("Not zero; qed"))
757 }
758
759 fn should_era_change(block_number: BlockNumberFor<T>) -> bool {
762 block_number % T::EraDuration::get() == Zero::zero()
763 }
764
765 fn enact_era_change() {
770 let slot_probability = T::SlotProbability::get();
771
772 let current_slot = Self::current_slot();
773
774 SolutionRanges::<T>::mutate(|solution_ranges| {
775 let next_solution_range;
776 let next_voting_solution_range;
777 if !ShouldAdjustSolutionRange::<T>::get() {
779 next_solution_range = solution_ranges.current;
780 next_voting_solution_range = solution_ranges.current;
781 } else if let Some(solution_range_override) = NextSolutionRangeOverride::<T>::take() {
782 next_solution_range = solution_range_override.solution_range;
783 next_voting_solution_range = solution_range_override.voting_solution_range;
784 } else {
785 next_solution_range = derive_next_solution_range(
786 u64::from(EraStartSlot::<T>::get().unwrap_or_default()),
788 u64::from(current_slot),
789 slot_probability,
790 solution_ranges.current,
791 T::EraDuration::get()
792 .try_into()
793 .unwrap_or_else(|_| panic!("Era duration is always within u64; qed")),
794 );
795
796 next_voting_solution_range = next_solution_range
797 .saturating_mul(u64::from(T::ExpectedVotesPerBlock::get()) + 1);
798 };
799 solution_ranges.next.replace(next_solution_range);
800 solution_ranges
801 .voting_next
802 .replace(next_voting_solution_range);
803
804 if let Some(solution_range_for_rewards) = EnableRewardsBelowSolutionRange::<T>::get()
805 && next_solution_range <= solution_range_for_rewards
806 {
807 EnableRewardsBelowSolutionRange::<T>::take();
808
809 let next_block_number =
810 frame_system::Pallet::<T>::current_block_number() + One::one();
811 EnableRewards::<T>::put(next_block_number);
812 }
813 });
814
815 EraStartSlot::<T>::put(current_slot);
816 }
817
818 fn do_initialize(block_number: BlockNumberFor<T>) {
819 let pre_digest = frame_system::Pallet::<T>::digest()
820 .logs
821 .iter()
822 .find_map(|s| s.as_subspace_pre_digest::<T::AccountId>())
823 .expect("Block must always have pre-digest");
824 let current_slot = pre_digest.slot();
825
826 BlockSlots::<T>::mutate(|block_slots| {
827 if let Some(to_remove) = block_number.checked_sub(&T::BlockSlotCount::get().into()) {
828 block_slots.remove(&to_remove);
829 }
830 block_slots
831 .try_insert(block_number, current_slot)
832 .expect("one entry just removed before inserting; qed");
833 });
834
835 {
836 CurrentBlockAuthorInfo::<T>::take();
838 let farmer_public_key = pre_digest.solution().public_key;
839
840 if !AllowAuthoringByAnyone::<T>::get() {
842 RootPlotPublicKey::<T>::mutate(|maybe_root_plot_public_key| {
843 if let Some(root_plot_public_key) = maybe_root_plot_public_key {
844 if root_plot_public_key != &farmer_public_key {
845 panic!("Client bug, authoring must be only done by the root user");
846 }
847 } else {
848 maybe_root_plot_public_key.replace(farmer_public_key);
849 frame_system::Pallet::<T>::deposit_log(
852 DigestItem::root_plot_public_key_update(Some(farmer_public_key)),
853 );
854 }
855 });
856 }
857
858 let key = (
859 farmer_public_key,
860 pre_digest.solution().sector_index,
861 pre_digest.solution().piece_offset,
862 pre_digest.solution().chunk,
863 current_slot,
864 );
865 if !ParentBlockVoters::<T>::get().contains_key(&key) {
866 let (public_key, sector_index, piece_offset, chunk, slot) = key;
867
868 CurrentBlockAuthorInfo::<T>::put((
869 public_key,
870 sector_index,
871 piece_offset,
872 chunk,
873 slot,
874 Some(pre_digest.solution().reward_address.clone()),
875 ));
876 }
877 }
878 CurrentBlockVoters::<T>::put(BTreeMap::<
879 (PublicKey, SectorIndex, PieceOffset, ScalarBytes, Slot),
880 (Option<T::AccountId>, RewardSignature),
881 >::default());
882
883 if let sp_consensus_subspace::SolutionRanges {
885 next: Some(next),
886 voting_next: Some(voting_next),
887 ..
888 } = SolutionRanges::<T>::get()
889 {
890 SolutionRanges::<T>::put(sp_consensus_subspace::SolutionRanges {
891 current: next,
892 next: None,
893 voting_current: voting_next,
894 voting_next: None,
895 });
896 }
897
898 let block_randomness = pre_digest
899 .pot_info()
900 .proof_of_time()
901 .derive_global_randomness();
902
903 BlockRandomness::<T>::put(block_randomness);
905
906 frame_system::Pallet::<T>::deposit_log(DigestItem::solution_range(
908 SolutionRanges::<T>::get().current,
909 ));
910
911 T::EraChangeTrigger::trigger::<T>(block_number);
913
914 {
915 let mut pot_slot_iterations =
916 PotSlotIterations::<T>::get().expect("Always initialized during genesis; qed");
917 frame_system::Pallet::<T>::deposit_log(DigestItem::pot_slot_iterations(
919 pot_slot_iterations.slot_iterations,
920 ));
921
922 if let Some(update) = pot_slot_iterations.update
925 && let Some(target_slot) = update.target_slot
926 && target_slot <= current_slot
927 {
928 debug!(
929 "Applying PoT slots update, changing to {} at block #{:?}",
930 update.slot_iterations, block_number
931 );
932 pot_slot_iterations = PotSlotIterationsValue {
933 slot_iterations: update.slot_iterations,
934 update: None,
935 };
936 PotSlotIterations::<T>::put(pot_slot_iterations);
937 }
938 let pot_entropy_injection_interval = T::PotEntropyInjectionInterval::get();
939 let pot_entropy_injection_delay = T::PotEntropyInjectionDelay::get();
940
941 let mut entropy = PotEntropy::<T>::get();
942 let lookback_in_blocks = pot_entropy_injection_interval
943 * BlockNumberFor::<T>::from(T::PotEntropyInjectionLookbackDepth::get());
944 let last_entropy_injection_block =
945 block_number / pot_entropy_injection_interval * pot_entropy_injection_interval;
946 let maybe_entropy_source_block_number =
947 last_entropy_injection_block.checked_sub(&lookback_in_blocks);
948
949 if (block_number % pot_entropy_injection_interval).is_zero() {
950 let current_block_entropy = derive_pot_entropy(
951 &pre_digest.solution().chunk,
952 pre_digest.pot_info().proof_of_time(),
953 );
954 entropy.insert(
956 block_number,
957 PotEntropyValue {
958 target_slot: None,
959 entropy: current_block_entropy,
960 },
961 );
962
963 if let Some(entropy_source_block_number) = maybe_entropy_source_block_number
965 && let Some(entropy_value) = entropy.get_mut(&entropy_source_block_number)
966 {
967 let target_slot = pre_digest
968 .slot()
969 .saturating_add(pot_entropy_injection_delay);
970 debug!("Pot entropy injection will happen at slot {target_slot:?}",);
971 entropy_value.target_slot.replace(target_slot);
972
973 if let Some(update) = &mut pot_slot_iterations.update
975 && update.target_slot.is_none()
976 {
977 debug!("Scheduling PoT slots update to happen at slot {target_slot:?}");
978 update.target_slot.replace(target_slot);
979 PotSlotIterations::<T>::put(pot_slot_iterations);
980 }
981 }
982
983 PotEntropy::<T>::put(entropy.clone());
984 }
985
986 if let Some(entropy_source_block_number) = maybe_entropy_source_block_number {
989 let maybe_entropy_value = entropy.get(&entropy_source_block_number).copied();
990 if let Some(PotEntropyValue {
991 target_slot,
992 entropy,
993 }) = maybe_entropy_value
994 {
995 let target_slot = target_slot
996 .expect("Target slot is guaranteed to be present due to logic above; qed");
997 let slot_iterations = if let Some(update) = pot_slot_iterations.update
999 && let Some(update_target_slot) = update.target_slot
1000 && update_target_slot == target_slot
1001 {
1002 debug!("Applying PoT slots update to the next PoT parameters change");
1003 update.slot_iterations
1004 } else {
1005 pot_slot_iterations.slot_iterations
1006 };
1007
1008 frame_system::Pallet::<T>::deposit_log(DigestItem::pot_parameters_change(
1009 PotParametersChange {
1010 slot: target_slot,
1011 slot_iterations,
1012 entropy,
1013 },
1014 ));
1015 }
1016 }
1017
1018 if let Some(entry) = entropy.first_entry()
1020 && let Some(target_slot) = entry.get().target_slot
1021 && target_slot < current_slot
1022 {
1023 entry.remove();
1024 PotEntropy::<T>::put(entropy);
1025 }
1026 }
1027 }
1028
1029 fn do_finalize(_block_number: BlockNumberFor<T>) {
1030 if let Some(next_solution_range) = SolutionRanges::<T>::get().next {
1033 frame_system::Pallet::<T>::deposit_log(DigestItem::next_solution_range(
1035 next_solution_range,
1036 ));
1037 }
1038
1039 if let Some((public_key, sector_index, piece_offset, scalar, slot, _reward_address)) =
1040 CurrentBlockAuthorInfo::<T>::get()
1041 {
1042 ParentBlockAuthorInfo::<T>::put((public_key, sector_index, piece_offset, scalar, slot));
1043 } else {
1044 ParentBlockAuthorInfo::<T>::take();
1045 }
1046
1047 ParentVoteVerificationData::<T>::put(current_vote_verification_data::<T>(true));
1048
1049 ParentBlockVoters::<T>::put(CurrentBlockVoters::<T>::get().unwrap_or_default());
1050
1051 DidProcessSegmentHeaders::<T>::take();
1052 }
1053
1054 fn do_store_segment_headers(segment_headers: Vec<SegmentHeader>) -> DispatchResult {
1055 assert!(
1056 !DidProcessSegmentHeaders::<T>::exists(),
1057 "Segment headers must be updated only once in the block"
1058 );
1059
1060 for segment_header in segment_headers {
1061 SegmentCommitment::<T>::insert(
1062 segment_header.segment_index(),
1063 segment_header.segment_commitment(),
1064 );
1065 frame_system::Pallet::<T>::deposit_log(DigestItem::segment_commitment(
1067 segment_header.segment_index(),
1068 segment_header.segment_commitment(),
1069 ));
1070 Self::deposit_event(Event::SegmentHeaderStored { segment_header });
1071 }
1072
1073 DidProcessSegmentHeaders::<T>::put(true);
1074 Ok(())
1075 }
1076
1077 fn do_enable_solution_range_adjustment(
1078 solution_range_override: Option<u64>,
1079 voting_solution_range_override: Option<u64>,
1080 ) -> DispatchResult {
1081 if ShouldAdjustSolutionRange::<T>::get() {
1082 return Err(Error::<T>::SolutionRangeAdjustmentAlreadyEnabled.into());
1083 }
1084
1085 ShouldAdjustSolutionRange::<T>::put(true);
1086
1087 if let Some(solution_range) = solution_range_override {
1088 let voting_solution_range = voting_solution_range_override.unwrap_or_else(|| {
1089 solution_range.saturating_mul(u64::from(T::ExpectedVotesPerBlock::get()) + 1)
1090 });
1091 SolutionRanges::<T>::mutate(|solution_ranges| {
1092 if solution_ranges.next.is_some() {
1094 solution_ranges.next.replace(solution_range);
1095 solution_ranges.voting_next.replace(voting_solution_range);
1096 } else {
1097 solution_ranges.current = solution_range;
1098 solution_ranges.voting_current = voting_solution_range;
1099
1100 NextSolutionRangeOverride::<T>::put(SolutionRangeOverride {
1103 solution_range,
1104 voting_solution_range,
1105 });
1106 frame_system::Pallet::<T>::deposit_log(DigestItem::next_solution_range(
1107 solution_range,
1108 ));
1109 }
1110 });
1111 }
1112
1113 Ok(())
1114 }
1115
1116 fn do_vote(
1117 signed_vote: SignedVote<BlockNumberFor<T>, T::Hash, T::AccountId>,
1118 ) -> DispatchResult {
1119 let Vote::V0 {
1120 height,
1121 parent_hash,
1122 solution,
1123 ..
1124 } = signed_vote.vote;
1125
1126 Self::deposit_event(Event::FarmerVote {
1127 public_key: solution.public_key,
1128 reward_address: solution.reward_address,
1129 height,
1130 parent_hash,
1131 });
1132
1133 Ok(())
1134 }
1135
1136 fn do_enable_rewards_at(
1137 enable_rewards_at: EnableRewardsAt<BlockNumberFor<T>>,
1138 ) -> DispatchResult {
1139 match enable_rewards_at {
1140 EnableRewardsAt::Height(block_number) => {
1141 let next_block_number =
1144 frame_system::Pallet::<T>::current_block_number() + One::one();
1145 EnableRewards::<T>::put(block_number.max(next_block_number));
1146 }
1147 EnableRewardsAt::SolutionRange(solution_range) => {
1148 EnableRewardsBelowSolutionRange::<T>::put(solution_range);
1149 }
1150 EnableRewardsAt::Manually => {
1151 }
1153 }
1154
1155 Ok(())
1156 }
1157
1158 pub fn pot_parameters() -> PotParameters {
1160 let block_number = frame_system::Pallet::<T>::block_number();
1161 let pot_slot_iterations =
1162 PotSlotIterations::<T>::get().expect("Always initialized during genesis; qed");
1163 let pot_entropy_injection_interval = T::PotEntropyInjectionInterval::get();
1164
1165 let entropy = PotEntropy::<T>::get();
1166 let lookback_in_blocks = pot_entropy_injection_interval
1167 * BlockNumberFor::<T>::from(T::PotEntropyInjectionLookbackDepth::get());
1168 let last_entropy_injection_block =
1169 block_number / pot_entropy_injection_interval * pot_entropy_injection_interval;
1170 let maybe_entropy_source_block_number =
1171 last_entropy_injection_block.checked_sub(&lookback_in_blocks);
1172
1173 let mut next_change = None;
1174
1175 if let Some(entropy_source_block_number) = maybe_entropy_source_block_number {
1176 let maybe_entropy_value = entropy.get(&entropy_source_block_number).copied();
1177 if let Some(PotEntropyValue {
1178 target_slot,
1179 entropy,
1180 }) = maybe_entropy_value
1181 {
1182 let target_slot = target_slot.expect(
1183 "Always present due to identical check present in block initialization; qed",
1184 );
1185 let slot_iterations = if let Some(update) = pot_slot_iterations.update
1187 && let Some(update_target_slot) = update.target_slot
1188 && update_target_slot == target_slot
1189 {
1190 update.slot_iterations
1191 } else {
1192 pot_slot_iterations.slot_iterations
1193 };
1194
1195 next_change.replace(PotParametersChange {
1196 slot: target_slot,
1197 slot_iterations,
1198 entropy,
1199 });
1200 }
1201 }
1202
1203 PotParameters::V0 {
1204 slot_iterations: pot_slot_iterations.slot_iterations,
1205 next_change,
1206 }
1207 }
1208
1209 pub fn current_slot() -> Slot {
1211 BlockSlots::<T>::get()
1212 .last_key_value()
1213 .map(|(_block, slot)| *slot)
1214 .unwrap_or_default()
1215 }
1216
1217 pub fn archived_history_size() -> u64 {
1219 let archived_segments = SegmentCommitment::<T>::count();
1220
1221 u64::from(archived_segments) * ArchivedHistorySegment::SIZE as u64
1222 }
1223}
1224
1225impl<T> Pallet<T>
1226where
1227 T: Config + CreateUnsigned<Call<T>>,
1228{
1229 pub fn submit_vote(signed_vote: SignedVote<BlockNumberFor<T>, T::Hash, T::AccountId>) {
1232 let call = Call::vote {
1233 signed_vote: Box::new(signed_vote),
1234 };
1235
1236 let ext = T::create_unsigned(call.into());
1237 match SubmitTransaction::<T, Call<T>>::submit_transaction(ext) {
1238 Ok(()) => {
1239 debug!("Submitted Subspace vote");
1240 }
1241 Err(()) => {
1242 error!("Error submitting Subspace vote");
1243 }
1244 }
1245 }
1246}
1247
1248impl<T: Config> Pallet<T> {
1253 fn validate_segment_header(
1254 source: TransactionSource,
1255 segment_headers: &[SegmentHeader],
1256 ) -> TransactionValidity {
1257 if !matches!(
1259 source,
1260 TransactionSource::Local | TransactionSource::InBlock,
1261 ) {
1262 warn!("Rejecting segment header extrinsic because it is not local/in-block.",);
1263
1264 return InvalidTransaction::Call.into();
1265 }
1266
1267 check_segment_headers::<T>(segment_headers)?;
1268
1269 ValidTransaction::with_tag_prefix("SubspaceSegmentHeader")
1270 .priority(TransactionPriority::MAX)
1272 .longevity(0)
1275 .propagate(false)
1277 .build()
1278 }
1279
1280 fn pre_dispatch_segment_header(
1281 segment_headers: &[SegmentHeader],
1282 ) -> Result<(), TransactionValidityError> {
1283 check_segment_headers::<T>(segment_headers)
1284 }
1285
1286 fn validate_vote(
1287 signed_vote: &SignedVote<BlockNumberFor<T>, T::Hash, T::AccountId>,
1288 ) -> Result<(ValidTransaction, Weight), TransactionValidityError> {
1289 check_vote::<T>(signed_vote, false)?;
1290
1291 ValidTransaction::with_tag_prefix("SubspaceVote")
1292 .priority(TransactionPriority::MAX)
1294 .longevity(2)
1296 .and_provides(signed_vote.signature)
1297 .build()
1298 .map(|validity| (validity, T::ExtensionWeightInfo::vote()))
1299 }
1300
1301 fn pre_dispatch_vote(
1302 signed_vote: &SignedVote<BlockNumberFor<T>, T::Hash, T::AccountId>,
1303 ) -> Result<Weight, TransactionValidityError> {
1304 match check_vote::<T>(signed_vote, true) {
1305 Ok(()) => Ok(T::ExtensionWeightInfo::vote()),
1306 Err(CheckVoteError::Equivocated { .. }) => {
1307 Ok(T::ExtensionWeightInfo::vote_with_equivocation())
1309 }
1310 Err(error) => Err(error.into()),
1311 }
1312 }
1313}
1314
1315fn current_vote_verification_data<T: Config>(is_block_initialized: bool) -> VoteVerificationData {
1319 let solution_ranges = SolutionRanges::<T>::get();
1320
1321 VoteVerificationData {
1322 solution_range: if is_block_initialized {
1323 solution_ranges.current
1324 } else {
1325 solution_ranges.next.unwrap_or(solution_ranges.current)
1326 },
1327 vote_solution_range: if is_block_initialized {
1328 solution_ranges.voting_current
1329 } else {
1330 solution_ranges
1331 .voting_next
1332 .unwrap_or(solution_ranges.voting_current)
1333 },
1334 current_slot: Pallet::<T>::current_slot(),
1335 parent_slot: ParentVoteVerificationData::<T>::get()
1336 .map(|parent_vote_verification_data| {
1337 if is_block_initialized {
1338 parent_vote_verification_data.current_slot
1339 } else {
1340 parent_vote_verification_data.parent_slot
1341 }
1342 })
1343 .unwrap_or_else(Pallet::<T>::current_slot),
1344 }
1345}
1346
1347#[derive(Debug, Eq, PartialEq)]
1348enum CheckVoteError {
1349 UnexpectedBeforeHeightTwo,
1350 HeightInTheFuture,
1351 HeightInThePast,
1352 IncorrectParentHash,
1353 SlotInTheFuture,
1354 SlotInThePast,
1355 BadRewardSignature(SignatureError),
1356 UnknownSegmentCommitment,
1357 InvalidHistorySize,
1358 InvalidSolution(String),
1359 QualityTooHigh,
1360 InvalidProofOfTime,
1361 InvalidFutureProofOfTime,
1362 DuplicateVote,
1363 Equivocated { slot: Slot, offender: PublicKey },
1364}
1365
1366impl From<CheckVoteError> for TransactionValidityError {
1367 #[inline]
1368 fn from(error: CheckVoteError) -> Self {
1369 TransactionValidityError::Invalid(match error {
1370 CheckVoteError::UnexpectedBeforeHeightTwo => InvalidTransaction::Call,
1371 CheckVoteError::HeightInTheFuture => InvalidTransaction::Future,
1372 CheckVoteError::HeightInThePast => InvalidTransaction::Stale,
1373 CheckVoteError::IncorrectParentHash => InvalidTransaction::Call,
1374 CheckVoteError::SlotInTheFuture => InvalidTransaction::Future,
1375 CheckVoteError::SlotInThePast => InvalidTransaction::Stale,
1376 CheckVoteError::BadRewardSignature(_) => InvalidTransaction::BadProof,
1377 CheckVoteError::UnknownSegmentCommitment => InvalidTransaction::Call,
1378 CheckVoteError::InvalidHistorySize => InvalidTransaction::Call,
1379 CheckVoteError::InvalidSolution(_) => InvalidTransaction::Call,
1380 CheckVoteError::QualityTooHigh => InvalidTransaction::Call,
1381 CheckVoteError::InvalidProofOfTime => InvalidTransaction::Future,
1382 CheckVoteError::InvalidFutureProofOfTime => InvalidTransaction::Call,
1383 CheckVoteError::DuplicateVote => InvalidTransaction::Call,
1384 CheckVoteError::Equivocated { .. } => InvalidTransaction::BadSigner,
1385 })
1386 }
1387}
1388
1389fn check_vote<T: Config>(
1390 signed_vote: &SignedVote<BlockNumberFor<T>, T::Hash, T::AccountId>,
1391 pre_dispatch: bool,
1392) -> Result<(), CheckVoteError> {
1393 let Vote::V0 {
1394 height,
1395 parent_hash,
1396 slot,
1397 solution,
1398 proof_of_time,
1399 future_proof_of_time,
1400 } = &signed_vote.vote;
1401 let height = *height;
1402 let slot = *slot;
1403
1404 let current_block_number = frame_system::Pallet::<T>::current_block_number();
1405
1406 if current_block_number <= One::one() || height <= One::one() {
1407 debug!("Votes are not expected at height below 2");
1408
1409 return Err(CheckVoteError::UnexpectedBeforeHeightTwo);
1410 }
1411
1412 if !(height == current_block_number || height == current_block_number - One::one()) {
1416 debug!(
1417 "Vote verification error: bad height {height:?}, current block number is \
1418 {current_block_number:?}"
1419 );
1420 return Err(if height > current_block_number {
1421 CheckVoteError::HeightInTheFuture
1422 } else {
1423 CheckVoteError::HeightInThePast
1424 });
1425 }
1426
1427 if *parent_hash != frame_system::Pallet::<T>::block_hash(height - One::one()) {
1431 debug!("Vote verification error: parent hash {parent_hash:?}",);
1432 return Err(CheckVoteError::IncorrectParentHash);
1433 }
1434
1435 let current_vote_verification_data = current_vote_verification_data::<T>(pre_dispatch);
1436 let parent_vote_verification_data = ParentVoteVerificationData::<T>::get()
1437 .expect("Above check for block number ensures that this value is always present; qed");
1438
1439 if pre_dispatch {
1440 let current_slot = current_vote_verification_data.current_slot;
1443 if slot > current_slot || (slot == current_slot && height != current_block_number) {
1444 debug!("Vote slot {slot:?} must be before current slot {current_slot:?}",);
1445 return Err(CheckVoteError::SlotInTheFuture);
1446 }
1447 }
1448
1449 let parent_slot = if pre_dispatch {
1450 if height == current_block_number {
1455 parent_vote_verification_data.current_slot
1456 } else {
1457 parent_vote_verification_data.parent_slot
1458 }
1459 } else {
1460 if height == current_block_number {
1465 current_vote_verification_data.current_slot
1466 } else {
1467 current_vote_verification_data.parent_slot
1468 }
1469 };
1470
1471 if slot <= parent_slot {
1472 debug!("Vote slot {slot:?} must be after parent slot {parent_slot:?}",);
1473 return Err(CheckVoteError::SlotInThePast);
1474 }
1475
1476 if let Err(error) = check_reward_signature(
1477 signed_vote.vote.hash().as_bytes(),
1478 &signed_vote.signature,
1479 &solution.public_key,
1480 &schnorrkel::signing_context(REWARD_SIGNING_CONTEXT),
1481 ) {
1482 debug!("Vote verification error: {error:?}");
1483 return Err(CheckVoteError::BadRewardSignature(error));
1484 }
1485
1486 let vote_verification_data = if height == current_block_number {
1487 current_vote_verification_data
1488 } else {
1489 parent_vote_verification_data
1490 };
1491
1492 let sector_id = SectorId::new(
1493 solution.public_key.hash(),
1494 solution.sector_index,
1495 solution.history_size,
1496 );
1497
1498 let recent_segments = T::RecentSegments::get();
1499 let recent_history_fraction = (
1500 T::RecentHistoryFraction::get().0,
1501 T::RecentHistoryFraction::get().1,
1502 );
1503 let segment_index = sector_id
1504 .derive_piece_index(
1505 solution.piece_offset,
1506 solution.history_size,
1507 T::MaxPiecesInSector::get(),
1508 recent_segments,
1509 recent_history_fraction,
1510 )
1511 .segment_index();
1512
1513 let segment_commitment = if let Some(segment_commitment) =
1514 Pallet::<T>::segment_commitment(segment_index)
1515 {
1516 segment_commitment
1517 } else {
1518 debug!("Vote verification error: no segment commitment for segment index {segment_index}");
1519 return Err(CheckVoteError::UnknownSegmentCommitment);
1520 };
1521
1522 let sector_expiration_check_segment_commitment = Pallet::<T>::segment_commitment(
1523 solution
1524 .history_size
1525 .sector_expiration_check(T::MinSectorLifetime::get())
1526 .ok_or(CheckVoteError::InvalidHistorySize)?
1527 .segment_index(),
1528 );
1529
1530 match verify_solution(
1531 solution.into(),
1532 slot.into(),
1533 (&VerifySolutionParams {
1534 proof_of_time: *proof_of_time,
1535 solution_range: vote_verification_data.vote_solution_range,
1536 piece_check_params: Some(PieceCheckParams {
1537 max_pieces_in_sector: T::MaxPiecesInSector::get(),
1538 segment_commitment,
1539 recent_segments,
1540 recent_history_fraction,
1541 min_sector_lifetime: T::MinSectorLifetime::get(),
1542 current_history_size: Pallet::<T>::history_size(),
1543 sector_expiration_check_segment_commitment,
1544 }),
1545 })
1546 .into(),
1547 ) {
1548 Ok(solution_distance) => {
1549 if solution_distance <= vote_verification_data.solution_range / 2 {
1550 debug!("Vote quality is too high");
1551 return Err(CheckVoteError::QualityTooHigh);
1552 }
1553 }
1554 Err(error) => {
1555 debug!("Vote verification error: {error:?}");
1556 return Err(CheckVoteError::InvalidSolution(error));
1557 }
1558 }
1559
1560 if !is_proof_of_time_valid(
1563 BlockHash::try_from(parent_hash.as_ref())
1564 .expect("Must be able to convert to block hash type"),
1565 SlotNumber::from(slot),
1566 WrappedPotOutput::from(*proof_of_time),
1567 !pre_dispatch,
1569 ) {
1570 debug!("Invalid proof of time");
1571
1572 return Err(CheckVoteError::InvalidProofOfTime);
1573 }
1574
1575 if pre_dispatch
1578 && !is_proof_of_time_valid(
1579 BlockHash::try_from(parent_hash.as_ref())
1580 .expect("Must be able to convert to block hash type"),
1581 SlotNumber::from(slot + T::BlockAuthoringDelay::get()),
1582 WrappedPotOutput::from(*future_proof_of_time),
1583 false,
1584 )
1585 {
1586 debug!("Invalid future proof of time");
1587
1588 return Err(CheckVoteError::InvalidFutureProofOfTime);
1589 }
1590
1591 let key = (
1592 solution.public_key,
1593 solution.sector_index,
1594 solution.piece_offset,
1595 solution.chunk,
1596 slot,
1597 );
1598 let mut is_equivocating = ParentBlockAuthorInfo::<T>::get().as_ref() == Some(&key)
1604 || CurrentBlockAuthorInfo::<T>::get()
1605 .map(
1606 |(public_key, sector_index, piece_offset, chunk, slot, _reward_address)| {
1607 (public_key, sector_index, piece_offset, chunk, slot)
1608 },
1609 )
1610 .as_ref()
1611 == Some(&key);
1612
1613 if !is_equivocating
1614 && let Some((_reward_address, signature)) = ParentBlockVoters::<T>::get().get(&key)
1615 {
1616 if signature != &signed_vote.signature {
1617 is_equivocating = true;
1618 } else {
1619 return Err(CheckVoteError::DuplicateVote);
1621 }
1622 }
1623
1624 if !is_equivocating
1625 && let Some((_reward_address, signature)) =
1626 CurrentBlockVoters::<T>::get().unwrap_or_default().get(&key)
1627 {
1628 if signature != &signed_vote.signature {
1629 is_equivocating = true;
1630 } else {
1631 return Err(CheckVoteError::DuplicateVote);
1633 }
1634 }
1635
1636 if pre_dispatch {
1637 CurrentBlockVoters::<T>::mutate(|current_reward_receivers| {
1639 current_reward_receivers
1640 .as_mut()
1641 .expect("Always set during block initialization")
1642 .insert(
1643 key,
1644 (
1645 if is_equivocating {
1646 None
1647 } else {
1648 Some(solution.reward_address.clone())
1649 },
1650 signed_vote.signature,
1651 ),
1652 );
1653 });
1654 }
1655
1656 if is_equivocating {
1657 let offender = solution.public_key;
1658
1659 CurrentBlockAuthorInfo::<T>::mutate(|maybe_info| {
1660 if let Some((public_key, _sector_index, _piece_offset, _chunk, _slot, reward_address)) =
1661 maybe_info
1662 && public_key == &offender
1663 {
1664 reward_address.take();
1666 }
1667 });
1668
1669 CurrentBlockVoters::<T>::mutate(|current_reward_receivers| {
1670 if let Some(current_reward_receivers) = current_reward_receivers {
1671 for (
1672 (public_key, _sector_index, _piece_offset, _chunk, _slot),
1673 (reward_address, _signature),
1674 ) in current_reward_receivers.iter_mut()
1675 {
1676 if public_key == &offender {
1677 reward_address.take();
1679 }
1680 }
1681 }
1682 });
1683
1684 return Err(CheckVoteError::Equivocated { slot, offender });
1685 }
1686
1687 Ok(())
1688}
1689
1690fn check_segment_headers<T: Config>(
1691 segment_headers: &[SegmentHeader],
1692) -> Result<(), TransactionValidityError> {
1693 let mut segment_headers_iter = segment_headers.iter();
1694
1695 let first_segment_header = match segment_headers_iter.next() {
1697 Some(first_segment_header) => first_segment_header,
1698 None => {
1699 return Err(InvalidTransaction::BadMandatory.into());
1700 }
1701 };
1702
1703 if first_segment_header.segment_index() > SegmentIndex::ZERO
1705 && !SegmentCommitment::<T>::contains_key(
1706 first_segment_header.segment_index() - SegmentIndex::ONE,
1707 )
1708 {
1709 return Err(InvalidTransaction::BadMandatory.into());
1710 }
1711
1712 if SegmentCommitment::<T>::contains_key(first_segment_header.segment_index()) {
1714 return Err(InvalidTransaction::BadMandatory.into());
1715 }
1716
1717 let mut last_segment_index = first_segment_header.segment_index();
1718
1719 for segment_header in segment_headers_iter {
1720 let segment_index = segment_header.segment_index();
1721
1722 if segment_index != last_segment_index + SegmentIndex::ONE {
1724 return Err(InvalidTransaction::BadMandatory.into());
1725 }
1726
1727 if SegmentCommitment::<T>::contains_key(segment_index) {
1729 return Err(InvalidTransaction::BadMandatory.into());
1730 }
1731
1732 last_segment_index = segment_index;
1733 }
1734
1735 Ok(())
1736}
1737
1738impl<T: Config> subspace_runtime_primitives::RewardsEnabled for Pallet<T> {
1739 fn rewards_enabled() -> bool {
1740 if let Some(height) = EnableRewards::<T>::get() {
1741 frame_system::Pallet::<T>::current_block_number() >= height
1742 } else {
1743 false
1744 }
1745 }
1746}
1747
1748impl<T: Config> subspace_runtime_primitives::FindBlockRewardAddress<T::AccountId> for Pallet<T> {
1749 fn find_block_reward_address() -> Option<T::AccountId> {
1750 CurrentBlockAuthorInfo::<T>::get().and_then(
1751 |(_public_key, _sector_index, _piece_offset, _chunk, _slot, reward_address)| {
1752 if let Some(height) = EnableRewards::<T>::get()
1754 && frame_system::Pallet::<T>::current_block_number() >= height
1755 {
1756 return reward_address;
1757 }
1758
1759 None
1760 },
1761 )
1762 }
1763}
1764
1765impl<T: Config> subspace_runtime_primitives::FindVotingRewardAddresses<T::AccountId> for Pallet<T> {
1766 fn find_voting_reward_addresses() -> Vec<T::AccountId> {
1767 if let Some(height) = EnableRewards::<T>::get()
1769 && frame_system::Pallet::<T>::current_block_number() >= height
1770 {
1771 return CurrentBlockVoters::<T>::get()
1774 .unwrap_or_else(ParentBlockVoters::<T>::get)
1775 .into_values()
1776 .filter_map(|(reward_address, _signature)| reward_address)
1777 .collect();
1778 }
1779
1780 Vec::new()
1781 }
1782}
1783
1784impl<T: Config> frame_support::traits::Randomness<T::Hash, BlockNumberFor<T>> for Pallet<T> {
1785 fn random(subject: &[u8]) -> (T::Hash, BlockNumberFor<T>) {
1786 let mut subject = subject.to_vec();
1787 subject.extend_from_slice(
1788 BlockRandomness::<T>::get()
1789 .expect("Block randomness is always set in block initialization; qed")
1790 .as_ref(),
1791 );
1792
1793 (
1794 T::Hashing::hash(&subject),
1795 frame_system::Pallet::<T>::current_block_number(),
1796 )
1797 }
1798
1799 fn random_seed() -> (T::Hash, BlockNumberFor<T>) {
1800 (
1801 T::Hashing::hash(
1802 BlockRandomness::<T>::get()
1803 .expect("Block randomness is always set in block initialization; qed")
1804 .as_ref(),
1805 ),
1806 frame_system::Pallet::<T>::current_block_number(),
1807 )
1808 }
1809}