mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-19 11:05:40 +00:00
BEEFY: define on-chain beefy-genesis and use it to coordinate voter initialization (#13215)
* beefy: add support to configure BEEFY genesis * client/beefy: more flexible test runtime api * client/beefy: add tests for custom BEEFY genesis * client/beefy: ignore old state that didn't account for pallet genesis * client/beefy: fix clippy * frame/beefy: default BEEFY-genesis is block One::one() * frame/beefy: add extra doc comments --------- Co-authored-by: parity-processbot <>
This commit is contained in:
@@ -28,7 +28,7 @@ use sp_runtime::traits::Block as BlockT;
|
|||||||
const VERSION_KEY: &[u8] = b"beefy_auxschema_version";
|
const VERSION_KEY: &[u8] = b"beefy_auxschema_version";
|
||||||
const WORKER_STATE: &[u8] = b"beefy_voter_state";
|
const WORKER_STATE: &[u8] = b"beefy_voter_state";
|
||||||
|
|
||||||
const CURRENT_VERSION: u32 = 1;
|
const CURRENT_VERSION: u32 = 2;
|
||||||
|
|
||||||
pub(crate) fn write_current_version<B: AuxStore>(backend: &B) -> ClientResult<()> {
|
pub(crate) fn write_current_version<B: AuxStore>(backend: &B) -> ClientResult<()> {
|
||||||
info!(target: LOG_TARGET, "🥩 write aux schema version {:?}", CURRENT_VERSION);
|
info!(target: LOG_TARGET, "🥩 write aux schema version {:?}", CURRENT_VERSION);
|
||||||
@@ -63,7 +63,8 @@ where
|
|||||||
|
|
||||||
match version {
|
match version {
|
||||||
None => (),
|
None => (),
|
||||||
Some(1) => return load_decode::<_, PersistedState<B>>(backend, WORKER_STATE),
|
Some(1) => (), // version 1 is totally obsolete and should be simply ignored
|
||||||
|
Some(2) => return load_decode::<_, PersistedState<B>>(backend, WORKER_STATE),
|
||||||
other =>
|
other =>
|
||||||
return Err(ClientError::Backend(format!("Unsupported BEEFY DB version: {:?}", other))),
|
return Err(ClientError::Backend(format!("Unsupported BEEFY DB version: {:?}", other))),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ use sp_keystore::SyncCryptoStorePtr;
|
|||||||
use sp_mmr_primitives::MmrApi;
|
use sp_mmr_primitives::MmrApi;
|
||||||
use sp_runtime::{
|
use sp_runtime::{
|
||||||
generic::BlockId,
|
generic::BlockId,
|
||||||
traits::{Block, One, Zero},
|
traits::{Block, Zero},
|
||||||
};
|
};
|
||||||
use std::{collections::VecDeque, marker::PhantomData, sync::Arc};
|
use std::{collections::VecDeque, marker::PhantomData, sync::Arc};
|
||||||
|
|
||||||
@@ -346,6 +346,12 @@ where
|
|||||||
R: ProvideRuntimeApi<B>,
|
R: ProvideRuntimeApi<B>,
|
||||||
R::Api: BeefyApi<B>,
|
R::Api: BeefyApi<B>,
|
||||||
{
|
{
|
||||||
|
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
|
// 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.
|
// a BEEFY justification, or at pallet genesis block; voter will resume from there.
|
||||||
let blockchain = backend.blockchain();
|
let blockchain = backend.blockchain();
|
||||||
@@ -378,20 +384,19 @@ where
|
|||||||
break state
|
break state
|
||||||
}
|
}
|
||||||
|
|
||||||
if *header.number() == One::one() {
|
if *header.number() == beefy_genesis {
|
||||||
// We've reached chain genesis, initialize voter here.
|
// We've reached BEEFY genesis, initialize voter here.
|
||||||
let genesis_num = *header.number();
|
|
||||||
let genesis_set = expect_validator_set(runtime, BlockId::hash(header.hash()))
|
let genesis_set = expect_validator_set(runtime, BlockId::hash(header.hash()))
|
||||||
.and_then(genesis_set_sanity_check)?;
|
.and_then(genesis_set_sanity_check)?;
|
||||||
info!(
|
info!(
|
||||||
target: LOG_TARGET,
|
target: LOG_TARGET,
|
||||||
"🥩 Loading BEEFY voter state from genesis on what appears to be first startup. \
|
"🥩 Loading BEEFY voter state from genesis on what appears to be first startup. \
|
||||||
Starting voting rounds at block {:?}, genesis validator set {:?}.",
|
Starting voting rounds at block {:?}, genesis validator set {:?}.",
|
||||||
genesis_num,
|
beefy_genesis,
|
||||||
genesis_set,
|
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)
|
break PersistedState::checked_new(best_grandpa, Zero::zero(), sessions, min_block_delta)
|
||||||
.ok_or_else(|| ClientError::Backend("Invalid BEEFY chain".into()))?
|
.ok_or_else(|| ClientError::Backend("Invalid BEEFY chain".into()))?
|
||||||
}
|
}
|
||||||
@@ -448,13 +453,16 @@ where
|
|||||||
None => break
|
None => break
|
||||||
};
|
};
|
||||||
let at = BlockId::hash(notif.header.hash());
|
let at = BlockId::hash(notif.header.hash());
|
||||||
if let Some(active) = runtime.runtime_api().validator_set(&at).ok().flatten() {
|
if let Some(start) = runtime.runtime_api().beefy_genesis(&at).ok().flatten() {
|
||||||
// Beefy pallet available, return best grandpa at the time.
|
if *notif.header.number() >= start {
|
||||||
info!(
|
// Beefy pallet available, return header for best grandpa at the time.
|
||||||
target: LOG_TARGET, "🥩 BEEFY pallet available: block {:?} validator set {:?}",
|
info!(
|
||||||
notif.header.number(), active
|
target: LOG_TARGET,
|
||||||
);
|
"🥩 BEEFY pallet available: block {:?} beefy genesis {:?}",
|
||||||
return Ok(notif.header)
|
notif.header.number(), start
|
||||||
|
);
|
||||||
|
return Ok(notif.header)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ = gossip_engine => {
|
_ = gossip_engine => {
|
||||||
|
|||||||
@@ -77,8 +77,8 @@ const BAD_MMR_ROOT: MmrRootHash = MmrRootHash::repeat_byte(0x42);
|
|||||||
type BeefyBlockImport = crate::BeefyBlockImport<
|
type BeefyBlockImport = crate::BeefyBlockImport<
|
||||||
Block,
|
Block,
|
||||||
substrate_test_runtime_client::Backend,
|
substrate_test_runtime_client::Backend,
|
||||||
two_validators::TestApi,
|
TestApi,
|
||||||
BlockImportAdapter<PeersClient, sp_api::TransactionFor<two_validators::TestApi, Block>>,
|
BlockImportAdapter<PeersClient, sp_api::TransactionFor<TestApi, Block>>,
|
||||||
>;
|
>;
|
||||||
|
|
||||||
pub(crate) type BeefyValidatorSet = ValidatorSet<AuthorityId>;
|
pub(crate) type BeefyValidatorSet = ValidatorSet<AuthorityId>;
|
||||||
@@ -198,12 +198,12 @@ impl TestNetFactory for BeefyTestNet {
|
|||||||
Option<BoxJustificationImport<Block>>,
|
Option<BoxJustificationImport<Block>>,
|
||||||
Self::PeerData,
|
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 inner = BlockImportAdapter::new(client.clone());
|
||||||
let (block_import, voter_links, rpc_links) = beefy_block_import_and_links(
|
let (block_import, voter_links, rpc_links) =
|
||||||
inner,
|
beefy_block_import_and_links(inner, client.as_backend(), api);
|
||||||
client.as_backend(),
|
|
||||||
Arc::new(two_validators::TestApi {}),
|
|
||||||
);
|
|
||||||
let peer_data = PeerData {
|
let peer_data = PeerData {
|
||||||
beefy_rpc_links: Mutex::new(Some(rpc_links)),
|
beefy_rpc_links: Mutex::new(Some(rpc_links)),
|
||||||
beefy_voter_links: Mutex::new(Some(voter_links)),
|
beefy_voter_links: Mutex::new(Some(voter_links)),
|
||||||
@@ -230,79 +230,79 @@ impl TestNetFactory for BeefyTestNet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! create_test_api {
|
#[derive(Clone)]
|
||||||
( $api_name:ident, mmr_root: $mmr_root:expr, $($inits:expr),+ ) => {
|
pub(crate) struct TestApi {
|
||||||
pub(crate) mod $api_name {
|
pub beefy_genesis: u64,
|
||||||
use super::*;
|
pub validator_set: BeefyValidatorSet,
|
||||||
|
pub mmr_root_hash: MmrRootHash,
|
||||||
#[derive(Clone, Default)]
|
|
||||||
pub(crate) struct TestApi {}
|
|
||||||
|
|
||||||
// compiler gets confused and warns us about unused inner
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub(crate) struct RuntimeApi {
|
|
||||||
inner: TestApi,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ProvideRuntimeApi<Block> 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<Block> for RuntimeApi {
|
|
||||||
fn validator_set() -> Option<BeefyValidatorSet> {
|
|
||||||
BeefyValidatorSet::new(make_beefy_ids(&[$($inits),+]), 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MmrApi<Block, MmrRootHash, NumberFor<Block>> for RuntimeApi {
|
|
||||||
fn mmr_root() -> Result<MmrRootHash, MmrError> {
|
|
||||||
Ok($mmr_root)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_proof(
|
|
||||||
_block_numbers: Vec<u64>,
|
|
||||||
_best_known_block_number: Option<u64>
|
|
||||||
) -> Result<(Vec<EncodableOpaqueLeaf>, Proof<MmrRootHash>), MmrError> {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn verify_proof(_leaves: Vec<EncodableOpaqueLeaf>, _proof: Proof<MmrRootHash>) -> Result<(), MmrError> {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn verify_proof_stateless(
|
|
||||||
_root: MmrRootHash,
|
|
||||||
_leaves: Vec<EncodableOpaqueLeaf>,
|
|
||||||
_proof: Proof<MmrRootHash>
|
|
||||||
) -> Result<(), MmrError> {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
create_test_api!(two_validators, mmr_root: GOOD_MMR_ROOT, BeefyKeyring::Alice, BeefyKeyring::Bob);
|
impl TestApi {
|
||||||
create_test_api!(
|
pub fn new(
|
||||||
four_validators,
|
beefy_genesis: u64,
|
||||||
mmr_root: GOOD_MMR_ROOT,
|
validator_set: &BeefyValidatorSet,
|
||||||
BeefyKeyring::Alice,
|
mmr_root_hash: MmrRootHash,
|
||||||
BeefyKeyring::Bob,
|
) -> Self {
|
||||||
BeefyKeyring::Charlie,
|
TestApi { beefy_genesis, validator_set: validator_set.clone(), mmr_root_hash }
|
||||||
BeefyKeyring::Dave
|
}
|
||||||
);
|
|
||||||
create_test_api!(
|
pub fn with_validator_set(validator_set: &BeefyValidatorSet) -> Self {
|
||||||
bad_four_validators,
|
TestApi {
|
||||||
mmr_root: BAD_MMR_ROOT,
|
beefy_genesis: 1,
|
||||||
BeefyKeyring::Alice,
|
validator_set: validator_set.clone(),
|
||||||
BeefyKeyring::Bob,
|
mmr_root_hash: GOOD_MMR_ROOT,
|
||||||
BeefyKeyring::Charlie,
|
}
|
||||||
BeefyKeyring::Dave
|
}
|
||||||
);
|
}
|
||||||
|
|
||||||
|
// compiler gets confused and warns us about unused inner
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) struct RuntimeApi {
|
||||||
|
inner: TestApi,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProvideRuntimeApi<Block> for TestApi {
|
||||||
|
type Api = RuntimeApi;
|
||||||
|
fn runtime_api(&self) -> ApiRef<Self::Api> {
|
||||||
|
RuntimeApi { inner: self.clone() }.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sp_api::mock_impl_runtime_apis! {
|
||||||
|
impl BeefyApi<Block> for RuntimeApi {
|
||||||
|
fn beefy_genesis() -> Option<NumberFor<Block>> {
|
||||||
|
Some(self.inner.beefy_genesis)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validator_set() -> Option<BeefyValidatorSet> {
|
||||||
|
Some(self.inner.validator_set.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MmrApi<Block, MmrRootHash, NumberFor<Block>> for RuntimeApi {
|
||||||
|
fn mmr_root() -> Result<MmrRootHash, MmrError> {
|
||||||
|
Ok(self.inner.mmr_root_hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_proof(
|
||||||
|
_block_numbers: Vec<u64>,
|
||||||
|
_best_known_block_number: Option<u64>
|
||||||
|
) -> Result<(Vec<EncodableOpaqueLeaf>, Proof<MmrRootHash>), MmrError> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_proof(_leaves: Vec<EncodableOpaqueLeaf>, _proof: Proof<MmrRootHash>) -> Result<(), MmrError> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_proof_stateless(
|
||||||
|
_root: MmrRootHash,
|
||||||
|
_leaves: Vec<EncodableOpaqueLeaf>,
|
||||||
|
_proof: Proof<MmrRootHash>
|
||||||
|
) -> Result<(), MmrError> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn add_mmr_digest(header: &mut Header, mmr_hash: MmrRootHash) {
|
fn add_mmr_digest(header: &mut Header, mmr_hash: MmrRootHash) {
|
||||||
header.digest_mut().push(DigestItem::Consensus(
|
header.digest_mut().push(DigestItem::Consensus(
|
||||||
@@ -332,9 +332,9 @@ pub(crate) fn create_beefy_keystore(authority: BeefyKeyring) -> SyncCryptoStoreP
|
|||||||
fn voter_init_setup(
|
fn voter_init_setup(
|
||||||
net: &mut BeefyTestNet,
|
net: &mut BeefyTestNet,
|
||||||
finality: &mut futures::stream::Fuse<FinalityNotifications<Block>>,
|
finality: &mut futures::stream::Fuse<FinalityNotifications<Block>>,
|
||||||
|
api: &TestApi,
|
||||||
) -> sp_blockchain::Result<PersistedState<Block>> {
|
) -> sp_blockchain::Result<PersistedState<Block>> {
|
||||||
let backend = net.peer(0).client().as_backend();
|
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 known_peers = Arc::new(Mutex::new(KnownPeers::new()));
|
||||||
let gossip_validator =
|
let gossip_validator =
|
||||||
Arc::new(crate::communication::gossip::GossipValidator::new(known_peers));
|
Arc::new(crate::communication::gossip::GossipValidator::new(known_peers));
|
||||||
@@ -345,9 +345,9 @@ fn voter_init_setup(
|
|||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
let best_grandpa =
|
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();
|
.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.
|
// Spawns beefy voters. Returns a future to spawn on the runtime.
|
||||||
@@ -357,7 +357,7 @@ fn initialize_beefy<API>(
|
|||||||
min_block_delta: u32,
|
min_block_delta: u32,
|
||||||
) -> impl Future<Output = ()>
|
) -> impl Future<Output = ()>
|
||||||
where
|
where
|
||||||
API: ProvideRuntimeApi<Block> + Default + Sync + Send,
|
API: ProvideRuntimeApi<Block> + Sync + Send,
|
||||||
API::Api: BeefyApi<Block> + MmrApi<Block, MmrRootHash, NumberFor<Block>>,
|
API::Api: BeefyApi<Block> + MmrApi<Block, MmrRootHash, NumberFor<Block>>,
|
||||||
{
|
{
|
||||||
let tasks = FuturesUnordered::new();
|
let tasks = FuturesUnordered::new();
|
||||||
@@ -544,7 +544,7 @@ async fn beefy_finalizing_blocks() {
|
|||||||
|
|
||||||
let mut net = BeefyTestNet::new(2);
|
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();
|
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));
|
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 min_block_delta = 1;
|
||||||
|
|
||||||
let mut net = BeefyTestNet::new(2);
|
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();
|
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));
|
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);
|
let mut net = BeefyTestNet::new(4);
|
||||||
|
|
||||||
// Alice, Bob, Charlie will vote on good payloads
|
// 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]
|
let good_peers = [BeefyKeyring::Alice, BeefyKeyring::Bob, BeefyKeyring::Charlie]
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
@@ -669,7 +669,7 @@ async fn correct_beefy_payload() {
|
|||||||
tokio::spawn(initialize_beefy(&mut net, good_peers, min_block_delta));
|
tokio::spawn(initialize_beefy(&mut net, good_peers, min_block_delta));
|
||||||
|
|
||||||
// Dave will vote on bad mmr roots
|
// 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)];
|
let bad_peers = vec![(3, &BeefyKeyring::Dave, bad_api)];
|
||||||
tokio::spawn(initialize_beefy(&mut net, bad_peers, min_block_delta));
|
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();
|
sp_tracing::try_init_simple();
|
||||||
|
|
||||||
let mut net = BeefyTestNet::new(2);
|
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 client = net.peer(0).client().clone();
|
||||||
let (mut block_import, _, peer_data) = net.make_block_import(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.
|
// Import with valid justification.
|
||||||
let parent_id = BlockId::Number(1);
|
let parent_id = BlockId::Number(1);
|
||||||
let block_num = 2;
|
let block_num = 2;
|
||||||
let keys = &[BeefyKeyring::Alice, BeefyKeyring::Bob];
|
let proof = crate::justification::tests::new_finality_proof(block_num, &good_set, keys);
|
||||||
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 versioned_proof: VersionedFinalityProof<NumberFor<Block>, Signature> = proof.into();
|
let versioned_proof: VersionedFinalityProof<NumberFor<Block>, Signature> = proof.into();
|
||||||
let encoded = versioned_proof.encode();
|
let encoded = versioned_proof.encode();
|
||||||
let justif = Some(Justifications::from((BEEFY_ENGINE_ID, encoded)));
|
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 parent_id = BlockId::Number(2);
|
||||||
let block_num = 3;
|
let block_num = 3;
|
||||||
let keys = &[BeefyKeyring::Alice];
|
let keys = &[BeefyKeyring::Alice];
|
||||||
let validator_set = ValidatorSet::new(make_beefy_ids(keys), 1).unwrap();
|
let bad_set = ValidatorSet::new(make_beefy_ids(keys), 1).unwrap();
|
||||||
let proof = crate::justification::tests::new_finality_proof(block_num, &validator_set, keys);
|
let proof = crate::justification::tests::new_finality_proof(block_num, &bad_set, keys);
|
||||||
let versioned_proof: VersionedFinalityProof<NumberFor<Block>, Signature> = proof.into();
|
let versioned_proof: VersionedFinalityProof<NumberFor<Block>, Signature> = proof.into();
|
||||||
let encoded = versioned_proof.encode();
|
let encoded = versioned_proof.encode();
|
||||||
let justif = Some(Justifications::from((BEEFY_ENGINE_ID, encoded)));
|
let justif = Some(Justifications::from((BEEFY_ENGINE_ID, encoded)));
|
||||||
@@ -865,7 +865,7 @@ async fn voter_initialization() {
|
|||||||
let min_block_delta = 10;
|
let min_block_delta = 10;
|
||||||
|
|
||||||
let mut net = BeefyTestNet::new(2);
|
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();
|
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));
|
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);
|
let mut net = BeefyTestNet::new(4);
|
||||||
|
|
||||||
// Alice, Bob, Charlie start first and make progress through voting.
|
// 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 fast_peers = [BeefyKeyring::Alice, BeefyKeyring::Bob, BeefyKeyring::Charlie];
|
||||||
let voting_peers =
|
let voting_peers =
|
||||||
fast_peers.iter().enumerate().map(|(id, key)| (id, key, api.clone())).collect();
|
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
|
// finalize 13 without justifications
|
||||||
net.peer(0).client().as_client().finalize_block(hashes[13], None).unwrap();
|
net.peer(0).client().as_client().finalize_block(hashes[13], None).unwrap();
|
||||||
|
|
||||||
// load persistent state - nothing in DB, should init at session boundary
|
let api = TestApi::with_validator_set(&validator_set);
|
||||||
let persisted_state = voter_init_setup(&mut net, &mut finality).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.
|
// Test initialization at session boundary.
|
||||||
// verify voter initialized with two sessions starting at blocks 1 and 10
|
// 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);
|
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]
|
#[tokio::test]
|
||||||
async fn should_initialize_voter_when_last_final_is_session_boundary() {
|
async fn should_initialize_voter_when_last_final_is_session_boundary() {
|
||||||
let keys = &[BeefyKeyring::Alice];
|
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,
|
// Test corner-case where session boundary == last beefy finalized,
|
||||||
// expect rounds initialized at last beefy finalized 10.
|
// 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
|
// 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
|
// verify voter initialized with single session starting at block 10
|
||||||
assert_eq!(persisted_state.voting_oracle().sessions().len(), 1);
|
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.
|
// 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
|
// 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
|
// verify voter initialized with single session starting at block 12
|
||||||
assert_eq!(persisted_state.voting_oracle().sessions().len(), 1);
|
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();
|
let state = load_persistent(&*backend).unwrap().unwrap();
|
||||||
assert_eq!(state, persisted_state);
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -994,8 +994,8 @@ pub(crate) mod tests {
|
|||||||
communication::notification::{BeefyBestBlockStream, BeefyVersionedFinalityProofStream},
|
communication::notification::{BeefyBestBlockStream, BeefyVersionedFinalityProofStream},
|
||||||
keystore::tests::Keyring,
|
keystore::tests::Keyring,
|
||||||
tests::{
|
tests::{
|
||||||
create_beefy_keystore, get_beefy_streams, make_beefy_ids, two_validators::TestApi,
|
create_beefy_keystore, get_beefy_streams, make_beefy_ids, BeefyPeer, BeefyTestNet,
|
||||||
BeefyPeer, BeefyTestNet,
|
TestApi,
|
||||||
},
|
},
|
||||||
BeefyRPCLinks, KnownPeers,
|
BeefyRPCLinks, KnownPeers,
|
||||||
};
|
};
|
||||||
@@ -1068,7 +1068,7 @@ pub(crate) mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let backend = peer.client().as_backend();
|
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 network = peer.network_service().clone();
|
||||||
let known_peers = Arc::new(Mutex::new(KnownPeers::new()));
|
let known_peers = Arc::new(Mutex::new(KnownPeers::new()));
|
||||||
let gossip_validator = Arc::new(GossipValidator::new(known_peers.clone()));
|
let gossip_validator = Arc::new(GossipValidator::new(known_peers.clone()));
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ pub use pallet::*;
|
|||||||
pub mod pallet {
|
pub mod pallet {
|
||||||
use super::*;
|
use super::*;
|
||||||
use frame_support::pallet_prelude::*;
|
use frame_support::pallet_prelude::*;
|
||||||
|
use frame_system::pallet_prelude::BlockNumberFor;
|
||||||
|
|
||||||
#[pallet::config]
|
#[pallet::config]
|
||||||
pub trait Config: frame_system::Config {
|
pub trait Config: frame_system::Config {
|
||||||
@@ -91,15 +92,32 @@ pub mod pallet {
|
|||||||
pub(super) type NextAuthorities<T: Config> =
|
pub(super) type NextAuthorities<T: Config> =
|
||||||
StorageValue<_, BoundedVec<T::BeefyId, T::MaxAuthorities>, ValueQuery>;
|
StorageValue<_, BoundedVec<T::BeefyId, T::MaxAuthorities>, 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<T: Config> =
|
||||||
|
StorageValue<_, Option<BlockNumberFor<T>>, ValueQuery>;
|
||||||
|
|
||||||
#[pallet::genesis_config]
|
#[pallet::genesis_config]
|
||||||
pub struct GenesisConfig<T: Config> {
|
pub struct GenesisConfig<T: Config> {
|
||||||
|
/// Initial set of BEEFY authorities.
|
||||||
pub authorities: Vec<T::BeefyId>,
|
pub authorities: Vec<T::BeefyId>,
|
||||||
|
/// 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<BlockNumberFor<T>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
impl<T: Config> Default for GenesisConfig<T> {
|
impl<T: Config> Default for GenesisConfig<T> {
|
||||||
fn default() -> Self {
|
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
|
// we panic here as runtime maintainers can simply reconfigure genesis and restart
|
||||||
// the chain easily
|
// the chain easily
|
||||||
.expect("Authorities vec too big");
|
.expect("Authorities vec too big");
|
||||||
|
<GenesisBlock<T>>::put(&self.genesis_block);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ use codec::{Codec, Decode, Encode};
|
|||||||
use scale_info::TypeInfo;
|
use scale_info::TypeInfo;
|
||||||
use sp_application_crypto::RuntimeAppPublic;
|
use sp_application_crypto::RuntimeAppPublic;
|
||||||
use sp_core::H256;
|
use sp_core::H256;
|
||||||
use sp_runtime::traits::Hash;
|
use sp_runtime::traits::{Hash, NumberFor};
|
||||||
use sp_std::prelude::*;
|
use sp_std::prelude::*;
|
||||||
|
|
||||||
/// Key type for BEEFY module.
|
/// Key type for BEEFY module.
|
||||||
@@ -201,6 +201,9 @@ sp_api::decl_runtime_apis! {
|
|||||||
/// API necessary for BEEFY voters.
|
/// API necessary for BEEFY voters.
|
||||||
pub trait BeefyApi
|
pub trait BeefyApi
|
||||||
{
|
{
|
||||||
|
/// Return the block number where BEEFY consensus is enabled/started
|
||||||
|
fn beefy_genesis() -> Option<NumberFor<Block>>;
|
||||||
|
|
||||||
/// Return the current active BEEFY validator set
|
/// Return the current active BEEFY validator set
|
||||||
fn validator_set() -> Option<ValidatorSet<crypto::AuthorityId>>;
|
fn validator_set() -> Option<ValidatorSet<crypto::AuthorityId>>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -973,6 +973,10 @@ cfg_if! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl beefy_primitives::BeefyApi<Block> for Runtime {
|
impl beefy_primitives::BeefyApi<Block> for Runtime {
|
||||||
|
fn beefy_genesis() -> Option<BlockNumber> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
fn validator_set() -> Option<beefy_primitives::ValidatorSet<beefy_primitives::crypto::AuthorityId>> {
|
fn validator_set() -> Option<beefy_primitives::ValidatorSet<beefy_primitives::crypto::AuthorityId>> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user