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 #[pallet::validate_unsigned]
426 impl<T: Config> ValidateUnsigned for Pallet<T> {
427 type Call = Call<T>;
428
429 fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> {
430 Self::pre_dispatch(call)
431 }
432
433 fn validate_unsigned(source: TransactionSource, call: &Self::Call) -> TransactionValidity {
434 Self::validate_unsigned(source, call)
435 }
436 }
437
438 impl<T: Config> Call<T> {
439 pub fn to_equivocation_evidence_for(&self) -> Option<EquivocationEvidenceFor<T>> {
440 match self {
441 Call::report_double_voting_unsigned { equivocation_proof, key_owner_proof } => {
442 Some(EquivocationEvidenceFor::<T>::DoubleVotingProof(
443 *equivocation_proof.clone(),
444 key_owner_proof.clone(),
445 ))
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 },
453 _ => None,
454 }
455 }
456 }
457
458 impl<T: Config> From<EquivocationEvidenceFor<T>> for Call<T> {
459 fn from(evidence: EquivocationEvidenceFor<T>) -> Self {
460 match evidence {
461 EquivocationEvidenceFor::DoubleVotingProof(equivocation_proof, key_owner_proof) => {
462 Call::report_double_voting_unsigned {
463 equivocation_proof: Box::new(equivocation_proof),
464 key_owner_proof,
465 }
466 },
467 EquivocationEvidenceFor::ForkVotingProof(equivocation_proof, key_owner_proof) => {
468 Call::report_fork_voting_unsigned {
469 equivocation_proof: Box::new(equivocation_proof),
470 key_owner_proof,
471 }
472 },
473 EquivocationEvidenceFor::FutureBlockVotingProof(
474 equivocation_proof,
475 key_owner_proof,
476 ) => Call::report_future_block_voting_unsigned {
477 equivocation_proof: Box::new(equivocation_proof),
478 key_owner_proof,
479 },
480 }
481 }
482 }
483}
484
485#[cfg(any(feature = "try-runtime", test))]
486impl<T: Config> Pallet<T> {
487 pub fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> {
491 Self::try_state_authorities()?;
492 Self::try_state_validators()?;
493
494 Ok(())
495 }
496
497 fn try_state_authorities() -> Result<(), sp_runtime::TryRuntimeError> {
502 if let Some(authorities_len) = <Authorities<T>>::decode_len() {
503 ensure!(
504 authorities_len as u32 <= T::MaxAuthorities::get(),
505 "Authorities number exceeds what the pallet config allows."
506 );
507 } else {
508 return Err(sp_runtime::TryRuntimeError::Other(
509 "Failed to decode length of authorities",
510 ));
511 }
512
513 if let Some(next_authorities_len) = <NextAuthorities<T>>::decode_len() {
514 ensure!(
515 next_authorities_len as u32 <= T::MaxAuthorities::get(),
516 "Next authorities number exceeds what the pallet config allows."
517 );
518 } else {
519 return Err(sp_runtime::TryRuntimeError::Other(
520 "Failed to decode length of next authorities",
521 ));
522 }
523 Ok(())
524 }
525
526 fn try_state_validators() -> Result<(), sp_runtime::TryRuntimeError> {
530 let validator_set_id = <ValidatorSetId<T>>::get();
531 ensure!(
532 SetIdSession::<T>::get(validator_set_id).is_some(),
533 "Validator set id must be present in SetIdSession"
534 );
535 Ok(())
536 }
537}
538
539impl<T: Config> Pallet<T> {
540 pub fn validator_set() -> Option<ValidatorSet<T::BeefyId>> {
542 let validators: BoundedVec<T::BeefyId, T::MaxAuthorities> = Authorities::<T>::get();
543 let id: sp_consensus_beefy::ValidatorSetId = ValidatorSetId::<T>::get();
544 ValidatorSet::<T::BeefyId>::new(validators, id)
545 }
546
547 pub fn submit_unsigned_double_voting_report(
551 equivocation_proof: DoubleVotingProof<
552 BlockNumberFor<T>,
553 T::BeefyId,
554 <T::BeefyId as RuntimeAppPublic>::Signature,
555 >,
556 key_owner_proof: T::KeyOwnerProof,
557 ) -> Option<()> {
558 T::EquivocationReportSystem::publish_evidence(EquivocationEvidenceFor::DoubleVotingProof(
559 equivocation_proof,
560 key_owner_proof,
561 ))
562 .ok()
563 }
564
565 pub fn submit_unsigned_fork_voting_report(
569 equivocation_proof: ForkVotingProof<
570 HeaderFor<T>,
571 T::BeefyId,
572 <T::AncestryHelper as AncestryHelper<HeaderFor<T>>>::Proof,
573 >,
574 key_owner_proof: T::KeyOwnerProof,
575 ) -> Option<()> {
576 T::EquivocationReportSystem::publish_evidence(EquivocationEvidenceFor::ForkVotingProof(
577 equivocation_proof,
578 key_owner_proof,
579 ))
580 .ok()
581 }
582
583 pub fn submit_unsigned_future_block_voting_report(
587 equivocation_proof: FutureBlockVotingProof<BlockNumberFor<T>, T::BeefyId>,
588 key_owner_proof: T::KeyOwnerProof,
589 ) -> Option<()> {
590 T::EquivocationReportSystem::publish_evidence(
591 EquivocationEvidenceFor::FutureBlockVotingProof(equivocation_proof, key_owner_proof),
592 )
593 .ok()
594 }
595
596 fn change_authorities(
597 new: BoundedVec<T::BeefyId, T::MaxAuthorities>,
598 queued: BoundedVec<T::BeefyId, T::MaxAuthorities>,
599 ) {
600 Authorities::<T>::put(&new);
601
602 let new_id = ValidatorSetId::<T>::get() + 1u64;
603 ValidatorSetId::<T>::put(new_id);
604
605 NextAuthorities::<T>::put(&queued);
606
607 if let Some(validator_set) = ValidatorSet::<T::BeefyId>::new(new, new_id) {
608 let log = DigestItem::Consensus(
609 BEEFY_ENGINE_ID,
610 ConsensusLog::AuthoritiesChange(validator_set.clone()).encode(),
611 );
612 frame_system::Pallet::<T>::deposit_log(log);
613
614 let next_id = new_id + 1;
615 if let Some(next_validator_set) = ValidatorSet::<T::BeefyId>::new(queued, next_id) {
616 <T::OnNewValidatorSet as OnNewValidatorSet<_>>::on_new_validator_set(
617 &validator_set,
618 &next_validator_set,
619 );
620 }
621 }
622 }
623
624 fn initialize(authorities: &Vec<T::BeefyId>) -> Result<(), ()> {
625 if authorities.is_empty() {
626 return Ok(());
627 }
628
629 if !Authorities::<T>::get().is_empty() {
630 return Err(());
631 }
632
633 let bounded_authorities =
634 BoundedSlice::<T::BeefyId, T::MaxAuthorities>::try_from(authorities.as_slice())
635 .map_err(|_| ())?;
636
637 let id = GENESIS_AUTHORITY_SET_ID;
638 Authorities::<T>::put(bounded_authorities);
639 ValidatorSetId::<T>::put(id);
640 NextAuthorities::<T>::put(bounded_authorities);
642
643 if let Some(validator_set) = ValidatorSet::<T::BeefyId>::new(authorities.clone(), id) {
644 let next_id = id + 1;
645 if let Some(next_validator_set) =
646 ValidatorSet::<T::BeefyId>::new(authorities.clone(), next_id)
647 {
648 <T::OnNewValidatorSet as OnNewValidatorSet<_>>::on_new_validator_set(
649 &validator_set,
650 &next_validator_set,
651 );
652 }
653 }
654
655 SetIdSession::<T>::insert(0, 0);
659
660 Ok(())
661 }
662}
663
664impl<T: Config> sp_runtime::BoundToRuntimeAppPublic for Pallet<T> {
665 type Public = T::BeefyId;
666}
667
668impl<T: Config> OneSessionHandler<T::AccountId> for Pallet<T>
669where
670 T: pallet_session::Config,
671{
672 type Key = T::BeefyId;
673
674 fn on_genesis_session<'a, I: 'a>(validators: I)
675 where
676 I: Iterator<Item = (&'a T::AccountId, T::BeefyId)>,
677 {
678 let authorities = validators.map(|(_, k)| k).collect::<Vec<_>>();
679 Self::initialize(&authorities).expect("Authorities vec too big");
682 }
683
684 fn on_new_session<'a, I: 'a>(_changed: bool, validators: I, queued_validators: I)
685 where
686 I: Iterator<Item = (&'a T::AccountId, T::BeefyId)>,
687 {
688 let next_authorities = validators.map(|(_, k)| k).collect::<Vec<_>>();
689 if next_authorities.len() as u32 > T::MaxAuthorities::get() {
690 log::error!(
691 target: LOG_TARGET,
692 "authorities list {:?} truncated to length {}",
693 next_authorities,
694 T::MaxAuthorities::get(),
695 );
696 }
697 let bounded_next_authorities =
698 BoundedVec::<_, T::MaxAuthorities>::truncate_from(next_authorities);
699
700 let next_queued_authorities = queued_validators.map(|(_, k)| k).collect::<Vec<_>>();
701 if next_queued_authorities.len() as u32 > T::MaxAuthorities::get() {
702 log::error!(
703 target: LOG_TARGET,
704 "queued authorities list {:?} truncated to length {}",
705 next_queued_authorities,
706 T::MaxAuthorities::get(),
707 );
708 }
709 let bounded_next_queued_authorities =
710 BoundedVec::<_, T::MaxAuthorities>::truncate_from(next_queued_authorities);
711
712 Self::change_authorities(bounded_next_authorities, bounded_next_queued_authorities);
715
716 let validator_set_id = ValidatorSetId::<T>::get();
717 let session_index = pallet_session::Pallet::<T>::current_index();
719 SetIdSession::<T>::insert(validator_set_id, &session_index);
720 let max_set_id_session_entries = T::MaxSetIdSessionEntries::get().max(1);
722 if validator_set_id >= max_set_id_session_entries {
723 SetIdSession::<T>::remove(validator_set_id - max_set_id_session_entries);
724 }
725 }
726
727 fn on_disabled(i: u32) {
728 let log = DigestItem::Consensus(
729 BEEFY_ENGINE_ID,
730 ConsensusLog::<T::BeefyId>::OnDisabled(i as AuthorityIndex).encode(),
731 );
732
733 frame_system::Pallet::<T>::deposit_log(log);
734 }
735}
736
737impl<T: Config> IsMember<T::BeefyId> for Pallet<T> {
738 fn is_member(authority_id: &T::BeefyId) -> bool {
739 Authorities::<T>::get().iter().any(|id| id == authority_id)
740 }
741}
742
743pub trait WeightInfo {
744 fn report_voting_equivocation(
745 votes_count: u32,
746 validator_count: u32,
747 max_nominators_per_validator: u32,
748 ) -> Weight;
749
750 fn set_new_genesis() -> Weight;
751}
752
753pub(crate) trait WeightInfoExt: WeightInfo {
754 fn report_double_voting(validator_count: u32, max_nominators_per_validator: u32) -> Weight {
755 Self::report_voting_equivocation(2, validator_count, max_nominators_per_validator)
756 }
757
758 fn report_fork_voting<T: Config>(
759 validator_count: u32,
760 max_nominators_per_validator: u32,
761 ancestry_proof: &<T::AncestryHelper as AncestryHelper<HeaderFor<T>>>::Proof,
762 ) -> Weight {
763 <T::AncestryHelper as AncestryHelperWeightInfo<HeaderFor<T>>>::is_proof_optimal(&ancestry_proof)
764 .saturating_add(<T::AncestryHelper as AncestryHelperWeightInfo<HeaderFor<T>>>::extract_validation_context())
765 .saturating_add(
766 <T::AncestryHelper as AncestryHelperWeightInfo<HeaderFor<T>>>::is_non_canonical(
767 ancestry_proof,
768 ),
769 )
770 .saturating_add(Self::report_voting_equivocation(
771 1,
772 validator_count,
773 max_nominators_per_validator,
774 ))
775 }
776
777 fn report_future_block_voting(
778 validator_count: u32,
779 max_nominators_per_validator: u32,
780 ) -> Weight {
781 DbWeight::get()
783 .reads(1)
784 .saturating_add(Self::report_voting_equivocation(
786 1,
787 validator_count,
788 max_nominators_per_validator,
789 ))
790 }
791}
792
793impl<T> WeightInfoExt for T where T: WeightInfo {}