[BEEFY] Avoid missing voting sessions during node restart (#3074)

Related to https://github.com/paritytech/polkadot-sdk/issues/3003 and
https://github.com/paritytech/polkadot-sdk/issues/2842

---------

Co-authored-by: Adrian Catangiu <adrian@parity.io>
This commit is contained in:
Serban Iorga
2024-02-01 12:24:16 +01:00
committed by GitHub
parent 8a8f6f9877
commit b177c2860e
8 changed files with 456 additions and 278 deletions
+128 -10
View File
@@ -28,10 +28,12 @@ use crate::{
},
request_response::{on_demand_justifications_protocol_config, BeefyJustifsRequestHandler},
},
error::Error,
gossip_protocol_name,
justification::*,
load_or_init_voter_state, wait_for_runtime_pallet, BeefyRPCLinks, BeefyVoterLinks, KnownPeers,
PersistedState,
wait_for_runtime_pallet,
worker::{BeefyWorkerBase, PersistedState},
BeefyRPCLinks, BeefyVoterLinks, KnownPeers,
};
use futures::{future, stream::FuturesUnordered, Future, FutureExt, StreamExt};
use parking_lot::Mutex;
@@ -363,7 +365,7 @@ async fn voter_init_setup(
net: &mut BeefyTestNet,
finality: &mut futures::stream::Fuse<FinalityNotifications<Block>>,
api: &TestApi,
) -> sp_blockchain::Result<PersistedState<Block>> {
) -> Result<PersistedState<Block>, Error> {
let backend = net.peer(0).client().as_backend();
let known_peers = Arc::new(Mutex::new(KnownPeers::new()));
let (gossip_validator, _) = GossipValidator::new(known_peers);
@@ -378,7 +380,14 @@ async fn voter_init_setup(
);
let (beefy_genesis, best_grandpa) =
wait_for_runtime_pallet(api, &mut gossip_engine, finality).await.unwrap();
load_or_init_voter_state(&*backend, api, beefy_genesis, best_grandpa, 1).await
let mut worker_base = BeefyWorkerBase {
backend,
runtime: Arc::new(api.clone()),
key_store: None.into(),
metrics: None,
_phantom: Default::default(),
};
worker_base.load_or_init_state(beefy_genesis, best_grandpa, 1).await
}
// Spawns beefy voters. Returns a future to spawn on the runtime.
@@ -1072,9 +1081,15 @@ async fn should_initialize_voter_at_custom_genesis() {
);
let (beefy_genesis, best_grandpa) =
wait_for_runtime_pallet(&api, &mut gossip_engine, &mut finality).await.unwrap();
let persisted_state = load_or_init_voter_state(&*backend, &api, beefy_genesis, best_grandpa, 1)
.await
.unwrap();
let mut worker_base = BeefyWorkerBase {
backend: backend.clone(),
runtime: Arc::new(api),
key_store: None.into(),
metrics: None,
_phantom: Default::default(),
};
let persisted_state =
worker_base.load_or_init_state(beefy_genesis, best_grandpa, 1).await.unwrap();
// Test initialization at session boundary.
// verify voter initialized with single session starting at block `custom_pallet_genesis` (7)
@@ -1107,10 +1122,15 @@ async fn should_initialize_voter_at_custom_genesis() {
// the network state persists and uses the old `GossipEngine` initialized for `peer(0)`
let (beefy_genesis, best_grandpa) =
wait_for_runtime_pallet(&api, &mut gossip_engine, &mut finality).await.unwrap();
let mut worker_base = BeefyWorkerBase {
backend: backend.clone(),
runtime: Arc::new(api),
key_store: None.into(),
metrics: None,
_phantom: Default::default(),
};
let new_persisted_state =
load_or_init_voter_state(&*backend, &api, beefy_genesis, best_grandpa, 1)
.await
.unwrap();
worker_base.load_or_init_state(beefy_genesis, best_grandpa, 1).await.unwrap();
// verify voter initialized with single session starting at block `new_pallet_genesis` (10)
let sessions = new_persisted_state.voting_oracle().sessions();
@@ -1285,6 +1305,104 @@ async fn should_initialize_voter_at_custom_genesis_when_state_unavailable() {
assert_eq!(state, persisted_state);
}
#[tokio::test]
async fn should_catch_up_when_loading_saved_voter_state() {
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();
// push 30 blocks with `AuthorityChange` digests every 10 blocks
let hashes = net.generate_blocks_and_sync(30, 10, &validator_set, false).await;
let mut finality = net.peer(0).client().as_client().finality_notification_stream().fuse();
// finalize 13 without justifications
net.peer(0).client().as_client().finalize_block(hashes[13], None).unwrap();
let api = TestApi::with_validator_set(&validator_set);
// load persistent state - nothing in DB, should init at genesis
//
// NOTE: code from `voter_init_setup()` is moved here because the new network event system
// doesn't allow creating a new `GossipEngine` as the notification handle is consumed by the
// first `GossipEngine`
let known_peers = Arc::new(Mutex::new(KnownPeers::new()));
let (gossip_validator, _) = GossipValidator::new(known_peers);
let gossip_validator = Arc::new(gossip_validator);
let mut gossip_engine = sc_network_gossip::GossipEngine::new(
net.peer(0).network_service().clone(),
net.peer(0).sync_service().clone(),
net.peer(0).take_notification_service(&beefy_gossip_proto_name()).unwrap(),
"/beefy/whatever",
gossip_validator,
None,
);
let (beefy_genesis, best_grandpa) =
wait_for_runtime_pallet(&api, &mut gossip_engine, &mut finality).await.unwrap();
let mut worker_base = BeefyWorkerBase {
backend: backend.clone(),
runtime: Arc::new(api.clone()),
key_store: None.into(),
metrics: None,
_phantom: Default::default(),
};
let persisted_state =
worker_base.load_or_init_state(beefy_genesis, best_grandpa, 1).await.unwrap();
// Test initialization at session boundary.
// verify voter initialized with two sessions starting at blocks 1 and 10
let sessions = persisted_state.voting_oracle().sessions();
assert_eq!(sessions.len(), 2);
assert_eq!(sessions[0].session_start(), 1);
assert_eq!(sessions[1].session_start(), 10);
let rounds = persisted_state.active_round().unwrap();
assert_eq!(rounds.session_start(), 1);
assert_eq!(rounds.validator_set_id(), validator_set.id());
// verify next vote target is mandatory block 1
assert_eq!(persisted_state.best_beefy(), 0);
assert_eq!(persisted_state.best_grandpa_number(), 13);
assert_eq!(persisted_state.voting_oracle().voting_target(), Some(1));
// verify state also saved to db
assert!(verify_persisted_version(&*backend));
let state = load_persistent(&*backend).unwrap().unwrap();
assert_eq!(state, persisted_state);
// now let's consider that the node goes offline, and then it restarts after a while
// finalize 25 without justifications
net.peer(0).client().as_client().finalize_block(hashes[25], None).unwrap();
// load persistent state - state preset in DB
// the network state persists and uses the old `GossipEngine` initialized for `peer(0)`
let (beefy_genesis, best_grandpa) =
wait_for_runtime_pallet(&api, &mut gossip_engine, &mut finality).await.unwrap();
let mut worker_base = BeefyWorkerBase {
backend: backend.clone(),
runtime: Arc::new(api),
key_store: None.into(),
metrics: None,
_phantom: Default::default(),
};
let persisted_state =
worker_base.load_or_init_state(beefy_genesis, best_grandpa, 1).await.unwrap();
// Verify voter initialized with old sessions plus a new one starting at block 20.
// There shouldn't be any duplicates.
let sessions = persisted_state.voting_oracle().sessions();
assert_eq!(sessions.len(), 3);
assert_eq!(sessions[0].session_start(), 1);
assert_eq!(sessions[1].session_start(), 10);
assert_eq!(sessions[2].session_start(), 20);
let rounds = persisted_state.active_round().unwrap();
assert_eq!(rounds.session_start(), 1);
assert_eq!(rounds.validator_set_id(), validator_set.id());
// verify next vote target is mandatory block 1
assert_eq!(persisted_state.best_beefy(), 0);
assert_eq!(persisted_state.best_grandpa_number(), 25);
assert_eq!(persisted_state.voting_oracle().voting_target(), Some(1));
}
#[tokio::test]
async fn beefy_finalizing_after_pallet_genesis() {
sp_tracing::try_init_simple();