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 GetPayeeRewardClaimStatus,
125 RpcDiscover,
127}
128
129impl Method {
130 pub const fn name(self) -> &'static str {
132 match self {
133 Method::GetContent => "dig.getContent",
134 Method::GetCapsule => "dig.getCapsule",
135 Method::GetModule => "dig.getModule",
136 Method::GetManifest => "dig.getManifest",
137 Method::GetMetadata => "dig.getMetadata",
138 Method::ListCapsules => "dig.listCapsules",
139 Method::GetProof => "dig.getProof",
140 Method::GetProofStatus => "dig.getProofStatus",
141 Method::GetAnchoredRoot => "dig.getAnchoredRoot",
142 Method::GetCollection => "dig.getCollection",
143 Method::ListCollectionItems => "dig.listCollectionItems",
144 Method::Health => "dig.health",
145 Method::Methods => "dig.methods",
146 Method::GetNetworkInfo => "dig.getNetworkInfo",
147 Method::GetPeers => "dig.getPeers",
148 Method::Announce => "dig.announce",
149 Method::GetAvailability => "dig.getAvailability",
150 Method::ListInventory => "dig.listInventory",
151 Method::FetchRange => "dig.fetchRange",
152 Method::GetModuleInfo => "dig.getModuleInfo",
153 Method::FetchModuleRange => "dig.fetchModuleRange",
154 Method::Stage => "dig.stage",
155 Method::CacheGetConfig => "cache.getConfig",
156 Method::CacheSetCapBytes => "cache.setCapBytes",
157 Method::CacheClear => "cache.clear",
158 Method::CacheListCached => "cache.listCached",
159 Method::CacheRemoveCached => "cache.removeCached",
160 Method::CacheFetchAndCache => "cache.fetchAndCache",
161 Method::CacheStats => "cache.stats",
162 Method::ControlPeerStatus => "control.peerStatus",
163 Method::ControlSubscribe => "control.subscribe",
164 Method::ControlUnsubscribe => "control.unsubscribe",
165 Method::ControlListSubscriptions => "control.listSubscriptions",
166 Method::ControlPeersConnect => "control.peers.connect",
167 Method::ControlPeersDisconnect => "control.peers.disconnect",
168 Method::ListRewardDistributors => "dig.listRewardDistributors",
169 Method::GetRewardProverStatus => "dig.getRewardProverStatus",
170 Method::GetRewardDistributor => "dig.getRewardDistributor",
171 Method::ListRewardDistributorCommitments => "dig.listRewardDistributorCommitments",
172 Method::GetPayeeRewardClaimStatus => "dig.getPayeeRewardClaimStatus",
173 Method::RpcDiscover => "rpc.discover",
174 }
175 }
176
177 pub fn from_name(name: &str) -> Option<Method> {
180 Method::ALL.iter().copied().find(|m| m.name() == name)
181 }
182
183 pub const fn tier(self) -> Tier {
189 match self {
190 Method::GetContent
191 | Method::GetCapsule
192 | Method::GetModule
193 | Method::GetManifest
194 | Method::GetMetadata
195 | Method::ListCapsules
196 | Method::GetProof
197 | Method::GetProofStatus
198 | Method::GetAnchoredRoot
199 | Method::GetCollection
200 | Method::ListCollectionItems
201 | Method::Health
202 | Method::Methods => Tier::PublicRead,
203
204 Method::GetNetworkInfo
205 | Method::GetPeers
206 | Method::Announce
207 | Method::GetAvailability
208 | Method::ListInventory
209 | Method::FetchRange
210 | Method::GetModuleInfo
211 | Method::FetchModuleRange => Tier::Peer,
212
213 Method::Stage
214 | Method::CacheGetConfig
215 | Method::CacheSetCapBytes
216 | Method::CacheClear
217 | Method::CacheListCached
218 | Method::CacheRemoveCached
219 | Method::CacheFetchAndCache
220 | Method::CacheStats
221 | Method::ControlPeerStatus
222 | Method::ControlSubscribe
223 | Method::ControlUnsubscribe
224 | Method::ControlListSubscriptions
225 | Method::ControlPeersConnect
226 | Method::ControlPeersDisconnect
227 | Method::ListRewardDistributors
228 | Method::GetRewardProverStatus
229 | Method::GetRewardDistributor
230 | Method::ListRewardDistributorCommitments
231 | Method::GetPayeeRewardClaimStatus
232 | Method::RpcDiscover => Tier::Control,
233 }
234 }
235
236 pub const fn is_peer_reachable(self) -> bool {
245 matches!(
246 self,
247 Method::GetContent
248 | Method::GetNetworkInfo
249 | Method::GetPeers
250 | Method::Announce
251 | Method::GetAvailability
252 | Method::ListInventory
253 | Method::FetchRange
254 | Method::GetModuleInfo
255 | Method::FetchModuleRange
256 | Method::GetAnchoredRoot
257 | Method::GetCollection
258 | Method::ListCollectionItems
259 )
260 }
261
262 pub const fn is_reward(self) -> bool {
280 match self {
281 Method::ListRewardDistributors
282 | Method::GetRewardProverStatus
283 | Method::GetRewardDistributor
284 | Method::ListRewardDistributorCommitments
285 | Method::GetPayeeRewardClaimStatus => true,
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 => false,
322 }
323 }
324
325 pub const fn summary(self) -> &'static str {
327 match self {
328 Method::GetContent => "Read a verified window of a resource's ciphertext.",
329 Method::GetCapsule => "Fetch the whole .dig module for (store, root).",
330 Method::GetModule => "Alias of dig.getCapsule.",
331 Method::GetManifest => "Fetch the public discovery manifest resource.",
332 Method::GetMetadata => "Fetch the plaintext metadata manifest.",
333 Method::ListCapsules => "List a store's confirmed capsules.",
334 Method::GetProof => "Get the real inclusion proof + execution-proof status.",
335 Method::GetProofStatus => "Poll a real execution-proof job by id.",
336 Method::GetAnchoredRoot => "Resolve a store's chain-anchored tip root.",
337 Method::GetCollection => "Get collection-level facts for NFT launcher ids.",
338 Method::ListCollectionItems => "List resolved NFT collection items (paginated).",
339 Method::Health => "Liveness and capability summary.",
340 Method::Methods => "List the method names this node implements.",
341 Method::GetNetworkInfo => "This node's peer-network posture.",
342 Method::GetPeers => "The peers this node currently knows.",
343 Method::Announce => "Accept a peer announcement (peer_id + addresses).",
344 Method::GetAvailability => "Batch-check whether this node holds items.",
345 Method::ListInventory => "Enumerate the stores / roots this node serves.",
346 Method::FetchRange => "Fetch a single verified range frame of a resource.",
347 Method::GetModuleInfo => {
348 "Get the whole-.dig-module transfer descriptor (size + hashes) for (store, root)."
349 }
350 Method::FetchModuleRange => "Fetch a single range frame of the whole .dig module blob.",
351 Method::Stage => "Compile a local folder into a capsule .dig in-process.",
352 Method::CacheGetConfig => "Get the local-cache configuration.",
353 Method::CacheSetCapBytes => "Set the local-cache size cap.",
354 Method::CacheClear => "Clear the local cache.",
355 Method::CacheListCached => "List the durable cached modules.",
356 Method::CacheRemoveCached => "Remove one cached capsule.",
357 Method::CacheFetchAndCache => "Fetch and cache one capsule.",
358 Method::CacheStats => {
359 "Cache telemetry: cap + usage, entry count + bytes, eviction + hit/miss counters."
360 }
361 Method::ControlPeerStatus => "Snapshot the node's peer network.",
362 Method::ControlSubscribe => {
363 "Subscribe the node to a store (persisted watch + gap-fill)."
364 }
365 Method::ControlUnsubscribe => "Unsubscribe the node from a store.",
366 Method::ControlListSubscriptions => "List the node's persisted store subscriptions.",
367 Method::ControlPeersConnect => "Dial a peer into the connected pool.",
368 Method::ControlPeersDisconnect => "Drop a pooled peer by peer_id.",
369 Method::ListRewardDistributors => {
370 "List the reward distributors this node funds or has a mirror claim to."
371 }
372 Method::GetRewardProverStatus => {
373 "Get the reward prover loop's status for one or every distributor."
374 }
375 Method::GetRewardDistributor => "Get one reward distributor's chain-derived state.",
376 Method::ListRewardDistributorCommitments => {
377 "List one reward distributor's per-epoch clawback commitment slots."
378 }
379 Method::GetPayeeRewardClaimStatus => {
380 "Report this node's own payee-side reward claim posture."
381 }
382 Method::RpcDiscover => "Return the OpenRPC self-describe document.",
383 }
384 }
385
386 pub const ALL: &'static [Method] = &[
388 Method::GetContent,
389 Method::GetCapsule,
390 Method::GetModule,
391 Method::GetManifest,
392 Method::GetMetadata,
393 Method::ListCapsules,
394 Method::GetProof,
395 Method::GetProofStatus,
396 Method::GetAnchoredRoot,
397 Method::GetCollection,
398 Method::ListCollectionItems,
399 Method::Health,
400 Method::Methods,
401 Method::GetNetworkInfo,
402 Method::GetPeers,
403 Method::Announce,
404 Method::GetAvailability,
405 Method::ListInventory,
406 Method::FetchRange,
407 Method::GetModuleInfo,
408 Method::FetchModuleRange,
409 Method::Stage,
410 Method::CacheGetConfig,
411 Method::CacheSetCapBytes,
412 Method::CacheClear,
413 Method::CacheListCached,
414 Method::CacheRemoveCached,
415 Method::CacheFetchAndCache,
416 Method::CacheStats,
417 Method::ControlPeerStatus,
418 Method::ControlSubscribe,
419 Method::ControlUnsubscribe,
420 Method::ControlListSubscriptions,
421 Method::ControlPeersConnect,
422 Method::ControlPeersDisconnect,
423 Method::ListRewardDistributors,
424 Method::GetRewardProverStatus,
425 Method::GetRewardDistributor,
426 Method::ListRewardDistributorCommitments,
427 Method::GetPayeeRewardClaimStatus,
428 Method::RpcDiscover,
429 ];
430
431 pub fn peer_reachable_names() -> Vec<&'static str> {
434 Method::ALL
435 .iter()
436 .copied()
437 .filter(|m| m.is_peer_reachable())
438 .map(Method::name)
439 .collect()
440 }
441}
442
443impl std::fmt::Display for Method {
444 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
445 f.write_str(self.name())
446 }
447}
448
449#[cfg(test)]
450mod tests {
451 use super::*;
452 use std::collections::HashSet;
453
454 #[test]
457 fn names_round_trip() {
458 for m in Method::ALL {
459 assert_eq!(Method::from_name(m.name()), Some(*m), "{}", m.name());
460 }
461 assert!(Method::from_name("dig.nope").is_none());
462 }
463
464 #[test]
467 fn names_unique() {
468 let names: HashSet<&str> = Method::ALL.iter().map(|m| m.name()).collect();
469 assert_eq!(names.len(), Method::ALL.len());
470 }
471
472 #[test]
478 fn peer_allowlist_matches_canonical() {
479 let mut got = Method::peer_reachable_names();
480 got.sort_unstable();
481 let mut want = vec![
482 "dig.getContent",
483 "dig.getNetworkInfo",
484 "dig.getPeers",
485 "dig.announce",
486 "dig.getAvailability",
487 "dig.listInventory",
488 "dig.fetchRange",
489 "dig.getModuleInfo",
490 "dig.fetchModuleRange",
491 "dig.getAnchoredRoot",
492 "dig.getCollection",
493 "dig.listCollectionItems",
494 ];
495 want.sort_unstable();
496 assert_eq!(got, want);
497
498 for m in [
500 Method::Stage,
501 Method::CacheGetConfig,
502 Method::CacheSetCapBytes,
503 Method::CacheClear,
504 Method::CacheListCached,
505 Method::CacheRemoveCached,
506 Method::CacheFetchAndCache,
507 Method::CacheStats,
508 Method::ControlPeerStatus,
509 Method::ControlSubscribe,
510 Method::ControlUnsubscribe,
511 Method::ControlListSubscriptions,
512 Method::ControlPeersConnect,
513 Method::ControlPeersDisconnect,
514 Method::RpcDiscover,
515 ] {
516 assert!(
517 !m.is_peer_reachable(),
518 "{} must NOT be peer-reachable",
519 m.name()
520 );
521 }
522 }
523
524 #[test]
527 fn anchored_reads_public_but_peer_reachable() {
528 for m in [
529 Method::GetAnchoredRoot,
530 Method::GetCollection,
531 Method::ListCollectionItems,
532 ] {
533 assert_eq!(m.tier(), Tier::PublicRead);
534 assert!(m.is_peer_reachable());
535 }
536 }
537
538 #[test]
544 fn completed_control_methods_present_and_gated() {
545 let expected = [
546 (Method::CacheStats, "cache.stats"),
547 (Method::ControlSubscribe, "control.subscribe"),
548 (Method::ControlUnsubscribe, "control.unsubscribe"),
549 (
550 Method::ControlListSubscriptions,
551 "control.listSubscriptions",
552 ),
553 (Method::ControlPeersConnect, "control.peers.connect"),
554 (Method::ControlPeersDisconnect, "control.peers.disconnect"),
555 ];
556 for (m, name) in expected {
557 assert_eq!(m.name(), name);
558 assert_eq!(Method::from_name(name), Some(m));
559 assert!(Method::ALL.contains(&m), "{name} missing from ALL");
560 assert_eq!(m.tier(), Tier::Control, "{name} must be Control");
561 assert!(!m.is_peer_reachable(), "{name} must NOT be peer-reachable");
562 }
563 }
564
565 #[test]
571 fn module_pull_methods_present_and_peer_reachable() {
572 for (m, name) in [
573 (Method::GetModuleInfo, "dig.getModuleInfo"),
574 (Method::FetchModuleRange, "dig.fetchModuleRange"),
575 ] {
576 assert_eq!(m.name(), name);
577 assert_eq!(Method::from_name(name), Some(m));
578 assert!(Method::ALL.contains(&m), "{name} missing from ALL");
579 assert_eq!(m.tier(), Tier::Peer, "{name} must be Peer");
580 assert!(m.is_peer_reachable(), "{name} must be peer-reachable");
581 }
582 }
583
584 #[test]
588 fn payee_reward_claim_status_present_under_its_wire_name() {
589 assert_eq!(
590 Method::GetPayeeRewardClaimStatus.name(),
591 "dig.getPayeeRewardClaimStatus"
592 );
593 assert_eq!(
594 Method::from_name("dig.getPayeeRewardClaimStatus"),
595 Some(Method::GetPayeeRewardClaimStatus)
596 );
597 assert!(Method::ALL.contains(&Method::GetPayeeRewardClaimStatus));
598 }
599
600 #[test]
619 fn every_reward_method_is_control_and_not_peer_reachable() {
620 let reward_methods: Vec<Method> = Method::ALL
621 .iter()
622 .copied()
623 .filter(|m| m.is_reward())
624 .collect();
625
626 assert!(
630 !reward_methods.is_empty(),
631 "no method reported is_reward() -- either the catalogue lost them or \
632 the classification was inverted"
633 );
634
635 let allowlist = Method::peer_reachable_names();
636 for m in reward_methods {
637 let name = m.name();
638 assert_eq!(
639 Method::from_name(name),
640 Some(m),
641 "{name} does not round-trip"
642 );
643 assert_eq!(m.tier(), Tier::Control, "{name} must be Control");
644 assert!(!m.is_peer_reachable(), "{name} must NOT be peer-reachable");
645 assert!(
646 !allowlist.contains(&name),
647 "{name} must not appear in the peer allowlist"
648 );
649 }
650 }
651
652 #[test]
660 fn reward_named_methods_are_classified_as_reward() {
661 for m in Method::ALL {
662 if m.name().contains("Reward") {
663 assert!(
664 m.is_reward(),
665 "{} is named a reward method but is_reward() says otherwise",
666 m.name()
667 );
668 }
669 }
670 }
671
672 #[test]
675 fn control_tier_never_peer_reachable() {
676 for m in Method::ALL {
677 if m.tier() == Tier::Control {
678 assert!(
679 !m.is_peer_reachable(),
680 "{} is Control but peer-reachable",
681 m.name()
682 );
683 }
684 }
685 }
686}