From f602ef5f6463f85f6f782c4291cca5a754dec05f Mon Sep 17 00:00:00 2001 From: Adrian Catangiu Date: Mon, 14 Aug 2023 15:35:36 +0300 Subject: [PATCH 1/2] frame/beefy: add privileged call to reset BEEFY consensus --- frame/beefy/src/default_weights.rs | 4 ++++ frame/beefy/src/lib.rs | 27 ++++++++++++++++++++++++--- frame/beefy/src/tests.rs | 21 +++++++++++++++++++++ 3 files changed, 49 insertions(+), 3 deletions(-) diff --git a/frame/beefy/src/default_weights.rs b/frame/beefy/src/default_weights.rs index 091d58f47f978..8042f0c932eb6 100644 --- a/frame/beefy/src/default_weights.rs +++ b/frame/beefy/src/default_weights.rs @@ -49,4 +49,8 @@ impl crate::WeightInfo for () { // fetching set id -> session index mappings .saturating_add(DbWeight::get().reads(2)) } + + fn set_new_genesis() -> Weight { + DbWeight::get().writes(1) + } } diff --git a/frame/beefy/src/lib.rs b/frame/beefy/src/lib.rs index 35d3273e1ef76..e542056ac7d8e 100644 --- a/frame/beefy/src/lib.rs +++ b/frame/beefy/src/lib.rs @@ -62,7 +62,7 @@ const LOG_TARGET: &str = "runtime::beefy"; #[frame_support::pallet] pub mod pallet { use super::*; - use frame_system::pallet_prelude::BlockNumberFor; + use frame_system::{ensure_root, pallet_prelude::BlockNumberFor}; #[pallet::config] pub trait Config: frame_system::Config { @@ -152,8 +152,8 @@ pub mod pallet { StorageMap<_, Twox64Concat, sp_consensus_beefy::ValidatorSetId, SessionIndex>; /// Block number where BEEFY consensus is enabled/started. - /// By changing this (through governance or sudo), BEEFY consensus is effectively - /// restarted from the new block number. + /// By changing this (through privileged `set_new_genesis()`), BEEFY consensus is effectively + /// restarted from the newly set block number. #[pallet::storage] #[pallet::getter(fn genesis_block)] pub(super) type GenesisBlock = @@ -198,6 +198,8 @@ pub mod pallet { InvalidEquivocationProof, /// A given equivocation report is valid but already previously reported. DuplicateOffenceReport, + /// Submitted configuration is invalid. + InvalidConfiguration, } #[pallet::call] @@ -265,6 +267,24 @@ pub mod pallet { )?; Ok(Pays::No.into()) } + + /// Reset BEEFY consensus by setting a new BEEFY genesis block. + /// + /// Note: new genesis cannot be set to a past block. + #[pallet::call_index(2)] + #[pallet::weight(::WeightInfo::set_new_genesis())] + pub fn set_new_genesis( + origin: OriginFor, + genesis_block: BlockNumberFor, + ) -> DispatchResult { + ensure_root(origin)?; + ensure!( + genesis_block >= frame_system::Pallet::::block_number(), + Error::::InvalidConfiguration + ); + GenesisBlock::::put(Some(genesis_block)); + Ok(()) + } } #[pallet::validate_unsigned] @@ -452,4 +472,5 @@ impl IsMember for Pallet { pub trait WeightInfo { fn report_equivocation(validator_count: u32, max_nominators_per_validator: u32) -> Weight; + fn set_new_genesis() -> Weight; } diff --git a/frame/beefy/src/tests.rs b/frame/beefy/src/tests.rs index e04dc330d0c07..8cdfa07756626 100644 --- a/frame/beefy/src/tests.rs +++ b/frame/beefy/src/tests.rs @@ -791,3 +791,24 @@ fn valid_equivocation_reports_dont_pay_fees() { assert_eq!(post_info.pays_fee, Pays::Yes); }) } + +#[test] +fn set_new_genesis_works() { + let authorities = test_authorities(); + + new_test_ext_raw_authorities(authorities).execute_with(|| { + start_era(1); + + let new_genesis = 10u64; + // the call for setting new genesis should work + assert_ok!(Beefy::set_new_genesis(RuntimeOrigin::root(), new_genesis,)); + // verify new genesis was set + assert_eq!(Beefy::genesis_block(), Some(new_genesis)); + + // setting to old block should fail + assert_err!( + Beefy::set_new_genesis(RuntimeOrigin::root(), 1u64,), + Error::::InvalidConfiguration, + ); + }); +} From 9d36df5642195e6618bde9c405f9ec8b7df1b76d Mon Sep 17 00:00:00 2001 From: Adrian Catangiu Date: Mon, 14 Aug 2023 16:47:10 +0300 Subject: [PATCH 2/2] frame/beefy: deposit log on consensus reset --- frame/beefy/src/lib.rs | 24 +++++++++++ frame/beefy/src/mock.rs | 47 +++++++++++---------- frame/beefy/src/tests.rs | 61 +++++++++++++++++++++++++-- primitives/consensus/beefy/src/lib.rs | 3 ++ 4 files changed, 109 insertions(+), 26 deletions(-) diff --git a/frame/beefy/src/lib.rs b/frame/beefy/src/lib.rs index e542056ac7d8e..e5b298bbd886d 100644 --- a/frame/beefy/src/lib.rs +++ b/frame/beefy/src/lib.rs @@ -299,6 +299,30 @@ pub mod pallet { Self::validate_unsigned(source, call) } } + + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_initialize(_n: BlockNumberFor) -> Weight { + // weight used by `on_finalize()` hook + frame_support::weights::constants::RocksDbWeight::get().reads(1) + } + + fn on_finalize(n: BlockNumberFor) { + // this check is only true when consensus is reset, so we can safely + // estimate the average weight is just one Db read. + if GenesisBlock::::get() == Some(n) { + let validators = Authorities::::get(); + let set_id = ValidatorSetId::::get(); + if let Some(validator_set) = ValidatorSet::::new(validators, set_id) { + let log = DigestItem::Consensus( + BEEFY_ENGINE_ID, + ConsensusLog::ConsensusReset(validator_set).encode(), + ); + >::deposit_log(log); + } + } + } + } } impl Pallet { diff --git a/frame/beefy/src/mock.rs b/frame/beefy/src/mock.rs index f2d8415bc01f7..cf44335aba001 100644 --- a/frame/beefy/src/mock.rs +++ b/frame/beefy/src/mock.rs @@ -300,31 +300,34 @@ pub fn new_test_ext_raw_authorities(authorities: Vec) -> TestExternalit t.into() } +pub fn push_block(i: u32) { + System::on_finalize(System::block_number()); + Session::on_finalize(System::block_number()); + Staking::on_finalize(System::block_number()); + Beefy::on_finalize(System::block_number()); + + let parent_hash = if System::block_number() > 1 { + let hdr = System::finalize(); + hdr.hash() + } else { + System::parent_hash() + }; + + System::reset_events(); + System::initialize(&(i as u64 + 1), &parent_hash, &Default::default()); + System::set_block_number((i + 1).into()); + Timestamp::set_timestamp(System::block_number() * 6000); + + System::on_initialize(System::block_number()); + Session::on_initialize(System::block_number()); + Staking::on_initialize(System::block_number()); + Beefy::on_initialize(System::block_number()); +} + pub fn start_session(session_index: SessionIndex) { for i in Session::current_index()..session_index { - System::on_finalize(System::block_number()); - Session::on_finalize(System::block_number()); - Staking::on_finalize(System::block_number()); - Beefy::on_finalize(System::block_number()); - - let parent_hash = if System::block_number() > 1 { - let hdr = System::finalize(); - hdr.hash() - } else { - System::parent_hash() - }; - - System::reset_events(); - System::initialize(&(i as u64 + 1), &parent_hash, &Default::default()); - System::set_block_number((i + 1).into()); - Timestamp::set_timestamp(System::block_number() * 6000); - - System::on_initialize(System::block_number()); - Session::on_initialize(System::block_number()); - Staking::on_initialize(System::block_number()); - Beefy::on_initialize(System::block_number()); + push_block(i); } - assert_eq!(Session::current_index(), session_index); } diff --git a/frame/beefy/src/tests.rs b/frame/beefy/src/tests.rs index 8cdfa07756626..4bd6b6d4318de 100644 --- a/frame/beefy/src/tests.rs +++ b/frame/beefy/src/tests.rs @@ -17,7 +17,7 @@ use std::vec; -use codec::Encode; +use codec::{Decode, Encode}; use sp_consensus_beefy::{ check_equivocation_proof, generate_equivocation_proof, known_payloads::MMR_ROOT_ID, Keyring as BeefyKeyring, Payload, ValidatorSet, KEY_TYPE as BEEFY_KEY_TYPE, @@ -28,7 +28,7 @@ use sp_runtime::DigestItem; use frame_support::{ assert_err, assert_ok, dispatch::{GetDispatchInfo, Pays}, - traits::{Currency, KeyOwnerProofSystem, OnInitialize}, + traits::{Currency, KeyOwnerProofSystem, OnFinalize, OnInitialize}, }; use crate::{mock::*, Call, Config, Error, Weight, WeightInfo}; @@ -801,14 +801,67 @@ fn set_new_genesis_works() { let new_genesis = 10u64; // the call for setting new genesis should work - assert_ok!(Beefy::set_new_genesis(RuntimeOrigin::root(), new_genesis,)); + assert_ok!(Beefy::set_new_genesis(RuntimeOrigin::root(), new_genesis)); // verify new genesis was set assert_eq!(Beefy::genesis_block(), Some(new_genesis)); // setting to old block should fail assert_err!( - Beefy::set_new_genesis(RuntimeOrigin::root(), 1u64,), + Beefy::set_new_genesis(RuntimeOrigin::root(), 1u64), Error::::InvalidConfiguration, ); }); } + +#[test] +fn deposit_consensus_reset_log_works() { + let authorities = test_authorities(); + + new_test_ext_raw_authorities(authorities).execute_with(|| { + fn find_consensus_reset_log() -> Option> { + for log in System::digest().logs.iter_mut() { + if let DigestItem::Consensus(BEEFY_ENGINE_ID, inner_log) = log { + let inner_decoded = + ConsensusLog::::decode(&mut &inner_log[..]).unwrap(); + if matches!(inner_decoded, ConsensusLog::ConsensusReset(_)) { + return Some(inner_decoded) + } + } + } + None + } + + start_era(1); + push_block(4); + + // no consensus reset log + assert!(find_consensus_reset_log().is_none()); + + let new_genesis = 6u64; + // the call for setting new genesis should work + assert_ok!(Beefy::set_new_genesis(RuntimeOrigin::root(), new_genesis)); + // verify new genesis was set + assert_eq!(Beefy::genesis_block(), Some(new_genesis)); + + // there should still be no digest (yet) + assert!(find_consensus_reset_log().is_none()); + + push_block(5); + + // there should still be no digest (yet) + assert!(find_consensus_reset_log().is_none()); + + assert_eq!(System::block_number(), new_genesis); + Beefy::on_finalize(System::block_number()); + + // consensus reset log should be there now + let log = find_consensus_reset_log().unwrap(); + + // validate logged authorities + let validators = Beefy::authorities(); + let set_id = Beefy::validator_set_id(); + let want_validator_set = ValidatorSet::::new(validators, set_id).unwrap(); + let want = ConsensusLog::ConsensusReset(want_validator_set); + assert_eq!(want.encode(), log.encode()); + }); +} diff --git a/primitives/consensus/beefy/src/lib.rs b/primitives/consensus/beefy/src/lib.rs index c69e26bf574d8..f4315984bfa70 100644 --- a/primitives/consensus/beefy/src/lib.rs +++ b/primitives/consensus/beefy/src/lib.rs @@ -209,6 +209,9 @@ pub enum ConsensusLog { /// MMR root hash. #[codec(index = 3)] MmrRoot(MmrRootHash), + /// BEEFY consensus has been reset. + #[codec(index = 4)] + ConsensusReset(ValidatorSet), } /// BEEFY vote message.