pezkuwi_node_network_protocol/
peer_set.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//! All peersets and protocols used for teyrchains.
18
19use derive_more::Display;
20use pezkuwi_primitives::Hash;
21use pezsc_network::{
22	config::SetConfig, peer_store::PeerStoreProvider, service::NotificationMetrics,
23	types::ProtocolName, NetworkBackend, NotificationService,
24};
25use pezsp_runtime::traits::Block;
26use std::{
27	collections::{hash_map::Entry, HashMap},
28	ops::{Index, IndexMut},
29	sync::Arc,
30};
31use strum::{EnumIter, IntoEnumIterator};
32
33/// The legacy collation protocol name. Only supported on version = 1.
34const LEGACY_COLLATION_PROTOCOL_V1: &str = "/pezkuwi/collation/1";
35
36/// The legacy protocol version. Is always 1 for collation.
37const LEGACY_COLLATION_PROTOCOL_VERSION_V1: u32 = 1;
38
39/// Max notification size is currently constant.
40pub const MAX_NOTIFICATION_SIZE: u64 = 100 * 1024;
41
42/// Maximum allowed incoming connection streams for validator nodes on the collation protocol.
43pub const MAX_AUTHORITY_INCOMING_STREAMS: u32 = 100;
44
45/// The peer-sets and thus the protocols which are used for the network.
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter)]
47pub enum PeerSet {
48	/// The validation peer-set is responsible for all messages related to candidate validation and
49	/// communication among validators.
50	Validation,
51	/// The collation peer-set is used for validator<>collator communication.
52	Collation,
53}
54
55/// Whether a node is an authority or not.
56///
57/// Peer set configuration gets adjusted accordingly.
58#[derive(Copy, Clone, Debug, Eq, PartialEq)]
59pub enum IsAuthority {
60	/// Node is authority.
61	Yes,
62	/// Node is not an authority.
63	No,
64}
65
66impl PeerSet {
67	/// Get `pezsc_network` peer set configurations for each peerset on the default version.
68	///
69	/// Those should be used in the network configuration to register the protocols with the
70	/// network service.
71	pub fn get_info<B: Block, N: NetworkBackend<B, <B as Block>::Hash>>(
72		self,
73		is_authority: IsAuthority,
74		peerset_protocol_names: &PeerSetProtocolNames,
75		metrics: NotificationMetrics,
76		peer_store_handle: Arc<dyn PeerStoreProvider>,
77	) -> (N::NotificationProtocolConfig, (PeerSet, Box<dyn NotificationService>)) {
78		// Networking layer relies on `get_main_name()` being the main name of the protocol
79		// for peersets and connection management.
80		let protocol = peerset_protocol_names.get_main_name(self);
81		let fallback_names = PeerSetProtocolNames::get_fallback_names(
82			self,
83			&peerset_protocol_names.genesis_hash,
84			peerset_protocol_names.fork_id.as_deref(),
85		);
86		let max_notification_size = self.get_max_notification_size(is_authority);
87
88		match self {
89			PeerSet::Validation => {
90				let (config, notification_service) = N::notification_config(
91					protocol,
92					fallback_names,
93					max_notification_size,
94					None,
95					SetConfig {
96						// we allow full nodes to connect to validators for gossip
97						// to ensure any `MIN_GOSSIP_PEERS` always include reserved peers
98						// we limit the amount of non-reserved slots to be less
99						// than `MIN_GOSSIP_PEERS` in total
100						in_peers: super::MIN_GOSSIP_PEERS as u32 / 2 - 1,
101						out_peers: super::MIN_GOSSIP_PEERS as u32 / 2 - 1,
102						reserved_nodes: Vec::new(),
103						non_reserved_mode: pezsc_network::config::NonReservedPeerMode::Accept,
104					},
105					metrics,
106					peer_store_handle,
107				);
108
109				(config, (PeerSet::Validation, notification_service))
110			},
111			PeerSet::Collation => {
112				let (config, notification_service) = N::notification_config(
113					protocol,
114					fallback_names,
115					max_notification_size,
116					None,
117					SetConfig {
118						// Non-authority nodes don't need to accept incoming connections on this
119						// peer set:
120						in_peers: if is_authority == IsAuthority::Yes {
121							MAX_AUTHORITY_INCOMING_STREAMS
122						} else {
123							0
124						},
125						out_peers: 0,
126						reserved_nodes: Vec::new(),
127						non_reserved_mode: if is_authority == IsAuthority::Yes {
128							pezsc_network::config::NonReservedPeerMode::Accept
129						} else {
130							pezsc_network::config::NonReservedPeerMode::Deny
131						},
132					},
133					metrics,
134					peer_store_handle,
135				);
136
137				(config, (PeerSet::Collation, notification_service))
138			},
139		}
140	}
141
142	/// Get the main protocol version for this peer set.
143	///
144	/// Networking layer relies on `get_main_version()` being the version
145	/// of the main protocol name reported by [`PeerSetProtocolNames::get_main_name()`].
146	pub fn get_main_version(self) -> ProtocolVersion {
147		match self {
148			PeerSet::Validation => ValidationVersion::V3.into(),
149			PeerSet::Collation => CollationVersion::V2.into(),
150		}
151	}
152
153	/// Get the max notification size for this peer set.
154	pub fn get_max_notification_size(self, _: IsAuthority) -> u64 {
155		MAX_NOTIFICATION_SIZE
156	}
157
158	/// Get the peer set label for metrics reporting.
159	pub fn get_label(self) -> &'static str {
160		match self {
161			PeerSet::Validation => "validation",
162			PeerSet::Collation => "collation",
163		}
164	}
165
166	/// Get the protocol label for metrics reporting.
167	pub fn get_protocol_label(self, version: ProtocolVersion) -> Option<&'static str> {
168		// Unfortunately, labels must be static strings, so we must manually cover them
169		// for all protocol versions here.
170		match self {
171			PeerSet::Validation => {
172				if version == ValidationVersion::V3.into() {
173					Some("validation/3")
174				} else {
175					None
176				}
177			},
178			PeerSet::Collation => {
179				if version == CollationVersion::V1.into() {
180					Some("collation/1")
181				} else if version == CollationVersion::V2.into() {
182					Some("collation/2")
183				} else {
184					None
185				}
186			},
187		}
188	}
189}
190
191/// A small and nifty collection that allows to store data pertaining to each peer set.
192#[derive(Debug, Default)]
193pub struct PerPeerSet<T> {
194	validation: T,
195	collation: T,
196}
197
198impl<T> Index<PeerSet> for PerPeerSet<T> {
199	type Output = T;
200	fn index(&self, index: PeerSet) -> &T {
201		match index {
202			PeerSet::Validation => &self.validation,
203			PeerSet::Collation => &self.collation,
204		}
205	}
206}
207
208impl<T> IndexMut<PeerSet> for PerPeerSet<T> {
209	fn index_mut(&mut self, index: PeerSet) -> &mut T {
210		match index {
211			PeerSet::Validation => &mut self.validation,
212			PeerSet::Collation => &mut self.collation,
213		}
214	}
215}
216
217/// Get `NonDefaultSetConfig`s for all available peer sets, at their default versions.
218///
219/// Should be used during network configuration (added to `NetworkConfiguration::extra_sets`)
220/// or shortly after startup to register the protocols with the network service.
221pub fn peer_sets_info<B: Block, N: NetworkBackend<B, <B as Block>::Hash>>(
222	is_authority: IsAuthority,
223	peerset_protocol_names: &PeerSetProtocolNames,
224	metrics: NotificationMetrics,
225	peer_store_handle: Arc<dyn PeerStoreProvider>,
226) -> Vec<(N::NotificationProtocolConfig, (PeerSet, Box<dyn NotificationService>))> {
227	PeerSet::iter()
228		.map(|s| {
229			s.get_info::<B, N>(
230				is_authority,
231				&peerset_protocol_names,
232				metrics.clone(),
233				Arc::clone(&peer_store_handle),
234			)
235		})
236		.collect()
237}
238
239/// A generic version of the protocol. This struct must not be created directly.
240#[derive(Debug, Clone, Copy, Display, PartialEq, Eq, Hash)]
241pub struct ProtocolVersion(u32);
242
243impl From<ProtocolVersion> for u32 {
244	fn from(version: ProtocolVersion) -> u32 {
245		version.0
246	}
247}
248
249/// Supported validation protocol versions. Only versions defined here must be used in the codebase.
250#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter)]
251pub enum ValidationVersion {
252	/// The third version.
253	V3 = 3,
254}
255
256/// Supported collation protocol versions. Only versions defined here must be used in the codebase.
257#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter)]
258pub enum CollationVersion {
259	/// The first version.
260	V1 = 1,
261	/// The second version.
262	V2 = 2,
263}
264
265/// Marker indicating the version is unknown.
266#[derive(Debug, Clone, Copy, PartialEq, Eq)]
267pub struct UnknownVersion;
268
269impl TryFrom<ProtocolVersion> for ValidationVersion {
270	type Error = UnknownVersion;
271
272	fn try_from(p: ProtocolVersion) -> Result<Self, UnknownVersion> {
273		for v in Self::iter() {
274			if v as u32 == p.0 {
275				return Ok(v);
276			}
277		}
278
279		Err(UnknownVersion)
280	}
281}
282
283impl TryFrom<ProtocolVersion> for CollationVersion {
284	type Error = UnknownVersion;
285
286	fn try_from(p: ProtocolVersion) -> Result<Self, UnknownVersion> {
287		for v in Self::iter() {
288			if v as u32 == p.0 {
289				return Ok(v);
290			}
291		}
292
293		Err(UnknownVersion)
294	}
295}
296
297impl From<ValidationVersion> for ProtocolVersion {
298	fn from(version: ValidationVersion) -> ProtocolVersion {
299		ProtocolVersion(version as u32)
300	}
301}
302
303impl From<CollationVersion> for ProtocolVersion {
304	fn from(version: CollationVersion) -> ProtocolVersion {
305		ProtocolVersion(version as u32)
306	}
307}
308
309/// On the wire protocol name to [`PeerSet`] mapping.
310#[derive(Debug, Clone)]
311pub struct PeerSetProtocolNames {
312	protocols: HashMap<ProtocolName, (PeerSet, ProtocolVersion)>,
313	names: HashMap<(PeerSet, ProtocolVersion), ProtocolName>,
314	genesis_hash: Hash,
315	fork_id: Option<String>,
316}
317
318impl PeerSetProtocolNames {
319	/// Construct [`PeerSetProtocolNames`] using `genesis_hash` and `fork_id`.
320	pub fn new(genesis_hash: Hash, fork_id: Option<&str>) -> Self {
321		let mut protocols = HashMap::new();
322		let mut names = HashMap::new();
323		for protocol in PeerSet::iter() {
324			match protocol {
325				PeerSet::Validation => {
326					for version in ValidationVersion::iter() {
327						Self::register_main_protocol(
328							&mut protocols,
329							&mut names,
330							protocol,
331							version.into(),
332							&genesis_hash,
333							fork_id,
334						);
335					}
336				},
337				PeerSet::Collation => {
338					for version in CollationVersion::iter() {
339						Self::register_main_protocol(
340							&mut protocols,
341							&mut names,
342							protocol,
343							version.into(),
344							&genesis_hash,
345							fork_id,
346						);
347					}
348					Self::register_legacy_collation_protocol(&mut protocols, protocol);
349				},
350			}
351		}
352		Self { protocols, names, genesis_hash, fork_id: fork_id.map(|fork_id| fork_id.into()) }
353	}
354
355	/// Helper function to register main protocol.
356	fn register_main_protocol(
357		protocols: &mut HashMap<ProtocolName, (PeerSet, ProtocolVersion)>,
358		names: &mut HashMap<(PeerSet, ProtocolVersion), ProtocolName>,
359		protocol: PeerSet,
360		version: ProtocolVersion,
361		genesis_hash: &Hash,
362		fork_id: Option<&str>,
363	) {
364		let protocol_name = Self::generate_name(genesis_hash, fork_id, protocol, version);
365		names.insert((protocol, version), protocol_name.clone());
366		Self::insert_protocol_or_panic(protocols, protocol_name, protocol, version);
367	}
368
369	/// Helper function to register legacy collation protocol.
370	fn register_legacy_collation_protocol(
371		protocols: &mut HashMap<ProtocolName, (PeerSet, ProtocolVersion)>,
372		protocol: PeerSet,
373	) {
374		Self::insert_protocol_or_panic(
375			protocols,
376			LEGACY_COLLATION_PROTOCOL_V1.into(),
377			protocol,
378			ProtocolVersion(LEGACY_COLLATION_PROTOCOL_VERSION_V1),
379		)
380	}
381
382	/// Helper function to make sure no protocols have the same name.
383	fn insert_protocol_or_panic(
384		protocols: &mut HashMap<ProtocolName, (PeerSet, ProtocolVersion)>,
385		name: ProtocolName,
386		protocol: PeerSet,
387		version: ProtocolVersion,
388	) {
389		match protocols.entry(name) {
390			Entry::Vacant(entry) => {
391				entry.insert((protocol, version));
392			},
393			Entry::Occupied(entry) => {
394				panic!(
395					"Protocol {:?} (version {}) has the same on-the-wire name as protocol {:?} (version {}): `{}`.",
396					protocol,
397					version,
398					entry.get().0,
399					entry.get().1,
400					entry.key(),
401				);
402			},
403		}
404	}
405
406	/// Lookup the protocol using its on the wire name.
407	pub fn try_get_protocol(&self, name: &ProtocolName) -> Option<(PeerSet, ProtocolVersion)> {
408		self.protocols.get(name).map(ToOwned::to_owned)
409	}
410
411	/// Get the main protocol name. It's used by the networking for keeping track
412	/// of peersets and connections.
413	pub fn get_main_name(&self, protocol: PeerSet) -> ProtocolName {
414		self.get_name(protocol, protocol.get_main_version())
415	}
416
417	/// Get the protocol name for specific version.
418	pub fn get_name(&self, protocol: PeerSet, version: ProtocolVersion) -> ProtocolName {
419		self.names
420			.get(&(protocol, version))
421			.expect("Protocols & versions are specified via enums defined above, and they are all registered in `new()`; qed")
422			.clone()
423	}
424
425	/// The protocol name of this protocol based on `genesis_hash` and `fork_id`.
426	fn generate_name(
427		genesis_hash: &Hash,
428		fork_id: Option<&str>,
429		protocol: PeerSet,
430		version: ProtocolVersion,
431	) -> ProtocolName {
432		let prefix = if let Some(fork_id) = fork_id {
433			format!("/{}/{}", hex::encode(genesis_hash), fork_id)
434		} else {
435			format!("/{}", hex::encode(genesis_hash))
436		};
437
438		let short_name = match protocol {
439			PeerSet::Validation => "validation",
440			PeerSet::Collation => "collation",
441		};
442
443		format!("{}/{}/{}", prefix, short_name, version).into()
444	}
445
446	/// Get the protocol fallback names. Currently, it only holds
447	/// the legacy name for the collation protocol version 1.
448	fn get_fallback_names(
449		protocol: PeerSet,
450		_genesis_hash: &Hash,
451		_fork_id: Option<&str>,
452	) -> Vec<ProtocolName> {
453		let mut fallbacks = vec![];
454		match protocol {
455			PeerSet::Validation => {
456				// The validation protocol no longer supports protocol versions 1 and 2,
457				// and only version 3 is used. Therefore, fallback protocols remain empty.
458			},
459			PeerSet::Collation => {
460				fallbacks.push(LEGACY_COLLATION_PROTOCOL_V1.into());
461			},
462		};
463		fallbacks
464	}
465}
466
467#[cfg(test)]
468mod tests {
469	use super::{
470		CollationVersion, Hash, PeerSet, PeerSetProtocolNames, ProtocolVersion, ValidationVersion,
471	};
472	use strum::IntoEnumIterator;
473
474	struct TestVersion(u32);
475
476	impl From<TestVersion> for ProtocolVersion {
477		fn from(version: TestVersion) -> ProtocolVersion {
478			ProtocolVersion(version.0)
479		}
480	}
481
482	#[test]
483	fn protocol_names_are_correctly_generated() {
484		let genesis_hash = Hash::from([
485			122, 200, 116, 29, 232, 183, 20, 109, 138, 86, 23, 253, 70, 41, 20, 85, 127, 230, 60,
486			38, 90, 127, 28, 16, 231, 218, 227, 40, 88, 238, 187, 128,
487		]);
488		let name = PeerSetProtocolNames::generate_name(
489			&genesis_hash,
490			None,
491			PeerSet::Validation,
492			TestVersion(3).into(),
493		);
494		let expected =
495			"/7ac8741de8b7146d8a5617fd462914557fe63c265a7f1c10e7dae32858eebb80/validation/3";
496		assert_eq!(name, expected.into());
497
498		let name = PeerSetProtocolNames::generate_name(
499			&genesis_hash,
500			None,
501			PeerSet::Collation,
502			TestVersion(5).into(),
503		);
504		let expected =
505			"/7ac8741de8b7146d8a5617fd462914557fe63c265a7f1c10e7dae32858eebb80/collation/5";
506		assert_eq!(name, expected.into());
507
508		let fork_id = Some("test-fork");
509		let name = PeerSetProtocolNames::generate_name(
510			&genesis_hash,
511			fork_id,
512			PeerSet::Validation,
513			TestVersion(7).into(),
514		);
515		let expected =
516			"/7ac8741de8b7146d8a5617fd462914557fe63c265a7f1c10e7dae32858eebb80/test-fork/validation/7";
517		assert_eq!(name, expected.into());
518
519		let name = PeerSetProtocolNames::generate_name(
520			&genesis_hash,
521			fork_id,
522			PeerSet::Collation,
523			TestVersion(11).into(),
524		);
525		let expected =
526			"/7ac8741de8b7146d8a5617fd462914557fe63c265a7f1c10e7dae32858eebb80/test-fork/collation/11";
527		assert_eq!(name, expected.into());
528	}
529
530	#[test]
531	fn all_protocol_names_are_known() {
532		let genesis_hash = Hash::from([
533			122, 200, 116, 29, 232, 183, 20, 109, 138, 86, 23, 253, 70, 41, 20, 85, 127, 230, 60,
534			38, 90, 127, 28, 16, 231, 218, 227, 40, 88, 238, 187, 128,
535		]);
536		let protocol_names = PeerSetProtocolNames::new(genesis_hash, None);
537
538		let validation_main =
539			"/7ac8741de8b7146d8a5617fd462914557fe63c265a7f1c10e7dae32858eebb80/validation/3";
540		assert_eq!(
541			protocol_names.try_get_protocol(&validation_main.into()),
542			Some((PeerSet::Validation, TestVersion(3).into())),
543		);
544
545		let validation_legacy = "/pezkuwi/validation/1";
546		assert!(protocol_names.try_get_protocol(&validation_legacy.into()).is_none());
547
548		let collation_main =
549			"/7ac8741de8b7146d8a5617fd462914557fe63c265a7f1c10e7dae32858eebb80/collation/1";
550		assert_eq!(
551			protocol_names.try_get_protocol(&collation_main.into()),
552			Some((PeerSet::Collation, TestVersion(1).into())),
553		);
554
555		let collation_legacy = "/pezkuwi/collation/1";
556		assert_eq!(
557			protocol_names.try_get_protocol(&collation_legacy.into()),
558			Some((PeerSet::Collation, TestVersion(1).into())),
559		);
560	}
561
562	#[test]
563	fn all_protocol_versions_are_registered() {
564		let genesis_hash = Hash::from([
565			122, 200, 116, 29, 232, 183, 20, 109, 138, 86, 23, 253, 70, 41, 20, 85, 127, 230, 60,
566			38, 90, 127, 28, 16, 231, 218, 227, 40, 88, 238, 187, 128,
567		]);
568		let protocol_names = PeerSetProtocolNames::new(genesis_hash, None);
569
570		for protocol in PeerSet::iter() {
571			match protocol {
572				PeerSet::Validation => {
573					for version in ValidationVersion::iter() {
574						assert_eq!(
575							protocol_names.get_name(protocol, version.into()),
576							PeerSetProtocolNames::generate_name(
577								&genesis_hash,
578								None,
579								protocol,
580								version.into(),
581							),
582						);
583					}
584				},
585				PeerSet::Collation => {
586					for version in CollationVersion::iter() {
587						assert_eq!(
588							protocol_names.get_name(protocol, version.into()),
589							PeerSetProtocolNames::generate_name(
590								&genesis_hash,
591								None,
592								protocol,
593								version.into(),
594							),
595						);
596					}
597				},
598			}
599		}
600	}
601
602	#[test]
603	fn all_protocol_versions_have_labels() {
604		for protocol in PeerSet::iter() {
605			match protocol {
606				PeerSet::Validation => {
607					for version in ValidationVersion::iter() {
608						protocol
609							.get_protocol_label(version.into())
610							.expect("All validation protocol versions must have a label.");
611					}
612				},
613				PeerSet::Collation => {
614					for version in CollationVersion::iter() {
615						protocol
616							.get_protocol_label(version.into())
617							.expect("All collation protocol versions must have a label.");
618					}
619				},
620			}
621		}
622	}
623}