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 ListRewardDistributors,
112 GetRewardProverStatus,
115 GetRewardDistributor,
118 ListRewardDistributorCommitments,
122 RpcDiscover,
124}
125
126impl Method {
127 pub const fn name(self) -> &'static str {
129 match self {
130 Method::GetContent => "dig.getContent",
131 Method::GetCapsule => "dig.getCapsule",
132 Method::GetModule => "dig.getModule",
133 Method::GetManifest => "dig.getManifest",
134 Method::GetMetadata => "dig.getMetadata",
135 Method::ListCapsules => "dig.listCapsules",
136 Method::GetProof => "dig.getProof",
137 Method::GetProofStatus => "dig.getProofStatus",
138 Method::GetAnchoredRoot => "dig.getAnchoredRoot",
139 Method::GetCollection => "dig.getCollection",
140 Method::ListCollectionItems => "dig.listCollectionItems",
141 Method::Health => "dig.health",
142 Method::Methods => "dig.methods",
143 Method::GetNetworkInfo => "dig.getNetworkInfo",
144 Method::GetPeers => "dig.getPeers",
145 Method::Announce => "dig.announce",
146 Method::GetAvailability => "dig.getAvailability",
147 Method::ListInventory => "dig.listInventory",
148 Method::FetchRange => "dig.fetchRange",
149 Method::GetModuleInfo => "dig.getModuleInfo",
150 Method::FetchModuleRange => "dig.fetchModuleRange",
151 Method::Stage => "dig.stage",
152 Method::CacheGetConfig => "cache.getConfig",
153 Method::CacheSetCapBytes => "cache.setCapBytes",
154 Method::CacheClear => "cache.clear",
155 Method::CacheListCached => "cache.listCached",
156 Method::CacheRemoveCached => "cache.removeCached",
157 Method::CacheFetchAndCache => "cache.fetchAndCache",
158 Method::CacheStats => "cache.stats",
159 Method::ControlPeerStatus => "control.peerStatus",
160 Method::ControlSubscribe => "control.subscribe",
161 Method::ControlUnsubscribe => "control.unsubscribe",
162 Method::ControlListSubscriptions => "control.listSubscriptions",
163 Method::ControlPeersConnect => "control.peers.connect",
164 Method::ControlPeersDisconnect => "control.peers.disconnect",
165 Method::ListRewardDistributors => "dig.listRewardDistributors",
166 Method::GetRewardProverStatus => "dig.getRewardProverStatus",
167 Method::GetRewardDistributor => "dig.getRewardDistributor",
168 Method::ListRewardDistributorCommitments => "dig.listRewardDistributorCommitments",
169 Method::RpcDiscover => "rpc.discover",
170 }
171 }
172
173 pub fn from_name(name: &str) -> Option<Method> {
176 Method::ALL.iter().copied().find(|m| m.name() == name)
177 }
178
179 pub const fn tier(self) -> Tier {
185 match self {
186 Method::GetContent
187 | Method::GetCapsule
188 | Method::GetModule
189 | Method::GetManifest
190 | Method::GetMetadata
191 | Method::ListCapsules
192 | Method::GetProof
193 | Method::GetProofStatus
194 | Method::GetAnchoredRoot
195 | Method::GetCollection
196 | Method::ListCollectionItems
197 | Method::Health
198 | Method::Methods => Tier::PublicRead,
199
200 Method::GetNetworkInfo
201 | Method::GetPeers
202 | Method::Announce
203 | Method::GetAvailability
204 | Method::ListInventory
205 | Method::FetchRange
206 | Method::GetModuleInfo
207 | Method::FetchModuleRange => Tier::Peer,
208
209 Method::Stage
210 | Method::CacheGetConfig
211 | Method::CacheSetCapBytes
212 | Method::CacheClear
213 | Method::CacheListCached
214 | Method::CacheRemoveCached
215 | Method::CacheFetchAndCache
216 | Method::CacheStats
217 | Method::ControlPeerStatus
218 | Method::ControlSubscribe
219 | Method::ControlUnsubscribe
220 | Method::ControlListSubscriptions
221 | Method::ControlPeersConnect
222 | Method::ControlPeersDisconnect
223 | Method::ListRewardDistributors
224 | Method::GetRewardProverStatus
225 | Method::GetRewardDistributor
226 | Method::ListRewardDistributorCommitments
227 | Method::RpcDiscover => Tier::Control,
228 }
229 }
230
231 pub const fn is_peer_reachable(self) -> bool {
240 matches!(
241 self,
242 Method::GetContent
243 | Method::GetNetworkInfo
244 | Method::GetPeers
245 | Method::Announce
246 | Method::GetAvailability
247 | Method::ListInventory
248 | Method::FetchRange
249 | Method::GetModuleInfo
250 | Method::FetchModuleRange
251 | Method::GetAnchoredRoot
252 | Method::GetCollection
253 | Method::ListCollectionItems
254 )
255 }
256
257 pub const fn summary(self) -> &'static str {
259 match self {
260 Method::GetContent => "Read a verified window of a resource's ciphertext.",
261 Method::GetCapsule => "Fetch the whole .dig module for (store, root).",
262 Method::GetModule => "Alias of dig.getCapsule.",
263 Method::GetManifest => "Fetch the public discovery manifest resource.",
264 Method::GetMetadata => "Fetch the plaintext metadata manifest.",
265 Method::ListCapsules => "List a store's confirmed capsules.",
266 Method::GetProof => "Get the real inclusion proof + execution-proof status.",
267 Method::GetProofStatus => "Poll a real execution-proof job by id.",
268 Method::GetAnchoredRoot => "Resolve a store's chain-anchored tip root.",
269 Method::GetCollection => "Get collection-level facts for NFT launcher ids.",
270 Method::ListCollectionItems => "List resolved NFT collection items (paginated).",
271 Method::Health => "Liveness and capability summary.",
272 Method::Methods => "List the method names this node implements.",
273 Method::GetNetworkInfo => "This node's peer-network posture.",
274 Method::GetPeers => "The peers this node currently knows.",
275 Method::Announce => "Accept a peer announcement (peer_id + addresses).",
276 Method::GetAvailability => "Batch-check whether this node holds items.",
277 Method::ListInventory => "Enumerate the stores / roots this node serves.",
278 Method::FetchRange => "Fetch a single verified range frame of a resource.",
279 Method::GetModuleInfo => {
280 "Get the whole-.dig-module transfer descriptor (size + hashes) for (store, root)."
281 }
282 Method::FetchModuleRange => "Fetch a single range frame of the whole .dig module blob.",
283 Method::Stage => "Compile a local folder into a capsule .dig in-process.",
284 Method::CacheGetConfig => "Get the local-cache configuration.",
285 Method::CacheSetCapBytes => "Set the local-cache size cap.",
286 Method::CacheClear => "Clear the local cache.",
287 Method::CacheListCached => "List the durable cached modules.",
288 Method::CacheRemoveCached => "Remove one cached capsule.",
289 Method::CacheFetchAndCache => "Fetch and cache one capsule.",
290 Method::CacheStats => {
291 "Cache telemetry: cap + usage, entry count + bytes, eviction + hit/miss counters."
292 }
293 Method::ControlPeerStatus => "Snapshot the node's peer network.",
294 Method::ControlSubscribe => {
295 "Subscribe the node to a store (persisted watch + gap-fill)."
296 }
297 Method::ControlUnsubscribe => "Unsubscribe the node from a store.",
298 Method::ControlListSubscriptions => "List the node's persisted store subscriptions.",
299 Method::ControlPeersConnect => "Dial a peer into the connected pool.",
300 Method::ControlPeersDisconnect => "Drop a pooled peer by peer_id.",
301 Method::ListRewardDistributors => {
302 "List the reward distributors this node funds or has a mirror claim to."
303 }
304 Method::GetRewardProverStatus => {
305 "Get the reward prover loop's status for one or every distributor."
306 }
307 Method::GetRewardDistributor => "Get one reward distributor's chain-derived state.",
308 Method::ListRewardDistributorCommitments => {
309 "List one reward distributor's per-epoch clawback commitment slots."
310 }
311 Method::RpcDiscover => "Return the OpenRPC self-describe document.",
312 }
313 }
314
315 pub const ALL: &'static [Method] = &[
317 Method::GetContent,
318 Method::GetCapsule,
319 Method::GetModule,
320 Method::GetManifest,
321 Method::GetMetadata,
322 Method::ListCapsules,
323 Method::GetProof,
324 Method::GetProofStatus,
325 Method::GetAnchoredRoot,
326 Method::GetCollection,
327 Method::ListCollectionItems,
328 Method::Health,
329 Method::Methods,
330 Method::GetNetworkInfo,
331 Method::GetPeers,
332 Method::Announce,
333 Method::GetAvailability,
334 Method::ListInventory,
335 Method::FetchRange,
336 Method::GetModuleInfo,
337 Method::FetchModuleRange,
338 Method::Stage,
339 Method::CacheGetConfig,
340 Method::CacheSetCapBytes,
341 Method::CacheClear,
342 Method::CacheListCached,
343 Method::CacheRemoveCached,
344 Method::CacheFetchAndCache,
345 Method::CacheStats,
346 Method::ControlPeerStatus,
347 Method::ControlSubscribe,
348 Method::ControlUnsubscribe,
349 Method::ControlListSubscriptions,
350 Method::ControlPeersConnect,
351 Method::ControlPeersDisconnect,
352 Method::ListRewardDistributors,
353 Method::GetRewardProverStatus,
354 Method::GetRewardDistributor,
355 Method::ListRewardDistributorCommitments,
356 Method::RpcDiscover,
357 ];
358
359 pub fn peer_reachable_names() -> Vec<&'static str> {
362 Method::ALL
363 .iter()
364 .copied()
365 .filter(|m| m.is_peer_reachable())
366 .map(Method::name)
367 .collect()
368 }
369}
370
371impl std::fmt::Display for Method {
372 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
373 f.write_str(self.name())
374 }
375}
376
377#[cfg(test)]
378mod tests {
379 use super::*;
380 use std::collections::HashSet;
381
382 #[test]
385 fn names_round_trip() {
386 for m in Method::ALL {
387 assert_eq!(Method::from_name(m.name()), Some(*m), "{}", m.name());
388 }
389 assert!(Method::from_name("dig.nope").is_none());
390 }
391
392 #[test]
395 fn names_unique() {
396 let names: HashSet<&str> = Method::ALL.iter().map(|m| m.name()).collect();
397 assert_eq!(names.len(), Method::ALL.len());
398 }
399
400 #[test]
406 fn peer_allowlist_matches_canonical() {
407 let mut got = Method::peer_reachable_names();
408 got.sort_unstable();
409 let mut want = vec![
410 "dig.getContent",
411 "dig.getNetworkInfo",
412 "dig.getPeers",
413 "dig.announce",
414 "dig.getAvailability",
415 "dig.listInventory",
416 "dig.fetchRange",
417 "dig.getModuleInfo",
418 "dig.fetchModuleRange",
419 "dig.getAnchoredRoot",
420 "dig.getCollection",
421 "dig.listCollectionItems",
422 ];
423 want.sort_unstable();
424 assert_eq!(got, want);
425
426 for m in [
428 Method::Stage,
429 Method::CacheGetConfig,
430 Method::CacheSetCapBytes,
431 Method::CacheClear,
432 Method::CacheListCached,
433 Method::CacheRemoveCached,
434 Method::CacheFetchAndCache,
435 Method::CacheStats,
436 Method::ControlPeerStatus,
437 Method::ControlSubscribe,
438 Method::ControlUnsubscribe,
439 Method::ControlListSubscriptions,
440 Method::ControlPeersConnect,
441 Method::ControlPeersDisconnect,
442 Method::RpcDiscover,
443 ] {
444 assert!(
445 !m.is_peer_reachable(),
446 "{} must NOT be peer-reachable",
447 m.name()
448 );
449 }
450 }
451
452 #[test]
455 fn anchored_reads_public_but_peer_reachable() {
456 for m in [
457 Method::GetAnchoredRoot,
458 Method::GetCollection,
459 Method::ListCollectionItems,
460 ] {
461 assert_eq!(m.tier(), Tier::PublicRead);
462 assert!(m.is_peer_reachable());
463 }
464 }
465
466 #[test]
472 fn completed_control_methods_present_and_gated() {
473 let expected = [
474 (Method::CacheStats, "cache.stats"),
475 (Method::ControlSubscribe, "control.subscribe"),
476 (Method::ControlUnsubscribe, "control.unsubscribe"),
477 (
478 Method::ControlListSubscriptions,
479 "control.listSubscriptions",
480 ),
481 (Method::ControlPeersConnect, "control.peers.connect"),
482 (Method::ControlPeersDisconnect, "control.peers.disconnect"),
483 ];
484 for (m, name) in expected {
485 assert_eq!(m.name(), name);
486 assert_eq!(Method::from_name(name), Some(m));
487 assert!(Method::ALL.contains(&m), "{name} missing from ALL");
488 assert_eq!(m.tier(), Tier::Control, "{name} must be Control");
489 assert!(!m.is_peer_reachable(), "{name} must NOT be peer-reachable");
490 }
491 }
492
493 #[test]
499 fn module_pull_methods_present_and_peer_reachable() {
500 for (m, name) in [
501 (Method::GetModuleInfo, "dig.getModuleInfo"),
502 (Method::FetchModuleRange, "dig.fetchModuleRange"),
503 ] {
504 assert_eq!(m.name(), name);
505 assert_eq!(Method::from_name(name), Some(m));
506 assert!(Method::ALL.contains(&m), "{name} missing from ALL");
507 assert_eq!(m.tier(), Tier::Peer, "{name} must be Peer");
508 assert!(m.is_peer_reachable(), "{name} must be peer-reachable");
509 }
510 }
511
512 #[test]
518 fn reward_distributor_methods_present_control_and_not_peer_reachable() {
519 let expected = [
520 (Method::ListRewardDistributors, "dig.listRewardDistributors"),
521 (Method::GetRewardProverStatus, "dig.getRewardProverStatus"),
522 (Method::GetRewardDistributor, "dig.getRewardDistributor"),
523 (
524 Method::ListRewardDistributorCommitments,
525 "dig.listRewardDistributorCommitments",
526 ),
527 ];
528 for (m, name) in expected {
529 assert_eq!(m.name(), name);
530 assert_eq!(Method::from_name(name), Some(m));
531 assert!(Method::ALL.contains(&m), "{name} missing from ALL");
532 assert_eq!(m.tier(), Tier::Control, "{name} must be Control");
533 assert!(!m.is_peer_reachable(), "{name} must NOT be peer-reachable");
534 assert!(
535 !Method::peer_reachable_names().contains(&name),
536 "{name} must not appear in the peer allowlist"
537 );
538 }
539 }
540
541 #[test]
544 fn control_tier_never_peer_reachable() {
545 for m in Method::ALL {
546 if m.tier() == Tier::Control {
547 assert!(
548 !m.is_peer_reachable(),
549 "{} is Control but peer-reachable",
550 m.name()
551 );
552 }
553 }
554 }
555}