Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.

Commit 7c195bf

Browse files
acatangiucoderobe
authored andcommitted
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 <[email protected]>
1 parent 3c8666b commit 7c195bf

File tree

7 files changed

+141
-59
lines changed

7 files changed

+141
-59
lines changed

client/consensus/beefy/src/aux_schema.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ use sp_runtime::traits::Block as BlockT;
2828
const VERSION_KEY: &[u8] = b"beefy_auxschema_version";
2929
const WORKER_STATE_KEY: &[u8] = b"beefy_voter_state";
3030

31-
const CURRENT_VERSION: u32 = 3;
31+
const CURRENT_VERSION: u32 = 4;
3232

3333
pub(crate) fn write_current_version<BE: AuxStore>(backend: &BE) -> ClientResult<()> {
3434
info!(target: LOG_TARGET, "🥩 write aux schema version {:?}", CURRENT_VERSION);
@@ -63,8 +63,8 @@ where
6363

6464
match version {
6565
None => (),
66-
Some(1) | Some(2) => (), // versions 1 & 2 are obsolete and should be simply ignored
67-
Some(3) => return load_decode::<_, PersistedState<B>>(backend, WORKER_STATE_KEY),
66+
Some(1) | Some(2) | Some(3) => (), // versions 1, 2 & 3 are obsolete and should be ignored
67+
Some(4) => return load_decode::<_, PersistedState<B>>(backend, WORKER_STATE_KEY),
6868
other =>
6969
return Err(ClientError::Backend(format!("Unsupported BEEFY DB version: {:?}", other))),
7070
}

client/consensus/beefy/src/communication/request_response/incoming_requests_handler.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
1919
use codec::Decode;
2020
use futures::{channel::oneshot, StreamExt};
21-
use log::{debug, trace};
21+
use log::{debug, error, trace};
2222
use sc_client_api::BlockBackend;
2323
use sc_network::{
2424
config as netconfig, config::RequestResponseConfig, types::ProtocolName, PeerId,
@@ -215,5 +215,9 @@ where
215215
},
216216
}
217217
}
218+
error!(
219+
target: crate::LOG_TARGET,
220+
"🥩 On-demand requests receiver stream terminated, closing worker."
221+
);
218222
}
219223
}

client/consensus/beefy/src/error.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
//! BEEFY gadget specific errors
2020
//!
21-
//! Used for BEEFY gadget interal error handling only
21+
//! Used for BEEFY gadget internal error handling only
2222
2323
use std::fmt::Debug;
2424

@@ -34,6 +34,8 @@ pub enum Error {
3434
Signature(String),
3535
#[error("Session uninitialized")]
3636
UninitSession,
37+
#[error("pallet-beefy was reset, please restart voter")]
38+
ConsensusReset,
3739
}
3840

3941
#[cfg(test)]
@@ -45,6 +47,7 @@ impl PartialEq for Error {
4547
(Error::RuntimeApi(_), Error::RuntimeApi(_)) => true,
4648
(Error::Signature(s1), Error::Signature(s2)) => s1 == s2,
4749
(Error::UninitSession, Error::UninitSession) => true,
50+
(Error::ConsensusReset, Error::ConsensusReset) => true,
4851
_ => false,
4952
}
5053
}

client/consensus/beefy/src/lib.rs

Lines changed: 49 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ use sp_blockchain::{
4747
use sp_consensus::{Error as ConsensusError, SyncOracle};
4848
use sp_consensus_beefy::{
4949
crypto::AuthorityId, BeefyApi, MmrRootHash, PayloadProvider, ValidatorSet, BEEFY_ENGINE_ID,
50-
GENESIS_AUTHORITY_SET_ID,
5150
};
5251
use sp_keystore::KeystorePtr;
5352
use sp_mmr_primitives::MmrApi;
@@ -282,8 +281,14 @@ pub async fn start_beefy_gadget<B, BE, C, N, P, R, S>(
282281
let persisted_state =
283282
match wait_for_runtime_pallet(&*runtime, &mut gossip_engine, &mut finality_notifications)
284283
.await
285-
.and_then(|best_grandpa| {
286-
load_or_init_voter_state(&*backend, &*runtime, best_grandpa, min_block_delta)
284+
.and_then(|(beefy_genesis, best_grandpa)| {
285+
load_or_init_voter_state(
286+
&*backend,
287+
&*runtime,
288+
beefy_genesis,
289+
best_grandpa,
290+
min_block_delta,
291+
)
287292
}) {
288293
Ok(state) => state,
289294
Err(e) => {
@@ -316,16 +321,17 @@ pub async fn start_beefy_gadget<B, BE, C, N, P, R, S>(
316321
persisted_state,
317322
};
318323

319-
futures::future::join(
320-
worker.run(block_import_justif, finality_notifications),
321-
on_demand_justifications_handler.run(),
324+
futures::future::select(
325+
Box::pin(worker.run(block_import_justif, finality_notifications)),
326+
Box::pin(on_demand_justifications_handler.run()),
322327
)
323328
.await;
324329
}
325330

326331
fn load_or_init_voter_state<B, BE, R>(
327332
backend: &BE,
328333
runtime: &R,
334+
beefy_genesis: NumberFor<B>,
329335
best_grandpa: <B as Block>::Header,
330336
min_block_delta: u32,
331337
) -> ClientResult<PersistedState<B>>
@@ -335,17 +341,22 @@ where
335341
R: ProvideRuntimeApi<B>,
336342
R::Api: BeefyApi<B>,
337343
{
338-
// Initialize voter state from AUX DB or from pallet genesis.
339-
if let Some(mut state) = crate::aux_schema::load_persistent(backend)? {
340-
// Overwrite persisted state with current best GRANDPA block.
341-
state.set_best_grandpa(best_grandpa);
342-
// Overwrite persisted data with newly provided `min_block_delta`.
343-
state.set_min_block_delta(min_block_delta);
344-
info!(target: LOG_TARGET, "🥩 Loading BEEFY voter state from db: {:?}.", state);
345-
Ok(state)
346-
} else {
347-
initialize_voter_state(backend, runtime, best_grandpa, min_block_delta)
348-
}
344+
// Initialize voter state from AUX DB if compatible.
345+
crate::aux_schema::load_persistent(backend)?
346+
// Verify state pallet genesis matches runtime.
347+
.filter(|state| state.pallet_genesis() == beefy_genesis)
348+
.and_then(|mut state| {
349+
// Overwrite persisted state with current best GRANDPA block.
350+
state.set_best_grandpa(best_grandpa.clone());
351+
// Overwrite persisted data with newly provided `min_block_delta`.
352+
state.set_min_block_delta(min_block_delta);
353+
info!(target: LOG_TARGET, "🥩 Loading BEEFY voter state from db: {:?}.", state);
354+
Some(Ok(state))
355+
})
356+
// No valid voter-state persisted, re-initialize from pallet genesis.
357+
.unwrap_or_else(|| {
358+
initialize_voter_state(backend, runtime, beefy_genesis, best_grandpa, min_block_delta)
359+
})
349360
}
350361

351362
// If no persisted state present, walk back the chain from first GRANDPA notification to either:
@@ -355,6 +366,7 @@ where
355366
fn initialize_voter_state<B, BE, R>(
356367
backend: &BE,
357368
runtime: &R,
369+
beefy_genesis: NumberFor<B>,
358370
best_grandpa: <B as Block>::Header,
359371
min_block_delta: u32,
360372
) -> ClientResult<PersistedState<B>>
@@ -369,6 +381,7 @@ where
369381
.beefy_genesis(best_grandpa.hash())
370382
.ok()
371383
.flatten()
384+
.filter(|genesis| *genesis == beefy_genesis)
372385
.ok_or_else(|| ClientError::Backend("BEEFY pallet expected to be active.".into()))?;
373386
// Walk back the imported blocks and initialize voter either, at the last block with
374387
// a BEEFY justification, or at pallet genesis block; voter will resume from there.
@@ -396,16 +409,20 @@ where
396409
rounds.conclude(best_beefy);
397410
sessions.push_front(rounds);
398411
}
399-
let state =
400-
PersistedState::checked_new(best_grandpa, best_beefy, sessions, min_block_delta)
401-
.ok_or_else(|| ClientError::Backend("Invalid BEEFY chain".into()))?;
412+
let state = PersistedState::checked_new(
413+
best_grandpa,
414+
best_beefy,
415+
sessions,
416+
min_block_delta,
417+
beefy_genesis,
418+
)
419+
.ok_or_else(|| ClientError::Backend("Invalid BEEFY chain".into()))?;
402420
break state
403421
}
404422

405423
if *header.number() == beefy_genesis {
406424
// We've reached BEEFY genesis, initialize voter here.
407-
let genesis_set =
408-
expect_validator_set(runtime, header.hash()).and_then(genesis_set_sanity_check)?;
425+
let genesis_set = expect_validator_set(runtime, header.hash())?;
409426
info!(
410427
target: LOG_TARGET,
411428
"🥩 Loading BEEFY voter state from genesis on what appears to be first startup. \
@@ -415,8 +432,14 @@ where
415432
);
416433

417434
sessions.push_front(Rounds::new(beefy_genesis, genesis_set));
418-
break PersistedState::checked_new(best_grandpa, Zero::zero(), sessions, min_block_delta)
419-
.ok_or_else(|| ClientError::Backend("Invalid BEEFY chain".into()))?
435+
break PersistedState::checked_new(
436+
best_grandpa,
437+
Zero::zero(),
438+
sessions,
439+
min_block_delta,
440+
beefy_genesis,
441+
)
442+
.ok_or_else(|| ClientError::Backend("Invalid BEEFY chain".into()))?
420443
}
421444

422445
if let Some(active) = worker::find_authorities_change::<B>(&header) {
@@ -451,7 +474,7 @@ async fn wait_for_runtime_pallet<B, R>(
451474
runtime: &R,
452475
mut gossip_engine: &mut GossipEngine<B>,
453476
finality: &mut Fuse<FinalityNotifications<B>>,
454-
) -> ClientResult<<B as Block>::Header>
477+
) -> ClientResult<(NumberFor<B>, <B as Block>::Header)>
455478
where
456479
B: Block,
457480
R: ProvideRuntimeApi<B>,
@@ -474,7 +497,7 @@ where
474497
"🥩 BEEFY pallet available: block {:?} beefy genesis {:?}",
475498
notif.header.number(), start
476499
);
477-
return Ok(notif.header)
500+
return Ok((start, notif.header))
478501
}
479502
}
480503
},
@@ -488,17 +511,6 @@ where
488511
Err(ClientError::Backend(err_msg))
489512
}
490513

491-
fn genesis_set_sanity_check(
492-
active: ValidatorSet<AuthorityId>,
493-
) -> ClientResult<ValidatorSet<AuthorityId>> {
494-
if active.id() == GENESIS_AUTHORITY_SET_ID {
495-
Ok(active)
496-
} else {
497-
error!(target: LOG_TARGET, "🥩 Unexpected ID for genesis validator set {:?}.", active);
498-
Err(ClientError::Backend("BEEFY Genesis sanity check failed.".into()))
499-
}
500-
}
501-
502514
fn expect_validator_set<B, R>(
503515
runtime: &R,
504516
at_hash: B::Hash,

client/consensus/beefy/src/tests.rs

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -373,8 +373,9 @@ async fn voter_init_setup(
373373
gossip_validator,
374374
None,
375375
);
376-
let best_grandpa = wait_for_runtime_pallet(api, &mut gossip_engine, finality).await.unwrap();
377-
load_or_init_voter_state(&*backend, api, best_grandpa, 1)
376+
let (beefy_genesis, best_grandpa) =
377+
wait_for_runtime_pallet(api, &mut gossip_engine, finality).await.unwrap();
378+
load_or_init_voter_state(&*backend, api, beefy_genesis, best_grandpa, 1)
378379
}
379380

380381
// Spawns beefy voters. Returns a future to spawn on the runtime.
@@ -981,9 +982,7 @@ async fn should_initialize_voter_at_genesis() {
981982

982983
// push 15 blocks with `AuthorityChange` digests every 10 blocks
983984
let hashes = net.generate_blocks_and_sync(15, 10, &validator_set, false).await;
984-
985985
let mut finality = net.peer(0).client().as_client().finality_notification_stream().fuse();
986-
987986
// finalize 13 without justifications
988987
net.peer(0).client().as_client().finalize_block(hashes[13], None).unwrap();
989988

@@ -1022,11 +1021,9 @@ async fn should_initialize_voter_at_custom_genesis() {
10221021
let custom_pallet_genesis = 7;
10231022
let api = TestApi::new(custom_pallet_genesis, &validator_set, GOOD_MMR_ROOT);
10241023

1025-
// push 15 blocks with `AuthorityChange` digests every 10 blocks
1026-
let hashes = net.generate_blocks_and_sync(15, 10, &validator_set, false).await;
1027-
1024+
// push 15 blocks with `AuthorityChange` digests every 15 blocks
1025+
let hashes = net.generate_blocks_and_sync(15, 15, &validator_set, false).await;
10281026
let mut finality = net.peer(0).client().as_client().finality_notification_stream().fuse();
1029-
10301027
// finalize 3, 5, 8 without justifications
10311028
net.peer(0).client().as_client().finalize_block(hashes[3], None).unwrap();
10321029
net.peer(0).client().as_client().finalize_block(hashes[5], None).unwrap();
@@ -1053,6 +1050,35 @@ async fn should_initialize_voter_at_custom_genesis() {
10531050
assert!(verify_persisted_version(&*backend));
10541051
let state = load_persistent(&*backend).unwrap().unwrap();
10551052
assert_eq!(state, persisted_state);
1053+
1054+
// now re-init after genesis changes
1055+
1056+
// should ignore existing aux db state and reinit at new genesis
1057+
let new_validator_set = ValidatorSet::new(make_beefy_ids(keys), 42).unwrap();
1058+
let new_pallet_genesis = 10;
1059+
let api = TestApi::new(new_pallet_genesis, &new_validator_set, GOOD_MMR_ROOT);
1060+
1061+
net.peer(0).client().as_client().finalize_block(hashes[10], None).unwrap();
1062+
// load persistent state - state preset in DB, but with different pallet genesis
1063+
let new_persisted_state = voter_init_setup(&mut net, &mut finality, &api).await.unwrap();
1064+
1065+
// verify voter initialized with single session starting at block `new_pallet_genesis` (10)
1066+
let sessions = new_persisted_state.voting_oracle().sessions();
1067+
assert_eq!(sessions.len(), 1);
1068+
assert_eq!(sessions[0].session_start(), new_pallet_genesis);
1069+
let rounds = new_persisted_state.active_round().unwrap();
1070+
assert_eq!(rounds.session_start(), new_pallet_genesis);
1071+
assert_eq!(rounds.validator_set_id(), new_validator_set.id());
1072+
1073+
// verify next vote target is mandatory block 10
1074+
assert_eq!(new_persisted_state.best_beefy_block(), 0);
1075+
assert_eq!(new_persisted_state.best_grandpa_number(), 10);
1076+
assert_eq!(new_persisted_state.voting_oracle().voting_target(), Some(new_pallet_genesis));
1077+
1078+
// verify state also saved to db
1079+
assert!(verify_persisted_version(&*backend));
1080+
let state = load_persistent(&*backend).unwrap().unwrap();
1081+
assert_eq!(state, new_persisted_state);
10561082
}
10571083

10581084
#[tokio::test]
@@ -1166,7 +1192,7 @@ async fn beefy_finalizing_after_pallet_genesis() {
11661192
sp_tracing::try_init_simple();
11671193

11681194
let peers = [BeefyKeyring::Alice, BeefyKeyring::Bob];
1169-
let validator_set = ValidatorSet::new(make_beefy_ids(&peers), 0).unwrap();
1195+
let validator_set = ValidatorSet::new(make_beefy_ids(&peers), 14).unwrap();
11701196
let session_len = 10;
11711197
let min_block_delta = 1;
11721198
let pallet_genesis = 15;

0 commit comments

Comments
 (0)