1#![doc = include_str!("../README.md")]
2#![cfg_attr(not(feature = "std"), no_std)]
3#![feature(array_chunks, assert_matches, let_chains, 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::generic::DigestItem;
41use sp_runtime::traits::{BlockNumberProvider, CheckedSub, Hash, One, Zero};
42use sp_runtime::transaction_validity::{
43 InvalidTransaction, TransactionPriority, TransactionSource, TransactionValidity,
44 TransactionValidityError, ValidTransaction,
45};
46use sp_runtime::Weight;
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, ScalarBytes, SlotNumber, REWARD_SIGNING_CONTEXT,
57};
58use subspace_runtime_primitives::CreateUnsigned;
59use subspace_verification::{
60 check_reward_signature, derive_next_solution_range, derive_pot_entropy, PieceCheckParams,
61 VerifySolutionParams,
62};
63
64pub trait EraChangeTrigger {
66 fn trigger<T: Config>(block_number: BlockNumberFor<T>);
69}
70
71pub struct NormalEraChange;
73
74impl EraChangeTrigger for NormalEraChange {
75 fn trigger<T: Config>(block_number: BlockNumberFor<T>) {
76 if <Pallet<T>>::should_era_change(block_number) {
77 <Pallet<T>>::enact_era_change();
78 }
79 }
80}
81
82#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]
84pub enum RawOrigin {
85 ValidatedUnsigned,
86}
87
88pub struct EnsureSubspaceOrigin;
90impl<O: Into<Result<RawOrigin, O>> + From<RawOrigin>> EnsureOrigin<O> for EnsureSubspaceOrigin {
91 type Success = ();
92
93 fn try_origin(o: O) -> Result<Self::Success, O> {
94 o.into().map(|o| match o {
95 RawOrigin::ValidatedUnsigned => (),
96 })
97 }
98
99 #[cfg(feature = "runtime-benchmarks")]
100 fn try_successful_origin() -> Result<O, ()> {
101 Ok(O::from(RawOrigin::ValidatedUnsigned))
102 }
103}
104
105#[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, MaxEncodedLen, TypeInfo)]
106struct VoteVerificationData {
107 solution_range: SolutionRange,
109 vote_solution_range: SolutionRange,
110 current_slot: Slot,
111 parent_slot: Slot,
112}
113
114#[frame_support::pallet]
115pub mod pallet {
116 use super::{EraChangeTrigger, ExtensionWeightInfo, VoteVerificationData};
117 use crate::weights::WeightInfo;
118 use crate::RawOrigin;
119 use frame_support::pallet_prelude::*;
120 use frame_system::pallet_prelude::*;
121 use sp_consensus_slots::Slot;
122 use sp_consensus_subspace::digests::CompatibleDigestItem;
123 use sp_consensus_subspace::inherents::{InherentError, InherentType, INHERENT_IDENTIFIER};
124 use sp_consensus_subspace::SignedVote;
125 use sp_runtime::traits::One;
126 use sp_runtime::DigestItem;
127 use sp_std::collections::btree_map::BTreeMap;
128 use sp_std::num::NonZeroU32;
129 use sp_std::prelude::*;
130 use subspace_core_primitives::hashes::Blake3Hash;
131 use subspace_core_primitives::pieces::PieceOffset;
132 use subspace_core_primitives::pot::PotCheckpoints;
133 use subspace_core_primitives::sectors::SectorIndex;
134 use subspace_core_primitives::segments::{HistorySize, SegmentHeader, SegmentIndex};
135 use subspace_core_primitives::solutions::{RewardSignature, SolutionRange};
136 use subspace_core_primitives::{PublicKey, Randomness, ScalarBytes};
137
138 pub(super) struct InitialSolutionRanges<T: Config> {
139 _config: T,
140 }
141
142 impl<T: Config> Get<sp_consensus_subspace::SolutionRanges> for InitialSolutionRanges<T> {
143 fn get() -> sp_consensus_subspace::SolutionRanges {
144 sp_consensus_subspace::SolutionRanges {
145 current: T::InitialSolutionRange::get(),
146 next: None,
147 voting_current: if T::ShouldAdjustSolutionRange::get() {
148 T::InitialSolutionRange::get()
149 .saturating_mul(u64::from(T::ExpectedVotesPerBlock::get()) + 1)
150 } else {
151 T::InitialSolutionRange::get()
152 },
153 voting_next: None,
154 }
155 }
156 }
157
158 #[derive(Debug, Encode, Decode, TypeInfo)]
160 pub(super) struct SolutionRangeOverride {
161 pub(super) solution_range: SolutionRange,
163 pub(super) voting_solution_range: SolutionRange,
165 }
166
167 #[pallet::pallet]
169 #[pallet::without_storage_info]
170 pub struct Pallet<T>(_);
171
172 #[pallet::config]
173 #[pallet::disable_frame_system_supertrait_check]
174 pub trait Config: frame_system::Config {
175 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
177
178 type SubspaceOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = ()>;
180
181 #[pallet::constant]
186 type BlockAuthoringDelay: Get<Slot>;
187
188 #[pallet::constant]
190 type PotEntropyInjectionInterval: Get<BlockNumberFor<Self>>;
191
192 #[pallet::constant]
194 type PotEntropyInjectionLookbackDepth: Get<u8>;
195
196 #[pallet::constant]
198 type PotEntropyInjectionDelay: Get<Slot>;
199
200 #[pallet::constant]
204 type EraDuration: Get<BlockNumberFor<Self>>;
205
206 #[pallet::constant]
208 type InitialSolutionRange: Get<SolutionRange>;
209
210 #[pallet::constant]
216 type SlotProbability: Get<(u64, u64)>;
217
218 #[pallet::constant]
221 type ConfirmationDepthK: Get<BlockNumberFor<Self>>;
222
223 #[pallet::constant]
225 type RecentSegments: Get<HistorySize>;
226
227 #[pallet::constant]
229 type RecentHistoryFraction: Get<(HistorySize, HistorySize)>;
230
231 #[pallet::constant]
233 type MinSectorLifetime: Get<HistorySize>;
234
235 #[pallet::constant]
239 type ExpectedVotesPerBlock: Get<u32>;
240
241 #[pallet::constant]
243 type MaxPiecesInSector: Get<u16>;
244
245 type ShouldAdjustSolutionRange: Get<bool>;
246 type EraChangeTrigger: EraChangeTrigger;
251
252 type WeightInfo: WeightInfo;
254
255 #[pallet::constant]
257 type BlockSlotCount: Get<u32>;
258
259 type ExtensionWeightInfo: ExtensionWeightInfo;
261 }
262
263 #[derive(Debug, Default, Encode, Decode, TypeInfo)]
264 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
265 pub enum AllowAuthoringBy {
266 #[default]
268 Anyone,
269 FirstFarmer,
272 RootFarmer(PublicKey),
274 }
275
276 #[derive(Debug, Copy, Clone, Encode, Decode, TypeInfo)]
277 pub(super) struct PotEntropyValue {
278 pub(super) target_slot: Option<Slot>,
280 pub(super) entropy: Blake3Hash,
281 }
282
283 #[derive(Debug, Copy, Clone, Encode, Decode, TypeInfo, PartialEq)]
284 pub(super) struct PotSlotIterationsValue {
285 pub(super) slot_iterations: NonZeroU32,
286 pub(super) update: Option<PotSlotIterationsUpdate>,
288 }
289
290 #[derive(Debug, Copy, Clone, Encode, Decode, TypeInfo, PartialEq)]
291 pub(super) struct PotSlotIterationsUpdate {
292 pub(super) target_slot: Option<Slot>,
294 pub(super) slot_iterations: NonZeroU32,
295 }
296
297 #[derive(Debug, Copy, Clone, Eq, PartialEq, Encode, Decode, TypeInfo)]
299 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
300 pub enum EnableRewardsAt<BlockNumber> {
301 Height(BlockNumber),
303 SolutionRange(u64),
305 Manually,
307 }
308
309 #[pallet::genesis_config]
310 pub struct GenesisConfig<T>
311 where
312 T: Config,
313 {
314 pub enable_rewards_at: EnableRewardsAt<BlockNumberFor<T>>,
316 pub allow_authoring_by: AllowAuthoringBy,
318 pub pot_slot_iterations: NonZeroU32,
320 #[serde(skip)]
321 pub phantom: PhantomData<T>,
322 }
323
324 impl<T> Default for GenesisConfig<T>
325 where
326 T: Config,
327 {
328 #[inline]
329 fn default() -> Self {
330 Self {
331 enable_rewards_at: EnableRewardsAt::Height(BlockNumberFor::<T>::one()),
332 allow_authoring_by: AllowAuthoringBy::Anyone,
333 pot_slot_iterations: NonZeroU32::MIN,
334 phantom: PhantomData,
335 }
336 }
337 }
338
339 #[pallet::genesis_build]
340 impl<T> BuildGenesisConfig for GenesisConfig<T>
341 where
342 T: Config,
343 {
344 fn build(&self) {
345 match self.enable_rewards_at {
346 EnableRewardsAt::Height(block_number) => {
347 EnableRewards::<T>::put(block_number);
348 }
349 EnableRewardsAt::SolutionRange(solution_range) => {
350 EnableRewardsBelowSolutionRange::<T>::put(solution_range);
351 }
352 EnableRewardsAt::Manually => {
353 }
355 }
356 match &self.allow_authoring_by {
357 AllowAuthoringBy::Anyone => {
358 AllowAuthoringByAnyone::<T>::put(true);
359 }
360 AllowAuthoringBy::FirstFarmer => {
361 AllowAuthoringByAnyone::<T>::put(false);
362 }
363 AllowAuthoringBy::RootFarmer(root_farmer) => {
364 AllowAuthoringByAnyone::<T>::put(false);
365 RootPlotPublicKey::<T>::put(root_farmer);
366 }
367 }
368 PotSlotIterations::<T>::put(PotSlotIterationsValue {
369 slot_iterations: self.pot_slot_iterations,
370 update: None,
371 });
372 }
373 }
374
375 #[pallet::event]
377 #[pallet::generate_deposit(pub (super) fn deposit_event)]
378 pub enum Event<T: Config> {
379 SegmentHeaderStored { segment_header: SegmentHeader },
381 FarmerVote {
383 public_key: PublicKey,
384 reward_address: T::AccountId,
385 height: BlockNumberFor<T>,
386 parent_hash: T::Hash,
387 },
388 }
389
390 #[pallet::origin]
391 pub type Origin = RawOrigin;
392
393 #[pallet::error]
394 pub enum Error<T> {
395 SolutionRangeAdjustmentAlreadyEnabled,
397 NotMultipleOfCheckpoints,
399 PotSlotIterationsMustIncrease,
401 PotSlotIterationsUpdateAlreadyScheduled,
403 }
404
405 #[pallet::storage]
407 #[pallet::getter(fn block_slots)]
408 pub(super) type BlockSlots<T: Config> =
409 StorageValue<_, BoundedBTreeMap<BlockNumberFor<T>, Slot, T::BlockSlotCount>, ValueQuery>;
410
411 #[pallet::storage]
413 #[pallet::getter(fn solution_ranges)]
414 pub(super) type SolutionRanges<T: Config> = StorageValue<
415 _,
416 sp_consensus_subspace::SolutionRanges,
417 ValueQuery,
418 InitialSolutionRanges<T>,
419 >;
420
421 #[pallet::storage]
423 #[pallet::getter(fn should_adjust_solution_range)]
424 pub(super) type ShouldAdjustSolutionRange<T: Config> =
425 StorageValue<_, bool, ValueQuery, T::ShouldAdjustSolutionRange>;
426
427 #[pallet::storage]
429 pub(super) type NextSolutionRangeOverride<T> = StorageValue<_, SolutionRangeOverride>;
430
431 #[pallet::storage]
433 pub(super) type EraStartSlot<T> = StorageValue<_, Slot>;
434
435 #[pallet::storage]
437 #[pallet::getter(fn segment_commitment)]
438 pub(super) type SegmentCommitment<T> = CountedStorageMap<
439 _,
440 Twox64Concat,
441 SegmentIndex,
442 subspace_core_primitives::segments::SegmentCommitment,
443 >;
444
445 #[pallet::storage]
450 pub(super) type DidProcessSegmentHeaders<T: Config> = StorageValue<_, bool, ValueQuery>;
451
452 #[pallet::storage]
454 pub(super) type ParentVoteVerificationData<T> = StorageValue<_, VoteVerificationData>;
455
456 #[pallet::storage]
458 pub(super) type ParentBlockAuthorInfo<T> =
459 StorageValue<_, (PublicKey, SectorIndex, PieceOffset, ScalarBytes, Slot)>;
460
461 #[pallet::storage]
463 pub(super) type EnableRewards<T: Config> = StorageValue<_, BlockNumberFor<T>>;
464
465 #[pallet::storage]
467 pub(super) type EnableRewardsBelowSolutionRange<T: Config> = StorageValue<_, u64>;
468
469 #[pallet::storage]
471 pub(super) type CurrentBlockAuthorInfo<T: Config> = StorageValue<
472 _,
473 (
474 PublicKey,
475 SectorIndex,
476 PieceOffset,
477 ScalarBytes,
478 Slot,
479 Option<T::AccountId>,
480 ),
481 >;
482
483 #[pallet::storage]
485 pub(super) type ParentBlockVoters<T: Config> = StorageValue<
486 _,
487 BTreeMap<
488 (PublicKey, SectorIndex, PieceOffset, ScalarBytes, Slot),
489 (Option<T::AccountId>, RewardSignature),
490 >,
491 ValueQuery,
492 >;
493
494 #[pallet::storage]
496 pub(super) type CurrentBlockVoters<T: Config> = StorageValue<
497 _,
498 BTreeMap<
499 (PublicKey, SectorIndex, PieceOffset, ScalarBytes, Slot),
500 (Option<T::AccountId>, RewardSignature),
501 >,
502 >;
503
504 #[pallet::storage]
506 pub(super) type PotSlotIterations<T> = StorageValue<_, PotSlotIterationsValue>;
507
508 #[pallet::storage]
511 pub(super) type PotEntropy<T: Config> =
512 StorageValue<_, BTreeMap<BlockNumberFor<T>, PotEntropyValue>, ValueQuery>;
513
514 #[pallet::storage]
517 pub type BlockRandomness<T> = StorageValue<_, Randomness>;
518
519 #[pallet::storage]
521 pub(super) type AllowAuthoringByAnyone<T> = StorageValue<_, bool, ValueQuery>;
522
523 #[pallet::storage]
527 #[pallet::getter(fn root_plot_public_key)]
528 pub(super) type RootPlotPublicKey<T> = StorageValue<_, PublicKey>;
529
530 #[pallet::hooks]
531 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
532 fn on_initialize(block_number: BlockNumberFor<T>) -> Weight {
533 Self::do_initialize(block_number);
534 Weight::zero()
535 }
536
537 fn on_finalize(block_number: BlockNumberFor<T>) {
538 Self::do_finalize(block_number)
539 }
540 }
541
542 #[pallet::call]
543 impl<T: Config> Pallet<T> {
544 #[pallet::call_index(0)]
547 #[pallet::weight((< T as Config >::WeightInfo::store_segment_headers(segment_headers.len() as u32), DispatchClass::Mandatory))]
548 pub fn store_segment_headers(
549 origin: OriginFor<T>,
550 segment_headers: Vec<SegmentHeader>,
551 ) -> DispatchResult {
552 ensure_none(origin)?;
553 Self::do_store_segment_headers(segment_headers)
554 }
555
556 #[pallet::call_index(1)]
559 #[pallet::weight(< T as Config >::WeightInfo::enable_solution_range_adjustment())]
560 pub fn enable_solution_range_adjustment(
561 origin: OriginFor<T>,
562 solution_range_override: Option<u64>,
563 voting_solution_range_override: Option<u64>,
564 ) -> DispatchResult {
565 ensure_root(origin)?;
566
567 Self::do_enable_solution_range_adjustment(
568 solution_range_override,
569 voting_solution_range_override,
570 )?;
571
572 frame_system::Pallet::<T>::deposit_log(
573 DigestItem::enable_solution_range_adjustment_and_override(solution_range_override),
574 );
575
576 Ok(())
577 }
578
579 #[pallet::call_index(2)]
581 #[pallet::weight((< T as Config >::WeightInfo::vote(), DispatchClass::Operational))]
582 #[allow(clippy::boxed_local)]
585 pub fn vote(
586 origin: OriginFor<T>,
587 signed_vote: Box<SignedVote<BlockNumberFor<T>, T::Hash, T::AccountId>>,
588 ) -> DispatchResult {
589 T::SubspaceOrigin::ensure_origin(origin)?;
590
591 Self::do_vote(*signed_vote)
592 }
593
594 #[pallet::call_index(3)]
596 #[pallet::weight(< T as Config >::WeightInfo::enable_rewards_at())]
597 pub fn enable_rewards_at(
598 origin: OriginFor<T>,
599 enable_rewards_at: EnableRewardsAt<BlockNumberFor<T>>,
600 ) -> DispatchResult {
601 ensure_root(origin)?;
602
603 Self::do_enable_rewards_at(enable_rewards_at)
604 }
605
606 #[pallet::call_index(4)]
608 #[pallet::weight(< T as Config >::WeightInfo::enable_authoring_by_anyone())]
609 pub fn enable_authoring_by_anyone(origin: OriginFor<T>) -> DispatchResult {
610 ensure_root(origin)?;
611
612 AllowAuthoringByAnyone::<T>::put(true);
613 RootPlotPublicKey::<T>::take();
614 frame_system::Pallet::<T>::deposit_log(DigestItem::root_plot_public_key_update(None));
616
617 Ok(())
618 }
619
620 #[pallet::call_index(5)]
622 #[pallet::weight(< T as Config >::WeightInfo::set_pot_slot_iterations())]
623 pub fn set_pot_slot_iterations(
624 origin: OriginFor<T>,
625 slot_iterations: NonZeroU32,
626 ) -> DispatchResult {
627 ensure_root(origin)?;
628
629 if slot_iterations.get() % u32::from(PotCheckpoints::NUM_CHECKPOINTS.get() * 2) != 0 {
630 return Err(Error::<T>::NotMultipleOfCheckpoints.into());
631 }
632
633 let mut pot_slot_iterations =
634 PotSlotIterations::<T>::get().expect("Always initialized during genesis; qed");
635
636 if pot_slot_iterations.slot_iterations >= slot_iterations {
637 return Err(Error::<T>::PotSlotIterationsMustIncrease.into());
638 }
639
640 if let Some(pot_slot_iterations_update_value) = pot_slot_iterations.update
642 && pot_slot_iterations_update_value.target_slot.is_some()
643 {
644 return Err(Error::<T>::PotSlotIterationsUpdateAlreadyScheduled.into());
645 }
646
647 pot_slot_iterations.update.replace(PotSlotIterationsUpdate {
648 target_slot: None,
650 slot_iterations,
651 });
652
653 PotSlotIterations::<T>::put(pot_slot_iterations);
654
655 Ok(())
656 }
657 }
658
659 #[pallet::inherent]
660 impl<T: Config> ProvideInherent for Pallet<T> {
661 type Call = Call<T>;
662 type Error = InherentError;
663 const INHERENT_IDENTIFIER: InherentIdentifier = INHERENT_IDENTIFIER;
664
665 fn create_inherent(data: &InherentData) -> Option<Self::Call> {
666 let inherent_data = data
667 .get_data::<InherentType>(&INHERENT_IDENTIFIER)
668 .expect("Subspace inherent data not correctly encoded")
669 .expect("Subspace inherent data must be provided");
670
671 let segment_headers = inherent_data.segment_headers;
672 if segment_headers.is_empty() {
673 None
674 } else {
675 Some(Call::store_segment_headers { segment_headers })
676 }
677 }
678
679 fn is_inherent_required(data: &InherentData) -> Result<Option<Self::Error>, Self::Error> {
680 let inherent_data = data
681 .get_data::<InherentType>(&INHERENT_IDENTIFIER)
682 .expect("Subspace inherent data not correctly encoded")
683 .expect("Subspace inherent data must be provided");
684
685 Ok(if inherent_data.segment_headers.is_empty() {
686 None
687 } else {
688 Some(InherentError::MissingSegmentHeadersList)
689 })
690 }
691
692 fn check_inherent(call: &Self::Call, data: &InherentData) -> Result<(), Self::Error> {
693 if let Call::store_segment_headers { segment_headers } = call {
694 let inherent_data = data
695 .get_data::<InherentType>(&INHERENT_IDENTIFIER)
696 .expect("Subspace inherent data not correctly encoded")
697 .expect("Subspace inherent data must be provided");
698
699 if segment_headers != &inherent_data.segment_headers {
700 return Err(InherentError::IncorrectSegmentHeadersList {
701 expected: inherent_data.segment_headers,
702 actual: segment_headers.clone(),
703 });
704 }
705 }
706
707 Ok(())
708 }
709
710 fn is_inherent(call: &Self::Call) -> bool {
711 matches!(call, Call::store_segment_headers { .. })
712 }
713 }
714
715 #[pallet::validate_unsigned]
716 impl<T: Config> ValidateUnsigned for Pallet<T> {
717 type Call = Call<T>;
718 fn validate_unsigned(source: TransactionSource, call: &Self::Call) -> TransactionValidity {
719 match call {
720 Call::store_segment_headers { segment_headers } => {
721 Self::validate_segment_header(source, segment_headers)
722 }
723 _ => InvalidTransaction::Call.into(),
724 }
725 }
726
727 fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> {
728 match call {
729 Call::store_segment_headers { segment_headers } => {
730 Self::pre_dispatch_segment_header(segment_headers)
731 }
732 _ => Err(InvalidTransaction::Call.into()),
733 }
734 }
735 }
736}
737
738impl<T: Config> Pallet<T> {
739 pub fn history_size() -> HistorySize {
741 let number_of_segments = u64::from(SegmentCommitment::<T>::count()).max(1);
743 HistorySize::from(NonZeroU64::new(number_of_segments).expect("Not zero; qed"))
744 }
745
746 fn should_era_change(block_number: BlockNumberFor<T>) -> bool {
749 block_number % T::EraDuration::get() == Zero::zero()
750 }
751
752 fn enact_era_change() {
757 let slot_probability = T::SlotProbability::get();
758
759 let current_slot = Self::current_slot();
760
761 SolutionRanges::<T>::mutate(|solution_ranges| {
762 let next_solution_range;
763 let next_voting_solution_range;
764 if !ShouldAdjustSolutionRange::<T>::get() {
766 next_solution_range = solution_ranges.current;
767 next_voting_solution_range = solution_ranges.current;
768 } else if let Some(solution_range_override) = NextSolutionRangeOverride::<T>::take() {
769 next_solution_range = solution_range_override.solution_range;
770 next_voting_solution_range = solution_range_override.voting_solution_range;
771 } else {
772 next_solution_range = derive_next_solution_range(
773 u64::from(EraStartSlot::<T>::get().unwrap_or_default()),
775 u64::from(current_slot),
776 slot_probability,
777 solution_ranges.current,
778 T::EraDuration::get()
779 .try_into()
780 .unwrap_or_else(|_| panic!("Era duration is always within u64; qed")),
781 );
782
783 next_voting_solution_range = next_solution_range
784 .saturating_mul(u64::from(T::ExpectedVotesPerBlock::get()) + 1);
785 };
786 solution_ranges.next.replace(next_solution_range);
787 solution_ranges
788 .voting_next
789 .replace(next_voting_solution_range);
790
791 if let Some(solution_range_for_rewards) = EnableRewardsBelowSolutionRange::<T>::get() {
792 if next_solution_range <= solution_range_for_rewards {
793 EnableRewardsBelowSolutionRange::<T>::take();
794
795 let next_block_number =
796 frame_system::Pallet::<T>::current_block_number() + One::one();
797 EnableRewards::<T>::put(next_block_number);
798 }
799 }
800 });
801
802 EraStartSlot::<T>::put(current_slot);
803 }
804
805 fn do_initialize(block_number: BlockNumberFor<T>) {
806 let pre_digest = frame_system::Pallet::<T>::digest()
807 .logs
808 .iter()
809 .find_map(|s| s.as_subspace_pre_digest::<T::AccountId>())
810 .expect("Block must always have pre-digest");
811 let current_slot = pre_digest.slot();
812
813 BlockSlots::<T>::mutate(|block_slots| {
814 if let Some(to_remove) = block_number.checked_sub(&T::BlockSlotCount::get().into()) {
815 block_slots.remove(&to_remove);
816 }
817 block_slots
818 .try_insert(block_number, current_slot)
819 .expect("one entry just removed before inserting; qed");
820 });
821
822 {
823 CurrentBlockAuthorInfo::<T>::take();
825 let farmer_public_key = pre_digest.solution().public_key;
826
827 if !AllowAuthoringByAnyone::<T>::get() {
829 RootPlotPublicKey::<T>::mutate(|maybe_root_plot_public_key| {
830 if let Some(root_plot_public_key) = maybe_root_plot_public_key {
831 if root_plot_public_key != &farmer_public_key {
832 panic!("Client bug, authoring must be only done by the root user");
833 }
834 } else {
835 maybe_root_plot_public_key.replace(farmer_public_key);
836 frame_system::Pallet::<T>::deposit_log(
839 DigestItem::root_plot_public_key_update(Some(farmer_public_key)),
840 );
841 }
842 });
843 }
844
845 let key = (
846 farmer_public_key,
847 pre_digest.solution().sector_index,
848 pre_digest.solution().piece_offset,
849 pre_digest.solution().chunk,
850 current_slot,
851 );
852 if !ParentBlockVoters::<T>::get().contains_key(&key) {
853 let (public_key, sector_index, piece_offset, chunk, slot) = key;
854
855 CurrentBlockAuthorInfo::<T>::put((
856 public_key,
857 sector_index,
858 piece_offset,
859 chunk,
860 slot,
861 Some(pre_digest.solution().reward_address.clone()),
862 ));
863 }
864 }
865 CurrentBlockVoters::<T>::put(BTreeMap::<
866 (PublicKey, SectorIndex, PieceOffset, ScalarBytes, Slot),
867 (Option<T::AccountId>, RewardSignature),
868 >::default());
869
870 if let sp_consensus_subspace::SolutionRanges {
872 next: Some(next),
873 voting_next: Some(voting_next),
874 ..
875 } = SolutionRanges::<T>::get()
876 {
877 SolutionRanges::<T>::put(sp_consensus_subspace::SolutionRanges {
878 current: next,
879 next: None,
880 voting_current: voting_next,
881 voting_next: None,
882 });
883 }
884
885 let block_randomness = pre_digest
886 .pot_info()
887 .proof_of_time()
888 .derive_global_randomness();
889
890 BlockRandomness::<T>::put(block_randomness);
892
893 frame_system::Pallet::<T>::deposit_log(DigestItem::solution_range(
895 SolutionRanges::<T>::get().current,
896 ));
897
898 T::EraChangeTrigger::trigger::<T>(block_number);
900
901 {
902 let mut pot_slot_iterations =
903 PotSlotIterations::<T>::get().expect("Always initialized during genesis; qed");
904 frame_system::Pallet::<T>::deposit_log(DigestItem::pot_slot_iterations(
906 pot_slot_iterations.slot_iterations,
907 ));
908
909 if let Some(update) = pot_slot_iterations.update
912 && let Some(target_slot) = update.target_slot
913 && target_slot <= current_slot
914 {
915 debug!(
916 target: "runtime::subspace",
917 "Applying PoT slots update, changing to {} at block #{:?}",
918 update.slot_iterations,
919 block_number
920 );
921 pot_slot_iterations = PotSlotIterationsValue {
922 slot_iterations: update.slot_iterations,
923 update: None,
924 };
925 PotSlotIterations::<T>::put(pot_slot_iterations);
926 }
927 let pot_entropy_injection_interval = T::PotEntropyInjectionInterval::get();
928 let pot_entropy_injection_delay = T::PotEntropyInjectionDelay::get();
929
930 let mut entropy = PotEntropy::<T>::get();
931 let lookback_in_blocks = pot_entropy_injection_interval
932 * BlockNumberFor::<T>::from(T::PotEntropyInjectionLookbackDepth::get());
933 let last_entropy_injection_block =
934 block_number / pot_entropy_injection_interval * pot_entropy_injection_interval;
935 let maybe_entropy_source_block_number =
936 last_entropy_injection_block.checked_sub(&lookback_in_blocks);
937
938 if (block_number % pot_entropy_injection_interval).is_zero() {
939 let current_block_entropy = derive_pot_entropy(
940 &pre_digest.solution().chunk,
941 pre_digest.pot_info().proof_of_time(),
942 );
943 entropy.insert(
945 block_number,
946 PotEntropyValue {
947 target_slot: None,
948 entropy: current_block_entropy,
949 },
950 );
951
952 if let Some(entropy_source_block_number) = maybe_entropy_source_block_number {
954 if let Some(entropy_value) = entropy.get_mut(&entropy_source_block_number) {
955 let target_slot = pre_digest
956 .slot()
957 .saturating_add(pot_entropy_injection_delay);
958 debug!(
959 target: "runtime::subspace",
960 "Pot entropy injection will happen at slot {target_slot:?}",
961 );
962 entropy_value.target_slot.replace(target_slot);
963
964 if let Some(update) = &mut pot_slot_iterations.update
966 && update.target_slot.is_none()
967 {
968 debug!(
969 target: "runtime::subspace",
970 "Scheduling PoT slots update to happen at slot {target_slot:?}"
971 );
972 update.target_slot.replace(target_slot);
973 PotSlotIterations::<T>::put(pot_slot_iterations);
974 }
975 }
976 }
977
978 PotEntropy::<T>::put(entropy.clone());
979 }
980
981 if let Some(entropy_source_block_number) = maybe_entropy_source_block_number {
984 let maybe_entropy_value = entropy.get(&entropy_source_block_number).copied();
985 if let Some(PotEntropyValue {
986 target_slot,
987 entropy,
988 }) = maybe_entropy_value
989 {
990 let target_slot = target_slot
991 .expect("Target slot is guaranteed to be present due to logic above; qed");
992 let slot_iterations = if let Some(update) = pot_slot_iterations.update
994 && let Some(update_target_slot) = update.target_slot
995 && update_target_slot == target_slot
996 {
997 debug!(
998 target: "runtime::subspace",
999 "Applying PoT slots update to the next PoT parameters change"
1000 );
1001 update.slot_iterations
1002 } else {
1003 pot_slot_iterations.slot_iterations
1004 };
1005
1006 frame_system::Pallet::<T>::deposit_log(DigestItem::pot_parameters_change(
1007 PotParametersChange {
1008 slot: target_slot,
1009 slot_iterations,
1010 entropy,
1011 },
1012 ));
1013 }
1014 }
1015
1016 if let Some(entry) = entropy.first_entry() {
1018 if let Some(target_slot) = entry.get().target_slot
1019 && target_slot < current_slot
1020 {
1021 entry.remove();
1022 PotEntropy::<T>::put(entropy);
1023 }
1024 }
1025 }
1026 }
1027
1028 fn do_finalize(_block_number: BlockNumberFor<T>) {
1029 if let Some(next_solution_range) = SolutionRanges::<T>::get().next {
1032 frame_system::Pallet::<T>::deposit_log(DigestItem::next_solution_range(
1034 next_solution_range,
1035 ));
1036 }
1037
1038 if let Some((public_key, sector_index, piece_offset, scalar, slot, _reward_address)) =
1039 CurrentBlockAuthorInfo::<T>::get()
1040 {
1041 ParentBlockAuthorInfo::<T>::put((public_key, sector_index, piece_offset, scalar, slot));
1042 } else {
1043 ParentBlockAuthorInfo::<T>::take();
1044 }
1045
1046 ParentVoteVerificationData::<T>::put(current_vote_verification_data::<T>(true));
1047
1048 ParentBlockVoters::<T>::put(CurrentBlockVoters::<T>::get().unwrap_or_default());
1049
1050 DidProcessSegmentHeaders::<T>::take();
1051 }
1052
1053 fn do_store_segment_headers(segment_headers: Vec<SegmentHeader>) -> DispatchResult {
1054 assert!(
1055 !DidProcessSegmentHeaders::<T>::exists(),
1056 "Segment headers must be updated only once in the block"
1057 );
1058
1059 for segment_header in segment_headers {
1060 SegmentCommitment::<T>::insert(
1061 segment_header.segment_index(),
1062 segment_header.segment_commitment(),
1063 );
1064 frame_system::Pallet::<T>::deposit_log(DigestItem::segment_commitment(
1066 segment_header.segment_index(),
1067 segment_header.segment_commitment(),
1068 ));
1069 Self::deposit_event(Event::SegmentHeaderStored { segment_header });
1070 }
1071
1072 DidProcessSegmentHeaders::<T>::put(true);
1073 Ok(())
1074 }
1075
1076 fn do_enable_solution_range_adjustment(
1077 solution_range_override: Option<u64>,
1078 voting_solution_range_override: Option<u64>,
1079 ) -> DispatchResult {
1080 if ShouldAdjustSolutionRange::<T>::get() {
1081 return Err(Error::<T>::SolutionRangeAdjustmentAlreadyEnabled.into());
1082 }
1083
1084 ShouldAdjustSolutionRange::<T>::put(true);
1085
1086 if let Some(solution_range) = solution_range_override {
1087 let voting_solution_range = voting_solution_range_override.unwrap_or_else(|| {
1088 solution_range.saturating_mul(u64::from(T::ExpectedVotesPerBlock::get()) + 1)
1089 });
1090 SolutionRanges::<T>::mutate(|solution_ranges| {
1091 if solution_ranges.next.is_some() {
1093 solution_ranges.next.replace(solution_range);
1094 solution_ranges.voting_next.replace(voting_solution_range);
1095 } else {
1096 solution_ranges.current = solution_range;
1097 solution_ranges.voting_current = voting_solution_range;
1098
1099 NextSolutionRangeOverride::<T>::put(SolutionRangeOverride {
1102 solution_range,
1103 voting_solution_range,
1104 });
1105 frame_system::Pallet::<T>::deposit_log(DigestItem::next_solution_range(
1106 solution_range,
1107 ));
1108 }
1109 });
1110 }
1111
1112 Ok(())
1113 }
1114
1115 fn do_vote(
1116 signed_vote: SignedVote<BlockNumberFor<T>, T::Hash, T::AccountId>,
1117 ) -> DispatchResult {
1118 let Vote::V0 {
1119 height,
1120 parent_hash,
1121 solution,
1122 ..
1123 } = signed_vote.vote;
1124
1125 Self::deposit_event(Event::FarmerVote {
1126 public_key: solution.public_key,
1127 reward_address: solution.reward_address,
1128 height,
1129 parent_hash,
1130 });
1131
1132 Ok(())
1133 }
1134
1135 fn do_enable_rewards_at(
1136 enable_rewards_at: EnableRewardsAt<BlockNumberFor<T>>,
1137 ) -> DispatchResult {
1138 match enable_rewards_at {
1139 EnableRewardsAt::Height(block_number) => {
1140 let next_block_number =
1143 frame_system::Pallet::<T>::current_block_number() + One::one();
1144 EnableRewards::<T>::put(block_number.max(next_block_number));
1145 }
1146 EnableRewardsAt::SolutionRange(solution_range) => {
1147 EnableRewardsBelowSolutionRange::<T>::put(solution_range);
1148 }
1149 EnableRewardsAt::Manually => {
1150 }
1152 }
1153
1154 Ok(())
1155 }
1156
1157 pub fn pot_parameters() -> PotParameters {
1159 let block_number = frame_system::Pallet::<T>::block_number();
1160 let pot_slot_iterations =
1161 PotSlotIterations::<T>::get().expect("Always initialized during genesis; qed");
1162 let pot_entropy_injection_interval = T::PotEntropyInjectionInterval::get();
1163
1164 let entropy = PotEntropy::<T>::get();
1165 let lookback_in_blocks = pot_entropy_injection_interval
1166 * BlockNumberFor::<T>::from(T::PotEntropyInjectionLookbackDepth::get());
1167 let last_entropy_injection_block =
1168 block_number / pot_entropy_injection_interval * pot_entropy_injection_interval;
1169 let maybe_entropy_source_block_number =
1170 last_entropy_injection_block.checked_sub(&lookback_in_blocks);
1171
1172 let mut next_change = None;
1173
1174 if let Some(entropy_source_block_number) = maybe_entropy_source_block_number {
1175 let maybe_entropy_value = entropy.get(&entropy_source_block_number).copied();
1176 if let Some(PotEntropyValue {
1177 target_slot,
1178 entropy,
1179 }) = maybe_entropy_value
1180 {
1181 let target_slot = target_slot.expect(
1182 "Always present due to identical check present in block initialization; qed",
1183 );
1184 let slot_iterations = if let Some(update) = pot_slot_iterations.update
1186 && let Some(update_target_slot) = update.target_slot
1187 && update_target_slot == target_slot
1188 {
1189 update.slot_iterations
1190 } else {
1191 pot_slot_iterations.slot_iterations
1192 };
1193
1194 next_change.replace(PotParametersChange {
1195 slot: target_slot,
1196 slot_iterations,
1197 entropy,
1198 });
1199 }
1200 }
1201
1202 PotParameters::V0 {
1203 slot_iterations: pot_slot_iterations.slot_iterations,
1204 next_change,
1205 }
1206 }
1207
1208 pub fn current_slot() -> Slot {
1210 BlockSlots::<T>::get()
1211 .last_key_value()
1212 .map(|(_block, slot)| *slot)
1213 .unwrap_or_default()
1214 }
1215
1216 pub fn archived_history_size() -> u64 {
1218 let archived_segments = SegmentCommitment::<T>::count();
1219
1220 u64::from(archived_segments) * ArchivedHistorySegment::SIZE as u64
1221 }
1222}
1223
1224impl<T> Pallet<T>
1225where
1226 T: Config + CreateUnsigned<Call<T>>,
1227{
1228 pub fn submit_vote(signed_vote: SignedVote<BlockNumberFor<T>, T::Hash, T::AccountId>) {
1231 let call = Call::vote {
1232 signed_vote: Box::new(signed_vote),
1233 };
1234
1235 let ext = T::create_unsigned(call.into());
1236 match SubmitTransaction::<T, Call<T>>::submit_transaction(ext) {
1237 Ok(()) => {
1238 debug!(target: "runtime::subspace", "Submitted Subspace vote");
1239 }
1240 Err(()) => {
1241 error!(target: "runtime::subspace", "Error submitting Subspace vote");
1242 }
1243 }
1244 }
1245}
1246
1247impl<T: Config> Pallet<T> {
1252 fn validate_segment_header(
1253 source: TransactionSource,
1254 segment_headers: &[SegmentHeader],
1255 ) -> TransactionValidity {
1256 if !matches!(
1258 source,
1259 TransactionSource::Local | TransactionSource::InBlock,
1260 ) {
1261 warn!(
1262 target: "runtime::subspace",
1263 "Rejecting segment header extrinsic because it is not local/in-block.",
1264 );
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!(
1410 target: "runtime::subspace",
1411 "Votes are not expected at height below 2"
1412 );
1413
1414 return Err(CheckVoteError::UnexpectedBeforeHeightTwo);
1415 }
1416
1417 if !(height == current_block_number || height == current_block_number - One::one()) {
1421 debug!(
1422 target: "runtime::subspace",
1423 "Vote verification error: bad height {height:?}, current block number is \
1424 {current_block_number:?}"
1425 );
1426 return Err(if height > current_block_number {
1427 CheckVoteError::HeightInTheFuture
1428 } else {
1429 CheckVoteError::HeightInThePast
1430 });
1431 }
1432
1433 if *parent_hash != frame_system::Pallet::<T>::block_hash(height - One::one()) {
1437 debug!(
1438 target: "runtime::subspace",
1439 "Vote verification error: parent hash {parent_hash:?}",
1440 );
1441 return Err(CheckVoteError::IncorrectParentHash);
1442 }
1443
1444 let current_vote_verification_data = current_vote_verification_data::<T>(pre_dispatch);
1445 let parent_vote_verification_data = ParentVoteVerificationData::<T>::get()
1446 .expect("Above check for block number ensures that this value is always present; qed");
1447
1448 if pre_dispatch {
1449 let current_slot = current_vote_verification_data.current_slot;
1452 if slot > current_slot || (slot == current_slot && height != current_block_number) {
1453 debug!(
1454 target: "runtime::subspace",
1455 "Vote slot {slot:?} must be before current slot {current_slot:?}",
1456 );
1457 return Err(CheckVoteError::SlotInTheFuture);
1458 }
1459 }
1460
1461 let parent_slot = if pre_dispatch {
1462 if height == current_block_number {
1467 parent_vote_verification_data.current_slot
1468 } else {
1469 parent_vote_verification_data.parent_slot
1470 }
1471 } else {
1472 if height == current_block_number {
1477 current_vote_verification_data.current_slot
1478 } else {
1479 current_vote_verification_data.parent_slot
1480 }
1481 };
1482
1483 if slot <= parent_slot {
1484 debug!(
1485 target: "runtime::subspace",
1486 "Vote slot {slot:?} must be after parent slot {parent_slot:?}",
1487 );
1488 return Err(CheckVoteError::SlotInThePast);
1489 }
1490
1491 if let Err(error) = check_reward_signature(
1492 signed_vote.vote.hash().as_bytes(),
1493 &signed_vote.signature,
1494 &solution.public_key,
1495 &schnorrkel::signing_context(REWARD_SIGNING_CONTEXT),
1496 ) {
1497 debug!(
1498 target: "runtime::subspace",
1499 "Vote verification error: {error:?}"
1500 );
1501 return Err(CheckVoteError::BadRewardSignature(error));
1502 }
1503
1504 let vote_verification_data = if height == current_block_number {
1505 current_vote_verification_data
1506 } else {
1507 parent_vote_verification_data
1508 };
1509
1510 let sector_id = SectorId::new(
1511 solution.public_key.hash(),
1512 solution.sector_index,
1513 solution.history_size,
1514 );
1515
1516 let recent_segments = T::RecentSegments::get();
1517 let recent_history_fraction = (
1518 T::RecentHistoryFraction::get().0,
1519 T::RecentHistoryFraction::get().1,
1520 );
1521 let segment_index = sector_id
1522 .derive_piece_index(
1523 solution.piece_offset,
1524 solution.history_size,
1525 T::MaxPiecesInSector::get(),
1526 recent_segments,
1527 recent_history_fraction,
1528 )
1529 .segment_index();
1530
1531 let segment_commitment =
1532 if let Some(segment_commitment) = Pallet::<T>::segment_commitment(segment_index) {
1533 segment_commitment
1534 } else {
1535 debug!(
1536 target: "runtime::subspace",
1537 "Vote verification error: no segment commitment for segment index {segment_index}"
1538 );
1539 return Err(CheckVoteError::UnknownSegmentCommitment);
1540 };
1541
1542 let sector_expiration_check_segment_commitment = Pallet::<T>::segment_commitment(
1543 solution
1544 .history_size
1545 .sector_expiration_check(T::MinSectorLifetime::get())
1546 .ok_or(CheckVoteError::InvalidHistorySize)?
1547 .segment_index(),
1548 );
1549
1550 match verify_solution(
1551 solution.into(),
1552 slot.into(),
1553 (&VerifySolutionParams {
1554 proof_of_time: *proof_of_time,
1555 solution_range: vote_verification_data.vote_solution_range,
1556 piece_check_params: Some(PieceCheckParams {
1557 max_pieces_in_sector: T::MaxPiecesInSector::get(),
1558 segment_commitment,
1559 recent_segments,
1560 recent_history_fraction,
1561 min_sector_lifetime: T::MinSectorLifetime::get(),
1562 current_history_size: Pallet::<T>::history_size(),
1563 sector_expiration_check_segment_commitment,
1564 }),
1565 })
1566 .into(),
1567 ) {
1568 Ok(solution_distance) => {
1569 if solution_distance <= vote_verification_data.solution_range / 2 {
1570 debug!(
1571 target: "runtime::subspace",
1572 "Vote quality is too high"
1573 );
1574 return Err(CheckVoteError::QualityTooHigh);
1575 }
1576 }
1577 Err(error) => {
1578 debug!(
1579 target: "runtime::subspace",
1580 "Vote verification error: {error:?}"
1581 );
1582 return Err(CheckVoteError::InvalidSolution(error));
1583 }
1584 }
1585
1586 if !is_proof_of_time_valid(
1589 BlockHash::try_from(parent_hash.as_ref())
1590 .expect("Must be able to convert to block hash type"),
1591 SlotNumber::from(slot),
1592 WrappedPotOutput::from(*proof_of_time),
1593 !pre_dispatch,
1595 ) {
1596 debug!(target: "runtime::subspace", "Invalid proof of time");
1597
1598 return Err(CheckVoteError::InvalidProofOfTime);
1599 }
1600
1601 if pre_dispatch
1604 && !is_proof_of_time_valid(
1605 BlockHash::try_from(parent_hash.as_ref())
1606 .expect("Must be able to convert to block hash type"),
1607 SlotNumber::from(slot + T::BlockAuthoringDelay::get()),
1608 WrappedPotOutput::from(*future_proof_of_time),
1609 false,
1610 )
1611 {
1612 debug!(target: "runtime::subspace", "Invalid future proof of time");
1613
1614 return Err(CheckVoteError::InvalidFutureProofOfTime);
1615 }
1616
1617 let key = (
1618 solution.public_key,
1619 solution.sector_index,
1620 solution.piece_offset,
1621 solution.chunk,
1622 slot,
1623 );
1624 let mut is_equivocating = ParentBlockAuthorInfo::<T>::get().as_ref() == Some(&key)
1630 || CurrentBlockAuthorInfo::<T>::get()
1631 .map(
1632 |(public_key, sector_index, piece_offset, chunk, slot, _reward_address)| {
1633 (public_key, sector_index, piece_offset, chunk, slot)
1634 },
1635 )
1636 .as_ref()
1637 == Some(&key);
1638
1639 if !is_equivocating {
1640 if let Some((_reward_address, signature)) = ParentBlockVoters::<T>::get().get(&key) {
1641 if signature != &signed_vote.signature {
1642 is_equivocating = true;
1643 } else {
1644 return Err(CheckVoteError::DuplicateVote);
1646 }
1647 }
1648 }
1649
1650 if !is_equivocating {
1651 if let Some((_reward_address, signature)) =
1652 CurrentBlockVoters::<T>::get().unwrap_or_default().get(&key)
1653 {
1654 if signature != &signed_vote.signature {
1655 is_equivocating = true;
1656 } else {
1657 return Err(CheckVoteError::DuplicateVote);
1659 }
1660 }
1661 }
1662
1663 if pre_dispatch {
1664 CurrentBlockVoters::<T>::mutate(|current_reward_receivers| {
1666 current_reward_receivers
1667 .as_mut()
1668 .expect("Always set during block initialization")
1669 .insert(
1670 key,
1671 (
1672 if is_equivocating {
1673 None
1674 } else {
1675 Some(solution.reward_address.clone())
1676 },
1677 signed_vote.signature,
1678 ),
1679 );
1680 });
1681 }
1682
1683 if is_equivocating {
1684 let offender = solution.public_key;
1685
1686 CurrentBlockAuthorInfo::<T>::mutate(|maybe_info| {
1687 if let Some((public_key, _sector_index, _piece_offset, _chunk, _slot, reward_address)) =
1688 maybe_info
1689 {
1690 if public_key == &offender {
1691 reward_address.take();
1693 }
1694 }
1695 });
1696
1697 CurrentBlockVoters::<T>::mutate(|current_reward_receivers| {
1698 if let Some(current_reward_receivers) = current_reward_receivers {
1699 for (
1700 (public_key, _sector_index, _piece_offset, _chunk, _slot),
1701 (reward_address, _signature),
1702 ) in current_reward_receivers.iter_mut()
1703 {
1704 if public_key == &offender {
1705 reward_address.take();
1707 }
1708 }
1709 }
1710 });
1711
1712 return Err(CheckVoteError::Equivocated { slot, offender });
1713 }
1714
1715 Ok(())
1716}
1717
1718fn check_segment_headers<T: Config>(
1719 segment_headers: &[SegmentHeader],
1720) -> Result<(), TransactionValidityError> {
1721 let mut segment_headers_iter = segment_headers.iter();
1722
1723 let first_segment_header = match segment_headers_iter.next() {
1725 Some(first_segment_header) => first_segment_header,
1726 None => {
1727 return Err(InvalidTransaction::BadMandatory.into());
1728 }
1729 };
1730
1731 if first_segment_header.segment_index() > SegmentIndex::ZERO
1733 && !SegmentCommitment::<T>::contains_key(
1734 first_segment_header.segment_index() - SegmentIndex::ONE,
1735 )
1736 {
1737 return Err(InvalidTransaction::BadMandatory.into());
1738 }
1739
1740 if SegmentCommitment::<T>::contains_key(first_segment_header.segment_index()) {
1742 return Err(InvalidTransaction::BadMandatory.into());
1743 }
1744
1745 let mut last_segment_index = first_segment_header.segment_index();
1746
1747 for segment_header in segment_headers_iter {
1748 let segment_index = segment_header.segment_index();
1749
1750 if segment_index != last_segment_index + SegmentIndex::ONE {
1752 return Err(InvalidTransaction::BadMandatory.into());
1753 }
1754
1755 if SegmentCommitment::<T>::contains_key(segment_index) {
1757 return Err(InvalidTransaction::BadMandatory.into());
1758 }
1759
1760 last_segment_index = segment_index;
1761 }
1762
1763 Ok(())
1764}
1765
1766impl<T: Config> subspace_runtime_primitives::RewardsEnabled for Pallet<T> {
1767 fn rewards_enabled() -> bool {
1768 if let Some(height) = EnableRewards::<T>::get() {
1769 frame_system::Pallet::<T>::current_block_number() >= height
1770 } else {
1771 false
1772 }
1773 }
1774}
1775
1776impl<T: Config> subspace_runtime_primitives::FindBlockRewardAddress<T::AccountId> for Pallet<T> {
1777 fn find_block_reward_address() -> Option<T::AccountId> {
1778 CurrentBlockAuthorInfo::<T>::get().and_then(
1779 |(_public_key, _sector_index, _piece_offset, _chunk, _slot, reward_address)| {
1780 if let Some(height) = EnableRewards::<T>::get() {
1782 if frame_system::Pallet::<T>::current_block_number() >= height {
1783 return reward_address;
1784 }
1785 }
1786
1787 None
1788 },
1789 )
1790 }
1791}
1792
1793impl<T: Config> subspace_runtime_primitives::FindVotingRewardAddresses<T::AccountId> for Pallet<T> {
1794 fn find_voting_reward_addresses() -> Vec<T::AccountId> {
1795 if let Some(height) = EnableRewards::<T>::get() {
1797 if frame_system::Pallet::<T>::current_block_number() >= height {
1798 return CurrentBlockVoters::<T>::get()
1801 .unwrap_or_else(ParentBlockVoters::<T>::get)
1802 .into_values()
1803 .filter_map(|(reward_address, _signature)| reward_address)
1804 .collect();
1805 }
1806 }
1807
1808 Vec::new()
1809 }
1810}
1811
1812impl<T: Config> frame_support::traits::Randomness<T::Hash, BlockNumberFor<T>> for Pallet<T> {
1813 fn random(subject: &[u8]) -> (T::Hash, BlockNumberFor<T>) {
1814 let mut subject = subject.to_vec();
1815 subject.extend_from_slice(
1816 BlockRandomness::<T>::get()
1817 .expect("Block randomness is always set in block initialization; qed")
1818 .as_ref(),
1819 );
1820
1821 (
1822 T::Hashing::hash(&subject),
1823 frame_system::Pallet::<T>::current_block_number(),
1824 )
1825 }
1826
1827 fn random_seed() -> (T::Hash, BlockNumberFor<T>) {
1828 (
1829 T::Hashing::hash(
1830 BlockRandomness::<T>::get()
1831 .expect("Block randomness is always set in block initialization; qed")
1832 .as_ref(),
1833 ),
1834 frame_system::Pallet::<T>::current_block_number(),
1835 )
1836 }
1837}