Skip to main content

agent_uri_dht/
config.rs

1//! Configuration for the simulated DHT.
2
3use std::num::NonZeroUsize;
4use std::time::Duration;
5
6/// Configuration for the simulated DHT.
7///
8/// Controls behavior such as replication factor, TTL, paging, and verification.
9#[derive(Debug, Clone)]
10pub struct SimulationConfig {
11    /// Maximum registrations at one capability path.
12    ///
13    /// This bounds the key an agent registers at, and only that key. A record
14    /// is materialized at every ancestor key as well, but it appears there
15    /// because of the path it chose rather than by choosing that key, so an
16    /// ancestor's occupancy is not charged to it. Bounding ancestors instead
17    /// would let whoever fills a popular top-level prefix decide whether an
18    /// unrelated agent may register anywhere beneath it (issue #54).
19    ///
20    /// An ancestor key therefore holds whatever its subtree holds, which is
21    /// the direct model's premise: it applies where one store owns the
22    /// namespace and a stored value has no practical size bound. A store that
23    /// does bound a value is required to shard instead, per SPECIFICATION.md
24    /// ยง6.2.
25    ///
26    /// The default of 1000 is therefore a simulator affordance, not a figure a
27    /// deployment can be sized against. A Kademlia overlay bounds a record at
28    /// the wire size a peer will accept, which for `libp2p-kad` is 1 to 27
29    /// registrations, which is exactly why `agent-uri-dht-libp2p` shards rather
30    /// than storing a subtree at one key. Code that handles
31    /// [`DhtError::KeyCapacityExceeded`] ports to it. A capacity chosen against
32    /// this number does not.
33    ///
34    /// [`DhtError::KeyCapacityExceeded`]: crate::DhtError::KeyCapacityExceeded
35    ///
36    /// Default: 1000
37    pub max_registrations_per_capability: usize,
38
39    /// Default TTL for registrations.
40    ///
41    /// Default: 1 hour
42    pub default_ttl: Duration,
43
44    /// Whether to verify attestations on registration.
45    ///
46    /// Set to false only for isolated indexing evaluations.
47    /// Default: true
48    pub verify_attestations: bool,
49
50    /// How many copies the simulator claims to hold.
51    ///
52    /// The simulator holds exactly one authoritative copy, so this is 1 and
53    /// every [`crate::Quorum`] resolves to 1 against it. It exists so that
54    /// quorum arithmetic is exercised rather than bypassed.
55    /// Default: 1
56    pub replication_factor: NonZeroUsize,
57
58    /// Maximum registrations returned in one lookup page.
59    ///
60    /// The simulator could return every match at once, but a distributed
61    /// backend cannot, so it pages deliberately. Callers written against an
62    /// unpaged simulator break on a real backend.
63    /// Default: 64
64    pub page_size: NonZeroUsize,
65
66    /// Whether expiry happens without being asked.
67    ///
68    /// When set, lookups omit lapsed records and writes evict them, so a store
69    /// in use stays clean without anyone calling
70    /// [`SimulatedDht::expire_stale`]. When clear, lapsed records stay visible
71    /// and stay stored until that call, which is what an evaluation measuring
72    /// how much of a namespace has gone stale needs.
73    ///
74    /// [`SimulatedDht::expire_stale`]: crate::SimulatedDht::expire_stale
75    ///
76    /// Default: true
77    pub auto_expire: bool,
78}
79
80impl Default for SimulationConfig {
81    fn default() -> Self {
82        Self {
83            max_registrations_per_capability: 1000,
84            default_ttl: Duration::from_hours(1),
85            verify_attestations: true,
86            replication_factor: NonZeroUsize::new(1).unwrap(),
87            page_size: NonZeroUsize::new(64).unwrap(),
88            auto_expire: true,
89        }
90    }
91}
92
93impl SimulationConfig {
94    /// Creates a new configuration with defaults.
95    #[must_use]
96    pub fn new() -> Self {
97        Self::default()
98    }
99
100    /// Sets the maximum registrations at one capability path.
101    #[must_use]
102    pub const fn with_max_registrations_per_capability(mut self, max: usize) -> Self {
103        self.max_registrations_per_capability = max;
104        self
105    }
106
107    /// Sets the default TTL.
108    #[must_use]
109    pub const fn with_default_ttl(mut self, ttl: Duration) -> Self {
110        self.default_ttl = ttl;
111        self
112    }
113
114    /// Enables or disables attestation verification.
115    #[must_use]
116    pub const fn with_verify_attestations(mut self, verify: bool) -> Self {
117        self.verify_attestations = verify;
118        self
119    }
120
121    /// Sets the number of copies the simulator claims to hold.
122    #[must_use]
123    pub const fn with_replication_factor(mut self, factor: NonZeroUsize) -> Self {
124        self.replication_factor = factor;
125        self
126    }
127
128    /// Sets the maximum number of registrations returned per lookup page.
129    #[must_use]
130    pub const fn with_page_size(mut self, page_size: NonZeroUsize) -> Self {
131        self.page_size = page_size;
132        self
133    }
134
135    /// Enables or disables automatic expiration.
136    #[must_use]
137    pub const fn with_auto_expire(mut self, auto_expire: bool) -> Self {
138        self.auto_expire = auto_expire;
139        self
140    }
141}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146
147    #[test]
148    fn default_config() {
149        let config = SimulationConfig::default();
150        assert_eq!(config.max_registrations_per_capability, 1000);
151        assert_eq!(config.default_ttl, Duration::from_hours(1));
152        assert!(config.verify_attestations);
153        assert_eq!(config.replication_factor.get(), 1);
154        assert_eq!(config.page_size.get(), 64);
155        assert!(config.auto_expire);
156    }
157
158    #[test]
159    fn builder_pattern() {
160        let config = SimulationConfig::new()
161            .with_max_registrations_per_capability(10)
162            .with_default_ttl(Duration::from_mins(30))
163            .with_verify_attestations(true)
164            .with_page_size(NonZeroUsize::new(8).unwrap())
165            .with_auto_expire(false);
166
167        assert_eq!(config.max_registrations_per_capability, 10);
168        assert_eq!(config.default_ttl, Duration::from_mins(30));
169        assert!(config.verify_attestations);
170        assert_eq!(config.page_size.get(), 8);
171        assert!(!config.auto_expire);
172    }
173}