Skip to content

Commit 47ccd8b

Browse files
authored
sc-consensus-beefy: fix initialization when state is unavailable (paritytech#1888)
Fix situation where BEEFY initial validator set could not be determined. If state is unavailable at BEEFY genesis block to get initial validator set, get the info from header digests. For this, we need to walk back the chain starting from BEEFY genesis looking for the BEEFY digest announcing the active validator set for that respective session. This commit fixes a silly bug where walking back the chain was stopped when reaching BEEFY genesis block, which is incorrect when BEEFY genesis is not session boundary block. When BEEFY genesis is set to some random block within a session, we need to walk back to the start of the session to see the validator set announcement. Added regression test for this fix. Fixes paritytech#1885 Signed-off-by: Adrian Catangiu <adrian@parity.io>
1 parent 594c8aa commit 47ccd8b

2 files changed

Lines changed: 58 additions & 10 deletions

File tree

substrate/client/consensus/beefy/src/lib.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ use crate::{
3333
worker::PersistedState,
3434
};
3535
use futures::{stream::Fuse, StreamExt};
36-
use log::{error, info};
36+
use log::{debug, error, info};
3737
use parking_lot::Mutex;
3838
use prometheus::Registry;
3939
use sc_client_api::{Backend, BlockBackend, BlockchainEvents, FinalityNotifications, Finalizer};
@@ -428,7 +428,7 @@ where
428428
let best_beefy = *header.number();
429429
// If no session boundaries detected so far, just initialize new rounds here.
430430
if sessions.is_empty() {
431-
let active_set = expect_validator_set(runtime, backend, &header, beefy_genesis)?;
431+
let active_set = expect_validator_set(runtime, backend, &header)?;
432432
let mut rounds = Rounds::new(best_beefy, active_set);
433433
// Mark the round as already finalized.
434434
rounds.conclude(best_beefy);
@@ -447,7 +447,7 @@ where
447447

448448
if *header.number() == beefy_genesis {
449449
// We've reached BEEFY genesis, initialize voter here.
450-
let genesis_set = expect_validator_set(runtime, backend, &header, beefy_genesis)?;
450+
let genesis_set = expect_validator_set(runtime, backend, &header)?;
451451
info!(
452452
target: LOG_TARGET,
453453
"🥩 Loading BEEFY voter state from genesis on what appears to be first startup. \
@@ -532,14 +532,14 @@ fn expect_validator_set<B, BE, R>(
532532
runtime: &R,
533533
backend: &BE,
534534
at_header: &B::Header,
535-
beefy_genesis: NumberFor<B>,
536535
) -> ClientResult<ValidatorSet<AuthorityId>>
537536
where
538537
B: Block,
539538
BE: Backend<B>,
540539
R: ProvideRuntimeApi<B>,
541540
R::Api: BeefyApi<B, AuthorityId>,
542541
{
542+
debug!(target: LOG_TARGET, "🥩 Try to find validator set active at header: {:?}", at_header);
543543
runtime
544544
.runtime_api()
545545
.validator_set(at_header.hash())
@@ -550,14 +550,14 @@ where
550550
// Digest emitted when validator set active 'at_header' was enacted.
551551
let blockchain = backend.blockchain();
552552
let mut header = at_header.clone();
553-
while *header.number() >= beefy_genesis {
553+
loop {
554+
debug!(target: LOG_TARGET, "🥩 look for auth set change digest in header number: {:?}", *header.number());
554555
match worker::find_authorities_change::<B>(&header) {
555556
Some(active) => return Some(active),
556557
// Move up the chain.
557558
None => header = blockchain.expect_header(*header.parent_hash()).ok()?,
558559
}
559560
}
560-
None
561561
})
562562
.ok_or_else(|| ClientError::Backend("Could not find initial validator set".into()))
563563
}

substrate/client/consensus/beefy/src/tests.rs

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ impl TestNetFactory for BeefyTestNet {
247247
#[derive(Clone)]
248248
pub(crate) struct TestApi {
249249
pub beefy_genesis: u64,
250-
pub validator_set: BeefyValidatorSet,
250+
pub validator_set: Option<BeefyValidatorSet>,
251251
pub mmr_root_hash: MmrRootHash,
252252
pub reported_equivocations:
253253
Option<Arc<Mutex<Vec<EquivocationProof<NumberFor<Block>, AuthorityId, Signature>>>>>,
@@ -261,7 +261,7 @@ impl TestApi {
261261
) -> Self {
262262
TestApi {
263263
beefy_genesis,
264-
validator_set: validator_set.clone(),
264+
validator_set: Some(validator_set.clone()),
265265
mmr_root_hash,
266266
reported_equivocations: None,
267267
}
@@ -270,7 +270,7 @@ impl TestApi {
270270
pub fn with_validator_set(validator_set: &BeefyValidatorSet) -> Self {
271271
TestApi {
272272
beefy_genesis: 1,
273-
validator_set: validator_set.clone(),
273+
validator_set: Some(validator_set.clone()),
274274
mmr_root_hash: GOOD_MMR_ROOT,
275275
reported_equivocations: None,
276276
}
@@ -300,7 +300,7 @@ sp_api::mock_impl_runtime_apis! {
300300
}
301301

302302
fn validator_set() -> Option<BeefyValidatorSet> {
303-
Some(self.inner.validator_set.clone())
303+
self.inner.validator_set.clone()
304304
}
305305

306306
fn submit_report_equivocation_unsigned_extrinsic(
@@ -1188,6 +1188,54 @@ async fn should_initialize_voter_at_latest_finalized() {
11881188
assert_eq!(state, persisted_state);
11891189
}
11901190

1191+
#[tokio::test]
1192+
async fn should_initialize_voter_at_custom_genesis_when_state_unavailable() {
1193+
let keys = &[BeefyKeyring::Alice];
1194+
let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap();
1195+
let mut net = BeefyTestNet::new(1);
1196+
let backend = net.peer(0).client().as_backend();
1197+
// custom pallet genesis is block number 7
1198+
let custom_pallet_genesis = 7;
1199+
let mut api = TestApi::new(custom_pallet_genesis, &validator_set, GOOD_MMR_ROOT);
1200+
// remove validator set from `TestApi`, practically simulating unavailable/pruned runtime state
1201+
api.validator_set = None;
1202+
1203+
// push 30 blocks with `AuthorityChange` digests every 5 blocks
1204+
let hashes = net.generate_blocks_and_sync(30, 5, &validator_set, false).await;
1205+
let mut finality = net.peer(0).client().as_client().finality_notification_stream().fuse();
1206+
// finalize 30 without justifications
1207+
net.peer(0).client().as_client().finalize_block(hashes[30], None).unwrap();
1208+
1209+
// load persistent state - nothing in DB, should init at genesis
1210+
let persisted_state = voter_init_setup(&mut net, &mut finality, &api).await.unwrap();
1211+
1212+
// Test initialization at session boundary.
1213+
// verify voter initialized with all sessions pending, first one starting at block 5 (start of
1214+
// session containing `custom_pallet_genesis`).
1215+
let sessions = persisted_state.voting_oracle().sessions();
1216+
// should have enqueued 6 sessions (every 5 blocks from 5 to 30)
1217+
assert_eq!(sessions.len(), 6);
1218+
assert_eq!(sessions[0].session_start(), 7);
1219+
assert_eq!(sessions[1].session_start(), 10);
1220+
assert_eq!(sessions[2].session_start(), 15);
1221+
assert_eq!(sessions[3].session_start(), 20);
1222+
assert_eq!(sessions[4].session_start(), 25);
1223+
assert_eq!(sessions[5].session_start(), 30);
1224+
let rounds = persisted_state.active_round().unwrap();
1225+
assert_eq!(rounds.session_start(), custom_pallet_genesis);
1226+
assert_eq!(rounds.validator_set_id(), validator_set.id());
1227+
1228+
// verify next vote target is mandatory block 7 (genesis)
1229+
assert_eq!(persisted_state.best_beefy_block(), 0);
1230+
assert_eq!(persisted_state.best_grandpa_number(), 30);
1231+
assert_eq!(persisted_state.voting_oracle().voting_target(), Some(custom_pallet_genesis));
1232+
1233+
// verify state also saved to db
1234+
assert!(verify_persisted_version(&*backend));
1235+
let state = load_persistent(&*backend).unwrap().unwrap();
1236+
assert_eq!(state, persisted_state);
1237+
}
1238+
11911239
#[tokio::test]
11921240
async fn beefy_finalizing_after_pallet_genesis() {
11931241
sp_tracing::try_init_simple();

0 commit comments

Comments
 (0)