beefy: error logs for validators with dummy keys (#3939)

This outputs:
```
2024-04-02 14:36:02.135 ERROR tokio-runtime-worker beefy: 🥩 for session starting at block 21990151
no BEEFY authority key found in store, you must generate valid session keys
(https://wiki.polkadot.network/docs/maintain-guides-how-to-validate-polkadot#generating-the-session-keys)
```
error log entry, once every session, for nodes running with
`Role::Authority` that have no public BEEFY key in their keystore

---------

Co-authored-by: Bastian Köcher <git@kchr.de>
This commit is contained in:
Adrian Catangiu
2024-04-02 15:28:48 +03:00
committed by GitHub
parent d5617cf3cd
commit 5eff3f94be
5 changed files with 50 additions and 73 deletions
+13 -12
View File
@@ -1233,6 +1233,7 @@ pub fn new_full<OverseerGenerator: OverseerGen>(
prometheus_registry: prometheus_registry.clone(), prometheus_registry: prometheus_registry.clone(),
links: beefy_links, links: beefy_links,
on_demand_justifications_handler: beefy_on_demand_justifications_handler, on_demand_justifications_handler: beefy_on_demand_justifications_handler,
is_authority: role.is_authority(),
}; };
let gadget = beefy::start_beefy_gadget::<_, _, _, _, _, _, _>(beefy_params); let gadget = beefy::start_beefy_gadget::<_, _, _, _, _, _, _>(beefy_params);
@@ -1242,18 +1243,18 @@ pub fn new_full<OverseerGenerator: OverseerGen>(
task_manager task_manager
.spawn_essential_handle() .spawn_essential_handle()
.spawn_blocking("beefy-gadget", None, gadget); .spawn_blocking("beefy-gadget", None, gadget);
// When offchain indexing is enabled, MMR gadget should also run. }
if is_offchain_indexing_enabled { // When offchain indexing is enabled, MMR gadget should also run.
task_manager.spawn_essential_handle().spawn_blocking( if is_offchain_indexing_enabled {
"mmr-gadget", task_manager.spawn_essential_handle().spawn_blocking(
None, "mmr-gadget",
MmrGadget::start( None,
client.clone(), MmrGadget::start(
backend.clone(), client.clone(),
sp_mmr_primitives::INDEXING_PREFIX.to_vec(), backend.clone(),
), sp_mmr_primitives::INDEXING_PREFIX.to_vec(),
); ),
} );
} }
let config = grandpa::Config { let config = grandpa::Config {
+1
View File
@@ -652,6 +652,7 @@ pub fn new_full_base(
prometheus_registry: prometheus_registry.clone(), prometheus_registry: prometheus_registry.clone(),
links: beefy_links, links: beefy_links,
on_demand_justifications_handler: beefy_on_demand_justifications_handler, on_demand_justifications_handler: beefy_on_demand_justifications_handler,
is_authority: role.is_authority(),
}; };
let beefy_gadget = beefy::start_beefy_gadget::<_, _, _, _, _, _, _>(beefy_params); let beefy_gadget = beefy::start_beefy_gadget::<_, _, _, _, _, _, _>(beefy_params);
+17 -1
View File
@@ -222,6 +222,8 @@ pub struct BeefyParams<B: Block, BE, C, N, P, R, S> {
pub links: BeefyVoterLinks<B>, pub links: BeefyVoterLinks<B>,
/// Handler for incoming BEEFY justifications requests from a remote peer. /// Handler for incoming BEEFY justifications requests from a remote peer.
pub on_demand_justifications_handler: BeefyJustifsRequestHandler<B, C>, pub on_demand_justifications_handler: BeefyJustifsRequestHandler<B, C>,
/// Whether running under "Authority" role.
pub is_authority: bool,
} }
/// Helper object holding BEEFY worker communication/gossip components. /// Helper object holding BEEFY worker communication/gossip components.
/// ///
@@ -270,6 +272,7 @@ where
min_block_delta: u32, min_block_delta: u32,
gossip_validator: Arc<GossipValidator<B>>, gossip_validator: Arc<GossipValidator<B>>,
finality_notifications: &mut Fuse<FinalityNotifications<B>>, finality_notifications: &mut Fuse<FinalityNotifications<B>>,
is_authority: bool,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
// Wait for BEEFY pallet to be active before starting voter. // Wait for BEEFY pallet to be active before starting voter.
let (beefy_genesis, best_grandpa) = let (beefy_genesis, best_grandpa) =
@@ -283,6 +286,7 @@ where
runtime.clone(), runtime.clone(),
&key_store, &key_store,
&metrics, &metrics,
is_authority,
) )
.await?; .await?;
// Update the gossip validator with the right starting round and set id. // Update the gossip validator with the right starting round and set id.
@@ -301,6 +305,7 @@ where
comms: BeefyComms<B>, comms: BeefyComms<B>,
links: BeefyVoterLinks<B>, links: BeefyVoterLinks<B>,
pending_justifications: BTreeMap<NumberFor<B>, BeefyVersionedFinalityProof<B>>, pending_justifications: BTreeMap<NumberFor<B>, BeefyVersionedFinalityProof<B>>,
is_authority: bool,
) -> BeefyWorker<B, BE, P, R, S> { ) -> BeefyWorker<B, BE, P, R, S> {
BeefyWorker { BeefyWorker {
backend: self.backend, backend: self.backend,
@@ -313,6 +318,7 @@ where
comms, comms,
links, links,
pending_justifications, pending_justifications,
is_authority,
} }
} }
@@ -423,6 +429,7 @@ where
runtime: Arc<R>, runtime: Arc<R>,
key_store: &BeefyKeystore<AuthorityId>, key_store: &BeefyKeystore<AuthorityId>,
metrics: &Option<VoterMetrics>, metrics: &Option<VoterMetrics>,
is_authority: bool,
) -> Result<PersistedState<B>, Error> { ) -> Result<PersistedState<B>, Error> {
// Initialize voter state from AUX DB if compatible. // Initialize voter state from AUX DB if compatible.
if let Some(mut state) = crate::aux_schema::load_persistent(backend.as_ref())? if let Some(mut state) = crate::aux_schema::load_persistent(backend.as_ref())?
@@ -455,7 +462,13 @@ where
"🥩 Handling missed BEEFY session after node restart: {:?}.", "🥩 Handling missed BEEFY session after node restart: {:?}.",
new_session_start new_session_start
); );
state.init_session_at(new_session_start, validator_set, key_store, metrics); state.init_session_at(
new_session_start,
validator_set,
key_store,
metrics,
is_authority,
);
} }
return Ok(state) return Ok(state)
} }
@@ -491,6 +504,7 @@ pub async fn start_beefy_gadget<B, BE, C, N, P, R, S>(
prometheus_registry, prometheus_registry,
links, links,
mut on_demand_justifications_handler, mut on_demand_justifications_handler,
is_authority,
} = beefy_params; } = beefy_params;
let BeefyNetworkParams { let BeefyNetworkParams {
@@ -553,6 +567,7 @@ pub async fn start_beefy_gadget<B, BE, C, N, P, R, S>(
min_block_delta, min_block_delta,
beefy_comms.gossip_validator.clone(), beefy_comms.gossip_validator.clone(),
&mut finality_notifications, &mut finality_notifications,
is_authority,
).fuse() => { ).fuse() => {
match builder_init_result { match builder_init_result {
Ok(builder) => break builder, Ok(builder) => break builder,
@@ -580,6 +595,7 @@ pub async fn start_beefy_gadget<B, BE, C, N, P, R, S>(
beefy_comms, beefy_comms,
links.clone(), links.clone(),
BTreeMap::new(), BTreeMap::new(),
is_authority,
); );
match futures::future::select( match futures::future::select(
@@ -379,6 +379,7 @@ async fn voter_init_setup(
Arc::new(api.clone()), Arc::new(api.clone()),
&key_store, &key_store,
&metrics, &metrics,
true,
) )
.await .await
} }
@@ -438,6 +439,7 @@ where
min_block_delta, min_block_delta,
prometheus_registry: None, prometheus_registry: None,
on_demand_justifications_handler: on_demand_justif_handler, on_demand_justifications_handler: on_demand_justif_handler,
is_authority: true,
}; };
let task = crate::start_beefy_gadget::<_, _, _, _, _, _, _>(beefy_params); let task = crate::start_beefy_gadget::<_, _, _, _, _, _, _>(beefy_params);
+17 -60
View File
@@ -33,7 +33,7 @@ use crate::{
}; };
use codec::{Codec, Decode, DecodeAll, Encode}; use codec::{Codec, Decode, DecodeAll, Encode};
use futures::{stream::Fuse, FutureExt, StreamExt}; use futures::{stream::Fuse, FutureExt, StreamExt};
use log::{debug, error, info, log_enabled, trace, warn}; use log::{debug, error, info, trace, warn};
use sc_client_api::{Backend, FinalityNotification, FinalityNotifications, HeaderBackend}; use sc_client_api::{Backend, FinalityNotification, FinalityNotifications, HeaderBackend};
use sc_utils::notification::NotificationReceiver; use sc_utils::notification::NotificationReceiver;
use sp_api::ProvideRuntimeApi; use sp_api::ProvideRuntimeApi;
@@ -51,7 +51,7 @@ use sp_runtime::{
SaturatedConversion, SaturatedConversion,
}; };
use std::{ use std::{
collections::{BTreeMap, BTreeSet, VecDeque}, collections::{BTreeMap, VecDeque},
fmt::Debug, fmt::Debug,
sync::Arc, sync::Arc,
}; };
@@ -332,6 +332,7 @@ impl<B: Block> PersistedState<B> {
validator_set: ValidatorSet<AuthorityId>, validator_set: ValidatorSet<AuthorityId>,
key_store: &BeefyKeystore<AuthorityId>, key_store: &BeefyKeystore<AuthorityId>,
metrics: &Option<VoterMetrics>, metrics: &Option<VoterMetrics>,
is_authority: bool,
) { ) {
debug!(target: LOG_TARGET, "🥩 New active validator set: {:?}", validator_set); debug!(target: LOG_TARGET, "🥩 New active validator set: {:?}", validator_set);
@@ -348,11 +349,16 @@ impl<B: Block> PersistedState<B> {
} }
} }
if log_enabled!(target: LOG_TARGET, log::Level::Debug) { // verify we have some BEEFY key available in keystore when role is authority.
// verify the new validator set - only do it if we're also logging the warning if is_authority && key_store.public_keys().map_or(false, |k| k.is_empty()) {
if verify_validator_set::<B>(&new_session_start, &validator_set, key_store).is_err() { error!(
metric_inc!(metrics, beefy_no_authority_found_in_store); target: LOG_TARGET,
} "🥩 for session starting at block {:?} no BEEFY authority key found in store, \
you must generate valid session keys \
(https://wiki.polkadot.network/docs/maintain-guides-how-to-validate-polkadot#generating-the-session-keys)",
new_session_start,
);
metric_inc!(metrics, beefy_no_authority_found_in_store);
} }
let id = validator_set.id(); let id = validator_set.id();
@@ -390,6 +396,8 @@ pub(crate) struct BeefyWorker<B: Block, BE, P, RuntimeApi, S> {
pub persisted_state: PersistedState<B>, pub persisted_state: PersistedState<B>,
/// BEEFY voter metrics /// BEEFY voter metrics
pub metrics: Option<VoterMetrics>, pub metrics: Option<VoterMetrics>,
/// Node runs under "Authority" role.
pub is_authority: bool,
} }
impl<B, BE, P, R, S> BeefyWorker<B, BE, P, R, S> impl<B, BE, P, R, S> BeefyWorker<B, BE, P, R, S>
@@ -425,6 +433,7 @@ where
validator_set, validator_set,
&self.key_store, &self.key_store,
&self.metrics, &self.metrics,
self.is_authority,
); );
} }
@@ -1040,33 +1049,6 @@ where
} }
} }
/// Verify `active` validator set for `block` against the key store
///
/// We want to make sure that we have _at least one_ key in our keystore that
/// is part of the validator set, that's because if there are no local keys
/// then we can't perform our job as a validator.
///
/// Note that for a non-authority node there will be no keystore, and we will
/// return an error and don't check. The error can usually be ignored.
fn verify_validator_set<B: Block>(
block: &NumberFor<B>,
active: &ValidatorSet<AuthorityId>,
key_store: &BeefyKeystore<AuthorityId>,
) -> Result<(), Error> {
let active: BTreeSet<&AuthorityId> = active.validators().iter().collect();
let public_keys = key_store.public_keys()?;
let store: BTreeSet<&AuthorityId> = public_keys.iter().collect();
if store.intersection(&active).count() == 0 {
let msg = "no authority public key found in store".to_string();
debug!(target: LOG_TARGET, "🥩 for block {:?} {}", block, msg);
Err(Error::Keystore(msg))
} else {
Ok(())
}
}
#[cfg(test)] #[cfg(test)]
pub(crate) mod tests { pub(crate) mod tests {
use super::*; use super::*;
@@ -1208,6 +1190,7 @@ pub(crate) mod tests {
comms, comms,
pending_justifications: BTreeMap::new(), pending_justifications: BTreeMap::new(),
persisted_state, persisted_state,
is_authority: true,
} }
} }
@@ -1471,32 +1454,6 @@ pub(crate) mod tests {
assert_eq!(extracted, Some(validator_set)); assert_eq!(extracted, Some(validator_set));
} }
#[tokio::test]
async fn keystore_vs_validator_set() {
let keys = &[Keyring::Alice];
let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap();
let mut net = BeefyTestNet::new(1);
let mut worker = create_beefy_worker(net.peer(0), &keys[0], 1, validator_set.clone());
// keystore doesn't contain other keys than validators'
assert_eq!(verify_validator_set::<Block>(&1, &validator_set, &worker.key_store), Ok(()));
// unknown `Bob` key
let keys = &[Keyring::Bob];
let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap();
let err_msg = "no authority public key found in store".to_string();
let expected = Err(Error::Keystore(err_msg));
assert_eq!(verify_validator_set::<Block>(&1, &validator_set, &worker.key_store), expected);
// worker has no keystore
worker.key_store = None.into();
let expected_err = Err(Error::Keystore("no Keystore".into()));
assert_eq!(
verify_validator_set::<Block>(&1, &validator_set, &worker.key_store),
expected_err
);
}
#[tokio::test] #[tokio::test]
async fn should_finalize_correctly() { async fn should_finalize_correctly() {
let keys = [Keyring::Alice]; let keys = [Keyring::Alice];