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 DuplicateOffenceReport,
206 InvalidConfiguration,
208 }
209
210 #[pallet::call]
211 impl<T: Config> Pallet<T> {
212 #[pallet::call_index(0)]
217 #[pallet::weight(T::WeightInfo::report_double_voting(
218 key_owner_proof.validator_count(),
219 T::MaxNominators::get(),
220 ))]
221 pub fn report_double_voting(
222 origin: OriginFor<T>,
223 equivocation_proof: Box<
224 DoubleVotingProof<
225 BlockNumberFor<T>,
226 T::BeefyId,
227 <T::BeefyId as RuntimeAppPublic>::Signature,
228 >,
229 >,
230 key_owner_proof: T::KeyOwnerProof,
231 ) -> DispatchResultWithPostInfo {
232 let reporter = ensure_signed(origin)?;
233
234 T::EquivocationReportSystem::process_evidence(
235 Some(reporter),
236 EquivocationEvidenceFor::DoubleVotingProof(*equivocation_proof, key_owner_proof),
237 )?;
238 Ok(Pays::No.into())
240 }
241
242 #[pallet::call_index(1)]
252 #[pallet::weight(T::WeightInfo::report_double_voting(
253 key_owner_proof.validator_count(),
254 T::MaxNominators::get(),
255 ))]
256 pub fn report_double_voting_unsigned(
257 origin: OriginFor<T>,
258 equivocation_proof: Box<
259 DoubleVotingProof<
260 BlockNumberFor<T>,
261 T::BeefyId,
262 <T::BeefyId as RuntimeAppPublic>::Signature,
263 >,
264 >,
265 key_owner_proof: T::KeyOwnerProof,
266 ) -> DispatchResultWithPostInfo {
267 ensure_none(origin)?;
268
269 T::EquivocationReportSystem::process_evidence(
270 None,
271 EquivocationEvidenceFor::DoubleVotingProof(*equivocation_proof, key_owner_proof),
272 )?;
273 Ok(Pays::No.into())
274 }
275
276 #[pallet::call_index(2)]
281 #[pallet::weight(<T as Config>::WeightInfo::set_new_genesis())]
282 pub fn set_new_genesis(
283 origin: OriginFor<T>,
284 delay_in_blocks: BlockNumberFor<T>,
285 ) -> DispatchResult {
286 ensure_root(origin)?;
287 ensure!(delay_in_blocks >= One::one(), Error::<T>::InvalidConfiguration);
288 let genesis_block = frame_system::Pallet::<T>::block_number() + delay_in_blocks;
289 GenesisBlock::<T>::put(Some(genesis_block));
290 Ok(())
291 }
292
293 #[pallet::call_index(3)]
297 #[pallet::weight(T::WeightInfo::report_fork_voting::<T>(
298 key_owner_proof.validator_count(),
299 T::MaxNominators::get(),
300 &equivocation_proof.ancestry_proof
301 ))]
302 pub fn report_fork_voting(
303 origin: OriginFor<T>,
304 equivocation_proof: Box<
305 ForkVotingProof<
306 HeaderFor<T>,
307 T::BeefyId,
308 <T::AncestryHelper as AncestryHelper<HeaderFor<T>>>::Proof,
309 >,
310 >,
311 key_owner_proof: T::KeyOwnerProof,
312 ) -> DispatchResultWithPostInfo {
313 let reporter = ensure_signed(origin)?;
314
315 T::EquivocationReportSystem::process_evidence(
316 Some(reporter),
317 EquivocationEvidenceFor::ForkVotingProof(*equivocation_proof, key_owner_proof),
318 )?;
319 Ok(Pays::No.into())
321 }
322
323 #[pallet::call_index(4)]
332 #[pallet::weight(T::WeightInfo::report_fork_voting::<T>(
333 key_owner_proof.validator_count(),
334 T::MaxNominators::get(),
335 &equivocation_proof.ancestry_proof
336 ))]
337 pub fn report_fork_voting_unsigned(
338 origin: OriginFor<T>,
339 equivocation_proof: Box<
340 ForkVotingProof<
341 HeaderFor<T>,
342 T::BeefyId,
343 <T::AncestryHelper as AncestryHelper<HeaderFor<T>>>::Proof,
344 >,
345 >,
346 key_owner_proof: T::KeyOwnerProof,
347 ) -> DispatchResultWithPostInfo {
348 ensure_none(origin)?;
349
350 T::EquivocationReportSystem::process_evidence(
351 None,
352 EquivocationEvidenceFor::ForkVotingProof(*equivocation_proof, key_owner_proof),
353 )?;
354 Ok(Pays::No.into())
356 }
357
358 #[pallet::call_index(5)]
362 #[pallet::weight(T::WeightInfo::report_future_block_voting(
363 key_owner_proof.validator_count(),
364 T::MaxNominators::get(),
365 ))]
366 pub fn report_future_block_voting(
367 origin: OriginFor<T>,
368 equivocation_proof: Box<FutureBlockVotingProof<BlockNumberFor<T>, T::BeefyId>>,
369 key_owner_proof: T::KeyOwnerProof,
370 ) -> DispatchResultWithPostInfo {
371 let reporter = ensure_signed(origin)?;
372
373 T::EquivocationReportSystem::process_evidence(
374 Some(reporter),
375 EquivocationEvidenceFor::FutureBlockVotingProof(
376 *equivocation_proof,
377 key_owner_proof,
378 ),
379 )?;
380 Ok(Pays::No.into())
382 }
383
384 #[pallet::call_index(6)]
393 #[pallet::weight(T::WeightInfo::report_future_block_voting(
394 key_owner_proof.validator_count(),
395 T::MaxNominators::get(),
396 ))]
397 pub fn report_future_block_voting_unsigned(
398 origin: OriginFor<T>,
399 equivocation_proof: Box<FutureBlockVotingProof<BlockNumberFor<T>, T::BeefyId>>,
400 key_owner_proof: T::KeyOwnerProof,
401 ) -> DispatchResultWithPostInfo {
402 ensure_none(origin)?;
403
404 T::EquivocationReportSystem::process_evidence(
405 None,
406 EquivocationEvidenceFor::FutureBlockVotingProof(
407 *equivocation_proof,
408 key_owner_proof,
409 ),
410 )?;
411 Ok(Pays::No.into())
413 }
414 }
415
416 #[pallet::hooks]
417 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
418 #[cfg(feature = "try-runtime")]
419 fn try_state(_n: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
420 Self::do_try_state()
421 }
422 }
423
424 #[pallet::validate_unsigned]
425 impl<T: Config> ValidateUnsigned for Pallet<T> {
426 type Call = Call<T>;
427
428 fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> {
429 Self::pre_dispatch(call)
430 }
431
432 fn validate_unsigned(source: TransactionSource, call: &Self::Call) -> TransactionValidity {
433 Self::validate_unsigned(source, call)
434 }
435 }
436
437 impl<T: Config> Call<T> {
438 pub fn to_equivocation_evidence_for(&self) -> Option<EquivocationEvidenceFor<T>> {
439 match self {
440 Call::report_double_voting_unsigned { equivocation_proof, key_owner_proof } =>
441 Some(EquivocationEvidenceFor::<T>::DoubleVotingProof(
442 *equivocation_proof.clone(),
443 key_owner_proof.clone(),
444 )),
445 Call::report_fork_voting_unsigned { equivocation_proof, key_owner_proof } =>
446 Some(EquivocationEvidenceFor::<T>::ForkVotingProof(
447 *equivocation_proof.clone(),
448 key_owner_proof.clone(),
449 )),
450 _ => None,
451 }
452 }
453 }
454
455 impl<T: Config> From<EquivocationEvidenceFor<T>> for Call<T> {
456 fn from(evidence: EquivocationEvidenceFor<T>) -> Self {
457 match evidence {
458 EquivocationEvidenceFor::DoubleVotingProof(equivocation_proof, key_owner_proof) =>
459 Call::report_double_voting_unsigned {
460 equivocation_proof: Box::new(equivocation_proof),
461 key_owner_proof,
462 },
463 EquivocationEvidenceFor::ForkVotingProof(equivocation_proof, key_owner_proof) =>
464 Call::report_fork_voting_unsigned {
465 equivocation_proof: Box::new(equivocation_proof),
466 key_owner_proof,
467 },
468 EquivocationEvidenceFor::FutureBlockVotingProof(
469 equivocation_proof,
470 key_owner_proof,
471 ) => Call::report_future_block_voting_unsigned {
472 equivocation_proof: Box::new(equivocation_proof),
473 key_owner_proof,
474 },
475 }
476 }
477 }
478}
479
480#[cfg(any(feature = "try-runtime", test))]
481impl<T: Config> Pallet<T> {
482 pub fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> {
486 Self::try_state_authorities()?;
487 Self::try_state_validators()?;
488
489 Ok(())
490 }
491
492 fn try_state_authorities() -> Result<(), sp_runtime::TryRuntimeError> {
497 if let Some(authorities_len) = <Authorities<T>>::decode_len() {
498 ensure!(
499 authorities_len as u32 <= T::MaxAuthorities::get(),
500 "Authorities number exceeds what the pallet config allows."
501 );
502 } else {
503 return Err(sp_runtime::TryRuntimeError::Other(
504 "Failed to decode length of authorities",
505 ));
506 }
507
508 if let Some(next_authorities_len) = <NextAuthorities<T>>::decode_len() {
509 ensure!(
510 next_authorities_len as u32 <= T::MaxAuthorities::get(),
511 "Next authorities number exceeds what the pallet config allows."
512 );
513 } else {
514 return Err(sp_runtime::TryRuntimeError::Other(
515 "Failed to decode length of next authorities",
516 ));
517 }
518 Ok(())
519 }
520
521 fn try_state_validators() -> Result<(), sp_runtime::TryRuntimeError> {
525 let validator_set_id = <ValidatorSetId<T>>::get();
526 ensure!(
527 SetIdSession::<T>::get(validator_set_id).is_some(),
528 "Validator set id must be present in SetIdSession"
529 );
530 Ok(())
531 }
532}
533
534impl<T: Config> Pallet<T> {
535 pub fn validator_set() -> Option<ValidatorSet<T::BeefyId>> {
537 let validators: BoundedVec<T::BeefyId, T::MaxAuthorities> = Authorities::<T>::get();
538 let id: sp_consensus_beefy::ValidatorSetId = ValidatorSetId::<T>::get();
539 ValidatorSet::<T::BeefyId>::new(validators, id)
540 }
541
542 pub fn submit_unsigned_double_voting_report(
546 equivocation_proof: DoubleVotingProof<
547 BlockNumberFor<T>,
548 T::BeefyId,
549 <T::BeefyId as RuntimeAppPublic>::Signature,
550 >,
551 key_owner_proof: T::KeyOwnerProof,
552 ) -> Option<()> {
553 T::EquivocationReportSystem::publish_evidence(EquivocationEvidenceFor::DoubleVotingProof(
554 equivocation_proof,
555 key_owner_proof,
556 ))
557 .ok()
558 }
559
560 pub fn submit_unsigned_fork_voting_report(
564 equivocation_proof: ForkVotingProof<
565 HeaderFor<T>,
566 T::BeefyId,
567 <T::AncestryHelper as AncestryHelper<HeaderFor<T>>>::Proof,
568 >,
569 key_owner_proof: T::KeyOwnerProof,
570 ) -> Option<()> {
571 T::EquivocationReportSystem::publish_evidence(EquivocationEvidenceFor::ForkVotingProof(
572 equivocation_proof,
573 key_owner_proof,
574 ))
575 .ok()
576 }
577
578 pub fn submit_unsigned_future_block_voting_report(
582 equivocation_proof: FutureBlockVotingProof<BlockNumberFor<T>, T::BeefyId>,
583 key_owner_proof: T::KeyOwnerProof,
584 ) -> Option<()> {
585 T::EquivocationReportSystem::publish_evidence(
586 EquivocationEvidenceFor::FutureBlockVotingProof(equivocation_proof, key_owner_proof),
587 )
588 .ok()
589 }
590
591 fn change_authorities(
592 new: BoundedVec<T::BeefyId, T::MaxAuthorities>,
593 queued: BoundedVec<T::BeefyId, T::MaxAuthorities>,
594 ) {
595 Authorities::<T>::put(&new);
596
597 let new_id = ValidatorSetId::<T>::get() + 1u64;
598 ValidatorSetId::<T>::put(new_id);
599
600 NextAuthorities::<T>::put(&queued);
601
602 if let Some(validator_set) = ValidatorSet::<T::BeefyId>::new(new, new_id) {
603 let log = DigestItem::Consensus(
604 BEEFY_ENGINE_ID,
605 ConsensusLog::AuthoritiesChange(validator_set.clone()).encode(),
606 );
607 frame_system::Pallet::<T>::deposit_log(log);
608
609 let next_id = new_id + 1;
610 if let Some(next_validator_set) = ValidatorSet::<T::BeefyId>::new(queued, next_id) {
611 <T::OnNewValidatorSet as OnNewValidatorSet<_>>::on_new_validator_set(
612 &validator_set,
613 &next_validator_set,
614 );
615 }
616 }
617 }
618
619 fn initialize(authorities: &Vec<T::BeefyId>) -> Result<(), ()> {
620 if authorities.is_empty() {
621 return Ok(())
622 }
623
624 if !Authorities::<T>::get().is_empty() {
625 return Err(())
626 }
627
628 let bounded_authorities =
629 BoundedSlice::<T::BeefyId, T::MaxAuthorities>::try_from(authorities.as_slice())
630 .map_err(|_| ())?;
631
632 let id = GENESIS_AUTHORITY_SET_ID;
633 Authorities::<T>::put(bounded_authorities);
634 ValidatorSetId::<T>::put(id);
635 NextAuthorities::<T>::put(bounded_authorities);
637
638 if let Some(validator_set) = ValidatorSet::<T::BeefyId>::new(authorities.clone(), id) {
639 let next_id = id + 1;
640 if let Some(next_validator_set) =
641 ValidatorSet::<T::BeefyId>::new(authorities.clone(), next_id)
642 {
643 <T::OnNewValidatorSet as OnNewValidatorSet<_>>::on_new_validator_set(
644 &validator_set,
645 &next_validator_set,
646 );
647 }
648 }
649
650 SetIdSession::<T>::insert(0, 0);
654
655 Ok(())
656 }
657}
658
659impl<T: Config> sp_runtime::BoundToRuntimeAppPublic for Pallet<T> {
660 type Public = T::BeefyId;
661}
662
663impl<T: Config> OneSessionHandler<T::AccountId> for Pallet<T>
664where
665 T: pallet_session::Config,
666{
667 type Key = T::BeefyId;
668
669 fn on_genesis_session<'a, I: 'a>(validators: I)
670 where
671 I: Iterator<Item = (&'a T::AccountId, T::BeefyId)>,
672 {
673 let authorities = validators.map(|(_, k)| k).collect::<Vec<_>>();
674 Self::initialize(&authorities).expect("Authorities vec too big");
677 }
678
679 fn on_new_session<'a, I: 'a>(_changed: bool, validators: I, queued_validators: I)
680 where
681 I: Iterator<Item = (&'a T::AccountId, T::BeefyId)>,
682 {
683 let next_authorities = validators.map(|(_, k)| k).collect::<Vec<_>>();
684 if next_authorities.len() as u32 > T::MaxAuthorities::get() {
685 log::error!(
686 target: LOG_TARGET,
687 "authorities list {:?} truncated to length {}",
688 next_authorities,
689 T::MaxAuthorities::get(),
690 );
691 }
692 let bounded_next_authorities =
693 BoundedVec::<_, T::MaxAuthorities>::truncate_from(next_authorities);
694
695 let next_queued_authorities = queued_validators.map(|(_, k)| k).collect::<Vec<_>>();
696 if next_queued_authorities.len() as u32 > T::MaxAuthorities::get() {
697 log::error!(
698 target: LOG_TARGET,
699 "queued authorities list {:?} truncated to length {}",
700 next_queued_authorities,
701 T::MaxAuthorities::get(),
702 );
703 }
704 let bounded_next_queued_authorities =
705 BoundedVec::<_, T::MaxAuthorities>::truncate_from(next_queued_authorities);
706
707 Self::change_authorities(bounded_next_authorities, bounded_next_queued_authorities);
710
711 let validator_set_id = ValidatorSetId::<T>::get();
712 let session_index = pallet_session::Pallet::<T>::current_index();
714 SetIdSession::<T>::insert(validator_set_id, &session_index);
715 let max_set_id_session_entries = T::MaxSetIdSessionEntries::get().max(1);
717 if validator_set_id >= max_set_id_session_entries {
718 SetIdSession::<T>::remove(validator_set_id - max_set_id_session_entries);
719 }
720 }
721
722 fn on_disabled(i: u32) {
723 let log = DigestItem::Consensus(
724 BEEFY_ENGINE_ID,
725 ConsensusLog::<T::BeefyId>::OnDisabled(i as AuthorityIndex).encode(),
726 );
727
728 frame_system::Pallet::<T>::deposit_log(log);
729 }
730}
731
732impl<T: Config> IsMember<T::BeefyId> for Pallet<T> {
733 fn is_member(authority_id: &T::BeefyId) -> bool {
734 Authorities::<T>::get().iter().any(|id| id == authority_id)
735 }
736}
737
738pub trait WeightInfo {
739 fn report_voting_equivocation(
740 votes_count: u32,
741 validator_count: u32,
742 max_nominators_per_validator: u32,
743 ) -> Weight;
744
745 fn set_new_genesis() -> Weight;
746}
747
748pub(crate) trait WeightInfoExt: WeightInfo {
749 fn report_double_voting(validator_count: u32, max_nominators_per_validator: u32) -> Weight {
750 Self::report_voting_equivocation(2, validator_count, max_nominators_per_validator)
751 }
752
753 fn report_fork_voting<T: Config>(
754 validator_count: u32,
755 max_nominators_per_validator: u32,
756 ancestry_proof: &<T::AncestryHelper as AncestryHelper<HeaderFor<T>>>::Proof,
757 ) -> Weight {
758 <T::AncestryHelper as AncestryHelperWeightInfo<HeaderFor<T>>>::is_proof_optimal(&ancestry_proof)
759 .saturating_add(<T::AncestryHelper as AncestryHelperWeightInfo<HeaderFor<T>>>::extract_validation_context())
760 .saturating_add(
761 <T::AncestryHelper as AncestryHelperWeightInfo<HeaderFor<T>>>::is_non_canonical(
762 ancestry_proof,
763 ),
764 )
765 .saturating_add(Self::report_voting_equivocation(
766 1,
767 validator_count,
768 max_nominators_per_validator,
769 ))
770 }
771
772 fn report_future_block_voting(
773 validator_count: u32,
774 max_nominators_per_validator: u32,
775 ) -> Weight {
776 DbWeight::get()
778 .reads(1)
779 .saturating_add(Self::report_voting_equivocation(
781 1,
782 validator_count,
783 max_nominators_per_validator,
784 ))
785 }
786}
787
788impl<T> WeightInfoExt for T where T: WeightInfo {}