1use 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
33const LEGACY_COLLATION_PROTOCOL_V1: &str = "/polkadot/collation/1";
35
36const LEGACY_COLLATION_PROTOCOL_VERSION_V1: u32 = 1;
38
39pub const MAX_NOTIFICATION_SIZE: u64 = 100 * 1024;
41
42pub const MAX_AUTHORITY_INCOMING_STREAMS: u32 = 310;
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter)]
47pub enum PeerSet {
48 Validation,
51 Collation,
53}
54
55#[derive(Copy, Clone, Debug, Eq, PartialEq)]
59pub enum IsAuthority {
60 Yes,
62 No,
64}
65
66impl PeerSet {
67 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 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 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 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 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 pub fn get_max_notification_size(self, _: IsAuthority) -> u64 {
155 MAX_NOTIFICATION_SIZE
156 }
157
158 pub fn get_label(self) -> &'static str {
160 match self {
161 PeerSet::Validation => "validation",
162 PeerSet::Collation => "collation",
163 }
164 }
165
166 pub fn get_protocol_label(self, version: ProtocolVersion) -> Option<&'static str> {
168 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#[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
219pub 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#[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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter)]
253pub enum ValidationVersion {
254 V3 = 3,
256}
257
258#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter)]
260pub enum CollationVersion {
261 V1 = 1,
263 V2 = 2,
265 V3 = 3,
267}
268
269#[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#[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 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 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 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 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 pub fn try_get_protocol(&self, name: &ProtocolName) -> Option<(PeerSet, ProtocolVersion)> {
412 self.protocols.get(name).map(ToOwned::to_owned)
413 }
414
415 pub fn get_main_name(&self, protocol: PeerSet) -> ProtocolName {
418 self.get_name(protocol, protocol.get_main_version())
419 }
420
421 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 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 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 },
462 PeerSet::Collation => {
463 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 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 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}