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