1use std::{
2 collections::{HashMap, HashSet},
3 num::NonZeroUsize,
4 sync::Arc,
5};
6
7pub use cliquenet::{Config as CliquenetConfig, NetAddr, Role};
8use cliquenet::{NetworkReceiver, NetworkSender, Slot, noise::Protocol, x25519::PublicKey};
9use hotshot_types::{
10 PeerConnectInfo,
11 data::{EpochNumber, ViewNumber},
12 epoch_membership::EpochMembershipCoordinator,
13 message::{EXTERNAL_MESSAGE_VERSION, MessageKind, UpgradeLock},
14 traits::{
15 metrics::{Counter, CounterFamily, Gauge, GaugeFamily, Metrics},
16 node_implementation::NodeType,
17 },
18 x25519::{self, Keypair},
19};
20use hotshot_utils::anytrace;
21use parking_lot::RwLock;
22use tracing::{error, info};
23
24use crate::message::{Message, MessageType, Unchecked, Validated};
25
26#[cfg(any(test, feature = "testing"))]
30pub type InboundFilter<T> = Box<dyn Fn(&Message<T, Unchecked>) -> bool + Send + Sync>;
31
32pub struct Cliquenet<T: NodeType> {
33 inner: Sender<T>,
34 receiver: NetworkReceiver,
35 #[cfg(any(test, feature = "testing"))]
36 ifilter: InboundFilter<T>,
37}
38
39impl<T: NodeType> std::fmt::Debug for Cliquenet<T> {
40 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41 f.debug_struct("Cliquenet")
42 .field("inner", &self.inner)
43 .field("receiver", &self.receiver)
44 .finish_non_exhaustive()
45 }
46}
47
48#[derive(Debug, Clone)]
49pub struct Sender<T: NodeType> {
50 my_keys: (T::SignatureKey, PublicKey),
51 sender: NetworkSender,
52 shared: Arc<RwLock<Shared<T::SignatureKey>>>,
53 upgrade_lock: UpgradeLock<T>,
54}
55
56#[derive(Debug)]
57struct Shared<K> {
58 peers: HashMap<K, PeerConnectInfo>,
59 epoch: EpochNumber,
60}
61
62pub const MIN_MESSAGE_LIMIT: NonZeroUsize =
64 NonZeroUsize::new(10 * 1024 * 1024).expect("10 MiB > 0");
65
66const MESSAGE_HEADROOM: usize = 64 * 1024;
68
69pub fn message_limit(max_block_size: u64) -> NonZeroUsize {
71 let block = usize::try_from(max_block_size).unwrap_or(usize::MAX);
72 let limit = block.saturating_add(MESSAGE_HEADROOM);
73 NonZeroUsize::new(limit).map_or(MIN_MESSAGE_LIMIT, |n| n.max(MIN_MESSAGE_LIMIT))
74}
75
76impl<T: NodeType> Cliquenet<T> {
77 #[expect(clippy::too_many_arguments)]
78 pub async fn create<A, P, S>(
79 name: S,
80 signing_key: T::SignatureKey,
81 keypair: Keypair,
82 addr: A,
83 parties: P,
84 max_message_size: Option<NonZeroUsize>,
85 upgrade_lock: UpgradeLock<T>,
86 metrics: Box<dyn Metrics>,
87 ) -> Result<Self, NetworkError>
88 where
89 A: Into<cliquenet::NetAddr>,
90 P: IntoIterator<Item = (T::SignatureKey, PeerConnectInfo)>,
91 S: Into<String>,
92 {
93 let parties: HashMap<T::SignatureKey, PeerConnectInfo> = parties.into_iter().collect();
94
95 let cfg = cliquenet::Config::builder()
96 .name(name)
97 .keypair(keypair.into())
98 .bind(addr.into())
99 .parties(
100 parties
101 .values()
102 .map(|info| (info.x25519_key.into(), info.p2p_addr.clone())),
103 )
104 .noise_protocols([(1.into(), Protocol::IK_25519_AesGcm_Blake2s)])
105 .maybe_max_message_size(max_message_size)
106 .build();
107
108 Self::create_with_config(signing_key, upgrade_lock, cfg, parties, metrics).await
109 }
110
111 pub(crate) async fn create_with_config<P>(
112 signing_key: T::SignatureKey,
113 upgrade_lock: UpgradeLock<T>,
114 config: cliquenet::Config,
115 parties: P,
116 metrics: Box<dyn Metrics>,
117 ) -> Result<Self, NetworkError>
118 where
119 P: IntoIterator<Item = (T::SignatureKey, PeerConnectInfo)>,
120 {
121 let public_key = config.public_key();
122 let metrics = CliquenetMetrics::new(metrics);
123 let network = cliquenet::Network::create(config.with_metrics(metrics)).await?;
124 let peers: HashMap<_, _> = parties.into_iter().collect();
125
126 info!(peers = %peers.len(), "cliquenet created");
127
128 let (send, recv) = network.split_into();
129
130 Ok(Self {
131 #[cfg(any(test, feature = "testing"))]
132 ifilter: Box::new(|_| false),
133 inner: Sender {
134 my_keys: (signing_key, public_key),
135 sender: send,
136 shared: Arc::new(RwLock::new(Shared {
137 peers,
138 epoch: EpochNumber::new(0),
139 })),
140 upgrade_lock,
141 },
142 receiver: recv,
143 })
144 }
145
146 pub fn sender(&self) -> &Sender<T> {
147 &self.inner
148 }
149
150 #[cfg(test)]
152 pub(crate) fn epoch(&self) -> EpochNumber {
153 self.inner.shared.read().epoch
154 }
155
156 #[cfg(any(test, feature = "testing"))]
158 pub fn drop_inbound(&mut self, filter: InboundFilter<T>) {
159 self.ifilter = filter;
160 }
161
162 pub async fn receive(&mut self) -> Result<Message<T, Unchecked>, NetworkError> {
163 #[allow(clippy::never_loop)]
164 loop {
165 let (src, bytes) = self
166 .receiver
167 .receive()
168 .await
169 .ok_or(cliquenet::NetworkError::ChannelClosed)?;
170 let msg = self.deserialize(&bytes)?;
171 let key = self
172 .inner
173 .shared
174 .read()
175 .peers
176 .get(&msg.sender)
177 .map(|info| info.x25519_key)
178 .or_else(|| {
179 (msg.sender == self.inner.my_keys.0).then_some(self.inner.my_keys.1.into())
180 });
181 if Some(src.into()) != key {
182 return Err(NetworkError::InvalidSender {
183 msg: key,
184 src: src.into(),
185 });
186 }
187 #[cfg(any(test, feature = "testing"))]
188 if (self.ifilter)(&msg) {
189 continue;
190 }
191 return Ok(msg);
192 }
193 }
194
195 pub async fn shutdown(&mut self) {
196 if let Ok(done) = self.inner.sender.shutdown() {
197 done.await
198 }
199 }
200
201 pub fn gc(&mut self, v: ViewNumber) -> Result<(), NetworkError> {
202 self.inner.sender.gc(Slot::new(*v))?;
203 Ok(())
204 }
205
206 pub fn add_peers(
207 &mut self,
208 r: Role,
209 ps: Vec<(T::SignatureKey, (PublicKey, NetAddr))>,
210 ) -> Result<(), NetworkError> {
211 let mut targets = Vec::new();
212 {
213 let mut shared = self.inner.shared.write();
214 for (k, (x, a)) in ps {
215 shared.peers.insert(
216 k,
217 PeerConnectInfo {
218 x25519_key: x.into(),
219 p2p_addr: a.clone(),
220 },
221 );
222 targets.push((x, a))
223 }
224 }
225 self.inner.sender.add_peers(r, targets)?;
226 Ok(())
227 }
228
229 pub fn remove_peers(&mut self, ps: Vec<&T::SignatureKey>) -> Result<(), NetworkError> {
230 let mut targets = Vec::new();
231 {
232 let mut shared = self.inner.shared.write();
233 for k in ps {
234 if let Some(info) = shared.peers.remove(k) {
235 targets.push(info.x25519_key.into())
236 }
237 }
238 }
239 self.inner.sender.remove_peers(targets)?;
240 Ok(())
241 }
242
243 pub fn assign_role(&mut self, r: Role, ps: Vec<&T::SignatureKey>) -> Result<(), NetworkError> {
244 let mut targets = Vec::new();
245 {
246 let shared = self.inner.shared.read();
247 for k in ps {
248 if let Some(info) = shared.peers.get(k) {
249 targets.push(info.x25519_key.into())
250 }
251 }
252 }
253 self.inner.sender.assign_peers(r, targets)?;
254 Ok(())
255 }
256
257 pub fn apply_epoch(
265 &mut self,
266 epoch: EpochNumber,
267 coord: &EpochMembershipCoordinator<T>,
268 ) -> Result<(), NetworkError> {
269 let ours = self.inner.shared.read().epoch;
270 if epoch <= ours {
271 info!(%epoch, %ours, "epoch already seen");
272 return Ok(());
273 }
274
275 let Some(curr_infos) = coord.epoch_peers(Some(epoch)) else {
277 error!(%epoch, "no stake table available");
278 return Ok(());
279 };
280
281 let prev_infos = if *epoch > 0 {
283 coord.epoch_peers(Some(epoch - 1)).unwrap_or_else(|| {
284 info!(%epoch, "previous epoch's stake table unavailable");
285 HashMap::new()
286 })
287 } else {
288 HashMap::new()
289 };
290
291 let next_infos = coord.epoch_peers(Some(epoch + 1)).unwrap_or_else(|| {
293 info!(%epoch, "next epoch's stake table not available");
294 HashMap::new()
295 });
296
297 let mut merged_infos = prev_infos.clone();
300 for (k, v) in curr_infos.iter().chain(&next_infos) {
301 merged_infos.insert(k.clone(), v.clone());
302 }
303
304 let wanted: HashSet<T::SignatureKey> = curr_infos
305 .keys()
306 .chain(next_infos.keys())
307 .cloned()
308 .collect();
309
310 let retained: HashSet<T::SignatureKey> = curr_infos
311 .keys()
312 .chain(prev_infos.keys())
313 .cloned()
314 .collect();
315
316 let mut to_add: Vec<(T::SignatureKey, PeerConnectInfo)> = Vec::new();
317 let mut to_del: Vec<(T::SignatureKey, PeerConnectInfo)> = Vec::new();
318
319 for k in &wanted {
320 if let Some(Some(new_info)) = merged_infos.get(k) {
321 if Some(new_info) != self.inner.shared.read().peers.get(k) {
322 info!(%epoch, peer = %k, "adding/updating network peer");
323 to_add.push((k.clone(), new_info.clone()));
324 } else {
325 info!(%epoch, peer = %k, "peer unchanged");
326 }
327 } else {
328 info!(%epoch, peer = %k, "ignoring peer without connection info");
329 }
330 }
331
332 for (k, info) in &self.inner.shared.read().peers {
334 if !(retained.contains(k) || wanted.contains(k)) {
335 info!(%epoch, peer = %k, "removing network peer");
336 to_del.push((k.clone(), info.clone()));
337 }
338 }
339
340 {
341 let peers = &mut self.inner.shared.write().peers;
342 for (k, _) in &to_del {
343 peers.remove(k);
344 }
345 for (k, info) in &to_add {
346 peers.insert(k.clone(), info.clone());
347 }
348 }
349
350 let add_targets: Vec<(PublicKey, NetAddr)> = to_add
351 .iter()
352 .map(|(_, i)| (i.x25519_key.into(), i.p2p_addr.clone()))
353 .collect();
354 let del_targets: Vec<PublicKey> = to_del.iter().map(|(_, i)| i.x25519_key.into()).collect();
355
356 if let Err(err) = self.inner.sender.add_peers(Role::Active, add_targets) {
357 error!(%epoch, %err, "network down; could not add peers to network");
358 return Err(err.into());
359 }
360
361 if let Err(err) = self.inner.sender.remove_peers(del_targets) {
362 error!(%epoch, %err, "network down; could not remove peers from network");
363 return Err(err.into());
364 }
365
366 info!(%epoch, peers = %self.inner.shared.read().peers.len());
367
368 self.inner.shared.write().epoch = epoch;
369
370 Ok(())
371 }
372
373 fn deserialize(&self, bytes: &[u8]) -> Result<Message<T, Unchecked>, NetworkError> {
374 match self
375 .inner
376 .upgrade_lock
377 .deserialize::<Message<T, Unchecked>>(bytes)
378 {
379 Ok((m, v)) => {
380 if v == EXTERNAL_MESSAGE_VERSION && !m.is_external() {
381 let e = anytrace::warn!("received a non-external message with version 0.0");
382 return Err(NetworkError::Serialize(e));
383 }
384 Ok(m)
385 },
386 Err(primary_err) => {
387 if let Ok((_v, hs_msg)) =
394 versions::decode::<hotshot_types::message::Message<T>>(bytes)
395 && let MessageKind::External(data) = hs_msg.kind
396 {
397 return Ok(Message {
398 sender: hs_msg.sender,
399 message_type: MessageType::External(data),
400 });
401 }
402 Err(NetworkError::Serialize(primary_err))
403 },
404 }
405 }
406}
407
408impl<T: NodeType> Sender<T> {
409 pub fn unicast(
410 &self,
411 v: ViewNumber,
412 to: &T::SignatureKey,
413 m: &Message<T, Validated>,
414 ) -> Result<(), NetworkError> {
415 let target = if *to == self.my_keys.0 {
416 self.my_keys.1
417 } else if let Some(info) = self.shared.read().peers.get(to) {
418 info.x25519_key.into()
419 } else {
420 error!(peer = %to, "unicast target not found");
421 return Ok(());
422 };
423 let bytes = self.serialize(m)?;
424 self.sender.unicast(Slot::new(*v), target, bytes)?;
425 Ok(())
426 }
427
428 pub fn multicast(
429 &self,
430 v: ViewNumber,
431 to: Vec<&T::SignatureKey>,
432 m: &Message<T, Validated>,
433 ) -> Result<(), NetworkError> {
434 let bytes = self.serialize(m)?;
435 let mut targets = Vec::new();
436 {
437 let shared = self.shared.read();
438 for t in to {
439 if let Some(info) = shared.peers.get(t) {
440 targets.push(info.x25519_key.into())
441 } else if *t == self.my_keys.0 {
442 targets.push(self.my_keys.1)
443 } else {
444 error!(peer = %t, "multicast target not found");
445 }
446 }
447 }
448 self.sender.multicast(Slot::new(*v), targets, bytes)?;
449 Ok(())
450 }
451
452 pub fn broadcast(&self, v: ViewNumber, m: &Message<T, Validated>) -> Result<(), NetworkError> {
453 let bytes = self.serialize(m)?;
454 self.sender.broadcast(Slot::new(*v), bytes)?;
455 Ok(())
456 }
457
458 pub fn max_message_size(&self) -> NonZeroUsize {
459 self.sender.config().max_message_size()
460 }
461
462 fn serialize(&self, m: &Message<T, Validated>) -> Result<Vec<u8>, NetworkError> {
463 if let MessageType::External(bytes) = &m.message_type {
464 return Ok(bytes.clone());
465 }
466 let v = self.upgrade_lock.serialize(m)?;
467 Ok(v)
468 }
469}
470
471#[derive(Debug, thiserror::Error)]
472pub enum NetworkError {
473 #[error("cliquenet: {0}")]
474 Cliquenet(#[from] cliquenet::NetworkError),
475
476 #[error("serialization: {0}")]
477 Serialize(#[from] anytrace::Error),
478
479 #[error("message sender {msg:?} != message source {src}")]
480 InvalidSender {
481 msg: Option<x25519::PublicKey>,
482 src: x25519::PublicKey,
483 },
484}
485
486impl NetworkError {
487 pub fn is_critical(&self) -> bool {
488 matches!(
489 self,
490 Self::Cliquenet(
491 cliquenet::NetworkError::Bind(..)
492 | cliquenet::NetworkError::ChannelClosed
493 | cliquenet::NetworkError::BudgetClosed
494 )
495 )
496 }
497}
498
499struct CliquenetMetrics {
500 metrics: Box<dyn Metrics>,
501 gauges: RwLock<Gauges>,
502 counters: RwLock<Counters>,
503}
504
505#[derive(Default)]
506struct Gauges {
507 gauges: HashMap<PublicKey, HashMap<String, Box<dyn Gauge>>>,
508 family: HashMap<String, Box<dyn GaugeFamily>>,
509}
510
511#[derive(Default)]
512struct Counters {
513 counters: HashMap<PublicKey, HashMap<String, Box<dyn Counter>>>,
514 family: HashMap<String, Box<dyn CounterFamily>>,
515}
516
517impl CliquenetMetrics {
518 pub fn new(m: Box<dyn Metrics>) -> Self {
519 Self {
520 metrics: m.subgroup("cliquenet".to_string()),
521 gauges: RwLock::new(Gauges::default()),
522 counters: RwLock::new(Counters::default()),
523 }
524 }
525}
526
527impl cliquenet::Metrics for CliquenetMetrics {
533 fn set(&self, key: &PublicKey, label: &str, val: usize) {
534 if let Some(g) = self
535 .gauges
536 .read()
537 .gauges
538 .get(key)
539 .and_then(|m| m.get(label))
540 {
541 return g.set(val);
542 }
543
544 let mut gauges = self.gauges.write();
545
546 if let Some(g) = gauges.gauges.get(key).and_then(|m| m.get(label)) {
548 return g.set(val);
549 }
550
551 let g = gauges
552 .family
553 .entry(label.to_string())
554 .or_insert_with(|| {
555 self.metrics
556 .gauge_family(label.to_string(), vec!["peer".to_string()])
557 })
558 .create(vec![key.to_string()]);
559
560 gauges
561 .gauges
562 .entry(*key)
563 .or_default()
564 .entry(label.to_string())
565 .or_insert(g)
566 .set(val)
567 }
568
569 fn add(&self, key: &PublicKey, label: &str, val: usize) {
570 if let Some(c) = self
571 .counters
572 .read()
573 .counters
574 .get(key)
575 .and_then(|m| m.get(label))
576 {
577 return c.add(val);
578 }
579
580 let mut counters = self.counters.write();
581
582 if let Some(c) = counters.counters.get(key).and_then(|m| m.get(label)) {
584 return c.add(val);
585 }
586
587 let c = counters
588 .family
589 .entry(label.to_string())
590 .or_insert_with(|| {
591 self.metrics
592 .counter_family(label.to_string(), vec!["peer".to_string()])
593 })
594 .create(vec![key.to_string()]);
595
596 counters
597 .counters
598 .entry(*key)
599 .or_default()
600 .entry(label.to_string())
601 .or_insert(c)
602 .add(val)
603 }
604
605 fn del(&self, key: &PublicKey) {
606 let key_string = key.to_string();
607
608 {
609 let mut gauges = self.gauges.write();
610 for (label, _) in gauges.gauges.remove(key).into_iter().flatten() {
611 if let Some(f) = gauges.family.get(&label) {
612 f.destroy(&[&key_string]);
613 }
614 }
615 }
616
617 {
618 let mut counters = self.counters.write();
619 for (label, _) in counters.counters.remove(key).into_iter().flatten() {
620 if let Some(f) = counters.family.get(&label) {
621 f.destroy(&[&key_string]);
622 }
623 }
624 }
625 }
626}