1#![doc = include_str!("../README.md")]
2#![cfg_attr(not(feature = "std"), no_std)]
3#![expect(clippy::clone_on_copy, reason = "Comes from Substrate")]
5#![cfg_attr(test, feature(portable_simd))]
6#![warn(unused_must_use, unsafe_code, unused_variables)]
7
8#[cfg(not(feature = "std"))]
9extern crate alloc;
10
11#[cfg(test)]
12mod mock;
13#[cfg(test)]
14mod tests;
15
16#[cfg(feature = "runtime-benchmarks")]
17mod benchmarking;
18
19pub mod extensions;
20pub mod weights;
21
22use crate::extensions::weights::WeightInfo as ExtensionWeightInfo;
23#[cfg(not(feature = "std"))]
24use alloc::string::String;
25use core::num::NonZeroU64;
26use frame_support::dispatch::DispatchResult;
27use frame_support::pallet_prelude::{EnsureOrigin, RuntimeDebug};
28use frame_support::traits::Get;
29use frame_system::offchain::SubmitTransaction;
30use frame_system::pallet_prelude::*;
31use log::{debug, error, warn};
32pub use pallet::*;
33use parity_scale_codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
34use scale_info::TypeInfo;
35use schnorrkel::SignatureError;
36use sp_consensus_slots::Slot;
37use sp_consensus_subspace::consensus::{is_proof_of_time_valid, verify_solution};
38use sp_consensus_subspace::digests::CompatibleDigestItem;
39use sp_consensus_subspace::{
40 PotParameters, PotParametersChange, SignedVote, Vote, WrappedPotOutput,
41};
42use sp_runtime::Weight;
43use sp_runtime::generic::DigestItem;
44use sp_runtime::traits::{BlockNumberProvider, CheckedSub, Hash, One, Zero};
45use sp_runtime::transaction_validity::{
46 InvalidTransaction, TransactionPriority, TransactionSource, TransactionValidity,
47 TransactionValidityError, ValidTransaction,
48};
49use sp_std::collections::btree_map::BTreeMap;
50use sp_std::prelude::*;
51use subspace_core_primitives::pieces::PieceOffset;
52use subspace_core_primitives::sectors::{SectorId, SectorIndex};
53use subspace_core_primitives::segments::{
54 ArchivedHistorySegment, HistorySize, SegmentHeader, SegmentIndex,
55};
56use subspace_core_primitives::solutions::{RewardSignature, SolutionRange};
57use subspace_core_primitives::{
58 BlockHash, PublicKey, REWARD_SIGNING_CONTEXT, ScalarBytes, SlotNumber,
59};
60use subspace_runtime_primitives::CreateUnsigned;
61use subspace_verification::{
62 PieceCheckParams, VerifySolutionParams, check_reward_signature, derive_next_solution_range,
63 derive_pot_entropy,
64};
65pub use weights::WeightInfo;
66
67pub trait EraChangeTrigger {
69 fn trigger<T: Config>(block_number: BlockNumberFor<T>);
72}
73
74pub struct NormalEraChange;
76
77impl EraChangeTrigger for NormalEraChange {
78 fn trigger<T: Config>(block_number: BlockNumberFor<T>) {
79 if <Pallet<T>>::should_era_change(block_number) {
80 <Pallet<T>>::enact_era_change();
81 }
82 }
83}
84
85#[derive(
87 PartialEq,
88 Eq,
89 Clone,
90 Encode,
91 Decode,
92 RuntimeDebug,
93 TypeInfo,
94 MaxEncodedLen,
95 DecodeWithMemTracking,
96)]
97pub enum RawOrigin {
98 ValidatedUnsigned,
99}
100
101pub struct EnsureSubspaceOrigin;
103impl<O: Into<Result<RawOrigin, O>> + From<RawOrigin>> EnsureOrigin<O> for EnsureSubspaceOrigin {
104 type Success = ();
105
106 fn try_origin(o: O) -> Result<Self::Success, O> {
107 o.into().map(|o| match o {
108 RawOrigin::ValidatedUnsigned => (),
109 })
110 }
111
112 #[cfg(feature = "runtime-benchmarks")]
113 fn try_successful_origin() -> Result<O, ()> {
114 Ok(O::from(RawOrigin::ValidatedUnsigned))
115 }
116}
117
118#[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, MaxEncodedLen, TypeInfo)]
119struct VoteVerificationData {
120 solution_range: SolutionRange,
122 vote_solution_range: SolutionRange,
123 current_slot: Slot,
124 parent_slot: Slot,
125}
126
127#[frame_support::pallet]
128pub mod pallet {
129 use super::{EraChangeTrigger, ExtensionWeightInfo, VoteVerificationData};
130 use crate::RawOrigin;
131 use crate::weights::WeightInfo;
132 use frame_support::pallet_prelude::*;
133 use frame_system::pallet_prelude::*;
134 use sp_consensus_slots::Slot;
135 use sp_consensus_subspace::SignedVote;
136 use sp_consensus_subspace::digests::CompatibleDigestItem;
137 use sp_consensus_subspace::inherents::{INHERENT_IDENTIFIER, InherentError, InherentType};
138 use sp_runtime::DigestItem;
139 use sp_runtime::traits::One;
140 use sp_std::collections::btree_map::BTreeMap;
141 use sp_std::num::NonZeroU32;
142 use sp_std::prelude::*;
143 use subspace_core_primitives::hashes::Blake3Hash;
144 use subspace_core_primitives::pieces::PieceOffset;
145 use subspace_core_primitives::pot::PotCheckpoints;
146 use subspace_core_primitives::sectors::SectorIndex;
147 use subspace_core_primitives::segments::{HistorySize, SegmentHeader, SegmentIndex};
148 use subspace_core_primitives::solutions::{RewardSignature, SolutionRange};
149 use subspace_core_primitives::{PublicKey, Randomness, ScalarBytes};
150
151 pub(super) struct InitialSolutionRanges<T: Config> {
152 _config: T,
153 }
154
155 impl<T: Config> Get<sp_consensus_subspace::SolutionRanges> for InitialSolutionRanges<T> {
156 fn get() -> sp_consensus_subspace::SolutionRanges {
157 sp_consensus_subspace::SolutionRanges {
158 current: T::InitialSolutionRange::get(),
159 next: None,
160 voting_current: if T::ShouldAdjustSolutionRange::get() {
161 T::InitialSolutionRange::get()
162 .saturating_mul(u64::from(T::ExpectedVotesPerBlock::get()) + 1)
163 } else {
164 T::InitialSolutionRange::get()
165 },
166 voting_next: None,
167 }
168 }
169 }
170
171 #[derive(Debug, Encode, Decode, TypeInfo)]
173 pub(super) struct SolutionRangeOverride {
174 pub(super) solution_range: SolutionRange,
176 pub(super) voting_solution_range: SolutionRange,
178 }
179
180 #[pallet::pallet]
182 #[pallet::without_storage_info]
183 pub struct Pallet<T>(_);
184
185 #[pallet::config]
186 #[pallet::disable_frame_system_supertrait_check]
187 pub trait Config: frame_system::Config<RuntimeEvent: From<Event<Self>>> {
188 type SubspaceOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = ()>;
190
191 #[pallet::constant]
196 type BlockAuthoringDelay: Get<Slot>;
197
198 #[pallet::constant]
200 type PotEntropyInjectionInterval: Get<BlockNumberFor<Self>>;
201
202 #[pallet::constant]
204 type PotEntropyInjectionLookbackDepth: Get<u8>;
205
206 #[pallet::constant]
208 type PotEntropyInjectionDelay: Get<Slot>;
209
210 #[pallet::constant]
214 type EraDuration: Get<BlockNumberFor<Self>>;
215
216 #[pallet::constant]
218 type InitialSolutionRange: Get<SolutionRange>;
219
220 #[pallet::constant]
226 type SlotProbability: Get<(u64, u64)>;
227
228 #[pallet::constant]
231 type ConfirmationDepthK: Get<BlockNumberFor<Self>>;
232
233 #[pallet::constant]
235 type RecentSegments: Get<HistorySize>;
236
237 #[pallet::constant]
239 type RecentHistoryFraction: Get<(HistorySize, HistorySize)>;
240
241 #[pallet::constant]
243 type MinSectorLifetime: Get<HistorySize>;
244
245 #[pallet::constant]
249 type ExpectedVotesPerBlock: Get<u32>;
250
251 #[pallet::constant]
253 type MaxPiecesInSector: Get<u16>;
254
255 type ShouldAdjustSolutionRange: Get<bool>;
256 type EraChangeTrigger: EraChangeTrigger;
261
262 type WeightInfo: WeightInfo;
264
265 #[pallet::constant]
267 type BlockSlotCount: Get<u32>;
268
269 type ExtensionWeightInfo: ExtensionWeightInfo;
271 }
272
273 #[derive(Debug, Default, Encode, Decode, TypeInfo)]
274 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
275 pub enum AllowAuthoringBy {
276 #[default]
278 Anyone,
279 FirstFarmer,
282 RootFarmer(PublicKey),
284 }
285
286 #[derive(Debug, Copy, Clone, Encode, Decode, TypeInfo)]
287 pub(super) struct PotEntropyValue {
288 pub(super) target_slot: Option<Slot>,
290 pub(super) entropy: Blake3Hash,
291 }
292
293 #[derive(Debug, Copy, Clone, Encode, Decode, TypeInfo, PartialEq)]
294 pub(super) struct PotSlotIterationsValue {
295 pub(super) slot_iterations: NonZeroU32,
296 pub(super) update: Option<PotSlotIterationsUpdate>,
298 }
299
300 #[derive(Debug, Copy, Clone, Encode, Decode, TypeInfo, PartialEq)]
301 pub(super) struct PotSlotIterationsUpdate {
302 pub(super) target_slot: Option<Slot>,
304 pub(super) slot_iterations: NonZeroU32,
305 }
306
307 #[derive(
309 Debug, Copy, Clone, Eq, PartialEq, Encode, Decode, TypeInfo, DecodeWithMemTracking,
310 )]
311 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
312 pub enum EnableRewardsAt<BlockNumber> {
313 Height(BlockNumber),
315 SolutionRange(u64),
317 Manually,
319 }
320
321 #[pallet::genesis_config]
322 pub struct GenesisConfig<T>
323 where
324 T: Config,
325 {
326 pub enable_rewards_at: EnableRewardsAt<BlockNumberFor<T>>,
328 pub allow_authoring_by: AllowAuthoringBy,
330 pub pot_slot_iterations: NonZeroU32,
332 #[serde(skip)]
333 pub phantom: PhantomData<T>,
334 }
335
336 impl<T> Default for GenesisConfig<T>
337 where
338 T: Config,
339 {
340 #[inline]
341 fn default() -> Self {
342 Self {
343 enable_rewards_at: EnableRewardsAt::Height(BlockNumberFor::<T>::one()),
344 allow_authoring_by: AllowAuthoringBy::Anyone,
345 pot_slot_iterations: NonZeroU32::MIN,
346 phantom: PhantomData,
347 }
348 }
349 }
350
351 #[pallet::genesis_build]
352 impl<T> BuildGenesisConfig for GenesisConfig<T>
353 where
354 T: Config,
355 {
356 fn build(&self) {
357 match self.enable_rewards_at {
358 EnableRewardsAt::Height(block_number) => {
359 EnableRewards::<T>::put(block_number);
360 }
361 EnableRewardsAt::SolutionRange(solution_range) => {
362 EnableRewardsBelowSolutionRange::<T>::put(solution_range);
363 }
364 EnableRewardsAt::Manually => {
365 }
367 }
368 match &self.allow_authoring_by {
369 AllowAuthoringBy::Anyone => {
370 AllowAuthoringByAnyone::<T>::put(true);
371 }
372 AllowAuthoringBy::FirstFarmer => {
373 AllowAuthoringByAnyone::<T>::put(false);
374 }
375 AllowAuthoringBy::RootFarmer(root_farmer) => {
376 AllowAuthoringByAnyone::<T>::put(false);
377 RootPlotPublicKey::<T>::put(root_farmer);
378 }
379 }
380 PotSlotIterations::<T>::put(PotSlotIterationsValue {
381 slot_iterations: self.pot_slot_iterations,
382 update: None,
383 });
384 }
385 }
386
387 #[pallet::event]
389 #[pallet::generate_deposit(pub (super) fn deposit_event)]
390 pub enum Event<T: Config> {
391 SegmentHeaderStored { segment_header: SegmentHeader },
393 FarmerVote {
395 public_key: PublicKey,
396 reward_address: T::AccountId,
397 height: BlockNumberFor<T>,
398 parent_hash: T::Hash,
399 },
400 }
401
402 #[pallet::origin]
403 pub type Origin = RawOrigin;
404
405 #[pallet::error]
406 pub enum Error<T> {
407 SolutionRangeAdjustmentAlreadyEnabled,
409 NotMultipleOfCheckpoints,
411 PotSlotIterationsMustIncrease,
413 PotSlotIterationsUpdateAlreadyScheduled,
415 }
416
417 #[pallet::storage]
419 #[pallet::getter(fn block_slots)]
420 pub(super) type BlockSlots<T: Config> =
421 StorageValue<_, BoundedBTreeMap<BlockNumberFor<T>, Slot, T::BlockSlotCount>, ValueQuery>;
422
423 #[pallet::storage]
425 #[pallet::getter(fn solution_ranges)]
426 pub(super) type SolutionRanges<T: Config> = StorageValue<
427 _,
428 sp_consensus_subspace::SolutionRanges,
429 ValueQuery,
430 InitialSolutionRanges<T>,
431 >;
432
433 #[pallet::storage]
435 #[pallet::getter(fn should_adjust_solution_range)]
436 pub(super) type ShouldAdjustSolutionRange<T: Config> =
437 StorageValue<_, bool, ValueQuery, T::ShouldAdjustSolutionRange>;
438
439 #[pallet::storage]
441 pub(super) type NextSolutionRangeOverride<T> = StorageValue<_, SolutionRangeOverride>;
442
443 #[pallet::storage]
445 pub(super) type EraStartSlot<T> = StorageValue<_, Slot>;
446
447 #[pallet::storage]
449 #[pallet::getter(fn segment_commitment)]
450 pub(super) type SegmentCommitment<T> = CountedStorageMap<
451 _,
452 Twox64Concat,
453 SegmentIndex,
454 subspace_core_primitives::segments::SegmentCommitment,
455 >;
456
457 #[pallet::storage]
462 pub(super) type DidProcessSegmentHeaders<T: Config> = StorageValue<_, bool, ValueQuery>;
463
464 #[pallet::storage]
466 pub(super) type ParentVoteVerificationData<T> = StorageValue<_, VoteVerificationData>;
467
468 #[pallet::storage]
470 pub(super) type ParentBlockAuthorInfo<T> =
471 StorageValue<_, (PublicKey, SectorIndex, PieceOffset, ScalarBytes, Slot)>;
472
473 #[pallet::storage]
475 pub(super) type EnableRewards<T: Config> = StorageValue<_, BlockNumberFor<T>>;
476
477 #[pallet::storage]
479 pub(super) type EnableRewardsBelowSolutionRange<T: Config> = StorageValue<_, u64>;
480
481 #[pallet::storage]
483 pub(super) type CurrentBlockAuthorInfo<T: Config> = StorageValue<
484 _,
485 (
486 PublicKey,
487 SectorIndex,
488 PieceOffset,
489 ScalarBytes,
490 Slot,
491 Option<T::AccountId>,
492 ),
493 >;
494
495 #[pallet::storage]
497 pub(super) type ParentBlockVoters<T: Config> = StorageValue<
498 _,
499 BTreeMap<
500 (PublicKey, SectorIndex, PieceOffset, ScalarBytes, Slot),
501 (Option<T::AccountId>, RewardSignature),
502 >,
503 ValueQuery,
504 >;
505
506 #[pallet::storage]
508 pub(super) type CurrentBlockVoters<T: Config> = StorageValue<
509 _,
510 BTreeMap<
511 (PublicKey, SectorIndex, PieceOffset, ScalarBytes, Slot),
512 (Option<T::AccountId>, RewardSignature),
513 >,
514 >;
515
516 #[pallet::storage]
518 pub(super) type PotSlotIterations<T> = StorageValue<_, PotSlotIterationsValue>;
519
520 #[pallet::storage]
523 pub(super) type PotEntropy<T: Config> =
524 StorageValue<_, BTreeMap<BlockNumberFor<T>, PotEntropyValue>, ValueQuery>;
525
526 #[pallet::storage]
529 pub type BlockRandomness<T> = StorageValue<_, Randomness>;
530
531 #[pallet::storage]
533 pub(super) type AllowAuthoringByAnyone<T> = StorageValue<_, bool, ValueQuery>;
534
535 #[pallet::storage]
539 #[pallet::getter(fn root_plot_public_key)]
540 pub(super) type RootPlotPublicKey<T> = StorageValue<_, PublicKey>;
541
542 #[pallet::hooks]
543 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
544 fn on_initialize(block_number: BlockNumberFor<T>) -> Weight {
545 Self::do_initialize(block_number);
546 Weight::zero()
547 }
548
549 fn on_finalize(block_number: BlockNumberFor<T>) {
550 Self::do_finalize(block_number)
551 }
552 }
553
554 #[pallet::call]
555 impl<T: Config> Pallet<T> {
556 #[pallet::call_index(0)]
559 #[pallet::weight((< T as Config >::WeightInfo::store_segment_headers(segment_headers.len() as u32), DispatchClass::Mandatory))]
560 pub fn store_segment_headers(
561 origin: OriginFor<T>,
562 segment_headers: Vec<SegmentHeader>,
563 ) -> DispatchResult {
564 ensure_none(origin)?;
565 Self::do_store_segment_headers(segment_headers)
566 }
567
568 #[pallet::call_index(1)]
571 #[pallet::weight(< T as Config >::WeightInfo::enable_solution_range_adjustment())]
572 pub fn enable_solution_range_adjustment(
573 origin: OriginFor<T>,
574 solution_range_override: Option<u64>,
575 voting_solution_range_override: Option<u64>,
576 ) -> DispatchResult {
577 ensure_root(origin)?;
578
579 Self::do_enable_solution_range_adjustment(
580 solution_range_override,
581 voting_solution_range_override,
582 )?;
583
584 frame_system::Pallet::<T>::deposit_log(
585 DigestItem::enable_solution_range_adjustment_and_override(solution_range_override),
586 );
587
588 Ok(())
589 }
590
591 #[pallet::call_index(2)]
593 #[pallet::weight((< T as Config >::WeightInfo::vote(), DispatchClass::Operational))]
594 #[allow(clippy::boxed_local)]
597 pub fn vote(
598 origin: OriginFor<T>,
599 signed_vote: Box<SignedVote<BlockNumberFor<T>, T::Hash, T::AccountId>>,
600 ) -> DispatchResult {
601 T::SubspaceOrigin::ensure_origin(origin)?;
602
603 Self::do_vote(*signed_vote)
604 }
605
606 #[pallet::call_index(3)]
608 #[pallet::weight(< T as Config >::WeightInfo::enable_rewards_at())]
609 pub fn enable_rewards_at(
610 origin: OriginFor<T>,
611 enable_rewards_at: EnableRewardsAt<BlockNumberFor<T>>,
612 ) -> DispatchResult {
613 ensure_root(origin)?;
614
615 Self::do_enable_rewards_at(enable_rewards_at)
616 }
617
618 #[pallet::call_index(4)]
620 #[pallet::weight(< T as Config >::WeightInfo::enable_authoring_by_anyone())]
621 pub fn enable_authoring_by_anyone(origin: OriginFor<T>) -> DispatchResult {
622 ensure_root(origin)?;
623
624 AllowAuthoringByAnyone::<T>::put(true);
625 RootPlotPublicKey::<T>::take();
626 frame_system::Pallet::<T>::deposit_log(DigestItem::root_plot_public_key_update(None));
628
629 Ok(())
630 }
631
632 #[pallet::call_index(5)]
634 #[pallet::weight(< T as Config >::WeightInfo::set_pot_slot_iterations())]
635 pub fn set_pot_slot_iterations(
636 origin: OriginFor<T>,
637 slot_iterations: NonZeroU32,
638 ) -> DispatchResult {
639 ensure_root(origin)?;
640
641 if !slot_iterations
642 .get()
643 .is_multiple_of(u32::from(PotCheckpoints::NUM_CHECKPOINTS.get() * 2))
644 {
645 return Err(Error::<T>::NotMultipleOfCheckpoints.into());
646 }
647
648 let mut pot_slot_iterations =
649 PotSlotIterations::<T>::get().expect("Always initialized during genesis; qed");
650
651 if pot_slot_iterations.slot_iterations >= slot_iterations {
652 return Err(Error::<T>::PotSlotIterationsMustIncrease.into());
653 }
654
655 if let Some(pot_slot_iterations_update_value) = pot_slot_iterations.update
657 && pot_slot_iterations_update_value.target_slot.is_some()
658 {
659 return Err(Error::<T>::PotSlotIterationsUpdateAlreadyScheduled.into());
660 }
661
662 pot_slot_iterations.update.replace(PotSlotIterationsUpdate {
663 target_slot: None,
665 slot_iterations,
666 });
667
668 PotSlotIterations::<T>::put(pot_slot_iterations);
669
670 Ok(())
671 }
672 }
673
674 #[pallet::inherent]
675 impl<T: Config> ProvideInherent for Pallet<T> {
676 type Call = Call<T>;
677 type Error = InherentError;
678 const INHERENT_IDENTIFIER: InherentIdentifier = INHERENT_IDENTIFIER;
679
680 fn create_inherent(data: &InherentData) -> Option<Self::Call> {
681 let inherent_data = data
682 .get_data::<InherentType>(&INHERENT_IDENTIFIER)
683 .expect("Subspace inherent data not correctly encoded")
684 .expect("Subspace inherent data must be provided");
685
686 let segment_headers = inherent_data.segment_headers;
687 if segment_headers.is_empty() {
688 None
689 } else {
690 Some(Call::store_segment_headers { segment_headers })
691 }
692 }
693
694 fn is_inherent_required(data: &InherentData) -> Result<Option<Self::Error>, Self::Error> {
695 let inherent_data = data
696 .get_data::<InherentType>(&INHERENT_IDENTIFIER)
697 .expect("Subspace inherent data not correctly encoded")
698 .expect("Subspace inherent data must be provided");
699
700 Ok(if inherent_data.segment_headers.is_empty() {
701 None
702 } else {
703 Some(InherentError::MissingSegmentHeadersList)
704 })
705 }
706
707 fn check_inherent(call: &Self::Call, data: &InherentData) -> Result<(), Self::Error> {
708 if let Call::store_segment_headers { segment_headers } = call {
709 let inherent_data = data
710 .get_data::<InherentType>(&INHERENT_IDENTIFIER)
711 .expect("Subspace inherent data not correctly encoded")
712 .expect("Subspace inherent data must be provided");
713
714 if segment_headers != &inherent_data.segment_headers {
715 return Err(InherentError::IncorrectSegmentHeadersList {
716 expected: inherent_data.segment_headers,
717 actual: segment_headers.clone(),
718 });
719 }
720 }
721
722 Ok(())
723 }
724
725 fn is_inherent(call: &Self::Call) -> bool {
726 matches!(call, Call::store_segment_headers { .. })
727 }
728 }
729
730 #[pallet::validate_unsigned]
731 impl<T: Config> ValidateUnsigned for Pallet<T> {
732 type Call = Call<T>;
733 fn validate_unsigned(source: TransactionSource, call: &Self::Call) -> TransactionValidity {
734 match call {
735 Call::store_segment_headers { segment_headers } => {
736 Self::validate_segment_header(source, segment_headers)
737 }
738 _ => InvalidTransaction::Call.into(),
739 }
740 }
741
742 fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> {
743 match call {
744 Call::store_segment_headers { segment_headers } => {
745 Self::pre_dispatch_segment_header(segment_headers)
746 }
747 _ => Err(InvalidTransaction::Call.into()),
748 }
749 }
750 }
751}
752
753impl<T: Config> Pallet<T> {
754 pub fn history_size() -> HistorySize {
756 let number_of_segments = u64::from(SegmentCommitment::<T>::count()).max(1);
758 HistorySize::from(NonZeroU64::new(number_of_segments).expect("Not zero; qed"))
759 }
760
761 fn should_era_change(block_number: BlockNumberFor<T>) -> bool {
764 block_number % T::EraDuration::get() == Zero::zero()
765 }
766
767 fn enact_era_change() {
772 let slot_probability = T::SlotProbability::get();
773
774 let current_slot = Self::current_slot();
775
776 SolutionRanges::<T>::mutate(|solution_ranges| {
777 let next_solution_range;
778 let next_voting_solution_range;
779 if !ShouldAdjustSolutionRange::<T>::get() {
781 next_solution_range = solution_ranges.current;
782 next_voting_solution_range = solution_ranges.current;
783 } else if let Some(solution_range_override) = NextSolutionRangeOverride::<T>::take() {
784 next_solution_range = solution_range_override.solution_range;
785 next_voting_solution_range = solution_range_override.voting_solution_range;
786 } else {
787 next_solution_range = derive_next_solution_range(
788 u64::from(EraStartSlot::<T>::get().unwrap_or_default()),
790 u64::from(current_slot),
791 slot_probability,
792 solution_ranges.current,
793 T::EraDuration::get()
794 .try_into()
795 .unwrap_or_else(|_| panic!("Era duration is always within u64; qed")),
796 );
797
798 next_voting_solution_range = next_solution_range
799 .saturating_mul(u64::from(T::ExpectedVotesPerBlock::get()) + 1);
800 };
801 solution_ranges.next.replace(next_solution_range);
802 solution_ranges
803 .voting_next
804 .replace(next_voting_solution_range);
805
806 if let Some(solution_range_for_rewards) = EnableRewardsBelowSolutionRange::<T>::get()
807 && next_solution_range <= solution_range_for_rewards
808 {
809 EnableRewardsBelowSolutionRange::<T>::take();
810
811 let next_block_number =
812 frame_system::Pallet::<T>::current_block_number() + One::one();
813 EnableRewards::<T>::put(next_block_number);
814 }
815 });
816
817 EraStartSlot::<T>::put(current_slot);
818 }
819
820 fn do_initialize(block_number: BlockNumberFor<T>) {
821 let pre_digest = frame_system::Pallet::<T>::digest()
822 .logs
823 .iter()
824 .find_map(|s| s.as_subspace_pre_digest::<T::AccountId>())
825 .expect("Block must always have pre-digest");
826 let current_slot = pre_digest.slot();
827
828 BlockSlots::<T>::mutate(|block_slots| {
829 if let Some(to_remove) = block_number.checked_sub(&T::BlockSlotCount::get().into()) {
830 block_slots.remove(&to_remove);
831 }
832 block_slots
833 .try_insert(block_number, current_slot)
834 .expect("one entry just removed before inserting; qed");
835 });
836
837 {
838 CurrentBlockAuthorInfo::<T>::take();
840 let farmer_public_key = pre_digest.solution().public_key;
841
842 if !AllowAuthoringByAnyone::<T>::get() {
844 RootPlotPublicKey::<T>::mutate(|maybe_root_plot_public_key| {
845 if let Some(root_plot_public_key) = maybe_root_plot_public_key {
846 if root_plot_public_key != &farmer_public_key {
847 panic!("Client bug, authoring must be only done by the root user");
848 }
849 } else {
850 maybe_root_plot_public_key.replace(farmer_public_key);
851 frame_system::Pallet::<T>::deposit_log(
854 DigestItem::root_plot_public_key_update(Some(farmer_public_key)),
855 );
856 }
857 });
858 }
859
860 let key = (
861 farmer_public_key,
862 pre_digest.solution().sector_index,
863 pre_digest.solution().piece_offset,
864 pre_digest.solution().chunk,
865 current_slot,
866 );
867 if !ParentBlockVoters::<T>::get().contains_key(&key) {
868 let (public_key, sector_index, piece_offset, chunk, slot) = key;
869
870 CurrentBlockAuthorInfo::<T>::put((
871 public_key,
872 sector_index,
873 piece_offset,
874 chunk,
875 slot,
876 Some(pre_digest.solution().reward_address.clone()),
877 ));
878 }
879 }
880 CurrentBlockVoters::<T>::put(BTreeMap::<
881 (PublicKey, SectorIndex, PieceOffset, ScalarBytes, Slot),
882 (Option<T::AccountId>, RewardSignature),
883 >::default());
884
885 if let sp_consensus_subspace::SolutionRanges {
887 next: Some(next),
888 voting_next: Some(voting_next),
889 ..
890 } = SolutionRanges::<T>::get()
891 {
892 SolutionRanges::<T>::put(sp_consensus_subspace::SolutionRanges {
893 current: next,
894 next: None,
895 voting_current: voting_next,
896 voting_next: None,
897 });
898 }
899
900 let block_randomness = pre_digest
901 .pot_info()
902 .proof_of_time()
903 .derive_global_randomness();
904
905 BlockRandomness::<T>::put(block_randomness);
907
908 frame_system::Pallet::<T>::deposit_log(DigestItem::solution_range(
910 SolutionRanges::<T>::get().current,
911 ));
912
913 T::EraChangeTrigger::trigger::<T>(block_number);
915
916 {
917 let mut pot_slot_iterations =
918 PotSlotIterations::<T>::get().expect("Always initialized during genesis; qed");
919 frame_system::Pallet::<T>::deposit_log(DigestItem::pot_slot_iterations(
921 pot_slot_iterations.slot_iterations,
922 ));
923
924 if let Some(update) = pot_slot_iterations.update
927 && let Some(target_slot) = update.target_slot
928 && target_slot <= current_slot
929 {
930 debug!(
931 "Applying PoT slots update, changing to {} at block #{:?}",
932 update.slot_iterations, block_number
933 );
934 pot_slot_iterations = PotSlotIterationsValue {
935 slot_iterations: update.slot_iterations,
936 update: None,
937 };
938 PotSlotIterations::<T>::put(pot_slot_iterations);
939 }
940 let pot_entropy_injection_interval = T::PotEntropyInjectionInterval::get();
941 let pot_entropy_injection_delay = T::PotEntropyInjectionDelay::get();
942
943 let mut entropy = PotEntropy::<T>::get();
944 let lookback_in_blocks = pot_entropy_injection_interval
945 * BlockNumberFor::<T>::from(T::PotEntropyInjectionLookbackDepth::get());
946 let last_entropy_injection_block =
947 block_number / pot_entropy_injection_interval * pot_entropy_injection_interval;
948 let maybe_entropy_source_block_number =
949 last_entropy_injection_block.checked_sub(&lookback_in_blocks);
950
951 if (block_number % pot_entropy_injection_interval).is_zero() {
952 let current_block_entropy = derive_pot_entropy(
953 &pre_digest.solution().chunk,
954 pre_digest.pot_info().proof_of_time(),
955 );
956 entropy.insert(
958 block_number,
959 PotEntropyValue {
960 target_slot: None,
961 entropy: current_block_entropy,
962 },
963 );
964
965 if let Some(entropy_source_block_number) = maybe_entropy_source_block_number
967 && let Some(entropy_value) = entropy.get_mut(&entropy_source_block_number)
968 {
969 let target_slot = pre_digest
970 .slot()
971 .saturating_add(pot_entropy_injection_delay);
972 debug!("Pot entropy injection will happen at slot {target_slot:?}",);
973 entropy_value.target_slot.replace(target_slot);
974
975 if let Some(update) = &mut pot_slot_iterations.update
977 && update.target_slot.is_none()
978 {
979 debug!("Scheduling PoT slots update to happen at slot {target_slot:?}");
980 update.target_slot.replace(target_slot);
981 PotSlotIterations::<T>::put(pot_slot_iterations);
982 }
983 }
984
985 PotEntropy::<T>::put(entropy.clone());
986 }
987
988 if let Some(entropy_source_block_number) = maybe_entropy_source_block_number {
991 let maybe_entropy_value = entropy.get(&entropy_source_block_number).copied();
992 if let Some(PotEntropyValue {
993 target_slot,
994 entropy,
995 }) = maybe_entropy_value
996 {
997 let target_slot = target_slot
998 .expect("Target slot is guaranteed to be present due to logic above; qed");
999 let slot_iterations = if let Some(update) = pot_slot_iterations.update
1001 && let Some(update_target_slot) = update.target_slot
1002 && update_target_slot == target_slot
1003 {
1004 debug!("Applying PoT slots update to the next PoT parameters change");
1005 update.slot_iterations
1006 } else {
1007 pot_slot_iterations.slot_iterations
1008 };
1009
1010 frame_system::Pallet::<T>::deposit_log(DigestItem::pot_parameters_change(
1011 PotParametersChange {
1012 slot: target_slot,
1013 slot_iterations,
1014 entropy,
1015 },
1016 ));
1017 }
1018 }
1019
1020 if let Some(entry) = entropy.first_entry()
1022 && let Some(target_slot) = entry.get().target_slot
1023 && target_slot < current_slot
1024 {
1025 entry.remove();
1026 PotEntropy::<T>::put(entropy);
1027 }
1028 }
1029 }
1030
1031 fn do_finalize(_block_number: BlockNumberFor<T>) {
1032 if let Some(next_solution_range) = SolutionRanges::<T>::get().next {
1035 frame_system::Pallet::<T>::deposit_log(DigestItem::next_solution_range(
1037 next_solution_range,
1038 ));
1039 }
1040
1041 if let Some((public_key, sector_index, piece_offset, scalar, slot, _reward_address)) =
1042 CurrentBlockAuthorInfo::<T>::get()
1043 {
1044 ParentBlockAuthorInfo::<T>::put((public_key, sector_index, piece_offset, scalar, slot));
1045 } else {
1046 ParentBlockAuthorInfo::<T>::take();
1047 }
1048
1049 ParentVoteVerificationData::<T>::put(current_vote_verification_data::<T>(true));
1050
1051 ParentBlockVoters::<T>::put(CurrentBlockVoters::<T>::get().unwrap_or_default());
1052
1053 DidProcessSegmentHeaders::<T>::take();
1054 }
1055
1056 fn do_store_segment_headers(segment_headers: Vec<SegmentHeader>) -> DispatchResult {
1057 assert!(
1058 !DidProcessSegmentHeaders::<T>::exists(),
1059 "Segment headers must be updated only once in the block"
1060 );
1061
1062 for segment_header in segment_headers {
1063 SegmentCommitment::<T>::insert(
1064 segment_header.segment_index(),
1065 segment_header.segment_commitment(),
1066 );
1067 frame_system::Pallet::<T>::deposit_log(DigestItem::segment_commitment(
1069 segment_header.segment_index(),
1070 segment_header.segment_commitment(),
1071 ));
1072 Self::deposit_event(Event::SegmentHeaderStored { segment_header });
1073 }
1074
1075 DidProcessSegmentHeaders::<T>::put(true);
1076 Ok(())
1077 }
1078
1079 fn do_enable_solution_range_adjustment(
1080 solution_range_override: Option<u64>,
1081 voting_solution_range_override: Option<u64>,
1082 ) -> DispatchResult {
1083 if ShouldAdjustSolutionRange::<T>::get() {
1084 return Err(Error::<T>::SolutionRangeAdjustmentAlreadyEnabled.into());
1085 }
1086
1087 ShouldAdjustSolutionRange::<T>::put(true);
1088
1089 if let Some(solution_range) = solution_range_override {
1090 let voting_solution_range = voting_solution_range_override.unwrap_or_else(|| {
1091 solution_range.saturating_mul(u64::from(T::ExpectedVotesPerBlock::get()) + 1)
1092 });
1093 SolutionRanges::<T>::mutate(|solution_ranges| {
1094 if solution_ranges.next.is_some() {
1096 solution_ranges.next.replace(solution_range);
1097 solution_ranges.voting_next.replace(voting_solution_range);
1098 } else {
1099 solution_ranges.current = solution_range;
1100 solution_ranges.voting_current = voting_solution_range;
1101
1102 NextSolutionRangeOverride::<T>::put(SolutionRangeOverride {
1105 solution_range,
1106 voting_solution_range,
1107 });
1108 frame_system::Pallet::<T>::deposit_log(DigestItem::next_solution_range(
1109 solution_range,
1110 ));
1111 }
1112 });
1113 }
1114
1115 Ok(())
1116 }
1117
1118 fn do_vote(
1119 signed_vote: SignedVote<BlockNumberFor<T>, T::Hash, T::AccountId>,
1120 ) -> DispatchResult {
1121 let Vote::V0 {
1122 height,
1123 parent_hash,
1124 solution,
1125 ..
1126 } = signed_vote.vote;
1127
1128 Self::deposit_event(Event::FarmerVote {
1129 public_key: solution.public_key,
1130 reward_address: solution.reward_address,
1131 height,
1132 parent_hash,
1133 });
1134
1135 Ok(())
1136 }
1137
1138 fn do_enable_rewards_at(
1139 enable_rewards_at: EnableRewardsAt<BlockNumberFor<T>>,
1140 ) -> DispatchResult {
1141 match enable_rewards_at {
1142 EnableRewardsAt::Height(block_number) => {
1143 let next_block_number =
1146 frame_system::Pallet::<T>::current_block_number() + One::one();
1147 EnableRewards::<T>::put(block_number.max(next_block_number));
1148 }
1149 EnableRewardsAt::SolutionRange(solution_range) => {
1150 EnableRewardsBelowSolutionRange::<T>::put(solution_range);
1151 }
1152 EnableRewardsAt::Manually => {
1153 }
1155 }
1156
1157 Ok(())
1158 }
1159
1160 pub fn pot_parameters() -> PotParameters {
1162 let block_number = frame_system::Pallet::<T>::block_number();
1163 let pot_slot_iterations =
1164 PotSlotIterations::<T>::get().expect("Always initialized during genesis; qed");
1165 let pot_entropy_injection_interval = T::PotEntropyInjectionInterval::get();
1166
1167 let entropy = PotEntropy::<T>::get();
1168 let lookback_in_blocks = pot_entropy_injection_interval
1169 * BlockNumberFor::<T>::from(T::PotEntropyInjectionLookbackDepth::get());
1170 let last_entropy_injection_block =
1171 block_number / pot_entropy_injection_interval * pot_entropy_injection_interval;
1172 let maybe_entropy_source_block_number =
1173 last_entropy_injection_block.checked_sub(&lookback_in_blocks);
1174
1175 let mut next_change = None;
1176
1177 if let Some(entropy_source_block_number) = maybe_entropy_source_block_number {
1178 let maybe_entropy_value = entropy.get(&entropy_source_block_number).copied();
1179 if let Some(PotEntropyValue {
1180 target_slot,
1181 entropy,
1182 }) = maybe_entropy_value
1183 {
1184 let target_slot = target_slot.expect(
1185 "Always present due to identical check present in block initialization; qed",
1186 );
1187 let slot_iterations = if let Some(update) = pot_slot_iterations.update
1189 && let Some(update_target_slot) = update.target_slot
1190 && update_target_slot == target_slot
1191 {
1192 update.slot_iterations
1193 } else {
1194 pot_slot_iterations.slot_iterations
1195 };
1196
1197 next_change.replace(PotParametersChange {
1198 slot: target_slot,
1199 slot_iterations,
1200 entropy,
1201 });
1202 }
1203 }
1204
1205 PotParameters::V0 {
1206 slot_iterations: pot_slot_iterations.slot_iterations,
1207 next_change,
1208 }
1209 }
1210
1211 pub fn current_slot() -> Slot {
1213 BlockSlots::<T>::get()
1214 .last_key_value()
1215 .map(|(_block, slot)| *slot)
1216 .unwrap_or_default()
1217 }
1218
1219 pub fn archived_history_size() -> u64 {
1221 let archived_segments = SegmentCommitment::<T>::count();
1222
1223 u64::from(archived_segments) * ArchivedHistorySegment::SIZE as u64
1224 }
1225}
1226
1227impl<T> Pallet<T>
1228where
1229 T: Config + CreateUnsigned<Call<T>>,
1230{
1231 pub fn submit_vote(signed_vote: SignedVote<BlockNumberFor<T>, T::Hash, T::AccountId>) {
1234 let call = Call::vote {
1235 signed_vote: Box::new(signed_vote),
1236 };
1237
1238 let ext = T::create_unsigned(call.into());
1239 match SubmitTransaction::<T, Call<T>>::submit_transaction(ext) {
1240 Ok(()) => {
1241 debug!("Submitted Subspace vote");
1242 }
1243 Err(()) => {
1244 error!("Error submitting Subspace vote");
1245 }
1246 }
1247 }
1248}
1249
1250impl<T: Config> Pallet<T> {
1255 fn validate_segment_header(
1256 source: TransactionSource,
1257 segment_headers: &[SegmentHeader],
1258 ) -> TransactionValidity {
1259 if !matches!(
1261 source,
1262 TransactionSource::Local | TransactionSource::InBlock,
1263 ) {
1264 warn!("Rejecting segment header extrinsic because it is not local/in-block.",);
1265
1266 return InvalidTransaction::Call.into();
1267 }
1268
1269 check_segment_headers::<T>(segment_headers)?;
1270
1271 ValidTransaction::with_tag_prefix("SubspaceSegmentHeader")
1272 .priority(TransactionPriority::MAX)
1274 .longevity(0)
1277 .propagate(false)
1279 .build()
1280 }
1281
1282 fn pre_dispatch_segment_header(
1283 segment_headers: &[SegmentHeader],
1284 ) -> Result<(), TransactionValidityError> {
1285 check_segment_headers::<T>(segment_headers)
1286 }
1287
1288 fn validate_vote(
1289 signed_vote: &SignedVote<BlockNumberFor<T>, T::Hash, T::AccountId>,
1290 ) -> Result<(ValidTransaction, Weight), TransactionValidityError> {
1291 check_vote::<T>(signed_vote, false)?;
1292
1293 ValidTransaction::with_tag_prefix("SubspaceVote")
1294 .priority(TransactionPriority::MAX)
1296 .longevity(2)
1298 .and_provides(signed_vote.signature)
1299 .build()
1300 .map(|validity| (validity, T::ExtensionWeightInfo::vote()))
1301 }
1302
1303 fn pre_dispatch_vote(
1304 signed_vote: &SignedVote<BlockNumberFor<T>, T::Hash, T::AccountId>,
1305 ) -> Result<Weight, TransactionValidityError> {
1306 match check_vote::<T>(signed_vote, true) {
1307 Ok(()) => Ok(T::ExtensionWeightInfo::vote()),
1308 Err(CheckVoteError::Equivocated { .. }) => {
1309 Ok(T::ExtensionWeightInfo::vote_with_equivocation())
1311 }
1312 Err(error) => Err(error.into()),
1313 }
1314 }
1315}
1316
1317fn current_vote_verification_data<T: Config>(is_block_initialized: bool) -> VoteVerificationData {
1321 let solution_ranges = SolutionRanges::<T>::get();
1322
1323 VoteVerificationData {
1324 solution_range: if is_block_initialized {
1325 solution_ranges.current
1326 } else {
1327 solution_ranges.next.unwrap_or(solution_ranges.current)
1328 },
1329 vote_solution_range: if is_block_initialized {
1330 solution_ranges.voting_current
1331 } else {
1332 solution_ranges
1333 .voting_next
1334 .unwrap_or(solution_ranges.voting_current)
1335 },
1336 current_slot: Pallet::<T>::current_slot(),
1337 parent_slot: ParentVoteVerificationData::<T>::get()
1338 .map(|parent_vote_verification_data| {
1339 if is_block_initialized {
1340 parent_vote_verification_data.current_slot
1341 } else {
1342 parent_vote_verification_data.parent_slot
1343 }
1344 })
1345 .unwrap_or_else(Pallet::<T>::current_slot),
1346 }
1347}
1348
1349#[derive(Debug, Eq, PartialEq)]
1350enum CheckVoteError {
1351 UnexpectedBeforeHeightTwo,
1352 HeightInTheFuture,
1353 HeightInThePast,
1354 IncorrectParentHash,
1355 SlotInTheFuture,
1356 SlotInThePast,
1357 BadRewardSignature(SignatureError),
1358 UnknownSegmentCommitment,
1359 InvalidHistorySize,
1360 InvalidSolution(String),
1361 QualityTooHigh,
1362 InvalidProofOfTime,
1363 InvalidFutureProofOfTime,
1364 DuplicateVote,
1365 Equivocated { slot: Slot, offender: PublicKey },
1366}
1367
1368impl From<CheckVoteError> for TransactionValidityError {
1369 #[inline]
1370 fn from(error: CheckVoteError) -> Self {
1371 TransactionValidityError::Invalid(match error {
1372 CheckVoteError::UnexpectedBeforeHeightTwo => InvalidTransaction::Call,
1373 CheckVoteError::HeightInTheFuture => InvalidTransaction::Future,
1374 CheckVoteError::HeightInThePast => InvalidTransaction::Stale,
1375 CheckVoteError::IncorrectParentHash => InvalidTransaction::Call,
1376 CheckVoteError::SlotInTheFuture => InvalidTransaction::Future,
1377 CheckVoteError::SlotInThePast => InvalidTransaction::Stale,
1378 CheckVoteError::BadRewardSignature(_) => InvalidTransaction::BadProof,
1379 CheckVoteError::UnknownSegmentCommitment => InvalidTransaction::Call,
1380 CheckVoteError::InvalidHistorySize => InvalidTransaction::Call,
1381 CheckVoteError::InvalidSolution(_) => InvalidTransaction::Call,
1382 CheckVoteError::QualityTooHigh => InvalidTransaction::Call,
1383 CheckVoteError::InvalidProofOfTime => InvalidTransaction::Future,
1384 CheckVoteError::InvalidFutureProofOfTime => InvalidTransaction::Call,
1385 CheckVoteError::DuplicateVote => InvalidTransaction::Call,
1386 CheckVoteError::Equivocated { .. } => InvalidTransaction::BadSigner,
1387 })
1388 }
1389}
1390
1391fn check_vote<T: Config>(
1392 signed_vote: &SignedVote<BlockNumberFor<T>, T::Hash, T::AccountId>,
1393 pre_dispatch: bool,
1394) -> Result<(), CheckVoteError> {
1395 let Vote::V0 {
1396 height,
1397 parent_hash,
1398 slot,
1399 solution,
1400 proof_of_time,
1401 future_proof_of_time,
1402 } = &signed_vote.vote;
1403 let height = *height;
1404 let slot = *slot;
1405
1406 let current_block_number = frame_system::Pallet::<T>::current_block_number();
1407
1408 if current_block_number <= One::one() || height <= One::one() {
1409 debug!("Votes are not expected at height below 2");
1410
1411 return Err(CheckVoteError::UnexpectedBeforeHeightTwo);
1412 }
1413
1414 if !(height == current_block_number || height == current_block_number - One::one()) {
1418 debug!(
1419 "Vote verification error: bad height {height:?}, current block number is \
1420 {current_block_number:?}"
1421 );
1422 return Err(if height > current_block_number {
1423 CheckVoteError::HeightInTheFuture
1424 } else {
1425 CheckVoteError::HeightInThePast
1426 });
1427 }
1428
1429 if *parent_hash != frame_system::Pallet::<T>::block_hash(height - One::one()) {
1433 debug!("Vote verification error: parent hash {parent_hash:?}",);
1434 return Err(CheckVoteError::IncorrectParentHash);
1435 }
1436
1437 let current_vote_verification_data = current_vote_verification_data::<T>(pre_dispatch);
1438 let parent_vote_verification_data = ParentVoteVerificationData::<T>::get()
1439 .expect("Above check for block number ensures that this value is always present; qed");
1440
1441 if pre_dispatch {
1442 let current_slot = current_vote_verification_data.current_slot;
1445 if slot > current_slot || (slot == current_slot && height != current_block_number) {
1446 debug!("Vote slot {slot:?} must be before current slot {current_slot:?}",);
1447 return Err(CheckVoteError::SlotInTheFuture);
1448 }
1449 }
1450
1451 let parent_slot = if pre_dispatch {
1452 if height == current_block_number {
1457 parent_vote_verification_data.current_slot
1458 } else {
1459 parent_vote_verification_data.parent_slot
1460 }
1461 } else {
1462 if height == current_block_number {
1467 current_vote_verification_data.current_slot
1468 } else {
1469 current_vote_verification_data.parent_slot
1470 }
1471 };
1472
1473 if slot <= parent_slot {
1474 debug!("Vote slot {slot:?} must be after parent slot {parent_slot:?}",);
1475 return Err(CheckVoteError::SlotInThePast);
1476 }
1477
1478 if let Err(error) = check_reward_signature(
1479 signed_vote.vote.hash().as_bytes(),
1480 &signed_vote.signature,
1481 &solution.public_key,
1482 &schnorrkel::signing_context(REWARD_SIGNING_CONTEXT),
1483 ) {
1484 debug!("Vote verification error: {error:?}");
1485 return Err(CheckVoteError::BadRewardSignature(error));
1486 }
1487
1488 let vote_verification_data = if height == current_block_number {
1489 current_vote_verification_data
1490 } else {
1491 parent_vote_verification_data
1492 };
1493
1494 let sector_id = SectorId::new(
1495 solution.public_key.hash(),
1496 solution.sector_index,
1497 solution.history_size,
1498 );
1499
1500 let recent_segments = T::RecentSegments::get();
1501 let recent_history_fraction = (
1502 T::RecentHistoryFraction::get().0,
1503 T::RecentHistoryFraction::get().1,
1504 );
1505 let segment_index = sector_id
1506 .derive_piece_index(
1507 solution.piece_offset,
1508 solution.history_size,
1509 T::MaxPiecesInSector::get(),
1510 recent_segments,
1511 recent_history_fraction,
1512 )
1513 .segment_index();
1514
1515 let segment_commitment = if let Some(segment_commitment) =
1516 Pallet::<T>::segment_commitment(segment_index)
1517 {
1518 segment_commitment
1519 } else {
1520 debug!("Vote verification error: no segment commitment for segment index {segment_index}");
1521 return Err(CheckVoteError::UnknownSegmentCommitment);
1522 };
1523
1524 let sector_expiration_check_segment_commitment = Pallet::<T>::segment_commitment(
1525 solution
1526 .history_size
1527 .sector_expiration_check(T::MinSectorLifetime::get())
1528 .ok_or(CheckVoteError::InvalidHistorySize)?
1529 .segment_index(),
1530 );
1531
1532 match verify_solution(
1533 solution.into(),
1534 slot.into(),
1535 (&VerifySolutionParams {
1536 proof_of_time: *proof_of_time,
1537 solution_range: vote_verification_data.vote_solution_range,
1538 piece_check_params: Some(PieceCheckParams {
1539 max_pieces_in_sector: T::MaxPiecesInSector::get(),
1540 segment_commitment,
1541 recent_segments,
1542 recent_history_fraction,
1543 min_sector_lifetime: T::MinSectorLifetime::get(),
1544 current_history_size: Pallet::<T>::history_size(),
1545 sector_expiration_check_segment_commitment,
1546 }),
1547 })
1548 .into(),
1549 ) {
1550 Ok(solution_distance) => {
1551 if solution_distance <= vote_verification_data.solution_range / 2 {
1552 debug!("Vote quality is too high");
1553 return Err(CheckVoteError::QualityTooHigh);
1554 }
1555 }
1556 Err(error) => {
1557 debug!("Vote verification error: {error:?}");
1558 return Err(CheckVoteError::InvalidSolution(error));
1559 }
1560 }
1561
1562 if !is_proof_of_time_valid(
1565 BlockHash::try_from(parent_hash.as_ref())
1566 .expect("Must be able to convert to block hash type"),
1567 SlotNumber::from(slot),
1568 WrappedPotOutput::from(*proof_of_time),
1569 !pre_dispatch,
1571 ) {
1572 debug!("Invalid proof of time");
1573
1574 return Err(CheckVoteError::InvalidProofOfTime);
1575 }
1576
1577 if pre_dispatch
1580 && !is_proof_of_time_valid(
1581 BlockHash::try_from(parent_hash.as_ref())
1582 .expect("Must be able to convert to block hash type"),
1583 SlotNumber::from(slot + T::BlockAuthoringDelay::get()),
1584 WrappedPotOutput::from(*future_proof_of_time),
1585 false,
1586 )
1587 {
1588 debug!("Invalid future proof of time");
1589
1590 return Err(CheckVoteError::InvalidFutureProofOfTime);
1591 }
1592
1593 let key = (
1594 solution.public_key,
1595 solution.sector_index,
1596 solution.piece_offset,
1597 solution.chunk,
1598 slot,
1599 );
1600 let mut is_equivocating = ParentBlockAuthorInfo::<T>::get().as_ref() == Some(&key)
1606 || CurrentBlockAuthorInfo::<T>::get()
1607 .map(
1608 |(public_key, sector_index, piece_offset, chunk, slot, _reward_address)| {
1609 (public_key, sector_index, piece_offset, chunk, slot)
1610 },
1611 )
1612 .as_ref()
1613 == Some(&key);
1614
1615 if !is_equivocating
1616 && let Some((_reward_address, signature)) = ParentBlockVoters::<T>::get().get(&key)
1617 {
1618 if signature != &signed_vote.signature {
1619 is_equivocating = true;
1620 } else {
1621 return Err(CheckVoteError::DuplicateVote);
1623 }
1624 }
1625
1626 if !is_equivocating
1627 && let Some((_reward_address, signature)) =
1628 CurrentBlockVoters::<T>::get().unwrap_or_default().get(&key)
1629 {
1630 if signature != &signed_vote.signature {
1631 is_equivocating = true;
1632 } else {
1633 return Err(CheckVoteError::DuplicateVote);
1635 }
1636 }
1637
1638 if pre_dispatch {
1639 CurrentBlockVoters::<T>::mutate(|current_reward_receivers| {
1641 current_reward_receivers
1642 .as_mut()
1643 .expect("Always set during block initialization")
1644 .insert(
1645 key,
1646 (
1647 if is_equivocating {
1648 None
1649 } else {
1650 Some(solution.reward_address.clone())
1651 },
1652 signed_vote.signature,
1653 ),
1654 );
1655 });
1656 }
1657
1658 if is_equivocating {
1659 let offender = solution.public_key;
1660
1661 CurrentBlockAuthorInfo::<T>::mutate(|maybe_info| {
1662 if let Some((public_key, _sector_index, _piece_offset, _chunk, _slot, reward_address)) =
1663 maybe_info
1664 && public_key == &offender
1665 {
1666 reward_address.take();
1668 }
1669 });
1670
1671 CurrentBlockVoters::<T>::mutate(|current_reward_receivers| {
1672 if let Some(current_reward_receivers) = current_reward_receivers {
1673 for (
1674 (public_key, _sector_index, _piece_offset, _chunk, _slot),
1675 (reward_address, _signature),
1676 ) in current_reward_receivers.iter_mut()
1677 {
1678 if public_key == &offender {
1679 reward_address.take();
1681 }
1682 }
1683 }
1684 });
1685
1686 return Err(CheckVoteError::Equivocated { slot, offender });
1687 }
1688
1689 Ok(())
1690}
1691
1692fn check_segment_headers<T: Config>(
1693 segment_headers: &[SegmentHeader],
1694) -> Result<(), TransactionValidityError> {
1695 let mut segment_headers_iter = segment_headers.iter();
1696
1697 let first_segment_header = match segment_headers_iter.next() {
1699 Some(first_segment_header) => first_segment_header,
1700 None => {
1701 return Err(InvalidTransaction::BadMandatory.into());
1702 }
1703 };
1704
1705 if first_segment_header.segment_index() > SegmentIndex::ZERO
1707 && !SegmentCommitment::<T>::contains_key(
1708 first_segment_header.segment_index() - SegmentIndex::ONE,
1709 )
1710 {
1711 return Err(InvalidTransaction::BadMandatory.into());
1712 }
1713
1714 if SegmentCommitment::<T>::contains_key(first_segment_header.segment_index()) {
1716 return Err(InvalidTransaction::BadMandatory.into());
1717 }
1718
1719 let mut last_segment_index = first_segment_header.segment_index();
1720
1721 for segment_header in segment_headers_iter {
1722 let segment_index = segment_header.segment_index();
1723
1724 if segment_index != last_segment_index + SegmentIndex::ONE {
1726 return Err(InvalidTransaction::BadMandatory.into());
1727 }
1728
1729 if SegmentCommitment::<T>::contains_key(segment_index) {
1731 return Err(InvalidTransaction::BadMandatory.into());
1732 }
1733
1734 last_segment_index = segment_index;
1735 }
1736
1737 Ok(())
1738}
1739
1740impl<T: Config> subspace_runtime_primitives::RewardsEnabled for Pallet<T> {
1741 fn rewards_enabled() -> bool {
1742 if let Some(height) = EnableRewards::<T>::get() {
1743 frame_system::Pallet::<T>::current_block_number() >= height
1744 } else {
1745 false
1746 }
1747 }
1748}
1749
1750impl<T: Config> subspace_runtime_primitives::FindBlockRewardAddress<T::AccountId> for Pallet<T> {
1751 fn find_block_reward_address() -> Option<T::AccountId> {
1752 CurrentBlockAuthorInfo::<T>::get().and_then(
1753 |(_public_key, _sector_index, _piece_offset, _chunk, _slot, reward_address)| {
1754 if let Some(height) = EnableRewards::<T>::get()
1756 && frame_system::Pallet::<T>::current_block_number() >= height
1757 {
1758 return reward_address;
1759 }
1760
1761 None
1762 },
1763 )
1764 }
1765}
1766
1767impl<T: Config> subspace_runtime_primitives::FindVotingRewardAddresses<T::AccountId> for Pallet<T> {
1768 fn find_voting_reward_addresses() -> Vec<T::AccountId> {
1769 if let Some(height) = EnableRewards::<T>::get()
1771 && frame_system::Pallet::<T>::current_block_number() >= height
1772 {
1773 return CurrentBlockVoters::<T>::get()
1776 .unwrap_or_else(ParentBlockVoters::<T>::get)
1777 .into_values()
1778 .filter_map(|(reward_address, _signature)| reward_address)
1779 .collect();
1780 }
1781
1782 Vec::new()
1783 }
1784}
1785
1786impl<T: Config> frame_support::traits::Randomness<T::Hash, BlockNumberFor<T>> for Pallet<T> {
1787 fn random(subject: &[u8]) -> (T::Hash, BlockNumberFor<T>) {
1788 let mut subject = subject.to_vec();
1789 subject.extend_from_slice(
1790 BlockRandomness::<T>::get()
1791 .expect("Block randomness is always set in block initialization; qed")
1792 .as_ref(),
1793 );
1794
1795 (
1796 T::Hashing::hash(&subject),
1797 frame_system::Pallet::<T>::current_block_number(),
1798 )
1799 }
1800
1801 fn random_seed() -> (T::Hash, BlockNumberFor<T>) {
1802 (
1803 T::Hashing::hash(
1804 BlockRandomness::<T>::get()
1805 .expect("Block randomness is always set in block initialization; qed")
1806 .as_ref(),
1807 ),
1808 frame_system::Pallet::<T>::current_block_number(),
1809 )
1810 }
1811}