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 = peerset_protocol_names.get_fallback_names(self);
82 let max_notification_size = self.get_max_notification_size(is_authority);
83
84 match self {
85 PeerSet::Validation => {
86 let (config, notification_service) = N::notification_config(
87 protocol,
88 fallback_names,
89 max_notification_size,
90 None,
91 SetConfig {
92 in_peers: super::MIN_GOSSIP_PEERS as u32 / 2 - 1,
97 out_peers: super::MIN_GOSSIP_PEERS as u32 / 2 - 1,
98 reserved_nodes: Vec::new(),
99 non_reserved_mode: sc_network::config::NonReservedPeerMode::Accept,
100 },
101 metrics,
102 peer_store_handle,
103 );
104
105 (config, (PeerSet::Validation, notification_service))
106 },
107 PeerSet::Collation => {
108 let (config, notification_service) = N::notification_config(
109 protocol,
110 fallback_names,
111 max_notification_size,
112 None,
113 SetConfig {
114 in_peers: if is_authority == IsAuthority::Yes {
117 MAX_AUTHORITY_INCOMING_STREAMS
118 } else {
119 0
120 },
121 out_peers: 0,
122 reserved_nodes: Vec::new(),
123 non_reserved_mode: if is_authority == IsAuthority::Yes {
124 sc_network::config::NonReservedPeerMode::Accept
125 } else {
126 sc_network::config::NonReservedPeerMode::Deny
127 },
128 },
129 metrics,
130 peer_store_handle,
131 );
132
133 (config, (PeerSet::Collation, notification_service))
134 },
135 }
136 }
137
138 pub fn get_max_notification_size(self, _: IsAuthority) -> u64 {
140 MAX_NOTIFICATION_SIZE
141 }
142
143 pub fn get_label(self) -> &'static str {
145 match self {
146 PeerSet::Validation => "validation",
147 PeerSet::Collation => "collation",
148 }
149 }
150
151 pub fn get_protocol_label(self, version: ProtocolVersion) -> Option<&'static str> {
153 match self {
156 PeerSet::Validation => {
157 if version == ValidationVersion::V3.into() {
158 Some("validation/3")
159 } else {
160 None
161 }
162 },
163 PeerSet::Collation => {
164 if version == CollationVersion::V1.into() {
165 Some("collation/1")
166 } else if version == CollationVersion::V2.into() {
167 Some("collation/2")
168 } else if version == CollationVersion::V3.into() {
169 Some("collation/3")
170 } else if version == CollationVersion::V4.into() {
171 Some("collation/4")
172 } else {
173 None
174 }
175 },
176 }
177 }
178}
179
180#[derive(Debug, Default)]
182pub struct PerPeerSet<T> {
183 validation: T,
184 collation: T,
185}
186
187impl<T> Index<PeerSet> for PerPeerSet<T> {
188 type Output = T;
189 fn index(&self, index: PeerSet) -> &T {
190 match index {
191 PeerSet::Validation => &self.validation,
192 PeerSet::Collation => &self.collation,
193 }
194 }
195}
196
197impl<T> IndexMut<PeerSet> for PerPeerSet<T> {
198 fn index_mut(&mut self, index: PeerSet) -> &mut T {
199 match index {
200 PeerSet::Validation => &mut self.validation,
201 PeerSet::Collation => &mut self.collation,
202 }
203 }
204}
205
206pub fn peer_sets_info<B: Block, N: NetworkBackend<B, <B as Block>::Hash>>(
211 is_authority: IsAuthority,
212 peerset_protocol_names: &PeerSetProtocolNames,
213 metrics: NotificationMetrics,
214 peer_store_handle: Arc<dyn PeerStoreProvider>,
215) -> Vec<(N::NotificationProtocolConfig, (PeerSet, Box<dyn NotificationService>))> {
216 PeerSet::iter()
217 .map(|s| {
218 s.get_info::<B, N>(
219 is_authority,
220 &peerset_protocol_names,
221 metrics.clone(),
222 Arc::clone(&peer_store_handle),
223 )
224 })
225 .collect()
226}
227
228#[derive(Debug, Clone, Copy, Display, PartialEq, Eq, Hash)]
230pub struct ProtocolVersion(u32);
231
232impl From<ProtocolVersion> for u32 {
233 fn from(version: ProtocolVersion) -> u32 {
234 version.0
235 }
236}
237
238#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter)]
240pub enum ValidationVersion {
241 V3 = 3,
243}
244
245#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter)]
247pub enum CollationVersion {
248 V1 = 1,
250 V2 = 2,
252 V3 = 3,
254 V4 = 4,
256}
257
258#[derive(Debug, Clone, Copy, PartialEq, Eq)]
260pub struct UnknownVersion;
261
262impl TryFrom<ProtocolVersion> for ValidationVersion {
263 type Error = UnknownVersion;
264
265 fn try_from(p: ProtocolVersion) -> Result<Self, UnknownVersion> {
266 for v in Self::iter() {
267 if v as u32 == p.0 {
268 return Ok(v);
269 }
270 }
271
272 Err(UnknownVersion)
273 }
274}
275
276impl TryFrom<ProtocolVersion> for CollationVersion {
277 type Error = UnknownVersion;
278
279 fn try_from(p: ProtocolVersion) -> Result<Self, UnknownVersion> {
280 for v in Self::iter() {
281 if v as u32 == p.0 {
282 return Ok(v);
283 }
284 }
285
286 Err(UnknownVersion)
287 }
288}
289
290impl From<ValidationVersion> for ProtocolVersion {
291 fn from(version: ValidationVersion) -> ProtocolVersion {
292 ProtocolVersion(version as u32)
293 }
294}
295
296impl From<CollationVersion> for ProtocolVersion {
297 fn from(version: CollationVersion) -> ProtocolVersion {
298 ProtocolVersion(version as u32)
299 }
300}
301
302#[derive(Debug, Clone)]
304pub struct PeerSetProtocolNames {
305 protocols: HashMap<ProtocolName, (PeerSet, ProtocolVersion)>,
306 names: HashMap<(PeerSet, ProtocolVersion), ProtocolName>,
307 genesis_hash: Hash,
308 fork_id: Option<String>,
309 main_collation_version: Option<CollationVersion>,
310}
311
312impl PeerSetProtocolNames {
313 pub fn new(genesis_hash: Hash, fork_id: Option<&str>) -> Self {
315 Self::new_with_main_collation_version(genesis_hash, fork_id, None)
316 }
317
318 pub fn new_with_main_collation_version(
325 genesis_hash: Hash,
326 fork_id: Option<&str>,
327 main_collation_version: Option<CollationVersion>,
328 ) -> Self {
329 let mut protocols = HashMap::new();
330 let mut names = HashMap::new();
331 for protocol in PeerSet::iter() {
332 match protocol {
333 PeerSet::Validation => {
334 for version in ValidationVersion::iter() {
335 Self::register_main_protocol(
336 &mut protocols,
337 &mut names,
338 protocol,
339 version.into(),
340 &genesis_hash,
341 fork_id,
342 );
343 }
344 },
345 PeerSet::Collation => {
346 for version in CollationVersion::iter() {
347 Self::register_main_protocol(
348 &mut protocols,
349 &mut names,
350 protocol,
351 version.into(),
352 &genesis_hash,
353 fork_id,
354 );
355 }
356 Self::register_legacy_collation_protocol(&mut protocols, protocol);
357 },
358 }
359 }
360 Self {
361 protocols,
362 names,
363 genesis_hash,
364 fork_id: fork_id.map(|fork_id| fork_id.into()),
365 main_collation_version,
366 }
367 }
368
369 fn register_main_protocol(
371 protocols: &mut HashMap<ProtocolName, (PeerSet, ProtocolVersion)>,
372 names: &mut HashMap<(PeerSet, ProtocolVersion), ProtocolName>,
373 protocol: PeerSet,
374 version: ProtocolVersion,
375 genesis_hash: &Hash,
376 fork_id: Option<&str>,
377 ) {
378 let protocol_name = Self::generate_name(genesis_hash, fork_id, protocol, version);
379 names.insert((protocol, version), protocol_name.clone());
380 Self::insert_protocol_or_panic(protocols, protocol_name, protocol, version);
381 }
382
383 fn register_legacy_collation_protocol(
385 protocols: &mut HashMap<ProtocolName, (PeerSet, ProtocolVersion)>,
386 protocol: PeerSet,
387 ) {
388 Self::insert_protocol_or_panic(
389 protocols,
390 LEGACY_COLLATION_PROTOCOL_V1.into(),
391 protocol,
392 ProtocolVersion(LEGACY_COLLATION_PROTOCOL_VERSION_V1),
393 )
394 }
395
396 fn insert_protocol_or_panic(
398 protocols: &mut HashMap<ProtocolName, (PeerSet, ProtocolVersion)>,
399 name: ProtocolName,
400 protocol: PeerSet,
401 version: ProtocolVersion,
402 ) {
403 match protocols.entry(name) {
404 Entry::Vacant(entry) => {
405 entry.insert((protocol, version));
406 },
407 Entry::Occupied(entry) => {
408 panic!(
409 "Protocol {:?} (version {}) has the same on-the-wire name as protocol {:?} (version {}): `{}`.",
410 protocol,
411 version,
412 entry.get().0,
413 entry.get().1,
414 entry.key(),
415 );
416 },
417 }
418 }
419
420 pub fn try_get_protocol(&self, name: &ProtocolName) -> Option<(PeerSet, ProtocolVersion)> {
422 self.protocols.get(name).map(ToOwned::to_owned)
423 }
424
425 pub fn get_main_name(&self, protocol: PeerSet) -> ProtocolName {
428 self.get_name(protocol, self.get_main_version(protocol))
429 }
430
431 pub fn get_name(&self, protocol: PeerSet, version: ProtocolVersion) -> ProtocolName {
433 self.names
434 .get(&(protocol, version))
435 .expect("Protocols & versions are specified via enums defined above, and they are all registered in `new()`; qed")
436 .clone()
437 }
438
439 fn generate_name(
441 genesis_hash: &Hash,
442 fork_id: Option<&str>,
443 protocol: PeerSet,
444 version: ProtocolVersion,
445 ) -> ProtocolName {
446 let prefix = if let Some(fork_id) = fork_id {
447 format!("/{}/{}", hex::encode(genesis_hash), fork_id)
448 } else {
449 format!("/{}", hex::encode(genesis_hash))
450 };
451
452 let short_name = match protocol {
453 PeerSet::Validation => "validation",
454 PeerSet::Collation => "collation",
455 };
456
457 format!("{}/{}/{}", prefix, short_name, version).into()
458 }
459
460 pub fn get_fallback_names(&self, protocol: PeerSet) -> Vec<ProtocolName> {
462 let mut fallbacks = vec![];
463 match protocol {
464 PeerSet::Validation => {
465 },
468 PeerSet::Collation => {
469 if self.get_main_version(PeerSet::Collation) == CollationVersion::V4.into() {
471 fallbacks.push(Self::generate_name(
472 &self.genesis_hash,
473 self.fork_id.as_deref(),
474 PeerSet::Collation,
475 CollationVersion::V3.into(),
476 ));
477 }
478 fallbacks.push(Self::generate_name(
481 &self.genesis_hash,
482 self.fork_id.as_deref(),
483 PeerSet::Collation,
484 CollationVersion::V2.into(),
485 ));
486 fallbacks.push(LEGACY_COLLATION_PROTOCOL_V1.into());
487 },
488 };
489 fallbacks
490 }
491
492 pub fn get_main_version(&self, protocol: PeerSet) -> ProtocolVersion {
495 match protocol {
496 PeerSet::Validation => Self::newest_validation_version().into(),
497 PeerSet::Collation => self
498 .main_collation_version
499 .unwrap_or_else(Self::newest_collation_version)
500 .into(),
501 }
502 }
503
504 fn newest_collation_version() -> CollationVersion {
505 CollationVersion::iter()
506 .max_by_key(|v| *v as u32)
507 .expect("`CollationVersion` has at least one variant; qed")
508 }
509
510 fn newest_validation_version() -> ValidationVersion {
511 ValidationVersion::iter()
512 .max_by_key(|v| *v as u32)
513 .expect("ValidationVersion` has at least one variant; qed")
514 }
515}
516
517#[cfg(test)]
518mod tests {
519 use super::{
520 CollationVersion, Hash, PeerSet, PeerSetProtocolNames, ProtocolVersion, ValidationVersion,
521 };
522 use strum::IntoEnumIterator;
523
524 struct TestVersion(u32);
525
526 impl From<TestVersion> for ProtocolVersion {
527 fn from(version: TestVersion) -> ProtocolVersion {
528 ProtocolVersion(version.0)
529 }
530 }
531
532 #[test]
533 fn protocol_names_are_correctly_generated() {
534 let genesis_hash = Hash::from([
535 122, 200, 116, 29, 232, 183, 20, 109, 138, 86, 23, 253, 70, 41, 20, 85, 127, 230, 60,
536 38, 90, 127, 28, 16, 231, 218, 227, 40, 88, 238, 187, 128,
537 ]);
538 let name = PeerSetProtocolNames::generate_name(
539 &genesis_hash,
540 None,
541 PeerSet::Validation,
542 TestVersion(3).into(),
543 );
544 let expected =
545 "/7ac8741de8b7146d8a5617fd462914557fe63c265a7f1c10e7dae32858eebb80/validation/3";
546 assert_eq!(name, expected.into());
547
548 let name = PeerSetProtocolNames::generate_name(
549 &genesis_hash,
550 None,
551 PeerSet::Collation,
552 TestVersion(5).into(),
553 );
554 let expected =
555 "/7ac8741de8b7146d8a5617fd462914557fe63c265a7f1c10e7dae32858eebb80/collation/5";
556 assert_eq!(name, expected.into());
557
558 let fork_id = Some("test-fork");
559 let name = PeerSetProtocolNames::generate_name(
560 &genesis_hash,
561 fork_id,
562 PeerSet::Validation,
563 TestVersion(7).into(),
564 );
565 let expected =
566 "/7ac8741de8b7146d8a5617fd462914557fe63c265a7f1c10e7dae32858eebb80/test-fork/validation/7";
567 assert_eq!(name, expected.into());
568
569 let name = PeerSetProtocolNames::generate_name(
570 &genesis_hash,
571 fork_id,
572 PeerSet::Collation,
573 TestVersion(11).into(),
574 );
575 let expected =
576 "/7ac8741de8b7146d8a5617fd462914557fe63c265a7f1c10e7dae32858eebb80/test-fork/collation/11";
577 assert_eq!(name, expected.into());
578 }
579
580 #[test]
581 fn all_protocol_names_are_known() {
582 let genesis_hash = Hash::from([
583 122, 200, 116, 29, 232, 183, 20, 109, 138, 86, 23, 253, 70, 41, 20, 85, 127, 230, 60,
584 38, 90, 127, 28, 16, 231, 218, 227, 40, 88, 238, 187, 128,
585 ]);
586 let protocol_names = PeerSetProtocolNames::new(genesis_hash, None);
587
588 let validation_main =
589 "/7ac8741de8b7146d8a5617fd462914557fe63c265a7f1c10e7dae32858eebb80/validation/3";
590 assert_eq!(
591 protocol_names.try_get_protocol(&validation_main.into()),
592 Some((PeerSet::Validation, TestVersion(3).into())),
593 );
594
595 let validation_legacy = "/polkadot/validation/1";
596 assert!(protocol_names.try_get_protocol(&validation_legacy.into()).is_none());
597
598 let collation_main =
599 "/7ac8741de8b7146d8a5617fd462914557fe63c265a7f1c10e7dae32858eebb80/collation/1";
600 assert_eq!(
601 protocol_names.try_get_protocol(&collation_main.into()),
602 Some((PeerSet::Collation, TestVersion(1).into())),
603 );
604
605 let collation_legacy = "/polkadot/collation/1";
606 assert_eq!(
607 protocol_names.try_get_protocol(&collation_legacy.into()),
608 Some((PeerSet::Collation, TestVersion(1).into())),
609 );
610 }
611
612 #[test]
613 fn all_protocol_versions_are_registered() {
614 let genesis_hash = Hash::from([
615 122, 200, 116, 29, 232, 183, 20, 109, 138, 86, 23, 253, 70, 41, 20, 85, 127, 230, 60,
616 38, 90, 127, 28, 16, 231, 218, 227, 40, 88, 238, 187, 128,
617 ]);
618 let protocol_names = PeerSetProtocolNames::new(genesis_hash, None);
619
620 for protocol in PeerSet::iter() {
621 match protocol {
622 PeerSet::Validation => {
623 for version in ValidationVersion::iter() {
624 assert_eq!(
625 protocol_names.get_name(protocol, version.into()),
626 PeerSetProtocolNames::generate_name(
627 &genesis_hash,
628 None,
629 protocol,
630 version.into(),
631 ),
632 );
633 }
634 },
635 PeerSet::Collation => {
636 for version in CollationVersion::iter() {
637 assert_eq!(
638 protocol_names.get_name(protocol, version.into()),
639 PeerSetProtocolNames::generate_name(
640 &genesis_hash,
641 None,
642 protocol,
643 version.into(),
644 ),
645 );
646 }
647 },
648 }
649 }
650 }
651
652 fn assert_all_versions_negotiable(
656 peer_set: PeerSet,
657 main_collation_version: Option<CollationVersion>,
658 versions: impl Iterator<Item = ProtocolVersion>,
659 ) {
660 let genesis_hash = Hash::from([
661 122, 200, 116, 29, 232, 183, 20, 109, 138, 86, 23, 253, 70, 41, 20, 85, 127, 230, 60,
662 38, 90, 127, 28, 16, 231, 218, 227, 40, 88, 238, 187, 128,
663 ]);
664 let protocol_names = PeerSetProtocolNames::new_with_main_collation_version(
665 genesis_hash,
666 None,
667 main_collation_version,
668 );
669 let main_version = protocol_names.get_main_version(peer_set);
670 let fallback_names = protocol_names.get_fallback_names(peer_set);
671
672 let mut negotiable_versions: std::collections::HashSet<ProtocolVersion> =
674 std::collections::HashSet::new();
675 negotiable_versions.insert(main_version);
676 for fallback in &fallback_names {
677 if let Some((ps, version)) = protocol_names.try_get_protocol(fallback) {
678 assert_eq!(ps, peer_set);
679 negotiable_versions.insert(version);
680 }
681 }
682
683 for version in versions {
684 assert!(
685 negotiable_versions.contains(&version),
686 "{:?} version {} is not negotiable: \
687 not the main version ({}) and not reachable via any fallback name ({:?}). \
688 Add it to get_fallback_names().",
689 peer_set,
690 version,
691 main_version,
692 fallback_names,
693 );
694 }
695 }
696
697 #[test]
698 fn all_collation_versions_are_negotiable() {
699 assert_all_versions_negotiable(
700 PeerSet::Collation,
701 None,
702 CollationVersion::iter().map(Into::into),
703 );
704 }
705
706 #[test]
707 fn all_validation_versions_are_negotiable() {
708 assert_all_versions_negotiable(
709 PeerSet::Validation,
710 None,
711 ValidationVersion::iter().map(Into::into),
712 );
713 }
714
715 #[test]
716 fn all_protocol_versions_have_labels() {
717 for protocol in PeerSet::iter() {
718 match protocol {
719 PeerSet::Validation => {
720 for version in ValidationVersion::iter() {
721 protocol
722 .get_protocol_label(version.into())
723 .expect("All validation protocol versions must have a label.");
724 }
725 },
726 PeerSet::Collation => {
727 for version in CollationVersion::iter() {
728 protocol
729 .get_protocol_label(version.into())
730 .expect("All collation protocol versions must have a label.");
731 }
732 },
733 }
734 }
735 }
736
737 #[test]
738 fn v3_main_caps_collation_at_v3() {
739 assert_all_versions_negotiable(
741 PeerSet::Collation,
742 Some(CollationVersion::V3),
743 CollationVersion::iter().filter(|v| *v != CollationVersion::V4).map(Into::into),
744 );
745
746 let genesis_hash = Hash::from([
748 122, 200, 116, 29, 232, 183, 20, 109, 138, 86, 23, 253, 70, 41, 20, 85, 127, 230, 60,
749 38, 90, 127, 28, 16, 231, 218, 227, 40, 88, 238, 187, 128,
750 ]);
751 let protocol_names = PeerSetProtocolNames::new_with_main_collation_version(
752 genesis_hash,
753 None,
754 Some(CollationVersion::V3),
755 );
756 let v4_name = protocol_names.get_name(PeerSet::Collation, CollationVersion::V4.into());
757 let main_name = protocol_names.get_main_name(PeerSet::Collation);
758 let fallbacks = protocol_names.get_fallback_names(PeerSet::Collation);
759
760 assert_ne!(main_name, v4_name);
761 assert!(!fallbacks.contains(&v4_name));
762 assert!(!fallbacks.contains(&main_name));
764 }
765}