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
74 + MaybeSerializeDeserialize
75 + MaxEncodedLen;
76
77 #[pallet::constant]
79 type MaxAuthorities: Get<u32>;
80
81 #[pallet::constant]
83 type MaxNominators: Get<u32>;
84
85 #[pallet::constant]
92 type MaxSetIdSessionEntries: Get<u64>;
93
94 type OnNewValidatorSet: OnNewValidatorSet<<Self as Config>::BeefyId>;
100
101 type AncestryHelper: AncestryHelper<HeaderFor<Self>>
103 + AncestryHelperWeightInfo<HeaderFor<Self>>;
104
105 type WeightInfo: WeightInfo;
107
108 type KeyOwnerProof: Parameter + GetSessionNumber + GetValidatorCount;
112
113 type EquivocationReportSystem: OffenceReportSystem<
117 Option<Self::AccountId>,
118 EquivocationEvidenceFor<Self>,
119 >;
120 }
121
122 #[pallet::pallet]
123 pub struct Pallet<T>(_);
124
125 #[pallet::storage]
127 pub type Authorities<T: Config> =
128 StorageValue<_, BoundedVec<T::BeefyId, T::MaxAuthorities>, ValueQuery>;
129
130 #[pallet::storage]
132 pub type ValidatorSetId<T: Config> =
133 StorageValue<_, sp_consensus_beefy::ValidatorSetId, ValueQuery>;
134
135 #[pallet::storage]
137 pub type NextAuthorities<T: Config> =
138 StorageValue<_, BoundedVec<T::BeefyId, T::MaxAuthorities>, ValueQuery>;
139
140 #[pallet::storage]
151 pub type SetIdSession<T: Config> =
152 StorageMap<_, Twox64Concat, sp_consensus_beefy::ValidatorSetId, SessionIndex>;
153
154 #[pallet::storage]
158 pub type GenesisBlock<T: Config> = StorageValue<_, Option<BlockNumberFor<T>>, ValueQuery>;
159
160 #[pallet::genesis_config]
161 pub struct GenesisConfig<T: Config> {
162 pub authorities: Vec<T::BeefyId>,
164 pub genesis_block: Option<BlockNumberFor<T>>,
169 }
170
171 impl<T: Config> Default for GenesisConfig<T> {
172 fn default() -> Self {
173 let genesis_block = Some(One::one());
176 Self { authorities: Vec::new(), genesis_block }
177 }
178 }
179
180 #[pallet::genesis_build]
181 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
182 fn build(&self) {
183 Pallet::<T>::initialize(&self.authorities)
184 .expect("Authorities vec too big");
187 GenesisBlock::<T>::put(&self.genesis_block);
188 }
189 }
190
191 #[pallet::error]
192 pub enum Error<T> {
193 InvalidKeyOwnershipProof,
195 InvalidDoubleVotingProof,
197 InvalidForkVotingProof,
199 InvalidFutureBlockVotingProof,
201 InvalidEquivocationProofSession,
203 InvalidEquivocationProofSessionMember,
205 DuplicateOffenceReport,
207 InvalidConfiguration,
209 }
210
211 #[pallet::call]
212 impl<T: Config> Pallet<T> {
213 #[pallet::call_index(0)]
218 #[pallet::weight(T::WeightInfo::report_double_voting(
219 key_owner_proof.validator_count(),
220 T::MaxNominators::get(),
221 ))]
222 pub fn report_double_voting(
223 origin: OriginFor<T>,
224 equivocation_proof: Box<
225 DoubleVotingProof<
226 BlockNumberFor<T>,
227 T::BeefyId,
228 <T::BeefyId as RuntimeAppPublic>::Signature,
229 >,
230 >,
231 key_owner_proof: T::KeyOwnerProof,
232 ) -> DispatchResultWithPostInfo {
233 let reporter = ensure_signed(origin)?;
234
235 T::EquivocationReportSystem::process_evidence(
236 Some(reporter),
237 EquivocationEvidenceFor::DoubleVotingProof(*equivocation_proof, key_owner_proof),
238 )?;
239 Ok(Pays::No.into())
241 }
242
243 #[pallet::call_index(1)]
253 #[pallet::weight(T::WeightInfo::report_double_voting(
254 key_owner_proof.validator_count(),
255 T::MaxNominators::get(),
256 ))]
257 pub fn report_double_voting_unsigned(
258 origin: OriginFor<T>,
259 equivocation_proof: Box<
260 DoubleVotingProof<
261 BlockNumberFor<T>,
262 T::BeefyId,
263 <T::BeefyId as RuntimeAppPublic>::Signature,
264 >,
265 >,
266 key_owner_proof: T::KeyOwnerProof,
267 ) -> DispatchResultWithPostInfo {
268 ensure_none(origin)?;
269
270 T::EquivocationReportSystem::process_evidence(
271 None,
272 EquivocationEvidenceFor::DoubleVotingProof(*equivocation_proof, key_owner_proof),
273 )?;
274 Ok(Pays::No.into())
275 }
276
277 #[pallet::call_index(2)]
282 #[pallet::weight(<T as Config>::WeightInfo::set_new_genesis())]
283 pub fn set_new_genesis(
284 origin: OriginFor<T>,
285 delay_in_blocks: BlockNumberFor<T>,
286 ) -> DispatchResult {
287 ensure_root(origin)?;
288 ensure!(delay_in_blocks >= One::one(), Error::<T>::InvalidConfiguration);
289 let genesis_block = frame_system::Pallet::<T>::block_number() + delay_in_blocks;
290 GenesisBlock::<T>::put(Some(genesis_block));
291 Ok(())
292 }
293
294 #[pallet::call_index(3)]
298 #[pallet::weight(T::WeightInfo::report_fork_voting::<T>(
299 key_owner_proof.validator_count(),
300 T::MaxNominators::get(),
301 &equivocation_proof.ancestry_proof
302 ))]
303 pub fn report_fork_voting(
304 origin: OriginFor<T>,
305 equivocation_proof: Box<
306 ForkVotingProof<
307 HeaderFor<T>,
308 T::BeefyId,
309 <T::AncestryHelper as AncestryHelper<HeaderFor<T>>>::Proof,
310 >,
311 >,
312 key_owner_proof: T::KeyOwnerProof,
313 ) -> DispatchResultWithPostInfo {
314 let reporter = ensure_signed(origin)?;
315
316 T::EquivocationReportSystem::process_evidence(
317 Some(reporter),
318 EquivocationEvidenceFor::ForkVotingProof(*equivocation_proof, key_owner_proof),
319 )?;
320 Ok(Pays::No.into())
322 }
323
324 #[pallet::call_index(4)]
333 #[pallet::weight(T::WeightInfo::report_fork_voting::<T>(
334 key_owner_proof.validator_count(),
335 T::MaxNominators::get(),
336 &equivocation_proof.ancestry_proof
337 ))]
338 pub fn report_fork_voting_unsigned(
339 origin: OriginFor<T>,
340 equivocation_proof: Box<
341 ForkVotingProof<
342 HeaderFor<T>,
343 T::BeefyId,
344 <T::AncestryHelper as AncestryHelper<HeaderFor<T>>>::Proof,
345 >,
346 >,
347 key_owner_proof: T::KeyOwnerProof,
348 ) -> DispatchResultWithPostInfo {
349 ensure_none(origin)?;
350
351 T::EquivocationReportSystem::process_evidence(
352 None,
353 EquivocationEvidenceFor::ForkVotingProof(*equivocation_proof, key_owner_proof),
354 )?;
355 Ok(Pays::No.into())
357 }
358
359 #[pallet::call_index(5)]
363 #[pallet::weight(T::WeightInfo::report_future_block_voting(
364 key_owner_proof.validator_count(),
365 T::MaxNominators::get(),
366 ))]
367 pub fn report_future_block_voting(
368 origin: OriginFor<T>,
369 equivocation_proof: Box<FutureBlockVotingProof<BlockNumberFor<T>, T::BeefyId>>,
370 key_owner_proof: T::KeyOwnerProof,
371 ) -> DispatchResultWithPostInfo {
372 let reporter = ensure_signed(origin)?;
373
374 T::EquivocationReportSystem::process_evidence(
375 Some(reporter),
376 EquivocationEvidenceFor::FutureBlockVotingProof(
377 *equivocation_proof,
378 key_owner_proof,
379 ),
380 )?;
381 Ok(Pays::No.into())
383 }
384
385 #[pallet::call_index(6)]
394 #[pallet::weight(T::WeightInfo::report_future_block_voting(
395 key_owner_proof.validator_count(),
396 T::MaxNominators::get(),
397 ))]
398 pub fn report_future_block_voting_unsigned(
399 origin: OriginFor<T>,
400 equivocation_proof: Box<FutureBlockVotingProof<BlockNumberFor<T>, T::BeefyId>>,
401 key_owner_proof: T::KeyOwnerProof,
402 ) -> DispatchResultWithPostInfo {
403 ensure_none(origin)?;
404
405 T::EquivocationReportSystem::process_evidence(
406 None,
407 EquivocationEvidenceFor::FutureBlockVotingProof(
408 *equivocation_proof,
409 key_owner_proof,
410 ),
411 )?;
412 Ok(Pays::No.into())
414 }
415 }
416
417 #[pallet::hooks]
418 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
419 #[cfg(feature = "try-runtime")]
420 fn try_state(_n: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
421 Self::do_try_state()
422 }
423 }
424
425 #[allow(deprecated)]
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 },
448 Call::report_fork_voting_unsigned { equivocation_proof, key_owner_proof } => {
449 Some(EquivocationEvidenceFor::<T>::ForkVotingProof(
450 *equivocation_proof.clone(),
451 key_owner_proof.clone(),
452 ))
453 },
454 Call::report_future_block_voting_unsigned {
455 equivocation_proof,
456 key_owner_proof,
457 } => Some(EquivocationEvidenceFor::<T>::FutureBlockVotingProof(
458 *equivocation_proof.clone(),
459 key_owner_proof.clone(),
460 )),
461 _ => None,
462 }
463 }
464 }
465
466 impl<T: Config> From<EquivocationEvidenceFor<T>> for Call<T> {
467 fn from(evidence: EquivocationEvidenceFor<T>) -> Self {
468 match evidence {
469 EquivocationEvidenceFor::DoubleVotingProof(equivocation_proof, key_owner_proof) => {
470 Call::report_double_voting_unsigned {
471 equivocation_proof: Box::new(equivocation_proof),
472 key_owner_proof,
473 }
474 },
475 EquivocationEvidenceFor::ForkVotingProof(equivocation_proof, key_owner_proof) => {
476 Call::report_fork_voting_unsigned {
477 equivocation_proof: Box::new(equivocation_proof),
478 key_owner_proof,
479 }
480 },
481 EquivocationEvidenceFor::FutureBlockVotingProof(
482 equivocation_proof,
483 key_owner_proof,
484 ) => Call::report_future_block_voting_unsigned {
485 equivocation_proof: Box::new(equivocation_proof),
486 key_owner_proof,
487 },
488 }
489 }
490 }
491}
492
493#[cfg(any(feature = "try-runtime", test))]
494impl<T: Config> Pallet<T> {
495 pub fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> {
499 Self::try_state_authorities()?;
500 Self::try_state_validators()?;
501
502 Ok(())
503 }
504
505 fn try_state_authorities() -> Result<(), sp_runtime::TryRuntimeError> {
510 if let Some(authorities_len) = <Authorities<T>>::decode_len() {
511 ensure!(
512 authorities_len as u32 <= T::MaxAuthorities::get(),
513 "Authorities number exceeds what the pallet config allows."
514 );
515 } else {
516 return Err(sp_runtime::TryRuntimeError::Other(
517 "Failed to decode length of authorities",
518 ));
519 }
520
521 if let Some(next_authorities_len) = <NextAuthorities<T>>::decode_len() {
522 ensure!(
523 next_authorities_len as u32 <= T::MaxAuthorities::get(),
524 "Next authorities number exceeds what the pallet config allows."
525 );
526 } else {
527 return Err(sp_runtime::TryRuntimeError::Other(
528 "Failed to decode length of next authorities",
529 ));
530 }
531 Ok(())
532 }
533
534 fn try_state_validators() -> Result<(), sp_runtime::TryRuntimeError> {
538 let validator_set_id = <ValidatorSetId<T>>::get();
539 ensure!(
540 SetIdSession::<T>::get(validator_set_id).is_some(),
541 "Validator set id must be present in SetIdSession"
542 );
543 Ok(())
544 }
545}
546
547impl<T: Config> Pallet<T> {
548 pub fn validator_set() -> Option<ValidatorSet<T::BeefyId>> {
550 let validators: BoundedVec<T::BeefyId, T::MaxAuthorities> = Authorities::<T>::get();
551 let id: sp_consensus_beefy::ValidatorSetId = ValidatorSetId::<T>::get();
552 ValidatorSet::<T::BeefyId>::new(validators, id)
553 }
554
555 pub fn submit_unsigned_double_voting_report(
559 equivocation_proof: DoubleVotingProof<
560 BlockNumberFor<T>,
561 T::BeefyId,
562 <T::BeefyId as RuntimeAppPublic>::Signature,
563 >,
564 key_owner_proof: T::KeyOwnerProof,
565 ) -> Option<()> {
566 T::EquivocationReportSystem::publish_evidence(EquivocationEvidenceFor::DoubleVotingProof(
567 equivocation_proof,
568 key_owner_proof,
569 ))
570 .ok()
571 }
572
573 pub fn submit_unsigned_fork_voting_report(
577 equivocation_proof: ForkVotingProof<
578 HeaderFor<T>,
579 T::BeefyId,
580 <T::AncestryHelper as AncestryHelper<HeaderFor<T>>>::Proof,
581 >,
582 key_owner_proof: T::KeyOwnerProof,
583 ) -> Option<()> {
584 T::EquivocationReportSystem::publish_evidence(EquivocationEvidenceFor::ForkVotingProof(
585 equivocation_proof,
586 key_owner_proof,
587 ))
588 .ok()
589 }
590
591 pub fn submit_unsigned_future_block_voting_report(
595 equivocation_proof: FutureBlockVotingProof<BlockNumberFor<T>, T::BeefyId>,
596 key_owner_proof: T::KeyOwnerProof,
597 ) -> Option<()> {
598 T::EquivocationReportSystem::publish_evidence(
599 EquivocationEvidenceFor::FutureBlockVotingProof(equivocation_proof, key_owner_proof),
600 )
601 .ok()
602 }
603
604 fn change_authorities(
605 new: BoundedVec<T::BeefyId, T::MaxAuthorities>,
606 queued: BoundedVec<T::BeefyId, T::MaxAuthorities>,
607 ) {
608 Authorities::<T>::put(&new);
609
610 let new_id = ValidatorSetId::<T>::get() + 1u64;
611 ValidatorSetId::<T>::put(new_id);
612
613 NextAuthorities::<T>::put(&queued);
614
615 if let Some(validator_set) = ValidatorSet::<T::BeefyId>::new(new, new_id) {
616 let log = DigestItem::Consensus(
617 BEEFY_ENGINE_ID,
618 ConsensusLog::AuthoritiesChange(validator_set.clone()).encode(),
619 );
620 frame_system::Pallet::<T>::deposit_log(log);
621
622 let next_id = new_id + 1;
623 if let Some(next_validator_set) = ValidatorSet::<T::BeefyId>::new(queued, next_id) {
624 <T::OnNewValidatorSet as OnNewValidatorSet<_>>::on_new_validator_set(
625 &validator_set,
626 &next_validator_set,
627 );
628 }
629 }
630 }
631
632 fn initialize(authorities: &Vec<T::BeefyId>) -> Result<(), ()> {
633 if authorities.is_empty() {
634 return Ok(());
635 }
636
637 if !Authorities::<T>::get().is_empty() {
638 return Err(());
639 }
640
641 let bounded_authorities =
642 BoundedSlice::<T::BeefyId, T::MaxAuthorities>::try_from(authorities.as_slice())
643 .map_err(|_| ())?;
644
645 let id = GENESIS_AUTHORITY_SET_ID;
646 Authorities::<T>::put(bounded_authorities);
647 ValidatorSetId::<T>::put(id);
648 NextAuthorities::<T>::put(bounded_authorities);
650
651 if let Some(validator_set) = ValidatorSet::<T::BeefyId>::new(authorities.clone(), id) {
652 let next_id = id + 1;
653 if let Some(next_validator_set) =
654 ValidatorSet::<T::BeefyId>::new(authorities.clone(), next_id)
655 {
656 <T::OnNewValidatorSet as OnNewValidatorSet<_>>::on_new_validator_set(
657 &validator_set,
658 &next_validator_set,
659 );
660 }
661 }
662
663 SetIdSession::<T>::insert(0, 0);
667
668 Ok(())
669 }
670}
671
672impl<T: Config> sp_runtime::BoundToRuntimeAppPublic for Pallet<T> {
673 type Public = T::BeefyId;
674}
675
676impl<T: Config> OneSessionHandler<T::AccountId> for Pallet<T>
677where
678 T: pallet_session::Config,
679{
680 type Key = T::BeefyId;
681
682 fn on_genesis_session<'a, I: 'a>(validators: I)
683 where
684 I: Iterator<Item = (&'a T::AccountId, T::BeefyId)>,
685 {
686 let authorities = validators.map(|(_, k)| k).collect::<Vec<_>>();
687 Self::initialize(&authorities).expect("Authorities vec too big");
690 }
691
692 fn on_new_session<'a, I: 'a>(_changed: bool, validators: I, queued_validators: I)
693 where
694 I: Iterator<Item = (&'a T::AccountId, T::BeefyId)>,
695 {
696 let next_authorities = validators.map(|(_, k)| k).collect::<Vec<_>>();
697 if next_authorities.len() as u32 > T::MaxAuthorities::get() {
698 log::error!(
699 target: LOG_TARGET,
700 "authorities list {:?} truncated to length {}",
701 next_authorities,
702 T::MaxAuthorities::get(),
703 );
704 }
705 let bounded_next_authorities =
706 BoundedVec::<_, T::MaxAuthorities>::truncate_from(next_authorities);
707
708 let next_queued_authorities = queued_validators.map(|(_, k)| k).collect::<Vec<_>>();
709 if next_queued_authorities.len() as u32 > T::MaxAuthorities::get() {
710 log::error!(
711 target: LOG_TARGET,
712 "queued authorities list {:?} truncated to length {}",
713 next_queued_authorities,
714 T::MaxAuthorities::get(),
715 );
716 }
717 let bounded_next_queued_authorities =
718 BoundedVec::<_, T::MaxAuthorities>::truncate_from(next_queued_authorities);
719
720 Self::change_authorities(bounded_next_authorities, bounded_next_queued_authorities);
723
724 let validator_set_id = ValidatorSetId::<T>::get();
725 let session_index = pallet_session::Pallet::<T>::current_index();
727 SetIdSession::<T>::insert(validator_set_id, &session_index);
728 let max_set_id_session_entries = T::MaxSetIdSessionEntries::get().max(1);
730 if validator_set_id >= max_set_id_session_entries {
731 SetIdSession::<T>::remove(validator_set_id - max_set_id_session_entries);
732 }
733 }
734
735 fn on_disabled(i: u32) {
736 let log = DigestItem::Consensus(
737 BEEFY_ENGINE_ID,
738 ConsensusLog::<T::BeefyId>::OnDisabled(i as AuthorityIndex).encode(),
739 );
740
741 frame_system::Pallet::<T>::deposit_log(log);
742 }
743}
744
745impl<T: Config> IsMember<T::BeefyId> for Pallet<T> {
746 fn is_member(authority_id: &T::BeefyId) -> bool {
747 Authorities::<T>::get().iter().any(|id| id == authority_id)
748 }
749}
750
751pub trait WeightInfo {
752 fn report_voting_equivocation(
753 votes_count: u32,
754 validator_count: u32,
755 max_nominators_per_validator: u32,
756 ) -> Weight;
757
758 fn set_new_genesis() -> Weight;
759}
760
761pub(crate) trait WeightInfoExt: WeightInfo {
762 fn report_double_voting(validator_count: u32, max_nominators_per_validator: u32) -> Weight {
763 Self::report_voting_equivocation(2, validator_count, max_nominators_per_validator)
764 }
765
766 fn report_fork_voting<T: Config>(
767 validator_count: u32,
768 max_nominators_per_validator: u32,
769 ancestry_proof: &<T::AncestryHelper as AncestryHelper<HeaderFor<T>>>::Proof,
770 ) -> Weight {
771 <T::AncestryHelper as AncestryHelperWeightInfo<HeaderFor<T>>>::is_proof_optimal(&ancestry_proof)
772 .saturating_add(<T::AncestryHelper as AncestryHelperWeightInfo<HeaderFor<T>>>::extract_validation_context())
773 .saturating_add(
774 <T::AncestryHelper as AncestryHelperWeightInfo<HeaderFor<T>>>::is_non_canonical(
775 ancestry_proof,
776 ),
777 )
778 .saturating_add(Self::report_voting_equivocation(
779 1,
780 validator_count,
781 max_nominators_per_validator,
782 ))
783 }
784
785 fn report_future_block_voting(
786 validator_count: u32,
787 max_nominators_per_validator: u32,
788 ) -> Weight {
789 DbWeight::get()
791 .reads(1)
792 .saturating_add(Self::report_voting_equivocation(
794 1,
795 validator_count,
796 max_nominators_per_validator,
797 ))
798 }
799}
800
801impl<T> WeightInfoExt for T where T: WeightInfo {}