1use crate::tier::Tier;
16
17#[non_exhaustive]
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
23pub enum Method {
24 GetContent,
27 GetCapsule,
29 GetModule,
31 GetManifest,
33 GetMetadata,
35 ListCapsules,
37 GetProof,
39 GetProofStatus,
41 GetAnchoredRoot,
44 GetCollection,
47 ListCollectionItems,
50 Health,
52 Methods,
54
55 GetNetworkInfo,
58 GetPeers,
60 Announce,
62 GetAvailability,
64 ListInventory,
66 FetchRange,
68 GetModuleInfo,
72 FetchModuleRange,
75
76 Stage,
79 CacheGetConfig,
81 CacheSetCapBytes,
83 CacheClear,
85 CacheListCached,
87 CacheRemoveCached,
89 CacheFetchAndCache,
91 CacheStats,
94 ControlPeerStatus,
96 ControlSubscribe,
99 ControlUnsubscribe,
101 ControlListSubscriptions,
103 ControlPeersConnect,
106 ControlPeersDisconnect,
109 RpcDiscover,
111}
112
113impl Method {
114 pub const fn name(self) -> &'static str {
116 match self {
117 Method::GetContent => "dig.getContent",
118 Method::GetCapsule => "dig.getCapsule",
119 Method::GetModule => "dig.getModule",
120 Method::GetManifest => "dig.getManifest",
121 Method::GetMetadata => "dig.getMetadata",
122 Method::ListCapsules => "dig.listCapsules",
123 Method::GetProof => "dig.getProof",
124 Method::GetProofStatus => "dig.getProofStatus",
125 Method::GetAnchoredRoot => "dig.getAnchoredRoot",
126 Method::GetCollection => "dig.getCollection",
127 Method::ListCollectionItems => "dig.listCollectionItems",
128 Method::Health => "dig.health",
129 Method::Methods => "dig.methods",
130 Method::GetNetworkInfo => "dig.getNetworkInfo",
131 Method::GetPeers => "dig.getPeers",
132 Method::Announce => "dig.announce",
133 Method::GetAvailability => "dig.getAvailability",
134 Method::ListInventory => "dig.listInventory",
135 Method::FetchRange => "dig.fetchRange",
136 Method::GetModuleInfo => "dig.getModuleInfo",
137 Method::FetchModuleRange => "dig.fetchModuleRange",
138 Method::Stage => "dig.stage",
139 Method::CacheGetConfig => "cache.getConfig",
140 Method::CacheSetCapBytes => "cache.setCapBytes",
141 Method::CacheClear => "cache.clear",
142 Method::CacheListCached => "cache.listCached",
143 Method::CacheRemoveCached => "cache.removeCached",
144 Method::CacheFetchAndCache => "cache.fetchAndCache",
145 Method::CacheStats => "cache.stats",
146 Method::ControlPeerStatus => "control.peerStatus",
147 Method::ControlSubscribe => "control.subscribe",
148 Method::ControlUnsubscribe => "control.unsubscribe",
149 Method::ControlListSubscriptions => "control.listSubscriptions",
150 Method::ControlPeersConnect => "control.peers.connect",
151 Method::ControlPeersDisconnect => "control.peers.disconnect",
152 Method::RpcDiscover => "rpc.discover",
153 }
154 }
155
156 pub fn from_name(name: &str) -> Option<Method> {
159 Method::ALL.iter().copied().find(|m| m.name() == name)
160 }
161
162 pub const fn tier(self) -> Tier {
168 match self {
169 Method::GetContent
170 | Method::GetCapsule
171 | Method::GetModule
172 | Method::GetManifest
173 | Method::GetMetadata
174 | Method::ListCapsules
175 | Method::GetProof
176 | Method::GetProofStatus
177 | Method::GetAnchoredRoot
178 | Method::GetCollection
179 | Method::ListCollectionItems
180 | Method::Health
181 | Method::Methods => Tier::PublicRead,
182
183 Method::GetNetworkInfo
184 | Method::GetPeers
185 | Method::Announce
186 | Method::GetAvailability
187 | Method::ListInventory
188 | Method::FetchRange
189 | Method::GetModuleInfo
190 | Method::FetchModuleRange => Tier::Peer,
191
192 Method::Stage
193 | Method::CacheGetConfig
194 | Method::CacheSetCapBytes
195 | Method::CacheClear
196 | Method::CacheListCached
197 | Method::CacheRemoveCached
198 | Method::CacheFetchAndCache
199 | Method::CacheStats
200 | Method::ControlPeerStatus
201 | Method::ControlSubscribe
202 | Method::ControlUnsubscribe
203 | Method::ControlListSubscriptions
204 | Method::ControlPeersConnect
205 | Method::ControlPeersDisconnect
206 | Method::RpcDiscover => Tier::Control,
207 }
208 }
209
210 pub const fn is_peer_reachable(self) -> bool {
219 matches!(
220 self,
221 Method::GetContent
222 | Method::GetNetworkInfo
223 | Method::GetPeers
224 | Method::Announce
225 | Method::GetAvailability
226 | Method::ListInventory
227 | Method::FetchRange
228 | Method::GetModuleInfo
229 | Method::FetchModuleRange
230 | Method::GetAnchoredRoot
231 | Method::GetCollection
232 | Method::ListCollectionItems
233 )
234 }
235
236 pub const fn summary(self) -> &'static str {
238 match self {
239 Method::GetContent => "Read a verified window of a resource's ciphertext.",
240 Method::GetCapsule => "Fetch the whole .dig module for (store, root).",
241 Method::GetModule => "Alias of dig.getCapsule.",
242 Method::GetManifest => "Fetch the public discovery manifest resource.",
243 Method::GetMetadata => "Fetch the plaintext metadata manifest.",
244 Method::ListCapsules => "List a store's confirmed capsules.",
245 Method::GetProof => "Get the real inclusion proof + execution-proof status.",
246 Method::GetProofStatus => "Poll a real execution-proof job by id.",
247 Method::GetAnchoredRoot => "Resolve a store's chain-anchored tip root.",
248 Method::GetCollection => "Get collection-level facts for NFT launcher ids.",
249 Method::ListCollectionItems => "List resolved NFT collection items (paginated).",
250 Method::Health => "Liveness and capability summary.",
251 Method::Methods => "List the method names this node implements.",
252 Method::GetNetworkInfo => "This node's peer-network posture.",
253 Method::GetPeers => "The peers this node currently knows.",
254 Method::Announce => "Accept a peer announcement (peer_id + addresses).",
255 Method::GetAvailability => "Batch-check whether this node holds items.",
256 Method::ListInventory => "Enumerate the stores / roots this node serves.",
257 Method::FetchRange => "Fetch a single verified range frame of a resource.",
258 Method::GetModuleInfo => {
259 "Get the whole-.dig-module transfer descriptor (size + hashes) for (store, root)."
260 }
261 Method::FetchModuleRange => "Fetch a single range frame of the whole .dig module blob.",
262 Method::Stage => "Compile a local folder into a capsule .dig in-process.",
263 Method::CacheGetConfig => "Get the local-cache configuration.",
264 Method::CacheSetCapBytes => "Set the local-cache size cap.",
265 Method::CacheClear => "Clear the local cache.",
266 Method::CacheListCached => "List the durable cached modules.",
267 Method::CacheRemoveCached => "Remove one cached capsule.",
268 Method::CacheFetchAndCache => "Fetch and cache one capsule.",
269 Method::CacheStats => {
270 "Cache telemetry: cap + usage, entry count + bytes, eviction + hit/miss counters."
271 }
272 Method::ControlPeerStatus => "Snapshot the node's peer network.",
273 Method::ControlSubscribe => {
274 "Subscribe the node to a store (persisted watch + gap-fill)."
275 }
276 Method::ControlUnsubscribe => "Unsubscribe the node from a store.",
277 Method::ControlListSubscriptions => "List the node's persisted store subscriptions.",
278 Method::ControlPeersConnect => "Dial a peer into the connected pool.",
279 Method::ControlPeersDisconnect => "Drop a pooled peer by peer_id.",
280 Method::RpcDiscover => "Return the OpenRPC self-describe document.",
281 }
282 }
283
284 pub const ALL: &'static [Method] = &[
286 Method::GetContent,
287 Method::GetCapsule,
288 Method::GetModule,
289 Method::GetManifest,
290 Method::GetMetadata,
291 Method::ListCapsules,
292 Method::GetProof,
293 Method::GetProofStatus,
294 Method::GetAnchoredRoot,
295 Method::GetCollection,
296 Method::ListCollectionItems,
297 Method::Health,
298 Method::Methods,
299 Method::GetNetworkInfo,
300 Method::GetPeers,
301 Method::Announce,
302 Method::GetAvailability,
303 Method::ListInventory,
304 Method::FetchRange,
305 Method::GetModuleInfo,
306 Method::FetchModuleRange,
307 Method::Stage,
308 Method::CacheGetConfig,
309 Method::CacheSetCapBytes,
310 Method::CacheClear,
311 Method::CacheListCached,
312 Method::CacheRemoveCached,
313 Method::CacheFetchAndCache,
314 Method::CacheStats,
315 Method::ControlPeerStatus,
316 Method::ControlSubscribe,
317 Method::ControlUnsubscribe,
318 Method::ControlListSubscriptions,
319 Method::ControlPeersConnect,
320 Method::ControlPeersDisconnect,
321 Method::RpcDiscover,
322 ];
323
324 pub fn peer_reachable_names() -> Vec<&'static str> {
327 Method::ALL
328 .iter()
329 .copied()
330 .filter(|m| m.is_peer_reachable())
331 .map(Method::name)
332 .collect()
333 }
334}
335
336impl std::fmt::Display for Method {
337 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
338 f.write_str(self.name())
339 }
340}
341
342#[cfg(test)]
343mod tests {
344 use super::*;
345 use std::collections::HashSet;
346
347 #[test]
350 fn names_round_trip() {
351 for m in Method::ALL {
352 assert_eq!(Method::from_name(m.name()), Some(*m), "{}", m.name());
353 }
354 assert!(Method::from_name("dig.nope").is_none());
355 }
356
357 #[test]
360 fn names_unique() {
361 let names: HashSet<&str> = Method::ALL.iter().map(|m| m.name()).collect();
362 assert_eq!(names.len(), Method::ALL.len());
363 }
364
365 #[test]
371 fn peer_allowlist_matches_canonical() {
372 let mut got = Method::peer_reachable_names();
373 got.sort_unstable();
374 let mut want = vec![
375 "dig.getContent",
376 "dig.getNetworkInfo",
377 "dig.getPeers",
378 "dig.announce",
379 "dig.getAvailability",
380 "dig.listInventory",
381 "dig.fetchRange",
382 "dig.getModuleInfo",
383 "dig.fetchModuleRange",
384 "dig.getAnchoredRoot",
385 "dig.getCollection",
386 "dig.listCollectionItems",
387 ];
388 want.sort_unstable();
389 assert_eq!(got, want);
390
391 for m in [
393 Method::Stage,
394 Method::CacheGetConfig,
395 Method::CacheSetCapBytes,
396 Method::CacheClear,
397 Method::CacheListCached,
398 Method::CacheRemoveCached,
399 Method::CacheFetchAndCache,
400 Method::CacheStats,
401 Method::ControlPeerStatus,
402 Method::ControlSubscribe,
403 Method::ControlUnsubscribe,
404 Method::ControlListSubscriptions,
405 Method::ControlPeersConnect,
406 Method::ControlPeersDisconnect,
407 Method::RpcDiscover,
408 ] {
409 assert!(
410 !m.is_peer_reachable(),
411 "{} must NOT be peer-reachable",
412 m.name()
413 );
414 }
415 }
416
417 #[test]
420 fn anchored_reads_public_but_peer_reachable() {
421 for m in [
422 Method::GetAnchoredRoot,
423 Method::GetCollection,
424 Method::ListCollectionItems,
425 ] {
426 assert_eq!(m.tier(), Tier::PublicRead);
427 assert!(m.is_peer_reachable());
428 }
429 }
430
431 #[test]
437 fn completed_control_methods_present_and_gated() {
438 let expected = [
439 (Method::CacheStats, "cache.stats"),
440 (Method::ControlSubscribe, "control.subscribe"),
441 (Method::ControlUnsubscribe, "control.unsubscribe"),
442 (
443 Method::ControlListSubscriptions,
444 "control.listSubscriptions",
445 ),
446 (Method::ControlPeersConnect, "control.peers.connect"),
447 (Method::ControlPeersDisconnect, "control.peers.disconnect"),
448 ];
449 for (m, name) in expected {
450 assert_eq!(m.name(), name);
451 assert_eq!(Method::from_name(name), Some(m));
452 assert!(Method::ALL.contains(&m), "{name} missing from ALL");
453 assert_eq!(m.tier(), Tier::Control, "{name} must be Control");
454 assert!(!m.is_peer_reachable(), "{name} must NOT be peer-reachable");
455 }
456 }
457
458 #[test]
464 fn module_pull_methods_present_and_peer_reachable() {
465 for (m, name) in [
466 (Method::GetModuleInfo, "dig.getModuleInfo"),
467 (Method::FetchModuleRange, "dig.fetchModuleRange"),
468 ] {
469 assert_eq!(m.name(), name);
470 assert_eq!(Method::from_name(name), Some(m));
471 assert!(Method::ALL.contains(&m), "{name} missing from ALL");
472 assert_eq!(m.tier(), Tier::Peer, "{name} must be Peer");
473 assert!(m.is_peer_reachable(), "{name} must be peer-reachable");
474 }
475 }
476
477 #[test]
480 fn control_tier_never_peer_reachable() {
481 for m in Method::ALL {
482 if m.tier() == Tier::Control {
483 assert!(
484 !m.is_peer_reachable(),
485 "{} is Control but peer-reachable",
486 m.name()
487 );
488 }
489 }
490 }
491}