sc-consensus-beefy: graceful support for pallet-beefy reset (#14217)

BEEFY consensus can be restarted by resetting "genesisBlock" in
pallet-beefy, but we don't want to also reset authority set IDs so
that they are uniquely identified across the entire chain history
regardless of how many times BEEFY consensus has been reset/restarted.

This is why the client now also accepts initial authority_set_id != 0.

BEEFY client now detects pallet-beefy reset/reinit and errors-out and
asks for a restart.
BEEFY client persisted state should be discarded on client restarts
following pallet-beefy reset/reinit.

End result is BEEFY client/voter can now completely reinitialize using
"new" on-chain info following pallet-beefy reset/reinit, discarding old state.

Fixes #14203
Fixes #14204

Signed-off-by: acatangiu <adrian@parity.io>
This commit is contained in:
Adrian Catangiu
2023-05-25 15:16:37 +03:00
committed by GitHub
parent 4185a213ac
commit b907f52066
7 changed files with 141 additions and 59 deletions
+49 -37
View File
@@ -47,7 +47,6 @@ use sp_blockchain::{
use sp_consensus::{Error as ConsensusError, SyncOracle};
use sp_consensus_beefy::{
crypto::AuthorityId, BeefyApi, MmrRootHash, PayloadProvider, ValidatorSet, BEEFY_ENGINE_ID,
GENESIS_AUTHORITY_SET_ID,
};
use sp_keystore::KeystorePtr;
use sp_mmr_primitives::MmrApi;
@@ -282,8 +281,14 @@ pub async fn start_beefy_gadget<B, BE, C, N, P, R, S>(
let persisted_state =
match wait_for_runtime_pallet(&*runtime, &mut gossip_engine, &mut finality_notifications)
.await
.and_then(|best_grandpa| {
load_or_init_voter_state(&*backend, &*runtime, best_grandpa, min_block_delta)
.and_then(|(beefy_genesis, best_grandpa)| {
load_or_init_voter_state(
&*backend,
&*runtime,
beefy_genesis,
best_grandpa,
min_block_delta,
)
}) {
Ok(state) => state,
Err(e) => {
@@ -316,9 +321,9 @@ pub async fn start_beefy_gadget<B, BE, C, N, P, R, S>(
persisted_state,
};
futures::future::join(
worker.run(block_import_justif, finality_notifications),
on_demand_justifications_handler.run(),
futures::future::select(
Box::pin(worker.run(block_import_justif, finality_notifications)),
Box::pin(on_demand_justifications_handler.run()),
)
.await;
}
@@ -326,6 +331,7 @@ pub async fn start_beefy_gadget<B, BE, C, N, P, R, S>(
fn load_or_init_voter_state<B, BE, R>(
backend: &BE,
runtime: &R,
beefy_genesis: NumberFor<B>,
best_grandpa: <B as Block>::Header,
min_block_delta: u32,
) -> ClientResult<PersistedState<B>>
@@ -335,17 +341,22 @@ where
R: ProvideRuntimeApi<B>,
R::Api: BeefyApi<B>,
{
// Initialize voter state from AUX DB or from pallet genesis.
if let Some(mut state) = crate::aux_schema::load_persistent(backend)? {
// Overwrite persisted state with current best GRANDPA block.
state.set_best_grandpa(best_grandpa);
// Overwrite persisted data with newly provided `min_block_delta`.
state.set_min_block_delta(min_block_delta);
info!(target: LOG_TARGET, "🥩 Loading BEEFY voter state from db: {:?}.", state);
Ok(state)
} else {
initialize_voter_state(backend, runtime, best_grandpa, min_block_delta)
}
// Initialize voter state from AUX DB if compatible.
crate::aux_schema::load_persistent(backend)?
// Verify state pallet genesis matches runtime.
.filter(|state| state.pallet_genesis() == beefy_genesis)
.and_then(|mut state| {
// Overwrite persisted state with current best GRANDPA block.
state.set_best_grandpa(best_grandpa.clone());
// Overwrite persisted data with newly provided `min_block_delta`.
state.set_min_block_delta(min_block_delta);
info!(target: LOG_TARGET, "🥩 Loading BEEFY voter state from db: {:?}.", state);
Some(Ok(state))
})
// No valid voter-state persisted, re-initialize from pallet genesis.
.unwrap_or_else(|| {
initialize_voter_state(backend, runtime, beefy_genesis, best_grandpa, min_block_delta)
})
}
// If no persisted state present, walk back the chain from first GRANDPA notification to either:
@@ -355,6 +366,7 @@ where
fn initialize_voter_state<B, BE, R>(
backend: &BE,
runtime: &R,
beefy_genesis: NumberFor<B>,
best_grandpa: <B as Block>::Header,
min_block_delta: u32,
) -> ClientResult<PersistedState<B>>
@@ -369,6 +381,7 @@ where
.beefy_genesis(best_grandpa.hash())
.ok()
.flatten()
.filter(|genesis| *genesis == beefy_genesis)
.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.
@@ -396,16 +409,20 @@ where
rounds.conclude(best_beefy);
sessions.push_front(rounds);
}
let state =
PersistedState::checked_new(best_grandpa, best_beefy, sessions, min_block_delta)
.ok_or_else(|| ClientError::Backend("Invalid BEEFY chain".into()))?;
let state = PersistedState::checked_new(
best_grandpa,
best_beefy,
sessions,
min_block_delta,
beefy_genesis,
)
.ok_or_else(|| ClientError::Backend("Invalid BEEFY chain".into()))?;
break state
}
if *header.number() == beefy_genesis {
// We've reached BEEFY genesis, initialize voter here.
let genesis_set =
expect_validator_set(runtime, header.hash()).and_then(genesis_set_sanity_check)?;
let genesis_set = expect_validator_set(runtime, header.hash())?;
info!(
target: LOG_TARGET,
"🥩 Loading BEEFY voter state from genesis on what appears to be first startup. \
@@ -415,8 +432,14 @@ where
);
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()))?
break PersistedState::checked_new(
best_grandpa,
Zero::zero(),
sessions,
min_block_delta,
beefy_genesis,
)
.ok_or_else(|| ClientError::Backend("Invalid BEEFY chain".into()))?
}
if let Some(active) = worker::find_authorities_change::<B>(&header) {
@@ -451,7 +474,7 @@ async fn wait_for_runtime_pallet<B, R>(
runtime: &R,
mut gossip_engine: &mut GossipEngine<B>,
finality: &mut Fuse<FinalityNotifications<B>>,
) -> ClientResult<<B as Block>::Header>
) -> ClientResult<(NumberFor<B>, <B as Block>::Header)>
where
B: Block,
R: ProvideRuntimeApi<B>,
@@ -474,7 +497,7 @@ where
"🥩 BEEFY pallet available: block {:?} beefy genesis {:?}",
notif.header.number(), start
);
return Ok(notif.header)
return Ok((start, notif.header))
}
}
},
@@ -488,17 +511,6 @@ where
Err(ClientError::Backend(err_msg))
}
fn genesis_set_sanity_check(
active: ValidatorSet<AuthorityId>,
) -> ClientResult<ValidatorSet<AuthorityId>> {
if active.id() == GENESIS_AUTHORITY_SET_ID {
Ok(active)
} else {
error!(target: LOG_TARGET, "🥩 Unexpected ID for genesis validator set {:?}.", active);
Err(ClientError::Backend("BEEFY Genesis sanity check failed.".into()))
}
}
fn expect_validator_set<B, R>(
runtime: &R,
at_hash: B::Hash,