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