1#![cfg_attr(not(feature = "std"), no_std)]
19
20extern crate alloc;
21
22mod default_weights;
23mod equivocation;
24#[cfg(test)]
25mod mock;
26#[cfg(test)]
27mod tests;
28
29use alloc::{boxed::Box, vec::Vec};
30use codec::{Encode, MaxEncodedLen};
31use log;
32
33use frame_support::{
34 dispatch::{DispatchResultWithPostInfo, Pays},
35 pallet_prelude::*,
36 traits::{Get, OneSessionHandler},
37 weights::{constants::RocksDbWeight as DbWeight, Weight},
38 BoundedSlice, BoundedVec, Parameter,
39};
40use frame_system::{
41 ensure_none, ensure_signed,
42 pallet_prelude::{BlockNumberFor, HeaderFor, OriginFor},
43};
44use sp_consensus_beefy::{
45 AncestryHelper, AncestryHelperWeightInfo, AuthorityIndex, BeefyAuthorityId, ConsensusLog,
46 DoubleVotingProof, ForkVotingProof, FutureBlockVotingProof, OnNewValidatorSet, ValidatorSet,
47 BEEFY_ENGINE_ID, GENESIS_AUTHORITY_SET_ID,
48};
49use sp_runtime::{
50 generic::DigestItem,
51 traits::{IsMember, Member, One},
52 RuntimeAppPublic,
53};
54use sp_session::{GetSessionNumber, GetValidatorCount};
55use sp_staking::{offence::OffenceReportSystem, SessionIndex};
56
57use crate::equivocation::EquivocationEvidenceFor;
58pub use crate::equivocation::{EquivocationOffence, EquivocationReportSystem, TimeSlot};
59pub use pallet::*;
60
61const LOG_TARGET: &str = "runtime::beefy";
62
63#[frame_support::pallet]
64pub mod pallet {
65 use super::*;
66 use frame_system::{ensure_root, pallet_prelude::BlockNumberFor};
67
68 #[pallet::config]
69 pub trait Config: frame_system::Config {
70 type BeefyId: Member
72 + Parameter
73 + BeefyAuthorityId<sp_runtime::traits::Keccak256>
75 + MaybeSerializeDeserialize
76 + MaxEncodedLen;
77
78 #[pallet::constant]
80 type MaxAuthorities: Get<u32>;
81
82 #[pallet::constant]
84 type MaxNominators: Get<u32>;
85
86 #[pallet::constant]
93 type MaxSetIdSessionEntries: Get<u64>;
94
95 type OnNewValidatorSet: OnNewValidatorSet<<Self as Config>::BeefyId>;
101
102 type AncestryHelper: AncestryHelper<HeaderFor<Self>>
104 + AncestryHelperWeightInfo<HeaderFor<Self>>;
105
106 type WeightInfo: WeightInfo;
108
109 type KeyOwnerProof: Parameter + GetSessionNumber + GetValidatorCount;
113
114 type EquivocationReportSystem: OffenceReportSystem<
118 Option<Self::AccountId>,
119 EquivocationEvidenceFor<Self>,
120 >;
121 }
122
123 #[pallet::pallet]
124 pub struct Pallet<T>(_);
125
126 #[pallet::storage]
128 pub type Authorities<T: Config> =
129 StorageValue<_, BoundedVec<T::BeefyId, T::MaxAuthorities>, ValueQuery>;
130
131 #[pallet::storage]
133 pub type ValidatorSetId<T: Config> =
134 StorageValue<_, sp_consensus_beefy::ValidatorSetId, ValueQuery>;
135
136 #[pallet::storage]
138 pub type NextAuthorities<T: Config> =
139 StorageValue<_, BoundedVec<T::BeefyId, T::MaxAuthorities>, ValueQuery>;
140
141 #[pallet::storage]
152 pub type SetIdSession<T: Config> =
153 StorageMap<_, Twox64Concat, sp_consensus_beefy::ValidatorSetId, SessionIndex>;
154
155 #[pallet::storage]
159 pub type GenesisBlock<T: Config> = StorageValue<_, Option<BlockNumberFor<T>>, ValueQuery>;
160
161 #[pallet::genesis_config]
162 pub struct GenesisConfig<T: Config> {
163 pub authorities: Vec<T::BeefyId>,
165 pub genesis_block: Option<BlockNumberFor<T>>,
170 }
171
172 impl<T: Config> Default for GenesisConfig<T> {
173 fn default() -> Self {
174 let genesis_block = Some(One::one());
177 Self { authorities: Vec::new(), genesis_block }
178 }
179 }
180
181 #[pallet::genesis_build]
182 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
183 fn build(&self) {
184 Pallet::<T>::initialize(&self.authorities)
185 .expect("Authorities vec too big");
188 GenesisBlock::<T>::put(&self.genesis_block);
189 }
190 }
191
192 #[pallet::error]
193 pub enum Error<T> {
194 InvalidKeyOwnershipProof,
196 InvalidDoubleVotingProof,
198 InvalidForkVotingProof,
200 InvalidFutureBlockVotingProof,
202 InvalidEquivocationProofSession,
204 InvalidEquivocationProofSessionMember,
206 DuplicateOffenceReport,
208 InvalidConfiguration,
210 }
211
212 #[pallet::call]
213 impl<T: Config> Pallet<T> {
214 #[pallet::call_index(0)]
219 #[pallet::weight(T::WeightInfo::report_double_voting(
220 key_owner_proof.validator_count(),
221 T::MaxNominators::get(),
222 ))]
223 pub fn report_double_voting(
224 origin: OriginFor<T>,
225 equivocation_proof: Box<
226 DoubleVotingProof<
227 BlockNumberFor<T>,
228 T::BeefyId,
229 <T::BeefyId as RuntimeAppPublic>::Signature,
230 >,
231 >,
232 key_owner_proof: T::KeyOwnerProof,
233 ) -> DispatchResultWithPostInfo {
234 let reporter = ensure_signed(origin)?;
235
236 T::EquivocationReportSystem::process_evidence(
237 Some(reporter),
238 EquivocationEvidenceFor::DoubleVotingProof(*equivocation_proof, key_owner_proof),
239 )?;
240 Ok(Pays::No.into())
242 }
243
244 #[pallet::call_index(1)]
254 #[pallet::weight(T::WeightInfo::report_double_voting(
255 key_owner_proof.validator_count(),
256 T::MaxNominators::get(),
257 ))]
258 pub fn report_double_voting_unsigned(
259 origin: OriginFor<T>,
260 equivocation_proof: Box<
261 DoubleVotingProof<
262 BlockNumberFor<T>,
263 T::BeefyId,
264 <T::BeefyId as RuntimeAppPublic>::Signature,
265 >,
266 >,
267 key_owner_proof: T::KeyOwnerProof,
268 ) -> DispatchResultWithPostInfo {
269 ensure_none(origin)?;
270
271 T::EquivocationReportSystem::process_evidence(
272 None,
273 EquivocationEvidenceFor::DoubleVotingProof(*equivocation_proof, key_owner_proof),
274 )?;
275 Ok(Pays::No.into())
276 }
277
278 #[pallet::call_index(2)]
283 #[pallet::weight(<T as Config>::WeightInfo::set_new_genesis())]
284 pub fn set_new_genesis(
285 origin: OriginFor<T>,
286 delay_in_blocks: BlockNumberFor<T>,
287 ) -> DispatchResult {
288 ensure_root(origin)?;
289 ensure!(delay_in_blocks >= One::one(), Error::<T>::InvalidConfiguration);
290 let genesis_block = frame_system::Pallet::<T>::block_number() + delay_in_blocks;
291 GenesisBlock::<T>::put(Some(genesis_block));
292 Ok(())
293 }
294
295 #[pallet::call_index(3)]
299 #[pallet::weight(T::WeightInfo::report_fork_voting::<T>(
300 key_owner_proof.validator_count(),
301 T::MaxNominators::get(),
302 &equivocation_proof.ancestry_proof
303 ))]
304 pub fn report_fork_voting(
305 origin: OriginFor<T>,
306 equivocation_proof: Box<
307 ForkVotingProof<
308 HeaderFor<T>,
309 T::BeefyId,
310 <T::AncestryHelper as AncestryHelper<HeaderFor<T>>>::Proof,
311 >,
312 >,
313 key_owner_proof: T::KeyOwnerProof,
314 ) -> DispatchResultWithPostInfo {
315 let reporter = ensure_signed(origin)?;
316
317 T::EquivocationReportSystem::process_evidence(
318 Some(reporter),
319 EquivocationEvidenceFor::ForkVotingProof(*equivocation_proof, key_owner_proof),
320 )?;
321 Ok(Pays::No.into())
323 }
324
325 #[pallet::call_index(4)]
334 #[pallet::weight(T::WeightInfo::report_fork_voting::<T>(
335 key_owner_proof.validator_count(),
336 T::MaxNominators::get(),
337 &equivocation_proof.ancestry_proof
338 ))]
339 pub fn report_fork_voting_unsigned(
340 origin: OriginFor<T>,
341 equivocation_proof: Box<
342 ForkVotingProof<
343 HeaderFor<T>,
344 T::BeefyId,
345 <T::AncestryHelper as AncestryHelper<HeaderFor<T>>>::Proof,
346 >,
347 >,
348 key_owner_proof: T::KeyOwnerProof,
349 ) -> DispatchResultWithPostInfo {
350 ensure_none(origin)?;
351
352 T::EquivocationReportSystem::process_evidence(
353 None,
354 EquivocationEvidenceFor::ForkVotingProof(*equivocation_proof, key_owner_proof),
355 )?;
356 Ok(Pays::No.into())
358 }
359
360 #[pallet::call_index(5)]
364 #[pallet::weight(T::WeightInfo::report_future_block_voting(
365 key_owner_proof.validator_count(),
366 T::MaxNominators::get(),
367 ))]
368 pub fn report_future_block_voting(
369 origin: OriginFor<T>,
370 equivocation_proof: Box<FutureBlockVotingProof<BlockNumberFor<T>, T::BeefyId>>,
371 key_owner_proof: T::KeyOwnerProof,
372 ) -> DispatchResultWithPostInfo {
373 let reporter = ensure_signed(origin)?;
374
375 T::EquivocationReportSystem::process_evidence(
376 Some(reporter),
377 EquivocationEvidenceFor::FutureBlockVotingProof(
378 *equivocation_proof,
379 key_owner_proof,
380 ),
381 )?;
382 Ok(Pays::No.into())
384 }
385
386 #[pallet::call_index(6)]
395 #[pallet::weight(T::WeightInfo::report_future_block_voting(
396 key_owner_proof.validator_count(),
397 T::MaxNominators::get(),
398 ))]
399 pub fn report_future_block_voting_unsigned(
400 origin: OriginFor<T>,
401 equivocation_proof: Box<FutureBlockVotingProof<BlockNumberFor<T>, T::BeefyId>>,
402 key_owner_proof: T::KeyOwnerProof,
403 ) -> DispatchResultWithPostInfo {
404 ensure_none(origin)?;
405
406 T::EquivocationReportSystem::process_evidence(
407 None,
408 EquivocationEvidenceFor::FutureBlockVotingProof(
409 *equivocation_proof,
410 key_owner_proof,
411 ),
412 )?;
413 Ok(Pays::No.into())
415 }
416 }
417
418 #[pallet::hooks]
419 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
420 #[cfg(feature = "try-runtime")]
421 fn try_state(_n: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
422 Self::do_try_state()
423 }
424 }
425
426 #[pallet::validate_unsigned]
427 impl<T: Config> ValidateUnsigned for Pallet<T> {
428 type Call = Call<T>;
429
430 fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> {
431 Self::pre_dispatch(call)
432 }
433
434 fn validate_unsigned(source: TransactionSource, call: &Self::Call) -> TransactionValidity {
435 Self::validate_unsigned(source, call)
436 }
437 }
438
439 impl<T: Config> Call<T> {
440 pub fn to_equivocation_evidence_for(&self) -> Option<EquivocationEvidenceFor<T>> {
441 match self {
442 Call::report_double_voting_unsigned { equivocation_proof, key_owner_proof } =>
443 Some(EquivocationEvidenceFor::<T>::DoubleVotingProof(
444 *equivocation_proof.clone(),
445 key_owner_proof.clone(),
446 )),
447 Call::report_fork_voting_unsigned { equivocation_proof, key_owner_proof } =>
448 Some(EquivocationEvidenceFor::<T>::ForkVotingProof(
449 *equivocation_proof.clone(),
450 key_owner_proof.clone(),
451 )),
452 _ => None,
453 }
454 }
455 }
456
457 impl<T: Config> From<EquivocationEvidenceFor<T>> for Call<T> {
458 fn from(evidence: EquivocationEvidenceFor<T>) -> Self {
459 match evidence {
460 EquivocationEvidenceFor::DoubleVotingProof(equivocation_proof, key_owner_proof) =>
461 Call::report_double_voting_unsigned {
462 equivocation_proof: Box::new(equivocation_proof),
463 key_owner_proof,
464 },
465 EquivocationEvidenceFor::ForkVotingProof(equivocation_proof, key_owner_proof) =>
466 Call::report_fork_voting_unsigned {
467 equivocation_proof: Box::new(equivocation_proof),
468 key_owner_proof,
469 },
470 EquivocationEvidenceFor::FutureBlockVotingProof(
471 equivocation_proof,
472 key_owner_proof,
473 ) => Call::report_future_block_voting_unsigned {
474 equivocation_proof: Box::new(equivocation_proof),
475 key_owner_proof,
476 },
477 }
478 }
479 }
480}
481
482#[cfg(any(feature = "try-runtime", test))]
483impl<T: Config> Pallet<T> {
484 pub fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> {
488 Self::try_state_authorities()?;
489 Self::try_state_validators()?;
490
491 Ok(())
492 }
493
494 fn try_state_authorities() -> Result<(), sp_runtime::TryRuntimeError> {
499 if let Some(authorities_len) = <Authorities<T>>::decode_len() {
500 ensure!(
501 authorities_len as u32 <= T::MaxAuthorities::get(),
502 "Authorities number exceeds what the pallet config allows."
503 );
504 } else {
505 return Err(sp_runtime::TryRuntimeError::Other(
506 "Failed to decode length of authorities",
507 ));
508 }
509
510 if let Some(next_authorities_len) = <NextAuthorities<T>>::decode_len() {
511 ensure!(
512 next_authorities_len as u32 <= T::MaxAuthorities::get(),
513 "Next authorities number exceeds what the pallet config allows."
514 );
515 } else {
516 return Err(sp_runtime::TryRuntimeError::Other(
517 "Failed to decode length of next authorities",
518 ));
519 }
520 Ok(())
521 }
522
523 fn try_state_validators() -> Result<(), sp_runtime::TryRuntimeError> {
527 let validator_set_id = <ValidatorSetId<T>>::get();
528 ensure!(
529 SetIdSession::<T>::get(validator_set_id).is_some(),
530 "Validator set id must be present in SetIdSession"
531 );
532 Ok(())
533 }
534}
535
536impl<T: Config> Pallet<T> {
537 pub fn validator_set() -> Option<ValidatorSet<T::BeefyId>> {
539 let validators: BoundedVec<T::BeefyId, T::MaxAuthorities> = Authorities::<T>::get();
540 let id: sp_consensus_beefy::ValidatorSetId = ValidatorSetId::<T>::get();
541 ValidatorSet::<T::BeefyId>::new(validators, id)
542 }
543
544 pub fn submit_unsigned_double_voting_report(
548 equivocation_proof: DoubleVotingProof<
549 BlockNumberFor<T>,
550 T::BeefyId,
551 <T::BeefyId as RuntimeAppPublic>::Signature,
552 >,
553 key_owner_proof: T::KeyOwnerProof,
554 ) -> Option<()> {
555 T::EquivocationReportSystem::publish_evidence(EquivocationEvidenceFor::DoubleVotingProof(
556 equivocation_proof,
557 key_owner_proof,
558 ))
559 .ok()
560 }
561
562 pub fn submit_unsigned_fork_voting_report(
566 equivocation_proof: ForkVotingProof<
567 HeaderFor<T>,
568 T::BeefyId,
569 <T::AncestryHelper as AncestryHelper<HeaderFor<T>>>::Proof,
570 >,
571 key_owner_proof: T::KeyOwnerProof,
572 ) -> Option<()> {
573 T::EquivocationReportSystem::publish_evidence(EquivocationEvidenceFor::ForkVotingProof(
574 equivocation_proof,
575 key_owner_proof,
576 ))
577 .ok()
578 }
579
580 pub fn submit_unsigned_future_block_voting_report(
584 equivocation_proof: FutureBlockVotingProof<BlockNumberFor<T>, T::BeefyId>,
585 key_owner_proof: T::KeyOwnerProof,
586 ) -> Option<()> {
587 T::EquivocationReportSystem::publish_evidence(
588 EquivocationEvidenceFor::FutureBlockVotingProof(equivocation_proof, key_owner_proof),
589 )
590 .ok()
591 }
592
593 fn change_authorities(
594 new: BoundedVec<T::BeefyId, T::MaxAuthorities>,
595 queued: BoundedVec<T::BeefyId, T::MaxAuthorities>,
596 ) {
597 Authorities::<T>::put(&new);
598
599 let new_id = ValidatorSetId::<T>::get() + 1u64;
600 ValidatorSetId::<T>::put(new_id);
601
602 NextAuthorities::<T>::put(&queued);
603
604 if let Some(validator_set) = ValidatorSet::<T::BeefyId>::new(new, new_id) {
605 let log = DigestItem::Consensus(
606 BEEFY_ENGINE_ID,
607 ConsensusLog::AuthoritiesChange(validator_set.clone()).encode(),
608 );
609 frame_system::Pallet::<T>::deposit_log(log);
610
611 let next_id = new_id + 1;
612 if let Some(next_validator_set) = ValidatorSet::<T::BeefyId>::new(queued, next_id) {
613 <T::OnNewValidatorSet as OnNewValidatorSet<_>>::on_new_validator_set(
614 &validator_set,
615 &next_validator_set,
616 );
617 }
618 }
619 }
620
621 fn initialize(authorities: &Vec<T::BeefyId>) -> Result<(), ()> {
622 if authorities.is_empty() {
623 return Ok(())
624 }
625
626 if !Authorities::<T>::get().is_empty() {
627 return Err(())
628 }
629
630 let bounded_authorities =
631 BoundedSlice::<T::BeefyId, T::MaxAuthorities>::try_from(authorities.as_slice())
632 .map_err(|_| ())?;
633
634 let id = GENESIS_AUTHORITY_SET_ID;
635 Authorities::<T>::put(bounded_authorities);
636 ValidatorSetId::<T>::put(id);
637 NextAuthorities::<T>::put(bounded_authorities);
639
640 if let Some(validator_set) = ValidatorSet::<T::BeefyId>::new(authorities.clone(), id) {
641 let next_id = id + 1;
642 if let Some(next_validator_set) =
643 ValidatorSet::<T::BeefyId>::new(authorities.clone(), next_id)
644 {
645 <T::OnNewValidatorSet as OnNewValidatorSet<_>>::on_new_validator_set(
646 &validator_set,
647 &next_validator_set,
648 );
649 }
650 }
651
652 SetIdSession::<T>::insert(0, 0);
656
657 Ok(())
658 }
659}
660
661impl<T: Config> sp_runtime::BoundToRuntimeAppPublic for Pallet<T> {
662 type Public = T::BeefyId;
663}
664
665impl<T: Config> OneSessionHandler<T::AccountId> for Pallet<T>
666where
667 T: pallet_session::Config,
668{
669 type Key = T::BeefyId;
670
671 fn on_genesis_session<'a, I: 'a>(validators: I)
672 where
673 I: Iterator<Item = (&'a T::AccountId, T::BeefyId)>,
674 {
675 let authorities = validators.map(|(_, k)| k).collect::<Vec<_>>();
676 Self::initialize(&authorities).expect("Authorities vec too big");
679 }
680
681 fn on_new_session<'a, I: 'a>(_changed: bool, validators: I, queued_validators: I)
682 where
683 I: Iterator<Item = (&'a T::AccountId, T::BeefyId)>,
684 {
685 let next_authorities = validators.map(|(_, k)| k).collect::<Vec<_>>();
686 if next_authorities.len() as u32 > T::MaxAuthorities::get() {
687 log::error!(
688 target: LOG_TARGET,
689 "authorities list {:?} truncated to length {}",
690 next_authorities,
691 T::MaxAuthorities::get(),
692 );
693 }
694 let bounded_next_authorities =
695 BoundedVec::<_, T::MaxAuthorities>::truncate_from(next_authorities);
696
697 let next_queued_authorities = queued_validators.map(|(_, k)| k).collect::<Vec<_>>();
698 if next_queued_authorities.len() as u32 > T::MaxAuthorities::get() {
699 log::error!(
700 target: LOG_TARGET,
701 "queued authorities list {:?} truncated to length {}",
702 next_queued_authorities,
703 T::MaxAuthorities::get(),
704 );
705 }
706 let bounded_next_queued_authorities =
707 BoundedVec::<_, T::MaxAuthorities>::truncate_from(next_queued_authorities);
708
709 Self::change_authorities(bounded_next_authorities, bounded_next_queued_authorities);
712
713 let validator_set_id = ValidatorSetId::<T>::get();
714 let session_index = pallet_session::Pallet::<T>::current_index();
716 SetIdSession::<T>::insert(validator_set_id, &session_index);
717 let max_set_id_session_entries = T::MaxSetIdSessionEntries::get().max(1);
719 if validator_set_id >= max_set_id_session_entries {
720 SetIdSession::<T>::remove(validator_set_id - max_set_id_session_entries);
721 }
722 }
723
724 fn on_disabled(i: u32) {
725 let log = DigestItem::Consensus(
726 BEEFY_ENGINE_ID,
727 ConsensusLog::<T::BeefyId>::OnDisabled(i as AuthorityIndex).encode(),
728 );
729
730 frame_system::Pallet::<T>::deposit_log(log);
731 }
732}
733
734impl<T: Config> IsMember<T::BeefyId> for Pallet<T> {
735 fn is_member(authority_id: &T::BeefyId) -> bool {
736 Authorities::<T>::get().iter().any(|id| id == authority_id)
737 }
738}
739
740pub trait WeightInfo {
741 fn report_voting_equivocation(
742 votes_count: u32,
743 validator_count: u32,
744 max_nominators_per_validator: u32,
745 ) -> Weight;
746
747 fn set_new_genesis() -> Weight;
748}
749
750pub(crate) trait WeightInfoExt: WeightInfo {
751 fn report_double_voting(validator_count: u32, max_nominators_per_validator: u32) -> Weight {
752 Self::report_voting_equivocation(2, validator_count, max_nominators_per_validator)
753 }
754
755 fn report_fork_voting<T: Config>(
756 validator_count: u32,
757 max_nominators_per_validator: u32,
758 ancestry_proof: &<T::AncestryHelper as AncestryHelper<HeaderFor<T>>>::Proof,
759 ) -> Weight {
760 <T::AncestryHelper as AncestryHelperWeightInfo<HeaderFor<T>>>::is_proof_optimal(&ancestry_proof)
761 .saturating_add(<T::AncestryHelper as AncestryHelperWeightInfo<HeaderFor<T>>>::extract_validation_context())
762 .saturating_add(
763 <T::AncestryHelper as AncestryHelperWeightInfo<HeaderFor<T>>>::is_non_canonical(
764 ancestry_proof,
765 ),
766 )
767 .saturating_add(Self::report_voting_equivocation(
768 1,
769 validator_count,
770 max_nominators_per_validator,
771 ))
772 }
773
774 fn report_future_block_voting(
775 validator_count: u32,
776 max_nominators_per_validator: u32,
777 ) -> Weight {
778 DbWeight::get()
780 .reads(1)
781 .saturating_add(Self::report_voting_equivocation(
783 1,
784 validator_count,
785 max_nominators_per_validator,
786 ))
787 }
788}
789
790impl<T> WeightInfoExt for T where T: WeightInfo {}