Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sc-consensus-beefy: fix initialization when state is unavailable #1888

Merged
merged 1 commit into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions substrate/client/consensus/beefy/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ use crate::{
worker::PersistedState,
};
use futures::{stream::Fuse, StreamExt};
use log::{error, info};
use log::{debug, error, info};
use parking_lot::Mutex;
use prometheus::Registry;
use sc_client_api::{Backend, BlockBackend, BlockchainEvents, FinalityNotifications, Finalizer};
Expand Down Expand Up @@ -428,7 +428,7 @@ where
let best_beefy = *header.number();
// If no session boundaries detected so far, just initialize new rounds here.
if sessions.is_empty() {
let active_set = expect_validator_set(runtime, backend, &header, beefy_genesis)?;
let active_set = expect_validator_set(runtime, backend, &header)?;
let mut rounds = Rounds::new(best_beefy, active_set);
// Mark the round as already finalized.
rounds.conclude(best_beefy);
Expand All @@ -447,7 +447,7 @@ where

if *header.number() == beefy_genesis {
// We've reached BEEFY genesis, initialize voter here.
let genesis_set = expect_validator_set(runtime, backend, &header, beefy_genesis)?;
let genesis_set = expect_validator_set(runtime, backend, &header)?;
info!(
target: LOG_TARGET,
"🥩 Loading BEEFY voter state from genesis on what appears to be first startup. \
Expand Down Expand Up @@ -532,14 +532,14 @@ fn expect_validator_set<B, BE, R>(
runtime: &R,
backend: &BE,
at_header: &B::Header,
beefy_genesis: NumberFor<B>,
) -> ClientResult<ValidatorSet<AuthorityId>>
where
B: Block,
BE: Backend<B>,
R: ProvideRuntimeApi<B>,
R::Api: BeefyApi<B, AuthorityId>,
{
debug!(target: LOG_TARGET, "🥩 Try to find validator set active at header: {:?}", at_header);
runtime
.runtime_api()
.validator_set(at_header.hash())
Expand All @@ -550,14 +550,14 @@ where
// Digest emitted when validator set active 'at_header' was enacted.
let blockchain = backend.blockchain();
let mut header = at_header.clone();
while *header.number() >= beefy_genesis {
loop {
debug!(target: LOG_TARGET, "🥩 look for auth set change digest in header number: {:?}", *header.number());
match worker::find_authorities_change::<B>(&header) {
Some(active) => return Some(active),
// Move up the chain.
None => header = blockchain.expect_header(*header.parent_hash()).ok()?,
}
}
None
})
.ok_or_else(|| ClientError::Backend("Could not find initial validator set".into()))
}
56 changes: 52 additions & 4 deletions substrate/client/consensus/beefy/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ impl TestNetFactory for BeefyTestNet {
#[derive(Clone)]
pub(crate) struct TestApi {
pub beefy_genesis: u64,
pub validator_set: BeefyValidatorSet,
pub validator_set: Option<BeefyValidatorSet>,
pub mmr_root_hash: MmrRootHash,
pub reported_equivocations:
Option<Arc<Mutex<Vec<EquivocationProof<NumberFor<Block>, AuthorityId, Signature>>>>>,
Expand All @@ -261,7 +261,7 @@ impl TestApi {
) -> Self {
TestApi {
beefy_genesis,
validator_set: validator_set.clone(),
validator_set: Some(validator_set.clone()),
mmr_root_hash,
reported_equivocations: None,
}
Expand All @@ -270,7 +270,7 @@ impl TestApi {
pub fn with_validator_set(validator_set: &BeefyValidatorSet) -> Self {
TestApi {
beefy_genesis: 1,
validator_set: validator_set.clone(),
validator_set: Some(validator_set.clone()),
mmr_root_hash: GOOD_MMR_ROOT,
reported_equivocations: None,
}
Expand Down Expand Up @@ -300,7 +300,7 @@ sp_api::mock_impl_runtime_apis! {
}

fn validator_set() -> Option<BeefyValidatorSet> {
Some(self.inner.validator_set.clone())
self.inner.validator_set.clone()
}

fn submit_report_equivocation_unsigned_extrinsic(
Expand Down Expand Up @@ -1188,6 +1188,54 @@ async fn should_initialize_voter_at_latest_finalized() {
assert_eq!(state, persisted_state);
}

#[tokio::test]
async fn should_initialize_voter_at_custom_genesis_when_state_unavailable() {
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 mut api = TestApi::new(custom_pallet_genesis, &validator_set, GOOD_MMR_ROOT);
// remove validator set from `TestApi`, practically simulating unavailable/pruned runtime state
api.validator_set = None;

// push 30 blocks with `AuthorityChange` digests every 5 blocks
let hashes = net.generate_blocks_and_sync(30, 5, &validator_set, false).await;
let mut finality = net.peer(0).client().as_client().finality_notification_stream().fuse();
// finalize 30 without justifications
net.peer(0).client().as_client().finalize_block(hashes[30], None).unwrap();

// load persistent state - nothing in DB, should init at genesis
let persisted_state = voter_init_setup(&mut net, &mut finality, &api).await.unwrap();

// Test initialization at session boundary.
// verify voter initialized with all sessions pending, first one starting at block 5 (start of
// session containing `custom_pallet_genesis`).
let sessions = persisted_state.voting_oracle().sessions();
// should have enqueued 6 sessions (every 5 blocks from 5 to 30)
assert_eq!(sessions.len(), 6);
assert_eq!(sessions[0].session_start(), 7);
assert_eq!(sessions[1].session_start(), 10);
assert_eq!(sessions[2].session_start(), 15);
assert_eq!(sessions[3].session_start(), 20);
assert_eq!(sessions[4].session_start(), 25);
assert_eq!(sessions[5].session_start(), 30);
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 (genesis)
assert_eq!(persisted_state.best_beefy_block(), 0);
assert_eq!(persisted_state.best_grandpa_number(), 30);
assert_eq!(persisted_state.voting_oracle().voting_target(), 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 beefy_finalizing_after_pallet_genesis() {
sp_tracing::try_init_simple();
Expand Down
Loading