diff --git a/client/beefy/src/aux_schema.rs b/client/beefy/src/aux_schema.rs index fafa9948c5444..217ea4769d8b9 100644 --- a/client/beefy/src/aux_schema.rs +++ b/client/beefy/src/aux_schema.rs @@ -28,7 +28,7 @@ use sp_runtime::traits::Block as BlockT; const VERSION_KEY: &[u8] = b"beefy_auxschema_version"; const WORKER_STATE: &[u8] = b"beefy_voter_state"; -const CURRENT_VERSION: u32 = 1; +const CURRENT_VERSION: u32 = 2; pub(crate) fn write_current_version(backend: &B) -> ClientResult<()> { info!(target: LOG_TARGET, "🥩 write aux schema version {:?}", CURRENT_VERSION); @@ -63,7 +63,8 @@ where match version { None => (), - Some(1) => return load_decode::<_, PersistedState>(backend, WORKER_STATE), + Some(1) => (), // version 1 is totally obsolete and should be simply ignored + Some(2) => return load_decode::<_, PersistedState>(backend, WORKER_STATE), other => return Err(ClientError::Backend(format!("Unsupported BEEFY DB version: {:?}", other))), } diff --git a/client/beefy/src/lib.rs b/client/beefy/src/lib.rs index 185f3b1ad502e..d7de7295a4a11 100644 --- a/client/beefy/src/lib.rs +++ b/client/beefy/src/lib.rs @@ -53,7 +53,7 @@ use sp_keystore::SyncCryptoStorePtr; use sp_mmr_primitives::MmrApi; use sp_runtime::{ generic::BlockId, - traits::{Block, One, Zero}, + traits::{Block, Zero}, }; use std::{collections::VecDeque, marker::PhantomData, sync::Arc}; @@ -346,6 +346,12 @@ where R: ProvideRuntimeApi, R::Api: BeefyApi, { + let beefy_genesis = runtime + .runtime_api() + .beefy_genesis(&BlockId::hash(best_grandpa.hash())) + .ok() + .flatten() + .ok_or_else(|| ClientError::Backend("BEEFY pallet expected to be active.".into()))?; // Walk back the imported blocks and initialize voter either, at the last block with // a BEEFY justification, or at pallet genesis block; voter will resume from there. let blockchain = backend.blockchain(); @@ -378,20 +384,19 @@ where break state } - if *header.number() == One::one() { - // We've reached chain genesis, initialize voter here. - let genesis_num = *header.number(); + if *header.number() == beefy_genesis { + // We've reached BEEFY genesis, initialize voter here. let genesis_set = expect_validator_set(runtime, BlockId::hash(header.hash())) .and_then(genesis_set_sanity_check)?; info!( target: LOG_TARGET, "🥩 Loading BEEFY voter state from genesis on what appears to be first startup. \ Starting voting rounds at block {:?}, genesis validator set {:?}.", - genesis_num, + beefy_genesis, genesis_set, ); - sessions.push_front(Rounds::new(genesis_num, genesis_set)); + sessions.push_front(Rounds::new(beefy_genesis, genesis_set)); break PersistedState::checked_new(best_grandpa, Zero::zero(), sessions, min_block_delta) .ok_or_else(|| ClientError::Backend("Invalid BEEFY chain".into()))? } @@ -448,13 +453,16 @@ where None => break }; let at = BlockId::hash(notif.header.hash()); - if let Some(active) = runtime.runtime_api().validator_set(&at).ok().flatten() { - // Beefy pallet available, return best grandpa at the time. - info!( - target: LOG_TARGET, "🥩 BEEFY pallet available: block {:?} validator set {:?}", - notif.header.number(), active - ); - return Ok(notif.header) + if let Some(start) = runtime.runtime_api().beefy_genesis(&at).ok().flatten() { + if *notif.header.number() >= start { + // Beefy pallet available, return header for best grandpa at the time. + info!( + target: LOG_TARGET, + "🥩 BEEFY pallet available: block {:?} beefy genesis {:?}", + notif.header.number(), start + ); + return Ok(notif.header) + } } }, _ = gossip_engine => { diff --git a/client/beefy/src/tests.rs b/client/beefy/src/tests.rs index 2642035ba5dd5..008137d9a9965 100644 --- a/client/beefy/src/tests.rs +++ b/client/beefy/src/tests.rs @@ -77,8 +77,8 @@ const BAD_MMR_ROOT: MmrRootHash = MmrRootHash::repeat_byte(0x42); type BeefyBlockImport = crate::BeefyBlockImport< Block, substrate_test_runtime_client::Backend, - two_validators::TestApi, - BlockImportAdapter>, + TestApi, + BlockImportAdapter>, >; pub(crate) type BeefyValidatorSet = ValidatorSet; @@ -198,12 +198,12 @@ impl TestNetFactory for BeefyTestNet { Option>, Self::PeerData, ) { + let keys = &[BeefyKeyring::Alice, BeefyKeyring::Bob]; + let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); + let api = Arc::new(TestApi::with_validator_set(&validator_set)); let inner = BlockImportAdapter::new(client.clone()); - let (block_import, voter_links, rpc_links) = beefy_block_import_and_links( - inner, - client.as_backend(), - Arc::new(two_validators::TestApi {}), - ); + let (block_import, voter_links, rpc_links) = + beefy_block_import_and_links(inner, client.as_backend(), api); let peer_data = PeerData { beefy_rpc_links: Mutex::new(Some(rpc_links)), beefy_voter_links: Mutex::new(Some(voter_links)), @@ -230,79 +230,79 @@ impl TestNetFactory for BeefyTestNet { } } -macro_rules! create_test_api { - ( $api_name:ident, mmr_root: $mmr_root:expr, $($inits:expr),+ ) => { - pub(crate) mod $api_name { - use super::*; - - #[derive(Clone, Default)] - pub(crate) struct TestApi {} +#[derive(Clone)] +pub(crate) struct TestApi { + pub beefy_genesis: u64, + pub validator_set: BeefyValidatorSet, + pub mmr_root_hash: MmrRootHash, +} - // compiler gets confused and warns us about unused inner - #[allow(dead_code)] - pub(crate) struct RuntimeApi { - inner: TestApi, - } +impl TestApi { + pub fn new( + beefy_genesis: u64, + validator_set: &BeefyValidatorSet, + mmr_root_hash: MmrRootHash, + ) -> Self { + TestApi { beefy_genesis, validator_set: validator_set.clone(), mmr_root_hash } + } - impl ProvideRuntimeApi for TestApi { - type Api = RuntimeApi; - fn runtime_api<'a>(&'a self) -> ApiRef<'a, Self::Api> { - RuntimeApi { inner: self.clone() }.into() - } - } - sp_api::mock_impl_runtime_apis! { - impl BeefyApi for RuntimeApi { - fn validator_set() -> Option { - BeefyValidatorSet::new(make_beefy_ids(&[$($inits),+]), 0) - } - } - - impl MmrApi> for RuntimeApi { - fn mmr_root() -> Result { - Ok($mmr_root) - } - - fn generate_proof( - _block_numbers: Vec, - _best_known_block_number: Option - ) -> Result<(Vec, Proof), MmrError> { - unimplemented!() - } - - fn verify_proof(_leaves: Vec, _proof: Proof) -> Result<(), MmrError> { - unimplemented!() - } - - fn verify_proof_stateless( - _root: MmrRootHash, - _leaves: Vec, - _proof: Proof - ) -> Result<(), MmrError> { - unimplemented!() - } - } - } + pub fn with_validator_set(validator_set: &BeefyValidatorSet) -> Self { + TestApi { + beefy_genesis: 1, + validator_set: validator_set.clone(), + mmr_root_hash: GOOD_MMR_ROOT, } - }; + } +} + +// compiler gets confused and warns us about unused inner +#[allow(dead_code)] +pub(crate) struct RuntimeApi { + inner: TestApi, +} + +impl ProvideRuntimeApi for TestApi { + type Api = RuntimeApi; + fn runtime_api(&self) -> ApiRef { + RuntimeApi { inner: self.clone() }.into() + } } +sp_api::mock_impl_runtime_apis! { + impl BeefyApi for RuntimeApi { + fn beefy_genesis() -> Option> { + Some(self.inner.beefy_genesis) + } + + fn validator_set() -> Option { + Some(self.inner.validator_set.clone()) + } + } + + impl MmrApi> for RuntimeApi { + fn mmr_root() -> Result { + Ok(self.inner.mmr_root_hash) + } + + fn generate_proof( + _block_numbers: Vec, + _best_known_block_number: Option + ) -> Result<(Vec, Proof), MmrError> { + unimplemented!() + } + + fn verify_proof(_leaves: Vec, _proof: Proof) -> Result<(), MmrError> { + unimplemented!() + } -create_test_api!(two_validators, mmr_root: GOOD_MMR_ROOT, BeefyKeyring::Alice, BeefyKeyring::Bob); -create_test_api!( - four_validators, - mmr_root: GOOD_MMR_ROOT, - BeefyKeyring::Alice, - BeefyKeyring::Bob, - BeefyKeyring::Charlie, - BeefyKeyring::Dave -); -create_test_api!( - bad_four_validators, - mmr_root: BAD_MMR_ROOT, - BeefyKeyring::Alice, - BeefyKeyring::Bob, - BeefyKeyring::Charlie, - BeefyKeyring::Dave -); + fn verify_proof_stateless( + _root: MmrRootHash, + _leaves: Vec, + _proof: Proof + ) -> Result<(), MmrError> { + unimplemented!() + } + } +} fn add_mmr_digest(header: &mut Header, mmr_hash: MmrRootHash) { header.digest_mut().push(DigestItem::Consensus( @@ -332,9 +332,9 @@ pub(crate) fn create_beefy_keystore(authority: BeefyKeyring) -> SyncCryptoStoreP fn voter_init_setup( net: &mut BeefyTestNet, finality: &mut futures::stream::Fuse>, + api: &TestApi, ) -> sp_blockchain::Result> { let backend = net.peer(0).client().as_backend(); - let api = Arc::new(crate::tests::two_validators::TestApi {}); let known_peers = Arc::new(Mutex::new(KnownPeers::new())); let gossip_validator = Arc::new(crate::communication::gossip::GossipValidator::new(known_peers)); @@ -345,9 +345,9 @@ fn voter_init_setup( None, ); let best_grandpa = - futures::executor::block_on(wait_for_runtime_pallet(&*api, &mut gossip_engine, finality)) + futures::executor::block_on(wait_for_runtime_pallet(api, &mut gossip_engine, finality)) .unwrap(); - load_or_init_voter_state(&*backend, &*api, best_grandpa, 1) + load_or_init_voter_state(&*backend, api, best_grandpa, 1) } // Spawns beefy voters. Returns a future to spawn on the runtime. @@ -357,7 +357,7 @@ fn initialize_beefy( min_block_delta: u32, ) -> impl Future where - API: ProvideRuntimeApi + Default + Sync + Send, + API: ProvideRuntimeApi + Sync + Send, API::Api: BeefyApi + MmrApi>, { let tasks = FuturesUnordered::new(); @@ -544,7 +544,7 @@ async fn beefy_finalizing_blocks() { let mut net = BeefyTestNet::new(2); - let api = Arc::new(two_validators::TestApi {}); + let api = Arc::new(TestApi::with_validator_set(&validator_set)); let beefy_peers = peers.iter().enumerate().map(|(id, key)| (id, key, api.clone())).collect(); tokio::spawn(initialize_beefy(&mut net, beefy_peers, min_block_delta)); @@ -582,7 +582,7 @@ async fn lagging_validators() { let min_block_delta = 1; let mut net = BeefyTestNet::new(2); - let api = Arc::new(two_validators::TestApi {}); + let api = Arc::new(TestApi::with_validator_set(&validator_set)); let beefy_peers = peers.iter().enumerate().map(|(id, key)| (id, key, api.clone())).collect(); tokio::spawn(initialize_beefy(&mut net, beefy_peers, min_block_delta)); @@ -660,7 +660,7 @@ async fn correct_beefy_payload() { let mut net = BeefyTestNet::new(4); // Alice, Bob, Charlie will vote on good payloads - let good_api = Arc::new(four_validators::TestApi {}); + let good_api = Arc::new(TestApi::new(1, &validator_set, GOOD_MMR_ROOT)); let good_peers = [BeefyKeyring::Alice, BeefyKeyring::Bob, BeefyKeyring::Charlie] .iter() .enumerate() @@ -669,7 +669,7 @@ async fn correct_beefy_payload() { tokio::spawn(initialize_beefy(&mut net, good_peers, min_block_delta)); // Dave will vote on bad mmr roots - let bad_api = Arc::new(bad_four_validators::TestApi {}); + let bad_api = Arc::new(TestApi::new(1, &validator_set, BAD_MMR_ROOT)); let bad_peers = vec![(3, &BeefyKeyring::Dave, bad_api)]; tokio::spawn(initialize_beefy(&mut net, bad_peers, min_block_delta)); @@ -714,6 +714,8 @@ async fn beefy_importing_blocks() { sp_tracing::try_init_simple(); let mut net = BeefyTestNet::new(2); + let keys = &[BeefyKeyring::Alice, BeefyKeyring::Bob]; + let good_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); let client = net.peer(0).client().clone(); let (mut block_import, _, peer_data) = net.make_block_import(client.clone()); @@ -769,9 +771,7 @@ async fn beefy_importing_blocks() { // Import with valid justification. let parent_id = BlockId::Number(1); let block_num = 2; - let keys = &[BeefyKeyring::Alice, BeefyKeyring::Bob]; - let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); - let proof = crate::justification::tests::new_finality_proof(block_num, &validator_set, keys); + let proof = crate::justification::tests::new_finality_proof(block_num, &good_set, keys); let versioned_proof: VersionedFinalityProof, Signature> = proof.into(); let encoded = versioned_proof.encode(); let justif = Some(Justifications::from((BEEFY_ENGINE_ID, encoded))); @@ -814,8 +814,8 @@ async fn beefy_importing_blocks() { let parent_id = BlockId::Number(2); let block_num = 3; let keys = &[BeefyKeyring::Alice]; - let validator_set = ValidatorSet::new(make_beefy_ids(keys), 1).unwrap(); - let proof = crate::justification::tests::new_finality_proof(block_num, &validator_set, keys); + let bad_set = ValidatorSet::new(make_beefy_ids(keys), 1).unwrap(); + let proof = crate::justification::tests::new_finality_proof(block_num, &bad_set, keys); let versioned_proof: VersionedFinalityProof, Signature> = proof.into(); let encoded = versioned_proof.encode(); let justif = Some(Justifications::from((BEEFY_ENGINE_ID, encoded))); @@ -865,7 +865,7 @@ async fn voter_initialization() { let min_block_delta = 10; let mut net = BeefyTestNet::new(2); - let api = Arc::new(two_validators::TestApi {}); + let api = Arc::new(TestApi::with_validator_set(&validator_set)); let beefy_peers = peers.iter().enumerate().map(|(id, key)| (id, key, api.clone())).collect(); tokio::spawn(initialize_beefy(&mut net, beefy_peers, min_block_delta)); @@ -898,7 +898,7 @@ async fn on_demand_beefy_justification_sync() { let mut net = BeefyTestNet::new(4); // Alice, Bob, Charlie start first and make progress through voting. - let api = Arc::new(four_validators::TestApi {}); + let api = Arc::new(TestApi::with_validator_set(&validator_set)); let fast_peers = [BeefyKeyring::Alice, BeefyKeyring::Bob, BeefyKeyring::Charlie]; let voting_peers = fast_peers.iter().enumerate().map(|(id, key)| (id, key, api.clone())).collect(); @@ -977,8 +977,9 @@ async fn should_initialize_voter_at_genesis() { // finalize 13 without justifications net.peer(0).client().as_client().finalize_block(hashes[13], None).unwrap(); - // load persistent state - nothing in DB, should init at session boundary - let persisted_state = voter_init_setup(&mut net, &mut finality).unwrap(); + let api = TestApi::with_validator_set(&validator_set); + // load persistent state - nothing in DB, should init at genesis + let persisted_state = voter_init_setup(&mut net, &mut finality, &api).unwrap(); // Test initialization at session boundary. // verify voter initialized with two sessions starting at blocks 1 and 10 @@ -1006,6 +1007,54 @@ async fn should_initialize_voter_at_genesis() { assert_eq!(state, persisted_state); } +#[tokio::test] +async fn should_initialize_voter_at_custom_genesis() { + let keys = &[BeefyKeyring::Alice]; + let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); + let mut net = BeefyTestNet::new(1); + let backend = net.peer(0).client().as_backend(); + // custom pallet genesis is block number 7 + let custom_pallet_genesis = 7; + let api = TestApi::new(custom_pallet_genesis, &validator_set, GOOD_MMR_ROOT); + + // push 15 blocks with `AuthorityChange` digests every 10 blocks + let hashes = net.generate_blocks_and_sync(15, 10, &validator_set, false).await; + + let mut finality = net.peer(0).client().as_client().finality_notification_stream().fuse(); + + // finalize 3, 5, 8 without justifications + net.peer(0).client().as_client().finalize_block(hashes[3], None).unwrap(); + net.peer(0).client().as_client().finalize_block(hashes[5], None).unwrap(); + net.peer(0).client().as_client().finalize_block(hashes[8], None).unwrap(); + + // load persistent state - nothing in DB, should init at genesis + let persisted_state = voter_init_setup(&mut net, &mut finality, &api).unwrap(); + + // Test initialization at session boundary. + // verify voter initialized with single session starting at block `custom_pallet_genesis` (7) + let sessions = persisted_state.voting_oracle().sessions(); + assert_eq!(sessions.len(), 1); + assert_eq!(sessions[0].session_start(), custom_pallet_genesis); + let rounds = persisted_state.active_round().unwrap(); + assert_eq!(rounds.session_start(), custom_pallet_genesis); + assert_eq!(rounds.validator_set_id(), validator_set.id()); + + // verify next vote target is mandatory block 7 + assert_eq!(persisted_state.best_beefy_block(), 0); + assert_eq!(persisted_state.best_grandpa_block(), 8); + assert_eq!( + persisted_state + .voting_oracle() + .voting_target(persisted_state.best_beefy_block(), 13), + Some(custom_pallet_genesis) + ); + + // verify state also saved to db + assert!(verify_persisted_version(&*backend)); + let state = load_persistent(&*backend).unwrap().unwrap(); + assert_eq!(state, persisted_state); +} + #[tokio::test] async fn should_initialize_voter_when_last_final_is_session_boundary() { let keys = &[BeefyKeyring::Alice]; @@ -1038,8 +1087,9 @@ async fn should_initialize_voter_when_last_final_is_session_boundary() { // Test corner-case where session boundary == last beefy finalized, // expect rounds initialized at last beefy finalized 10. + let api = TestApi::with_validator_set(&validator_set); // load persistent state - nothing in DB, should init at session boundary - let persisted_state = voter_init_setup(&mut net, &mut finality).unwrap(); + let persisted_state = voter_init_setup(&mut net, &mut finality, &api).unwrap(); // verify voter initialized with single session starting at block 10 assert_eq!(persisted_state.voting_oracle().sessions().len(), 1); @@ -1095,8 +1145,9 @@ async fn should_initialize_voter_at_latest_finalized() { // Test initialization at last BEEFY finalized. + let api = TestApi::with_validator_set(&validator_set); // load persistent state - nothing in DB, should init at last BEEFY finalized - let persisted_state = voter_init_setup(&mut net, &mut finality).unwrap(); + let persisted_state = voter_init_setup(&mut net, &mut finality, &api).unwrap(); // verify voter initialized with single session starting at block 12 assert_eq!(persisted_state.voting_oracle().sessions().len(), 1); @@ -1119,3 +1170,37 @@ async fn should_initialize_voter_at_latest_finalized() { let state = load_persistent(&*backend).unwrap().unwrap(); assert_eq!(state, persisted_state); } + +#[tokio::test] +async fn beefy_finalizing_after_pallet_genesis() { + sp_tracing::try_init_simple(); + + let peers = [BeefyKeyring::Alice, BeefyKeyring::Bob]; + let validator_set = ValidatorSet::new(make_beefy_ids(&peers), 0).unwrap(); + let session_len = 10; + let min_block_delta = 1; + let pallet_genesis = 15; + + let mut net = BeefyTestNet::new(2); + + let api = Arc::new(TestApi::new(pallet_genesis, &validator_set, GOOD_MMR_ROOT)); + let beefy_peers = peers.iter().enumerate().map(|(id, key)| (id, key, api.clone())).collect(); + tokio::spawn(initialize_beefy(&mut net, beefy_peers, min_block_delta)); + + // push 42 blocks including `AuthorityChange` digests every 10 blocks. + let hashes = net.generate_blocks_and_sync(42, session_len, &validator_set, true).await; + + let net = Arc::new(Mutex::new(net)); + let peers = peers.into_iter().enumerate(); + + // Minimum BEEFY block delta is 1. + + // GRANDPA finalize blocks leading up to BEEFY pallet genesis -> BEEFY should finalize nothing. + finalize_block_and_wait_for_beefy(&net, peers.clone(), &hashes[1..14], &[]).await; + + // GRANDPA finalize block #16 -> BEEFY should finalize #15 (genesis mandatory) and #16. + finalize_block_and_wait_for_beefy(&net, peers.clone(), &[hashes[16]], &[15, 16]).await; + + // GRANDPA finalize #21 -> BEEFY finalize #20 (mandatory) and #21 + finalize_block_and_wait_for_beefy(&net, peers.clone(), &[hashes[21]], &[20, 21]).await; +} diff --git a/client/beefy/src/worker.rs b/client/beefy/src/worker.rs index 19ab52f520225..4d057bffc0cd9 100644 --- a/client/beefy/src/worker.rs +++ b/client/beefy/src/worker.rs @@ -994,8 +994,8 @@ pub(crate) mod tests { communication::notification::{BeefyBestBlockStream, BeefyVersionedFinalityProofStream}, keystore::tests::Keyring, tests::{ - create_beefy_keystore, get_beefy_streams, make_beefy_ids, two_validators::TestApi, - BeefyPeer, BeefyTestNet, + create_beefy_keystore, get_beefy_streams, make_beefy_ids, BeefyPeer, BeefyTestNet, + TestApi, }, BeefyRPCLinks, KnownPeers, }; @@ -1068,7 +1068,7 @@ pub(crate) mod tests { }; let backend = peer.client().as_backend(); - let api = Arc::new(TestApi {}); + let api = Arc::new(TestApi::with_validator_set(&genesis_validator_set)); let network = peer.network_service().clone(); let known_peers = Arc::new(Mutex::new(KnownPeers::new())); let gossip_validator = Arc::new(GossipValidator::new(known_peers.clone())); diff --git a/frame/beefy/src/lib.rs b/frame/beefy/src/lib.rs index 4cb23107e7843..86c8763a51dd3 100644 --- a/frame/beefy/src/lib.rs +++ b/frame/beefy/src/lib.rs @@ -49,6 +49,7 @@ pub use pallet::*; pub mod pallet { use super::*; use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::BlockNumberFor; #[pallet::config] pub trait Config: frame_system::Config { @@ -91,15 +92,32 @@ pub mod pallet { pub(super) type NextAuthorities = StorageValue<_, BoundedVec, ValueQuery>; + /// Block number where BEEFY consensus is enabled/started. + /// If changing this, make sure `Self::ValidatorSetId` is also reset to + /// `GENESIS_AUTHORITY_SET_ID` in the state of the new block number configured here. + #[pallet::storage] + #[pallet::getter(fn genesis_block)] + pub(super) type GenesisBlock = + StorageValue<_, Option>, ValueQuery>; + #[pallet::genesis_config] pub struct GenesisConfig { + /// Initial set of BEEFY authorities. pub authorities: Vec, + /// Block number where BEEFY consensus should start. + /// Should match the session where initial authorities are active. + /// *Note:* Ideally use block number where GRANDPA authorities are changed, + /// to guarantee the client gets a finality notification for exactly this block. + pub genesis_block: Option>, } #[cfg(feature = "std")] impl Default for GenesisConfig { fn default() -> Self { - Self { authorities: Vec::new() } + // BEEFY genesis will be first BEEFY-MANDATORY block, + // use block number one instead of chain-genesis. + let genesis_block = Some(sp_runtime::traits::One::one()); + Self { authorities: Vec::new(), genesis_block } } } @@ -110,6 +128,7 @@ pub mod pallet { // we panic here as runtime maintainers can simply reconfigure genesis and restart // the chain easily .expect("Authorities vec too big"); + >::put(&self.genesis_block); } } } diff --git a/primitives/beefy/src/lib.rs b/primitives/beefy/src/lib.rs index eb7b8db89b214..8c219040b3523 100644 --- a/primitives/beefy/src/lib.rs +++ b/primitives/beefy/src/lib.rs @@ -43,7 +43,7 @@ use codec::{Codec, Decode, Encode}; use scale_info::TypeInfo; use sp_application_crypto::RuntimeAppPublic; use sp_core::H256; -use sp_runtime::traits::Hash; +use sp_runtime::traits::{Hash, NumberFor}; use sp_std::prelude::*; /// Key type for BEEFY module. @@ -201,6 +201,9 @@ sp_api::decl_runtime_apis! { /// API necessary for BEEFY voters. pub trait BeefyApi { + /// Return the block number where BEEFY consensus is enabled/started + fn beefy_genesis() -> Option>; + /// Return the current active BEEFY validator set fn validator_set() -> Option>; } diff --git a/test-utils/runtime/src/lib.rs b/test-utils/runtime/src/lib.rs index 8b64528e243bd..44df9b58f4e79 100644 --- a/test-utils/runtime/src/lib.rs +++ b/test-utils/runtime/src/lib.rs @@ -973,6 +973,10 @@ cfg_if! { } impl beefy_primitives::BeefyApi for Runtime { + fn beefy_genesis() -> Option { + None + } + fn validator_set() -> Option> { None }