1use crate::{
20 communication::{
21 notification::{
22 BeefyBestBlockSender, BeefyBestBlockStream, BeefyVersionedFinalityProofSender,
23 BeefyVersionedFinalityProofStream,
24 },
25 peers::KnownPeers,
26 request_response::{
27 outgoing_requests_engine::OnDemandJustificationsEngine, BeefyJustifsRequestHandler,
28 },
29 },
30 error::Error,
31 import::BeefyBlockImport,
32 metrics::register_metrics,
33};
34use futures::{stream::Fuse, FutureExt, StreamExt};
35use log::{debug, error, info, trace, warn};
36use parking_lot::Mutex;
37use prometheus_endpoint::Registry;
38use sc_client_api::{Backend, BlockBackend, BlockchainEvents, FinalityNotification, Finalizer};
39use sc_consensus::BlockImport;
40use sc_network::{NetworkRequest, NotificationService, ProtocolName};
41use sc_network_gossip::{GossipEngine, Network as GossipNetwork, Syncing as GossipSyncing};
42use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver};
43use sp_api::ProvideRuntimeApi;
44use sp_blockchain::{Backend as BlockchainBackend, HeaderBackend};
45use sp_consensus::{Error as ConsensusError, SyncOracle};
46use sp_consensus_beefy::{
47 AuthorityIdBound, BeefyApi, ConsensusLog, PayloadProvider, ValidatorSet, BEEFY_ENGINE_ID,
48};
49use sp_keystore::KeystorePtr;
50use sp_runtime::traits::{Block, Header as HeaderT, NumberFor, Zero};
51use std::{
52 collections::{BTreeMap, VecDeque},
53 future::Future,
54 marker::PhantomData,
55 pin::Pin,
56 sync::Arc,
57 time::Duration,
58};
59
60mod aux_schema;
61mod error;
62mod keystore;
63mod metrics;
64mod round;
65mod worker;
66
67pub mod communication;
68pub mod import;
69pub mod justification;
70
71use crate::{
72 communication::gossip::GossipValidator,
73 fisherman::Fisherman,
74 justification::BeefyVersionedFinalityProof,
75 keystore::BeefyKeystore,
76 metrics::VoterMetrics,
77 round::Rounds,
78 worker::{BeefyWorker, PersistedState},
79};
80pub use communication::beefy_protocol_name::{
81 gossip_protocol_name, justifications_protocol_name as justifs_protocol_name,
82};
83use sp_runtime::generic::OpaqueDigestItemId;
84
85mod fisherman;
86#[cfg(test)]
87mod tests;
88
89const LOG_TARGET: &str = "beefy";
90
91const HEADER_SYNC_DELAY: Duration = Duration::from_secs(60);
92
93type FinalityNotifications<Block> =
94 sc_utils::mpsc::TracingUnboundedReceiver<UnpinnedFinalityNotification<Block>>;
95pub trait Client<B, BE>:
100 BlockchainEvents<B> + HeaderBackend<B> + Finalizer<B, BE> + Send + Sync
101where
102 B: Block,
103 BE: Backend<B>,
104{
105 }
107
108impl<B, BE, T> Client<B, BE> for T
109where
110 B: Block,
111 BE: Backend<B>,
112 T: BlockchainEvents<B>
113 + HeaderBackend<B>
114 + Finalizer<B, BE>
115 + ProvideRuntimeApi<B>
116 + Send
117 + Sync,
118{
119 }
121
122#[derive(Clone)]
125pub struct BeefyVoterLinks<B: Block, AuthorityId: AuthorityIdBound> {
126 pub from_block_import_justif_stream: BeefyVersionedFinalityProofStream<B, AuthorityId>,
129
130 pub to_rpc_justif_sender: BeefyVersionedFinalityProofSender<B, AuthorityId>,
133 pub to_rpc_best_block_sender: BeefyBestBlockSender<B>,
135}
136
137#[derive(Clone)]
139pub struct BeefyRPCLinks<B: Block, AuthorityId: AuthorityIdBound> {
140 pub from_voter_justif_stream: BeefyVersionedFinalityProofStream<B, AuthorityId>,
142 pub from_voter_best_beefy_stream: BeefyBestBlockStream<B>,
144}
145
146pub fn beefy_block_import_and_links<B, BE, RuntimeApi, I, AuthorityId: AuthorityIdBound>(
148 wrapped_block_import: I,
149 backend: Arc<BE>,
150 runtime: Arc<RuntimeApi>,
151 prometheus_registry: Option<Registry>,
152) -> (
153 BeefyBlockImport<B, BE, RuntimeApi, I, AuthorityId>,
154 BeefyVoterLinks<B, AuthorityId>,
155 BeefyRPCLinks<B, AuthorityId>,
156)
157where
158 B: Block,
159 BE: Backend<B>,
160 I: BlockImport<B, Error = ConsensusError> + Send + Sync,
161 RuntimeApi: ProvideRuntimeApi<B> + Send + Sync,
162 RuntimeApi::Api: BeefyApi<B, AuthorityId>,
163 AuthorityId: AuthorityIdBound,
164{
165 let (to_rpc_justif_sender, from_voter_justif_stream) =
167 BeefyVersionedFinalityProofStream::<B, AuthorityId>::channel();
168 let (to_rpc_best_block_sender, from_voter_best_beefy_stream) =
169 BeefyBestBlockStream::<B>::channel();
170
171 let (to_voter_justif_sender, from_block_import_justif_stream) =
173 BeefyVersionedFinalityProofStream::<B, AuthorityId>::channel();
174 let metrics = register_metrics(prometheus_registry);
175
176 let import = BeefyBlockImport::new(
178 backend,
179 runtime,
180 wrapped_block_import,
181 to_voter_justif_sender,
182 metrics,
183 );
184 let voter_links = BeefyVoterLinks {
185 from_block_import_justif_stream,
186 to_rpc_justif_sender,
187 to_rpc_best_block_sender,
188 };
189 let rpc_links = BeefyRPCLinks { from_voter_best_beefy_stream, from_voter_justif_stream };
190
191 (import, voter_links, rpc_links)
192}
193
194pub struct BeefyNetworkParams<B: Block, N, S> {
196 pub network: Arc<N>,
198 pub sync: Arc<S>,
200 pub notification_service: Box<dyn NotificationService>,
202 pub gossip_protocol_name: ProtocolName,
205 pub justifications_protocol_name: ProtocolName,
208
209 pub _phantom: PhantomData<B>,
210}
211
212pub struct BeefyParams<B: Block, BE, C, N, P, R, S, AuthorityId: AuthorityIdBound> {
214 pub client: Arc<C>,
216 pub backend: Arc<BE>,
218 pub payload_provider: P,
220 pub runtime: Arc<R>,
222 pub key_store: Option<KeystorePtr>,
224 pub network_params: BeefyNetworkParams<B, N, S>,
226 pub min_block_delta: u32,
228 pub prometheus_registry: Option<Registry>,
230 pub links: BeefyVoterLinks<B, AuthorityId>,
232 pub on_demand_justifications_handler: BeefyJustifsRequestHandler<B, C>,
234 pub is_authority: bool,
236}
237pub(crate) struct BeefyComms<B: Block, N, AuthorityId: AuthorityIdBound> {
241 pub gossip_engine: GossipEngine<B>,
242 pub gossip_validator: Arc<GossipValidator<B, N, AuthorityId>>,
243 pub on_demand_justifications: OnDemandJustificationsEngine<B, AuthorityId>,
244}
245
246pub(crate) struct BeefyWorkerBuilder<B: Block, BE, RuntimeApi, AuthorityId: AuthorityIdBound> {
253 backend: Arc<BE>,
255 runtime: Arc<RuntimeApi>,
256 key_store: BeefyKeystore<AuthorityId>,
257 metrics: Option<VoterMetrics>,
259 persisted_state: PersistedState<B, AuthorityId>,
260}
261
262impl<B, BE, R, AuthorityId> BeefyWorkerBuilder<B, BE, R, AuthorityId>
263where
264 B: Block + codec::Codec,
265 BE: Backend<B>,
266 R: ProvideRuntimeApi<B>,
267 R::Api: BeefyApi<B, AuthorityId>,
268 AuthorityId: AuthorityIdBound,
269{
270 pub async fn async_initialize<N>(
277 backend: Arc<BE>,
278 runtime: Arc<R>,
279 key_store: BeefyKeystore<AuthorityId>,
280 metrics: Option<VoterMetrics>,
281 min_block_delta: u32,
282 gossip_validator: Arc<GossipValidator<B, N, AuthorityId>>,
283 finality_notifications: &mut Fuse<FinalityNotifications<B>>,
284 is_authority: bool,
285 ) -> Result<Self, Error> {
286 let (beefy_genesis, best_grandpa) =
288 wait_for_runtime_pallet(&*runtime, finality_notifications).await?;
289
290 let persisted_state = Self::load_or_init_state(
291 beefy_genesis,
292 best_grandpa,
293 min_block_delta,
294 backend.clone(),
295 runtime.clone(),
296 &key_store,
297 &metrics,
298 is_authority,
299 )
300 .await?;
301 persisted_state
303 .gossip_filter_config()
304 .map(|f| gossip_validator.update_filter(f))?;
305
306 Ok(BeefyWorkerBuilder { backend, runtime, key_store, metrics, persisted_state })
307 }
308
309 pub fn build<P, S, N>(
311 self,
312 payload_provider: P,
313 sync: Arc<S>,
314 comms: BeefyComms<B, N, AuthorityId>,
315 links: BeefyVoterLinks<B, AuthorityId>,
316 pending_justifications: BTreeMap<NumberFor<B>, BeefyVersionedFinalityProof<B, AuthorityId>>,
317 is_authority: bool,
318 ) -> BeefyWorker<B, BE, P, R, S, N, AuthorityId> {
319 let key_store = Arc::new(self.key_store);
320 BeefyWorker {
321 backend: self.backend.clone(),
322 runtime: self.runtime.clone(),
323 key_store: key_store.clone(),
324 payload_provider,
325 sync,
326 fisherman: Arc::new(Fisherman::new(self.backend, self.runtime, key_store)),
327 metrics: self.metrics,
328 persisted_state: self.persisted_state,
329 comms,
330 links,
331 pending_justifications,
332 is_authority,
333 }
334 }
335
336 async fn init_state(
342 beefy_genesis: NumberFor<B>,
343 best_grandpa: <B as Block>::Header,
344 min_block_delta: u32,
345 backend: Arc<BE>,
346 runtime: Arc<R>,
347 ) -> Result<PersistedState<B, AuthorityId>, Error> {
348 let blockchain = backend.blockchain();
349
350 let beefy_genesis = runtime
351 .runtime_api()
352 .beefy_genesis(best_grandpa.hash())
353 .ok()
354 .flatten()
355 .filter(|genesis| *genesis == beefy_genesis)
356 .ok_or_else(|| Error::Backend("BEEFY pallet expected to be active.".into()))?;
357 let mut sessions = VecDeque::new();
360 let mut header = best_grandpa.clone();
361 let state = loop {
362 if let Some(true) = blockchain
363 .justifications(header.hash())
364 .ok()
365 .flatten()
366 .map(|justifs| justifs.get(BEEFY_ENGINE_ID).is_some())
367 {
368 debug!(
369 target: LOG_TARGET,
370 "🥩 Initialize BEEFY voter at last BEEFY finalized block: {:?}.",
371 *header.number()
372 );
373 let best_beefy = *header.number();
374 if sessions.is_empty() {
376 let active_set =
377 expect_validator_set(runtime.as_ref(), backend.as_ref(), &header).await?;
378 let mut rounds = Rounds::new(best_beefy, active_set);
379 rounds.conclude(best_beefy);
381 sessions.push_front(rounds);
382 }
383 let state = PersistedState::checked_new(
384 best_grandpa,
385 best_beefy,
386 sessions,
387 min_block_delta,
388 beefy_genesis,
389 )
390 .ok_or_else(|| Error::Backend("Invalid BEEFY chain".into()))?;
391 break state;
392 }
393
394 if *header.number() == beefy_genesis {
395 let genesis_set =
397 expect_validator_set(runtime.as_ref(), backend.as_ref(), &header).await?;
398 info!(
399 target: LOG_TARGET,
400 "🥩 Loading BEEFY voter state from genesis on what appears to be first startup. \
401 Starting voting rounds at block {:?}, genesis validator set {:?}.",
402 beefy_genesis,
403 genesis_set,
404 );
405
406 sessions.push_front(Rounds::new(beefy_genesis, genesis_set));
407 break PersistedState::checked_new(
408 best_grandpa,
409 Zero::zero(),
410 sessions,
411 min_block_delta,
412 beefy_genesis,
413 )
414 .ok_or_else(|| Error::Backend("Invalid BEEFY chain".into()))?;
415 }
416
417 if let Some(active) = find_authorities_change::<B, AuthorityId>(&header) {
418 debug!(
419 target: LOG_TARGET,
420 "🥩 Marking block {:?} as BEEFY Mandatory.",
421 *header.number()
422 );
423 sessions.push_front(Rounds::new(*header.number(), active));
424 }
425
426 header = wait_for_parent_header(blockchain, header, HEADER_SYNC_DELAY).await?;
428 };
429
430 aux_schema::write_current_version_and_voter_state(backend.as_ref(), &state)?;
431 Ok(state)
432 }
433
434 async fn load_or_init_state(
435 beefy_genesis: NumberFor<B>,
436 best_grandpa: <B as Block>::Header,
437 min_block_delta: u32,
438 backend: Arc<BE>,
439 runtime: Arc<R>,
440 key_store: &BeefyKeystore<AuthorityId>,
441 metrics: &Option<VoterMetrics>,
442 is_authority: bool,
443 ) -> Result<PersistedState<B, AuthorityId>, Error> {
444 if let Some(mut state) = crate::aux_schema::load_and_migrate_persistent(backend.as_ref())?
446 .filter(|state| state.pallet_genesis() == beefy_genesis)
448 {
449 state.set_best_grandpa(best_grandpa.clone());
451 state.set_min_block_delta(min_block_delta);
453 debug!(target: LOG_TARGET, "🥩 Loading BEEFY voter state from db.");
454 trace!(target: LOG_TARGET, "🥩 Loaded state: {:?}.", state);
455
456 let mut new_sessions = vec![];
458 let mut header = best_grandpa.clone();
459 while *header.number() > state.best_beefy() {
460 if state.voting_oracle().can_add_session(*header.number()) {
461 if let Some(active) = find_authorities_change::<B, AuthorityId>(&header) {
462 new_sessions.push((active, *header.number()));
463 }
464 }
465 header =
466 wait_for_parent_header(backend.blockchain(), header, HEADER_SYNC_DELAY).await?;
467 }
468
469 for (validator_set, new_session_start) in new_sessions.drain(..).rev() {
471 debug!(
472 target: LOG_TARGET,
473 "🥩 Handling missed BEEFY session after node restart: {:?}.",
474 new_session_start
475 );
476 state.init_session_at(
477 new_session_start,
478 validator_set,
479 key_store,
480 metrics,
481 is_authority,
482 );
483 }
484 return Ok(state);
485 }
486
487 Self::init_state(beefy_genesis, best_grandpa, min_block_delta, backend, runtime).await
489 }
490}
491
492struct UnpinnedFinalityNotification<B: Block> {
496 pub hash: B::Hash,
498 pub header: B::Header,
500 pub tree_route: Arc<[B::Hash]>,
504}
505
506impl<B: Block> From<FinalityNotification<B>> for UnpinnedFinalityNotification<B> {
507 fn from(value: FinalityNotification<B>) -> Self {
508 UnpinnedFinalityNotification {
509 hash: value.hash,
510 header: value.header,
511 tree_route: value.tree_route,
512 }
513 }
514}
515
516pub async fn start_beefy_gadget<B, BE, C, N, P, R, S, AuthorityId>(
520 beefy_params: BeefyParams<B, BE, C, N, P, R, S, AuthorityId>,
521) where
522 B: Block,
523 BE: Backend<B>,
524 C: Client<B, BE> + BlockBackend<B>,
525 P: PayloadProvider<B> + Clone,
526 R: ProvideRuntimeApi<B>,
527 R::Api: BeefyApi<B, AuthorityId>,
528 N: GossipNetwork<B> + NetworkRequest + Send + Sync + 'static,
529 S: GossipSyncing<B> + SyncOracle + 'static,
530 AuthorityId: AuthorityIdBound,
531{
532 let BeefyParams {
533 client,
534 backend,
535 payload_provider,
536 runtime,
537 key_store,
538 network_params,
539 min_block_delta,
540 prometheus_registry,
541 links,
542 mut on_demand_justifications_handler,
543 is_authority,
544 } = beefy_params;
545
546 let BeefyNetworkParams {
547 network,
548 sync,
549 notification_service,
550 gossip_protocol_name,
551 justifications_protocol_name,
552 ..
553 } = network_params;
554
555 let metrics = register_metrics(prometheus_registry.clone());
556
557 let mut block_import_justif = links.from_block_import_justif_stream.subscribe(100_000).fuse();
558
559 let finality_notifications = client.finality_notification_stream();
562 let (mut transformer, mut finality_notifications) =
563 finality_notification_transformer_future(finality_notifications);
564
565 let known_peers = Arc::new(Mutex::new(KnownPeers::new()));
566 let gossip_validator =
569 communication::gossip::GossipValidator::new(known_peers.clone(), network.clone());
570 let gossip_validator = Arc::new(gossip_validator);
571 let gossip_engine = GossipEngine::new(
572 network.clone(),
573 sync.clone(),
574 notification_service,
575 gossip_protocol_name.clone(),
576 gossip_validator.clone(),
577 None,
578 );
579
580 let on_demand_justifications = OnDemandJustificationsEngine::new(
583 network.clone(),
584 justifications_protocol_name.clone(),
585 known_peers,
586 prometheus_registry.clone(),
587 );
588 let mut beefy_comms = BeefyComms { gossip_engine, gossip_validator, on_demand_justifications };
589
590 loop {
593 let worker_builder = futures::select! {
595 builder_init_result = BeefyWorkerBuilder::async_initialize(
596 backend.clone(),
597 runtime.clone(),
598 key_store.clone().into(),
599 metrics.clone(),
600 min_block_delta,
601 beefy_comms.gossip_validator.clone(),
602 &mut finality_notifications,
603 is_authority,
604 ).fuse() => {
605 match builder_init_result {
606 Ok(builder) => builder,
607 Err(e) => {
608 error!(target: LOG_TARGET, "🥩 Error: {:?}. Terminating.", e);
609 return
610 },
611 }
612 },
613 _ = &mut beefy_comms.gossip_engine => {
615 error!(target: LOG_TARGET, "🥩 Gossip engine has unexpectedly terminated.");
616 return
617 },
618 _ = &mut transformer => {
619 error!(target: LOG_TARGET, "🥩 Finality notification transformer task has unexpectedly terminated.");
620 return
621 },
622 };
623
624 let worker = worker_builder.build(
625 payload_provider.clone(),
626 sync.clone(),
627 beefy_comms,
628 links.clone(),
629 BTreeMap::new(),
630 is_authority,
631 );
632
633 futures::select! {
634 result = worker.run(&mut block_import_justif, &mut finality_notifications).fuse() => {
635 match result {
636 (error::Error::ConsensusReset, reuse_comms) => {
637 error!(target: LOG_TARGET, "🥩 Error: {:?}. Restarting voter.", error::Error::ConsensusReset);
638 beefy_comms = reuse_comms;
639 continue;
640 },
641 (err, _) => {
642 error!(target: LOG_TARGET, "🥩 Error: {:?}. Terminating.", err)
643 }
644 }
645 },
646 odj_handler_error = on_demand_justifications_handler.run().fuse() => {
647 error!(target: LOG_TARGET, "🥩 Error: {:?}. Terminating.", odj_handler_error)
648 },
649 _ = &mut transformer => {
650 error!(target: LOG_TARGET, "🥩 Finality notification transformer task has unexpectedly terminated.");
651 }
652 }
653 return;
654 }
655}
656
657fn finality_notification_transformer_future<B>(
660 mut finality_notifications: sc_client_api::FinalityNotifications<B>,
661) -> (
662 Pin<Box<futures::future::Fuse<impl Future<Output = ()> + Sized>>>,
663 Fuse<TracingUnboundedReceiver<UnpinnedFinalityNotification<B>>>,
664)
665where
666 B: Block,
667{
668 let (tx, rx) = tracing_unbounded("beefy-notification-transformer-channel", 10000);
669 let transformer_fut = async move {
670 while let Some(notification) = finality_notifications.next().await {
671 debug!(target: LOG_TARGET, "🥩 Transforming grandpa notification. #{}({:?})", notification.header.number(), notification.hash);
672 if let Err(err) = tx.unbounded_send(UnpinnedFinalityNotification::from(notification)) {
673 error!(target: LOG_TARGET, "🥩 Unable to send transformed notification. Shutting down. err = {}", err);
674 return;
675 };
676 }
677 };
678 (Box::pin(transformer_fut.fuse()), rx.fuse())
679}
680
681async fn wait_for_parent_header<B, BC>(
688 blockchain: &BC,
689 current: <B as Block>::Header,
690 delay: Duration,
691) -> Result<<B as Block>::Header, Error>
692where
693 B: Block,
694 BC: BlockchainBackend<B>,
695{
696 if *current.number() == Zero::zero() {
697 let msg = format!("header {} is Genesis, there is no parent for it", current.hash());
698 warn!(target: LOG_TARGET, "{}", msg);
699 return Err(Error::Backend(msg));
700 }
701 loop {
702 match blockchain
703 .header(*current.parent_hash())
704 .map_err(|e| Error::Backend(e.to_string()))?
705 {
706 Some(parent) => return Ok(parent),
707 None => {
708 info!(
709 target: LOG_TARGET,
710 "🥩 Parent of header number {} not found. \
711 BEEFY gadget waiting for header sync to finish ...",
712 current.number()
713 );
714 tokio::time::sleep(delay).await;
715 },
716 }
717 }
718}
719
720async fn wait_for_runtime_pallet<B, R, AuthorityId: AuthorityIdBound>(
723 runtime: &R,
724 finality: &mut Fuse<FinalityNotifications<B>>,
725) -> Result<(NumberFor<B>, <B as Block>::Header), Error>
726where
727 B: Block,
728 R: ProvideRuntimeApi<B>,
729 R::Api: BeefyApi<B, AuthorityId>,
730{
731 info!(target: LOG_TARGET, "🥩 BEEFY gadget waiting for BEEFY pallet to become available...");
732 loop {
733 let notif = finality.next().await.ok_or_else(|| {
734 let err_msg = "🥩 Finality stream has unexpectedly terminated.".into();
735 error!(target: LOG_TARGET, "{}", err_msg);
736 Error::Backend(err_msg)
737 })?;
738 let at = notif.header.hash();
739 if let Some(start) = runtime.runtime_api().beefy_genesis(at).ok().flatten() {
740 if *notif.header.number() >= start {
741 info!(
743 target: LOG_TARGET,
744 "🥩 BEEFY pallet available: block {:?} beefy genesis {:?}",
745 notif.header.number(), start
746 );
747 return Ok((start, notif.header));
748 }
749 }
750 }
751}
752
753async fn expect_validator_set<B, BE, R, AuthorityId: AuthorityIdBound>(
759 runtime: &R,
760 backend: &BE,
761 at_header: &B::Header,
762) -> Result<ValidatorSet<AuthorityId>, Error>
763where
764 B: Block,
765 BE: Backend<B>,
766 R: ProvideRuntimeApi<B>,
767 R::Api: BeefyApi<B, AuthorityId>,
768{
769 let blockchain = backend.blockchain();
770 debug!(
773 target: LOG_TARGET,
774 "🥩 Trying to find validator set active at header(number {:?}, hash {:?})",
775 at_header.number(),
776 at_header.hash()
777 );
778 let mut header = at_header.clone();
779 loop {
780 debug!(target: LOG_TARGET, "🥩 Looking for auth set change at block number: {:?}", *header.number());
781 if let Ok(Some(active)) = runtime.runtime_api().validator_set(header.hash()) {
782 return Ok(active);
783 } else {
784 match find_authorities_change::<B, AuthorityId>(&header) {
785 Some(active) => return Ok(active),
786 None => {
789 header = wait_for_parent_header(blockchain, header, HEADER_SYNC_DELAY)
790 .await
791 .map_err(|e| Error::Backend(e.to_string()))?
792 },
793 }
794 }
795 }
796}
797
798pub(crate) fn find_authorities_change<B, AuthorityId>(
801 header: &B::Header,
802) -> Option<ValidatorSet<AuthorityId>>
803where
804 B: Block,
805 AuthorityId: AuthorityIdBound,
806{
807 let id = OpaqueDigestItemId::Consensus(&BEEFY_ENGINE_ID);
808
809 let filter = |log: ConsensusLog<AuthorityId>| match log {
810 ConsensusLog::AuthoritiesChange(validator_set) => Some(validator_set),
811 _ => None,
812 };
813 header.digest().convert_first(|l| l.try_to(id).and_then(filter))
814}