1use 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
33const LEGACY_COLLATION_PROTOCOL_V1: &str = "/pezkuwi/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 = 100;
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: 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 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 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 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 {
184 None
185 }
186 },
187 }
188 }
189}
190
191#[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
217pub 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#[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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter)]
251pub enum ValidationVersion {
252 V3 = 3,
254}
255
256#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter)]
258pub enum CollationVersion {
259 V1 = 1,
261 V2 = 2,
263}
264
265#[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#[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 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 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 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 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 pub fn try_get_protocol(&self, name: &ProtocolName) -> Option<(PeerSet, ProtocolVersion)> {
408 self.protocols.get(name).map(ToOwned::to_owned)
409 }
410
411 pub fn get_main_name(&self, protocol: PeerSet) -> ProtocolName {
414 self.get_name(protocol, protocol.get_main_version())
415 }
416
417 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 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 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 },
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}