nectar_primitives/spec.rs
1//! Canonical Swarm network spec - `network_id`, kademlia tuning, defaults.
2//!
3//! Every Swarm node implementation needs a small set of canonical knobs:
4//! the network ID, the kademlia saturation thresholds, the bootnode-mode
5//! over-saturation cap, the neighborhood-depth low-watermark, the clock-skew
6//! tolerance used during handshake. Bee hard-codes these in
7//! `pkg/topology/kademlia/kademlia.go:54-56` and `pkg/bzz/timestamp.go`.
8//!
9//! This trait surfaces them on the spec object so vertex / apiarist /
10//! reth-swarm derive them from one place instead of duplicating consts.
11
12use std::time::Duration;
13
14use crate::{Bin, NetworkId, ProximityOrder};
15
16/// Canonical Swarm network spec.
17///
18/// Default-method bodies mirror bee's hard-coded constants. Implementors only
19/// have to provide `network_id`; they may override any of the others to
20/// customise a deployment (e.g. a dense testnet with tighter saturation).
21///
22/// Trait can grow with default methods without breaking implementors -
23/// `#[non_exhaustive]` is not a valid attribute on traits in Rust, but the
24/// default-method discipline gives equivalent backward-compatibility.
25pub trait SwarmSpec {
26 /// Network identifier used in [`compute_overlay`](crate::compute_overlay)
27 /// and the BzzAddress sign-data.
28 fn network_id(&self) -> NetworkId;
29
30 /// Maximum proximity order (= number of bins minus one).
31 fn max_proximity_order(&self) -> ProximityOrder {
32 ProximityOrder::MAX
33 }
34
35 /// Minimum desired peers per bin before the bin is considered saturated.
36 /// Bee default: 8 (`defaultSaturationPeers`).
37 fn saturation_peers(&self) -> u8 {
38 8
39 }
40
41 /// Soft cap: above this, non-bootnode peers reject further inbound dials.
42 /// Bee default: 18 (`defaultOverSaturationPeers`).
43 fn over_saturation_peers(&self) -> u8 {
44 18
45 }
46
47 /// Soft cap for **bootnode** mode (higher than regular).
48 /// Bee default: 20 (`defaultBootNodeOverSaturationPeers`).
49 fn bootnode_over_saturation_peers(&self) -> u8 {
50 20
51 }
52
53 /// Minimum peers required in the deepest bins to maintain neighborhood
54 /// depth (bee default: 2 - `nnLowWatermark`).
55 fn neighborhood_low_watermark(&self) -> u8 {
56 2
57 }
58
59 /// Maximum clock skew permitted between local and remote timestamps
60 /// during handshake / hive verification. Bee's `bzz/timestamp.go`
61 /// hard-codes 5s but operational deployments commonly relax to minutes
62 /// or hours; this default is 6h to match what was previously embedded
63 /// in vertex.
64 fn clock_skew_tolerance(&self) -> Duration {
65 Duration::from_secs(6 * 60 * 60)
66 }
67
68 /// Convenience: the routing-table bin count (`max_proximity_order() + 1`).
69 fn bin_count(&self) -> usize {
70 usize::from(self.max_proximity_order().get()) + 1
71 }
72
73 /// Convenience: the deepest bin, derived from `max_proximity_order`.
74 fn max_bin(&self) -> Bin {
75 // PO is range-validated; the conversion is total.
76 Bin::from(self.max_proximity_order())
77 }
78}
79
80/// Concrete static spec used when callers just need to plug a `network_id`
81/// into the canonical defaults.
82#[derive(Debug, Clone, Copy, PartialEq, Eq)]
83pub struct StaticSpec {
84 network_id: NetworkId,
85}
86
87impl StaticSpec {
88 /// Construct a spec for `network_id` with all default knobs.
89 pub const fn new(network_id: NetworkId) -> Self {
90 Self { network_id }
91 }
92}
93
94impl SwarmSpec for StaticSpec {
95 fn network_id(&self) -> NetworkId {
96 self.network_id
97 }
98}
99
100/// Canonical mainnet spec ([`NetworkId::MAINNET`]).
101pub const MAINNET: StaticSpec = StaticSpec::new(NetworkId::MAINNET);
102
103/// Canonical testnet (Sepolia) spec ([`NetworkId::TESTNET`]).
104pub const TESTNET: StaticSpec = StaticSpec::new(NetworkId::TESTNET);
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 #[test]
111 fn defaults_match_bee() {
112 let s = MAINNET;
113 assert_eq!(s.network_id(), NetworkId::MAINNET);
114 assert_eq!(s.max_proximity_order(), ProximityOrder::MAX);
115 assert_eq!(s.saturation_peers(), 8);
116 assert_eq!(s.over_saturation_peers(), 18);
117 assert_eq!(s.bootnode_over_saturation_peers(), 20);
118 assert_eq!(s.neighborhood_low_watermark(), 2);
119 assert_eq!(s.clock_skew_tolerance(), Duration::from_secs(21600));
120 assert_eq!(s.bin_count(), 32);
121 assert_eq!(s.max_bin(), Bin::MAX);
122 }
123
124 #[test]
125 fn testnet_distinct_from_mainnet() {
126 assert_ne!(MAINNET.network_id(), TESTNET.network_id());
127 }
128
129 #[test]
130 fn override_saturation_via_custom_impl() {
131 struct Tight;
132 impl SwarmSpec for Tight {
133 fn network_id(&self) -> NetworkId {
134 NetworkId::TESTNET
135 }
136 fn saturation_peers(&self) -> u8 {
137 4
138 }
139 }
140 assert_eq!(Tight.saturation_peers(), 4);
141 assert_eq!(Tight.over_saturation_peers(), 18); // default unchanged
142 }
143}