Skip to main content

pezkuwi_gossip_support/
lib.rs

1// Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute
2// This file is part of Pezkuwi.
3
4// Pezkuwi is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Pezkuwi is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Pezkuwi.  If not, see <http://www.gnu.org/licenses/>.
16
17//! This subsystem is responsible for keeping track of session changes
18//! and issuing a connection request to the relevant validators
19//! on every new session.
20//!
21//! In addition to that, it creates a gossip overlay topology
22//! which limits the amount of messages sent and received
23//! to be an order of sqrt of the validators. Our neighbors
24//! in this graph will be forwarded to the network bridge with
25//! the `NetworkBridgeRxMessage::NewGossipTopology` message.
26
27use std::{
28	collections::{HashMap, HashSet},
29	fmt,
30	time::{Duration, Instant},
31	u32,
32};
33
34use futures::{channel::oneshot, select, FutureExt as _};
35use futures_timer::Delay;
36use rand::{Rng, SeedableRng};
37use rand_chacha::ChaCha20Rng;
38
39use pezsc_network::{config::parse_addr, Multiaddr};
40use pezsp_application_crypto::{AppCrypto, ByteArray};
41use pezsp_keystore::{Keystore, KeystorePtr};
42
43use pezkuwi_node_network_protocol::{
44	authority_discovery::AuthorityDiscovery, peer_set::PeerSet, GossipSupportNetworkMessage,
45	PeerId, ValidationProtocols,
46};
47use pezkuwi_node_subsystem::{
48	messages::{
49		ChainApiMessage, GossipSupportMessage, NetworkBridgeEvent, NetworkBridgeRxMessage,
50		NetworkBridgeTxMessage, RuntimeApiMessage, RuntimeApiRequest,
51	},
52	overseer, ActiveLeavesUpdate, FromOrchestra, OverseerSignal, SpawnedSubsystem, SubsystemError,
53};
54use pezkuwi_node_subsystem_util as util;
55use pezkuwi_primitives::{AuthorityDiscoveryId, Hash, SessionIndex, SessionInfo, ValidatorIndex};
56
57#[cfg(test)]
58mod tests;
59
60mod metrics;
61
62use metrics::Metrics;
63
64const LOG_TARGET: &str = "teyrchain::gossip-support";
65// How much time should we wait to reissue a connection request
66// since the last authority discovery resolution failure.
67#[cfg(not(test))]
68const BACKOFF_DURATION: Duration = Duration::from_secs(5);
69
70#[cfg(test)]
71const BACKOFF_DURATION: Duration = Duration::from_millis(500);
72
73// The authorithy_discovery queries runs every ten minutes,
74// so it make sense to run a bit more often than that to
75// detect changes as often as we can, but not too often since
76// it won't help.
77#[cfg(not(test))]
78const TRY_RERESOLVE_AUTHORITIES: Duration = Duration::from_secs(5 * 60);
79
80#[cfg(test)]
81const TRY_RERESOLVE_AUTHORITIES: Duration = Duration::from_secs(2);
82
83/// Duration after which we consider low connectivity a problem.
84///
85/// Especially at startup low connectivity is expected (authority discovery cache needs to be
86/// populated). Authority discovery on Kusama takes around 8 minutes, so warning after 10 minutes
87/// should be fine:
88///
89/// https://github.com/pezkuwichain/pezkuwi-sdk/blob/main/bizinikiwi/client/authority-discovery/src/lib.rs#L88
90const LOW_CONNECTIVITY_WARN_DELAY: Duration = Duration::from_secs(600);
91
92/// If connectivity is lower than this in percent, issue warning in logs.
93const LOW_CONNECTIVITY_WARN_THRESHOLD: usize = 85;
94
95/// The Gossip Support subsystem.
96pub struct GossipSupport<AD> {
97	keystore: KeystorePtr,
98
99	last_session_index: Option<SessionIndex>,
100	/// Whether we are currently an authority or not.
101	is_authority_now: bool,
102	/// The minimum known session we build the topology for.
103	min_known_session: SessionIndex,
104	// Some(timestamp) if we failed to resolve
105	// at least a third of authorities the last time.
106	// `None` otherwise.
107	last_failure: Option<Instant>,
108
109	// Validators can restart during a session, so if they change
110	// their PeerID, we will connect to them in the best case after
111	// a session, so we need to try more often to resolved peers and
112	// reconnect to them. The authorithy_discovery queries runs every ten
113	// minutes, so we can't detect changes in the address more often
114	// that that.
115	last_connection_request: Option<Instant>,
116
117	/// First time we did not reach our connectivity threshold.
118	///
119	/// This is the time of the first failed attempt to connect to >2/3 of all validators in a
120	/// potential sequence of failed attempts. It will be cleared once we reached >2/3
121	/// connectivity.
122	failure_start: Option<Instant>,
123
124	/// Successfully resolved connections
125	///
126	/// waiting for actual connection.
127	resolved_authorities: HashMap<AuthorityDiscoveryId, HashSet<Multiaddr>>,
128
129	/// Actually connected authorities.
130	connected_authorities: HashMap<AuthorityDiscoveryId, PeerId>,
131	/// By `PeerId`.
132	///
133	/// Needed for efficient handling of disconnect events.
134	connected_peers: HashMap<PeerId, HashSet<AuthorityDiscoveryId>>,
135	/// Authority discovery service.
136	authority_discovery: AD,
137
138	/// The oldest session we need to build a topology for because
139	/// the finalized blocks are from a session we haven't built a topology for.
140	finalized_needed_session: Option<u32>,
141	/// Subsystem metrics.
142	metrics: Metrics,
143}
144
145#[overseer::contextbounds(GossipSupport, prefix = self::overseer)]
146impl<AD> GossipSupport<AD>
147where
148	AD: AuthorityDiscovery,
149{
150	/// Create a new instance of the [`GossipSupport`] subsystem.
151	pub fn new(keystore: KeystorePtr, authority_discovery: AD, metrics: Metrics) -> Self {
152		// Initialize metrics to `0`.
153		metrics.on_is_not_authority();
154		metrics.on_is_not_teyrchain_validator();
155
156		Self {
157			keystore,
158			last_session_index: None,
159			last_failure: None,
160			last_connection_request: None,
161			failure_start: None,
162			resolved_authorities: HashMap::new(),
163			connected_authorities: HashMap::new(),
164			connected_peers: HashMap::new(),
165			min_known_session: u32::MAX,
166			authority_discovery,
167			finalized_needed_session: None,
168			is_authority_now: false,
169			metrics,
170		}
171	}
172
173	async fn run<Context>(mut self, mut ctx: Context) -> Self {
174		fn get_connectivity_check_delay() -> Delay {
175			Delay::new(LOW_CONNECTIVITY_WARN_DELAY)
176		}
177		let mut next_connectivity_check = get_connectivity_check_delay().fuse();
178		loop {
179			let message = select!(
180				_ = next_connectivity_check => {
181					self.check_connectivity();
182					next_connectivity_check = get_connectivity_check_delay().fuse();
183					continue
184				}
185				result = ctx.recv().fuse() =>
186					match result {
187						Ok(message) => message,
188						Err(e) => {
189							gum::debug!(
190								target: LOG_TARGET,
191								err = ?e,
192								"Failed to receive a message from Overseer, exiting",
193							);
194							return self
195						},
196					}
197			);
198			match message {
199				FromOrchestra::Communication {
200					msg: GossipSupportMessage::NetworkBridgeUpdate(ev),
201				} => self.handle_connect_disconnect(ev),
202				FromOrchestra::Signal(OverseerSignal::ActiveLeaves(ActiveLeavesUpdate {
203					activated,
204					..
205				})) => {
206					gum::trace!(target: LOG_TARGET, "active leaves signal");
207
208					let leaves = activated.into_iter().map(|a| a.hash);
209					if let Err(e) = self.handle_active_leaves(ctx.sender(), leaves).await {
210						gum::debug!(target: LOG_TARGET, error = ?e);
211					}
212				},
213				FromOrchestra::Signal(OverseerSignal::BlockFinalized(_hash, _number)) => {
214					if let Some(session_index) = self.last_session_index {
215						if let Err(e) = self
216							.build_topology_for_last_finalized_if_needed(
217								ctx.sender(),
218								session_index,
219							)
220							.await
221						{
222							gum::warn!(
223								target: LOG_TARGET,
224								"Failed to build topology for last finalized session: {:?}",
225								e
226							);
227						}
228					}
229				},
230				FromOrchestra::Signal(OverseerSignal::Conclude) => return self,
231			}
232		}
233	}
234
235	/// 1. Determine if the current session index has changed.
236	/// 2. If it has, determine relevant validators and issue a connection request.
237	async fn handle_active_leaves(
238		&mut self,
239		sender: &mut impl overseer::GossipSupportSenderTrait,
240		leaves: impl Iterator<Item = Hash>,
241	) -> Result<(), util::Error> {
242		for leaf in leaves {
243			let current_index = util::request_session_index_for_child(leaf, sender).await.await??;
244			let since_failure = self.last_failure.map(|i| i.elapsed()).unwrap_or_default();
245			let since_last_reconnect =
246				self.last_connection_request.map(|i| i.elapsed()).unwrap_or_default();
247
248			let force_request = since_failure >= BACKOFF_DURATION;
249			let re_resolve_authorities = since_last_reconnect >= TRY_RERESOLVE_AUTHORITIES;
250			let leaf_session = Some((current_index, leaf));
251			let maybe_new_session = match self.last_session_index {
252				Some(i) if current_index <= i => None,
253				_ => leaf_session,
254			};
255
256			let maybe_issue_connection = if force_request || re_resolve_authorities {
257				leaf_session
258			} else {
259				maybe_new_session
260			};
261
262			if let Some((session_index, relay_parent)) = maybe_issue_connection {
263				let session_info =
264					util::request_session_info(leaf, session_index, sender).await.await??;
265
266				let session_info = match session_info {
267					Some(s) => s,
268					None => {
269						gum::warn!(
270							relay_parent = ?leaf,
271							session_index = self.last_session_index,
272							"Failed to get session info.",
273						);
274
275						continue;
276					},
277				};
278
279				// Note: we only update `last_session_index` once we've
280				// successfully gotten the `SessionInfo`.
281				let is_new_session = maybe_new_session.is_some();
282				if is_new_session {
283					gum::debug!(
284						target: LOG_TARGET,
285						%session_index,
286						"New session detected",
287					);
288					self.last_session_index = Some(session_index);
289					self.is_authority_now =
290						ensure_i_am_an_authority(&self.keystore, &session_info.discovery_keys)
291							.is_ok();
292				}
293
294				// Connect to authorities from the past/present/future.
295				//
296				// This is maybe not the right place for this logic to live,
297				// but at the moment we're limited by the network bridge's ability
298				// to handle connection requests (it only allows one, globally).
299				//
300				// Certain network protocols - mostly req/res, but some gossip,
301				// will require being connected to past/future validators as well
302				// as current. That is, the old authority sets are not made obsolete
303				// by virtue of a new session being entered. Therefore we maintain
304				// connections to a much broader set of validators.
305				{
306					let mut connections = authorities_past_present_future(sender, leaf).await?;
307					self.last_connection_request = Some(Instant::now());
308					// Remove all of our locally controlled validator indices so we don't connect to
309					// ourself.
310					let connections =
311						if remove_all_controlled(&self.keystore, &mut connections) != 0 {
312							connections
313						} else {
314							// If we control none of them, issue an empty connection request
315							// to clean up all connections.
316							Vec::new()
317						};
318
319					if force_request || is_new_session {
320						self.issue_connection_request(sender, connections).await;
321					} else if re_resolve_authorities {
322						self.issue_connection_request_to_changed(sender, connections).await;
323					}
324				}
325
326				if is_new_session {
327					if let Err(err) = self
328						.build_topology_for_last_finalized_if_needed(sender, session_index)
329						.await
330					{
331						gum::warn!(
332							target: LOG_TARGET,
333							"Failed to build topology for last finalized session: {:?}",
334							err
335						);
336					}
337
338					// Gossip topology is only relevant for authorities in the current session.
339					let our_index = self.get_key_index_and_update_metrics(&session_info)?;
340					update_gossip_topology(
341						sender,
342						our_index,
343						session_info.discovery_keys.clone(),
344						relay_parent,
345						session_index,
346					)
347					.await?;
348				}
349				// authority_discovery is just a cache so let's try every time we try to re-connect
350				// if new authorities are present.
351				self.update_authority_ids(sender, session_info.discovery_keys).await;
352			}
353		}
354		Ok(())
355	}
356
357	/// Build the gossip topology for the session of the last finalized block if we haven't built
358	/// one.
359	///
360	/// This is needed to ensure that if finality is lagging accross session boundary and a restart
361	/// happens after the new session started, we built a topology from the session we haven't
362	/// finalized the blocks yet.
363	/// Once finalized blocks start to be from a session we've built a topology for, we can stop.
364	async fn build_topology_for_last_finalized_if_needed(
365		&mut self,
366		sender: &mut impl overseer::GossipSupportSenderTrait,
367		current_session_index: u32,
368	) -> Result<(), util::Error> {
369		self.min_known_session = self.min_known_session.min(current_session_index);
370
371		if self
372			.finalized_needed_session
373			.map(|oldest_needed_session| oldest_needed_session < self.min_known_session)
374			.unwrap_or(true)
375		{
376			let (tx, rx) = oneshot::channel();
377			sender.send_message(ChainApiMessage::FinalizedBlockNumber(tx)).await;
378			let finalized_block_number = match rx.await? {
379				Ok(block_number) => block_number,
380				_ => return Ok(()),
381			};
382
383			let (tx, rx) = oneshot::channel();
384			sender
385				.send_message(ChainApiMessage::FinalizedBlockHash(finalized_block_number, tx))
386				.await;
387
388			let finalized_block_hash = match rx.await? {
389				Ok(Some(block_hash)) => block_hash,
390				_ => return Ok(()),
391			};
392
393			let finalized_session_index =
394				util::request_session_index_for_child(finalized_block_hash, sender)
395					.await
396					.await??;
397
398			if finalized_session_index < self.min_known_session
399				&& Some(finalized_session_index) != self.finalized_needed_session
400			{
401				gum::debug!(
402					target: LOG_TARGET,
403					?finalized_block_hash,
404					?finalized_block_number,
405					?finalized_session_index,
406					"Building topology for finalized block session",
407				);
408
409				let finalized_session_info = match util::request_session_info(
410					finalized_block_hash,
411					finalized_session_index,
412					sender,
413				)
414				.await
415				.await??
416				{
417					Some(session_info) => session_info,
418					_ => return Ok(()),
419				};
420
421				let our_index = self.get_key_index_and_update_metrics(&finalized_session_info)?;
422				update_gossip_topology(
423					sender,
424					our_index,
425					finalized_session_info.discovery_keys.clone(),
426					finalized_block_hash,
427					finalized_session_index,
428				)
429				.await?;
430			}
431			self.finalized_needed_session = Some(finalized_session_index);
432		}
433		Ok(())
434	}
435
436	// Checks if the node is an authority and also updates `pezkuwi_node_is_authority` and
437	// `pezkuwi_node_is_teyrchain_validator` metrics accordingly.
438	// On success, returns the index of our keys in `session_info.discovery_keys`.
439	fn get_key_index_and_update_metrics(
440		&mut self,
441		session_info: &SessionInfo,
442	) -> Result<usize, util::Error> {
443		let authority_check_result =
444			ensure_i_am_an_authority(&self.keystore, &session_info.discovery_keys);
445
446		match authority_check_result.as_ref() {
447			Ok(index) => {
448				gum::trace!(target: LOG_TARGET, "We are now an authority",);
449				self.metrics.on_is_authority();
450
451				// The subset of authorities participating in teyrchain consensus.
452				let teyrchain_validators_this_session = session_info.validators.len();
453
454				// First `maxValidators` entries are the teyrchain validators. We'll check
455				// if our index is in this set to avoid searching for the keys.
456				// https://github.com/pezkuwichain/pezkuwi-sdk/blob/main/pezkuwi/runtime/teyrchains/src/configuration.rs#L148
457				if *index < teyrchain_validators_this_session {
458					gum::trace!(target: LOG_TARGET, "We are now a teyrchain validator",);
459					self.metrics.on_is_teyrchain_validator();
460				} else {
461					gum::trace!(target: LOG_TARGET, "We are no longer a teyrchain validator",);
462					self.metrics.on_is_not_teyrchain_validator();
463				}
464			},
465			Err(util::Error::NotAValidator) => {
466				gum::trace!(target: LOG_TARGET, "We are no longer an authority",);
467				self.metrics.on_is_not_authority();
468				self.metrics.on_is_not_teyrchain_validator();
469			},
470			// Don't update on runtime errors.
471			Err(_) => {},
472		};
473
474		authority_check_result
475	}
476
477	async fn resolve_authorities(
478		&mut self,
479		authorities: Vec<AuthorityDiscoveryId>,
480	) -> (Vec<HashSet<Multiaddr>>, HashMap<AuthorityDiscoveryId, HashSet<Multiaddr>>, usize) {
481		let mut validator_addrs = Vec::with_capacity(authorities.len());
482		let mut resolved = HashMap::with_capacity(authorities.len());
483		let mut failures = 0;
484
485		for authority in authorities {
486			if let Some(addrs) =
487				self.authority_discovery.get_addresses_by_authority_id(authority.clone()).await
488			{
489				validator_addrs.push(addrs.clone());
490				resolved.insert(authority, addrs);
491			} else {
492				failures += 1;
493				gum::debug!(
494					target: LOG_TARGET,
495					"Couldn't resolve addresses of authority: {:?}",
496					authority
497				);
498			}
499		}
500		(validator_addrs, resolved, failures)
501	}
502
503	async fn issue_connection_request_to_changed<Sender>(
504		&mut self,
505		sender: &mut Sender,
506		authorities: Vec<AuthorityDiscoveryId>,
507	) where
508		Sender: overseer::GossipSupportSenderTrait,
509	{
510		let (_, resolved, _) = self.resolve_authorities(authorities).await;
511
512		let mut changed = Vec::new();
513
514		for (authority, new_addresses) in &resolved {
515			let new_peer_ids = new_addresses
516				.iter()
517				.flat_map(|addr| parse_addr(addr.clone()).ok().map(|(p, _)| p))
518				.collect::<HashSet<_>>();
519			match self.resolved_authorities.get(authority) {
520				Some(old_addresses) => {
521					let old_peer_ids = old_addresses
522						.iter()
523						.flat_map(|addr| parse_addr(addr.clone()).ok().map(|(p, _)| p))
524						.collect::<HashSet<_>>();
525					if !old_peer_ids.is_superset(&new_peer_ids) {
526						changed.push(new_addresses.clone());
527					}
528				},
529				None => changed.push(new_addresses.clone()),
530			}
531		}
532		gum::debug!(
533			target: LOG_TARGET,
534			num_changed = ?changed.len(),
535			?changed,
536			"Issuing a connection request to changed validators"
537		);
538		if !changed.is_empty() {
539			self.resolved_authorities = resolved;
540
541			sender
542				.send_message(NetworkBridgeTxMessage::AddToResolvedValidators {
543					validator_addrs: changed,
544					peer_set: PeerSet::Validation,
545				})
546				.await;
547		}
548	}
549
550	async fn issue_connection_request<Sender>(
551		&mut self,
552		sender: &mut Sender,
553		authorities: Vec<AuthorityDiscoveryId>,
554	) where
555		Sender: overseer::GossipSupportSenderTrait,
556	{
557		let num = authorities.len();
558
559		let (validator_addrs, resolved, failures) = self.resolve_authorities(authorities).await;
560
561		self.resolved_authorities = resolved;
562		gum::debug!(target: LOG_TARGET, %num, "Issuing a connection request");
563
564		sender
565			.send_message(NetworkBridgeTxMessage::ConnectToResolvedValidators {
566				validator_addrs,
567				peer_set: PeerSet::Validation,
568			})
569			.await;
570
571		// issue another request for the same session
572		// if at least a third of the authorities were not resolved.
573		if num != 0 && 3 * failures >= num {
574			let timestamp = Instant::now();
575			match self.failure_start {
576				None => self.failure_start = Some(timestamp),
577				Some(first) if first.elapsed() >= LOW_CONNECTIVITY_WARN_DELAY => {
578					gum::warn!(
579						target: LOG_TARGET,
580						connected = ?(num - failures),
581						target = ?num,
582						"Low connectivity - authority lookup failed for too many validators."
583					);
584				},
585				Some(_) => {
586					gum::debug!(
587						target: LOG_TARGET,
588						connected = ?(num - failures),
589						target = ?num,
590						"Low connectivity (due to authority lookup failures) - expected on startup."
591					);
592				},
593			}
594			self.last_failure = Some(timestamp);
595		} else {
596			self.last_failure = None;
597			self.failure_start = None;
598		};
599	}
600
601	async fn update_authority_ids<Sender>(
602		&mut self,
603		sender: &mut Sender,
604		authorities: Vec<AuthorityDiscoveryId>,
605	) where
606		Sender: overseer::GossipSupportSenderTrait,
607	{
608		let mut authority_ids: HashMap<PeerId, HashSet<AuthorityDiscoveryId>> = HashMap::new();
609		for authority in authorities {
610			let peer_ids = self
611				.authority_discovery
612				.get_addresses_by_authority_id(authority.clone())
613				.await
614				.into_iter()
615				.flat_map(|list| list.into_iter())
616				.flat_map(|addr| parse_addr(addr).ok().map(|(p, _)| p))
617				.collect::<HashSet<_>>();
618
619			gum::trace!(
620				target: LOG_TARGET,
621				?peer_ids,
622				?authority,
623				"Resolved to peer ids"
624			);
625
626			for p in peer_ids {
627				authority_ids.entry(p).or_default().insert(authority.clone());
628			}
629		}
630
631		// peer was authority and now isn't
632		for (peer_id, current) in self.connected_peers.iter_mut() {
633			// empty -> nonempty is handled in the next loop
634			if !current.is_empty() && !authority_ids.contains_key(peer_id) {
635				sender
636					.send_message(NetworkBridgeRxMessage::UpdatedAuthorityIds {
637						peer_id: *peer_id,
638						authority_ids: HashSet::new(),
639					})
640					.await;
641
642				for a in current.drain() {
643					self.connected_authorities.remove(&a);
644				}
645			}
646		}
647
648		// peer has new authority set.
649		for (peer_id, new) in authority_ids {
650			// If the peer is connected _and_ the authority IDs have changed.
651			if let Some(prev) = self.connected_peers.get(&peer_id).filter(|x| x != &&new) {
652				sender
653					.send_message(NetworkBridgeRxMessage::UpdatedAuthorityIds {
654						peer_id,
655						authority_ids: new.clone(),
656					})
657					.await;
658
659				prev.iter().for_each(|a| {
660					self.connected_authorities.remove(a);
661				});
662				new.iter().for_each(|a| {
663					self.connected_authorities.insert(a.clone(), peer_id);
664				});
665
666				self.connected_peers.insert(peer_id, new);
667			}
668		}
669	}
670
671	fn handle_connect_disconnect(&mut self, ev: NetworkBridgeEvent<GossipSupportNetworkMessage>) {
672		match ev {
673			NetworkBridgeEvent::PeerConnected(peer_id, _, _, o_authority) => {
674				if let Some(authority_ids) = o_authority {
675					authority_ids.iter().for_each(|a| {
676						self.connected_authorities.insert(a.clone(), peer_id);
677					});
678					self.connected_peers.insert(peer_id, authority_ids);
679				} else {
680					self.connected_peers.insert(peer_id, HashSet::new());
681				}
682			},
683			NetworkBridgeEvent::PeerDisconnected(peer_id) => {
684				if let Some(authority_ids) = self.connected_peers.remove(&peer_id) {
685					authority_ids.into_iter().for_each(|a| {
686						self.connected_authorities.remove(&a);
687					});
688				}
689			},
690			NetworkBridgeEvent::UpdatedAuthorityIds(_, _) => {
691				// The `gossip-support` subsystem itself issues these messages.
692			},
693			NetworkBridgeEvent::OurViewChange(_) => {},
694			NetworkBridgeEvent::PeerViewChange(_, _) => {},
695			NetworkBridgeEvent::NewGossipTopology { .. } => {},
696			NetworkBridgeEvent::PeerMessage(_, message) => {
697				// match void -> LLVM unreachable
698				match message {
699					ValidationProtocols::V3(m) => match m {},
700				}
701			},
702		}
703	}
704
705	/// Check connectivity and report on it in logs.
706	fn check_connectivity(&mut self) {
707		let absolute_connected = self.connected_authorities.len();
708		let absolute_resolved = self.resolved_authorities.len();
709		let connected_ratio =
710			(100 * absolute_connected).checked_div(absolute_resolved).unwrap_or(100);
711		let unconnected_authorities = self
712			.resolved_authorities
713			.iter()
714			.filter(|(a, _)| !self.connected_authorities.contains_key(a));
715		if connected_ratio <= LOW_CONNECTIVITY_WARN_THRESHOLD && self.is_authority_now {
716			gum::error!(
717				target: LOG_TARGET,
718				session_index = self.last_session_index.as_ref().map(|s| *s).unwrap_or_default(),
719				"Connectivity seems low, we are only connected to {connected_ratio}% of available validators (see debug logs for details), if this persists more than a session action needs to be taken"
720			);
721		}
722		let pretty = PrettyAuthorities(unconnected_authorities);
723		gum::debug!(
724			target: LOG_TARGET,
725			?connected_ratio,
726			?absolute_connected,
727			?absolute_resolved,
728			unconnected_authorities = %pretty,
729			"Connectivity Report"
730		);
731	}
732}
733
734// Get the authorities of the past, present, and future.
735async fn authorities_past_present_future(
736	sender: &mut impl overseer::GossipSupportSenderTrait,
737	relay_parent: Hash,
738) -> Result<Vec<AuthorityDiscoveryId>, util::Error> {
739	let authorities = util::request_authorities(relay_parent, sender).await.await??;
740	gum::debug!(
741		target: LOG_TARGET,
742		authority_count = ?authorities.len(),
743		"Determined past/present/future authorities",
744	);
745	Ok(authorities)
746}
747
748/// Return an error if we're not a validator in the given set (do not have keys).
749/// Otherwise, returns the index of our keys in `authorities`.
750fn ensure_i_am_an_authority(
751	keystore: &KeystorePtr,
752	authorities: &[AuthorityDiscoveryId],
753) -> Result<usize, util::Error> {
754	for (i, v) in authorities.iter().enumerate() {
755		if Keystore::has_keys(&**keystore, &[(v.to_raw_vec(), AuthorityDiscoveryId::ID)]) {
756			return Ok(i);
757		}
758	}
759	Err(util::Error::NotAValidator)
760}
761
762/// Filter out all controlled keys in the given set. Returns the number of keys removed.
763fn remove_all_controlled(
764	keystore: &KeystorePtr,
765	authorities: &mut Vec<AuthorityDiscoveryId>,
766) -> usize {
767	let mut to_remove = Vec::new();
768	for (i, v) in authorities.iter().enumerate() {
769		if Keystore::has_keys(&**keystore, &[(v.to_raw_vec(), AuthorityDiscoveryId::ID)]) {
770			to_remove.push(i);
771		}
772	}
773
774	for i in to_remove.iter().rev().copied() {
775		authorities.remove(i);
776	}
777
778	to_remove.len()
779}
780
781/// We partition the list of all sorted `authorities` into `sqrt(len)` groups of `sqrt(len)` size
782/// and form a matrix where each validator is connected to all validators in its row and column.
783/// This is similar to `[web3]` research proposed topology, except for the groups are not teyrchain
784/// groups (because not all validators are teyrchain validators and the group size is small),
785/// but formed randomly via BABE randomness from two epochs ago.
786/// This limits the amount of gossip peers to 2 * `sqrt(len)` and ensures the diameter of 2.
787///
788/// [web3]: https://research.web3.foundation/en/latest/polkadot/networking/3-avail-valid.html#topology
789async fn update_gossip_topology(
790	sender: &mut impl overseer::GossipSupportSenderTrait,
791	our_index: usize,
792	authorities: Vec<AuthorityDiscoveryId>,
793	relay_parent: Hash,
794	session_index: SessionIndex,
795) -> Result<(), util::Error> {
796	// retrieve BABE randomness
797	let random_seed = {
798		let (tx, rx) = oneshot::channel();
799
800		// TODO https://github.com/pezkuwichain/pezkuwi-sdk/issues/299:
801		// get the random seed from the `SessionInfo` instead.
802		sender
803			.send_message(RuntimeApiMessage::Request(
804				relay_parent,
805				RuntimeApiRequest::CurrentBabeEpoch(tx),
806			))
807			.await;
808
809		let randomness = rx.await??.randomness;
810		let mut subject = [0u8; 40];
811		subject[..8].copy_from_slice(b"gossipsu");
812		subject[8..].copy_from_slice(&randomness);
813		pezsp_crypto_hashing::blake2_256(&subject)
814	};
815
816	// shuffle the validators and create the index mapping
817	let (shuffled_indices, canonical_shuffling) = {
818		let mut rng: ChaCha20Rng = SeedableRng::from_seed(random_seed);
819		let len = authorities.len();
820		let mut shuffled_indices = vec![0; len];
821		let mut canonical_shuffling: Vec<_> = authorities
822			.iter()
823			.enumerate()
824			.map(|(i, a)| (a.clone(), ValidatorIndex(i as _)))
825			.collect();
826
827		fisher_yates_shuffle(&mut rng, &mut canonical_shuffling[..]);
828		for (i, (_, validator_index)) in canonical_shuffling.iter().enumerate() {
829			shuffled_indices[validator_index.0 as usize] = i;
830		}
831
832		(shuffled_indices, canonical_shuffling)
833	};
834
835	sender
836		.send_message(NetworkBridgeRxMessage::NewGossipTopology {
837			session: session_index,
838			local_index: Some(ValidatorIndex(our_index as _)),
839			canonical_shuffling,
840			shuffled_indices,
841		})
842		.await;
843
844	Ok(())
845}
846
847// Durstenfeld algorithm for the Fisher-Yates shuffle
848// https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm
849fn fisher_yates_shuffle<T, R: Rng + ?Sized>(rng: &mut R, items: &mut [T]) {
850	for i in (1..items.len()).rev() {
851		// invariant: elements with index > i have been locked in place.
852		let index = rng.gen_range(0u32..(i as u32 + 1));
853		items.swap(i, index as usize);
854	}
855}
856
857#[overseer::subsystem(GossipSupport, error = SubsystemError, prefix = self::overseer)]
858impl<Context, AD> GossipSupport<AD>
859where
860	AD: AuthorityDiscovery + Clone,
861{
862	fn start(self, ctx: Context) -> SpawnedSubsystem {
863		let future = self.run(ctx).map(|_| Ok(())).boxed();
864
865		SpawnedSubsystem { name: "gossip-support-subsystem", future }
866	}
867}
868
869/// Helper struct to get a nice rendering of unreachable authorities.
870struct PrettyAuthorities<I>(I);
871
872impl<'a, I> fmt::Display for PrettyAuthorities<I>
873where
874	I: Iterator<Item = (&'a AuthorityDiscoveryId, &'a HashSet<Multiaddr>)> + Clone,
875{
876	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
877		let mut authorities = self.0.clone().peekable();
878		if authorities.peek().is_none() {
879			write!(f, "None")?;
880		} else {
881			write!(f, "\n")?;
882		}
883		for (authority, addrs) in authorities {
884			write!(f, "{}:\n", authority)?;
885			for addr in addrs {
886				write!(f, "  {}\n", addr)?;
887			}
888			write!(f, "\n")?;
889		}
890		Ok(())
891	}
892}