Skip to main content

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}