referrerpolicy=no-referrer-when-downgrade

polkadot_node_network_protocol/
peer_set.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot 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// Polkadot 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 Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16
17//! All peersets and protocols used for parachains.
18
19use derive_more::Display;
20use polkadot_primitives::Hash;
21use sc_network::{
22	config::SetConfig, peer_store::PeerStoreProvider, service::NotificationMetrics,
23	types::ProtocolName, NetworkBackend, NotificationService,
24};
25use sp_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 = "/polkadot/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 `sc_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: sc_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							sc_network::config::NonReservedPeerMode::Accept
129						} else {
130							sc_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			PeerSet::Collation =>
178				if version == CollationVersion::V1.into() {
179					Some("collation/1")
180				} else if version == CollationVersion::V2.into() {
181					Some("collation/2")
182				} else {
183					None
184				},
185		}
186	}
187}
188
189/// A small and nifty collection that allows to store data pertaining to each peer set.
190#[derive(Debug, Default)]
191pub struct PerPeerSet<T> {
192	validation: T,
193	collation: T,
194}
195
196impl<T> Index<PeerSet> for PerPeerSet<T> {
197	type Output = T;
198	fn index(&self, index: PeerSet) -> &T {
199		match index {
200			PeerSet::Validation => &self.validation,
201			PeerSet::Collation => &self.collation,
202		}
203	}
204}
205
206impl<T> IndexMut<PeerSet> for PerPeerSet<T> {
207	fn index_mut(&mut self, index: PeerSet) -> &mut T {
208		match index {
209			PeerSet::Validation => &mut self.validation,
210			PeerSet::Collation => &mut self.collation,
211		}
212	}
213}
214
215/// Get `NonDefaultSetConfig`s for all available peer sets, at their default versions.
216///
217/// Should be used during network configuration (added to `NetworkConfiguration::extra_sets`)
218/// or shortly after startup to register the protocols with the network service.
219pub fn peer_sets_info<B: Block, N: NetworkBackend<B, <B as Block>::Hash>>(
220	is_authority: IsAuthority,
221	peerset_protocol_names: &PeerSetProtocolNames,
222	metrics: NotificationMetrics,
223	peer_store_handle: Arc<dyn PeerStoreProvider>,
224) -> Vec<(N::NotificationProtocolConfig, (PeerSet, Box<dyn NotificationService>))> {
225	PeerSet::iter()
226		.map(|s| {
227			s.get_info::<B, N>(
228				is_authority,
229				&peerset_protocol_names,
230				metrics.clone(),
231				Arc::clone(&peer_store_handle),
232			)
233		})
234		.collect()
235}
236
237/// A generic version of the protocol. This struct must not be created directly.
238#[derive(Debug, Clone, Copy, Display, PartialEq, Eq, Hash)]
239pub struct ProtocolVersion(u32);
240
241impl From<ProtocolVersion> for u32 {
242	fn from(version: ProtocolVersion) -> u32 {
243		version.0
244	}
245}
246
247/// Supported validation protocol versions. Only versions defined here must be used in the codebase.
248#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter)]
249pub enum ValidationVersion {
250	/// The third version.
251	V3 = 3,
252}
253
254/// Supported collation protocol versions. Only versions defined here must be used in the codebase.
255#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter)]
256pub enum CollationVersion {
257	/// The first version.
258	V1 = 1,
259	/// The second version.
260	V2 = 2,
261}
262
263/// Marker indicating the version is unknown.
264#[derive(Debug, Clone, Copy, PartialEq, Eq)]
265pub struct UnknownVersion;
266
267impl TryFrom<ProtocolVersion> for ValidationVersion {
268	type Error = UnknownVersion;
269
270	fn try_from(p: ProtocolVersion) -> Result<Self, UnknownVersion> {
271		for v in Self::iter() {
272			if v as u32 == p.0 {
273				return Ok(v)
274			}
275		}
276
277		Err(UnknownVersion)
278	}
279}
280
281impl TryFrom<ProtocolVersion> for CollationVersion {
282	type Error = UnknownVersion;
283
284	fn try_from(p: ProtocolVersion) -> Result<Self, UnknownVersion> {
285		for v in Self::iter() {
286			if v as u32 == p.0 {
287				return Ok(v)
288			}
289		}
290
291		Err(UnknownVersion)
292	}
293}
294
295impl From<ValidationVersion> for ProtocolVersion {
296	fn from(version: ValidationVersion) -> ProtocolVersion {
297		ProtocolVersion(version as u32)
298	}
299}
300
301impl From<CollationVersion> for ProtocolVersion {
302	fn from(version: CollationVersion) -> ProtocolVersion {
303		ProtocolVersion(version as u32)
304	}
305}
306
307/// On the wire protocol name to [`PeerSet`] mapping.
308#[derive(Debug, Clone)]
309pub struct PeerSetProtocolNames {
310	protocols: HashMap<ProtocolName, (PeerSet, ProtocolVersion)>,
311	names: HashMap<(PeerSet, ProtocolVersion), ProtocolName>,
312	genesis_hash: Hash,
313	fork_id: Option<String>,
314}
315
316impl PeerSetProtocolNames {
317	/// Construct [`PeerSetProtocolNames`] using `genesis_hash` and `fork_id`.
318	pub fn new(genesis_hash: Hash, fork_id: Option<&str>) -> Self {
319		let mut protocols = HashMap::new();
320		let mut names = HashMap::new();
321		for protocol in PeerSet::iter() {
322			match protocol {
323				PeerSet::Validation =>
324					for version in ValidationVersion::iter() {
325						Self::register_main_protocol(
326							&mut protocols,
327							&mut names,
328							protocol,
329							version.into(),
330							&genesis_hash,
331							fork_id,
332						);
333					},
334				PeerSet::Collation => {
335					for version in CollationVersion::iter() {
336						Self::register_main_protocol(
337							&mut protocols,
338							&mut names,
339							protocol,
340							version.into(),
341							&genesis_hash,
342							fork_id,
343						);
344					}
345					Self::register_legacy_collation_protocol(&mut protocols, protocol);
346				},
347			}
348		}
349		Self { protocols, names, genesis_hash, fork_id: fork_id.map(|fork_id| fork_id.into()) }
350	}
351
352	/// Helper function to register main protocol.
353	fn register_main_protocol(
354		protocols: &mut HashMap<ProtocolName, (PeerSet, ProtocolVersion)>,
355		names: &mut HashMap<(PeerSet, ProtocolVersion), ProtocolName>,
356		protocol: PeerSet,
357		version: ProtocolVersion,
358		genesis_hash: &Hash,
359		fork_id: Option<&str>,
360	) {
361		let protocol_name = Self::generate_name(genesis_hash, fork_id, protocol, version);
362		names.insert((protocol, version), protocol_name.clone());
363		Self::insert_protocol_or_panic(protocols, protocol_name, protocol, version);
364	}
365
366	/// Helper function to register legacy collation protocol.
367	fn register_legacy_collation_protocol(
368		protocols: &mut HashMap<ProtocolName, (PeerSet, ProtocolVersion)>,
369		protocol: PeerSet,
370	) {
371		Self::insert_protocol_or_panic(
372			protocols,
373			LEGACY_COLLATION_PROTOCOL_V1.into(),
374			protocol,
375			ProtocolVersion(LEGACY_COLLATION_PROTOCOL_VERSION_V1),
376		)
377	}
378
379	/// Helper function to make sure no protocols have the same name.
380	fn insert_protocol_or_panic(
381		protocols: &mut HashMap<ProtocolName, (PeerSet, ProtocolVersion)>,
382		name: ProtocolName,
383		protocol: PeerSet,
384		version: ProtocolVersion,
385	) {
386		match protocols.entry(name) {
387			Entry::Vacant(entry) => {
388				entry.insert((protocol, version));
389			},
390			Entry::Occupied(entry) => {
391				panic!(
392					"Protocol {:?} (version {}) has the same on-the-wire name as protocol {:?} (version {}): `{}`.",
393					protocol,
394					version,
395					entry.get().0,
396					entry.get().1,
397					entry.key(),
398				);
399			},
400		}
401	}
402
403	/// Lookup the protocol using its on the wire name.
404	pub fn try_get_protocol(&self, name: &ProtocolName) -> Option<(PeerSet, ProtocolVersion)> {
405		self.protocols.get(name).map(ToOwned::to_owned)
406	}
407
408	/// Get the main protocol name. It's used by the networking for keeping track
409	/// of peersets and connections.
410	pub fn get_main_name(&self, protocol: PeerSet) -> ProtocolName {
411		self.get_name(protocol, protocol.get_main_version())
412	}
413
414	/// Get the protocol name for specific version.
415	pub fn get_name(&self, protocol: PeerSet, version: ProtocolVersion) -> ProtocolName {
416		self.names
417			.get(&(protocol, version))
418			.expect("Protocols & versions are specified via enums defined above, and they are all registered in `new()`; qed")
419			.clone()
420	}
421
422	/// The protocol name of this protocol based on `genesis_hash` and `fork_id`.
423	fn generate_name(
424		genesis_hash: &Hash,
425		fork_id: Option<&str>,
426		protocol: PeerSet,
427		version: ProtocolVersion,
428	) -> ProtocolName {
429		let prefix = if let Some(fork_id) = fork_id {
430			format!("/{}/{}", hex::encode(genesis_hash), fork_id)
431		} else {
432			format!("/{}", hex::encode(genesis_hash))
433		};
434
435		let short_name = match protocol {
436			PeerSet::Validation => "validation",
437			PeerSet::Collation => "collation",
438		};
439
440		format!("{}/{}/{}", prefix, short_name, version).into()
441	}
442
443	/// Get the protocol fallback names. Currently, it only holds
444	/// the legacy name for the collation protocol version 1.
445	fn get_fallback_names(
446		protocol: PeerSet,
447		_genesis_hash: &Hash,
448		_fork_id: Option<&str>,
449	) -> Vec<ProtocolName> {
450		let mut fallbacks = vec![];
451		match protocol {
452			PeerSet::Validation => {
453				// The validation protocol no longer supports protocol versions 1 and 2,
454				// and only version 3 is used. Therefore, fallback protocols remain empty.
455			},
456			PeerSet::Collation => {
457				fallbacks.push(LEGACY_COLLATION_PROTOCOL_V1.into());
458			},
459		};
460		fallbacks
461	}
462}
463
464#[cfg(test)]
465mod tests {
466	use super::{
467		CollationVersion, Hash, PeerSet, PeerSetProtocolNames, ProtocolVersion, ValidationVersion,
468	};
469	use strum::IntoEnumIterator;
470
471	struct TestVersion(u32);
472
473	impl From<TestVersion> for ProtocolVersion {
474		fn from(version: TestVersion) -> ProtocolVersion {
475			ProtocolVersion(version.0)
476		}
477	}
478
479	#[test]
480	fn protocol_names_are_correctly_generated() {
481		let genesis_hash = Hash::from([
482			122, 200, 116, 29, 232, 183, 20, 109, 138, 86, 23, 253, 70, 41, 20, 85, 127, 230, 60,
483			38, 90, 127, 28, 16, 231, 218, 227, 40, 88, 238, 187, 128,
484		]);
485		let name = PeerSetProtocolNames::generate_name(
486			&genesis_hash,
487			None,
488			PeerSet::Validation,
489			TestVersion(3).into(),
490		);
491		let expected =
492			"/7ac8741de8b7146d8a5617fd462914557fe63c265a7f1c10e7dae32858eebb80/validation/3";
493		assert_eq!(name, expected.into());
494
495		let name = PeerSetProtocolNames::generate_name(
496			&genesis_hash,
497			None,
498			PeerSet::Collation,
499			TestVersion(5).into(),
500		);
501		let expected =
502			"/7ac8741de8b7146d8a5617fd462914557fe63c265a7f1c10e7dae32858eebb80/collation/5";
503		assert_eq!(name, expected.into());
504
505		let fork_id = Some("test-fork");
506		let name = PeerSetProtocolNames::generate_name(
507			&genesis_hash,
508			fork_id,
509			PeerSet::Validation,
510			TestVersion(7).into(),
511		);
512		let expected =
513			"/7ac8741de8b7146d8a5617fd462914557fe63c265a7f1c10e7dae32858eebb80/test-fork/validation/7";
514		assert_eq!(name, expected.into());
515
516		let name = PeerSetProtocolNames::generate_name(
517			&genesis_hash,
518			fork_id,
519			PeerSet::Collation,
520			TestVersion(11).into(),
521		);
522		let expected =
523			"/7ac8741de8b7146d8a5617fd462914557fe63c265a7f1c10e7dae32858eebb80/test-fork/collation/11";
524		assert_eq!(name, expected.into());
525	}
526
527	#[test]
528	fn all_protocol_names_are_known() {
529		let genesis_hash = Hash::from([
530			122, 200, 116, 29, 232, 183, 20, 109, 138, 86, 23, 253, 70, 41, 20, 85, 127, 230, 60,
531			38, 90, 127, 28, 16, 231, 218, 227, 40, 88, 238, 187, 128,
532		]);
533		let protocol_names = PeerSetProtocolNames::new(genesis_hash, None);
534
535		let validation_main =
536			"/7ac8741de8b7146d8a5617fd462914557fe63c265a7f1c10e7dae32858eebb80/validation/3";
537		assert_eq!(
538			protocol_names.try_get_protocol(&validation_main.into()),
539			Some((PeerSet::Validation, TestVersion(3).into())),
540		);
541
542		let validation_legacy = "/polkadot/validation/1";
543		assert!(protocol_names.try_get_protocol(&validation_legacy.into()).is_none());
544
545		let collation_main =
546			"/7ac8741de8b7146d8a5617fd462914557fe63c265a7f1c10e7dae32858eebb80/collation/1";
547		assert_eq!(
548			protocol_names.try_get_protocol(&collation_main.into()),
549			Some((PeerSet::Collation, TestVersion(1).into())),
550		);
551
552		let collation_legacy = "/polkadot/collation/1";
553		assert_eq!(
554			protocol_names.try_get_protocol(&collation_legacy.into()),
555			Some((PeerSet::Collation, TestVersion(1).into())),
556		);
557	}
558
559	#[test]
560	fn all_protocol_versions_are_registered() {
561		let genesis_hash = Hash::from([
562			122, 200, 116, 29, 232, 183, 20, 109, 138, 86, 23, 253, 70, 41, 20, 85, 127, 230, 60,
563			38, 90, 127, 28, 16, 231, 218, 227, 40, 88, 238, 187, 128,
564		]);
565		let protocol_names = PeerSetProtocolNames::new(genesis_hash, None);
566
567		for protocol in PeerSet::iter() {
568			match protocol {
569				PeerSet::Validation =>
570					for version in ValidationVersion::iter() {
571						assert_eq!(
572							protocol_names.get_name(protocol, version.into()),
573							PeerSetProtocolNames::generate_name(
574								&genesis_hash,
575								None,
576								protocol,
577								version.into(),
578							),
579						);
580					},
581				PeerSet::Collation =>
582					for version in CollationVersion::iter() {
583						assert_eq!(
584							protocol_names.get_name(protocol, version.into()),
585							PeerSetProtocolNames::generate_name(
586								&genesis_hash,
587								None,
588								protocol,
589								version.into(),
590							),
591						);
592					},
593			}
594		}
595	}
596
597	#[test]
598	fn all_protocol_versions_have_labels() {
599		for protocol in PeerSet::iter() {
600			match protocol {
601				PeerSet::Validation =>
602					for version in ValidationVersion::iter() {
603						protocol
604							.get_protocol_label(version.into())
605							.expect("All validation protocol versions must have a label.");
606					},
607				PeerSet::Collation =>
608					for version in CollationVersion::iter() {
609						protocol
610							.get_protocol_label(version.into())
611							.expect("All collation protocol versions must have a label.");
612					},
613			}
614		}
615	}
616}