1#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
22pub enum Routing {
23 Owned,
25 Delegated,
27 OpenBootstrap,
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
33pub enum Category {
34 Status,
36 Config,
38 Log,
40 Cache,
42 HostedStores,
44 Sync,
46 Updater,
48 Pairing,
50 Peers,
52 Subscriptions,
54 Wallet,
57 Profile,
61}
62
63#[non_exhaustive]
69#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
70pub enum ControlMethod {
71 Status,
74 ConfigGet,
76 ConfigSetUpstream,
78 LogSetLevel,
80
81 CacheGet,
84 CacheSetCap,
86 CacheClear,
88
89 HostedStoresList,
92 HostedStoresPin,
94 HostedStoresUnpin,
96 HostedStoresStatus,
98
99 SyncStatus,
102 SyncTrigger,
104
105 UpdaterStatus,
108 UpdaterSetChannel,
110 UpdaterPause,
112 UpdaterResume,
114 UpdaterCheckNow,
116
117 PairingList,
120 PairingApprove,
122 PairingRevoke,
124
125 PeerStatus,
128 PeerCounts,
130 PeersConnect,
132 PeersDisconnect,
134
135 Subscribe,
138 Unsubscribe,
140 ListSubscriptions,
142
143 WalletBalance,
146 WalletCoins,
148 WalletCoinById,
150 WalletCoinSpend,
152 WalletCoinsByParent,
154 WalletArrivals,
156 WalletPeak,
158 WalletSyncStatus,
160 WalletBroadcast,
162 WalletWatch,
164 WalletUnwatch,
166 WalletWatched,
168
169 ProfilePutBody,
172 ProfileGetBody,
174
175 PairingRequest,
178 PairingPoll,
180}
181
182impl ControlMethod {
183 pub const fn name(self) -> &'static str {
185 match self {
186 ControlMethod::Status => "control.status",
187 ControlMethod::ConfigGet => "control.config.get",
188 ControlMethod::ConfigSetUpstream => "control.config.setUpstream",
189 ControlMethod::LogSetLevel => "control.log.setLevel",
190 ControlMethod::CacheGet => "control.cache.get",
191 ControlMethod::CacheSetCap => "control.cache.setCap",
192 ControlMethod::CacheClear => "control.cache.clear",
193 ControlMethod::HostedStoresList => "control.hostedStores.list",
194 ControlMethod::HostedStoresPin => "control.hostedStores.pin",
195 ControlMethod::HostedStoresUnpin => "control.hostedStores.unpin",
196 ControlMethod::HostedStoresStatus => "control.hostedStores.status",
197 ControlMethod::SyncStatus => "control.sync.status",
198 ControlMethod::SyncTrigger => "control.sync.trigger",
199 ControlMethod::UpdaterStatus => "control.updater.status",
200 ControlMethod::UpdaterSetChannel => "control.updater.setChannel",
201 ControlMethod::UpdaterPause => "control.updater.pause",
202 ControlMethod::UpdaterResume => "control.updater.resume",
203 ControlMethod::UpdaterCheckNow => "control.updater.checkNow",
204 ControlMethod::PairingList => "control.pairing.list",
205 ControlMethod::PairingApprove => "control.pairing.approve",
206 ControlMethod::PairingRevoke => "control.pairing.revoke",
207 ControlMethod::PeerStatus => "control.peerStatus",
208 ControlMethod::PeerCounts => "control.peerCounts",
209 ControlMethod::PeersConnect => "control.peers.connect",
210 ControlMethod::PeersDisconnect => "control.peers.disconnect",
211 ControlMethod::Subscribe => "control.subscribe",
212 ControlMethod::Unsubscribe => "control.unsubscribe",
213 ControlMethod::ListSubscriptions => "control.listSubscriptions",
214 ControlMethod::WalletBalance => "control.wallet.balance",
215 ControlMethod::WalletCoins => "control.wallet.coins",
216 ControlMethod::WalletCoinById => "control.wallet.coinById",
217 ControlMethod::WalletCoinSpend => "control.wallet.coinSpend",
218 ControlMethod::WalletCoinsByParent => "control.wallet.coinsByParent",
219 ControlMethod::WalletArrivals => "control.wallet.arrivals",
220 ControlMethod::WalletPeak => "control.wallet.peak",
221 ControlMethod::WalletSyncStatus => "control.wallet.syncStatus",
222 ControlMethod::WalletBroadcast => "control.wallet.broadcast",
223 ControlMethod::WalletWatch => "control.wallet.watch",
224 ControlMethod::WalletUnwatch => "control.wallet.unwatch",
225 ControlMethod::WalletWatched => "control.wallet.watched",
226 ControlMethod::ProfilePutBody => "control.profile.putBody",
227 ControlMethod::ProfileGetBody => "control.profile.getBody",
228 ControlMethod::PairingRequest => "pairing.request",
229 ControlMethod::PairingPoll => "pairing.poll",
230 }
231 }
232
233 pub fn from_name(name: &str) -> Option<ControlMethod> {
235 ControlMethod::ALL
236 .iter()
237 .copied()
238 .find(|m| m.name() == name)
239 }
240
241 pub const fn requires_auth(self) -> bool {
268 !self.is_open_read()
269 && !matches!(
270 self,
271 ControlMethod::PairingRequest | ControlMethod::PairingPoll
272 )
273 }
274
275 pub const fn is_open_read(self) -> bool {
309 matches!(
310 self,
311 ControlMethod::WalletBalance
312 | ControlMethod::WalletCoins
313 | ControlMethod::WalletCoinById
314 | ControlMethod::WalletCoinSpend
315 | ControlMethod::WalletCoinsByParent
316 | ControlMethod::WalletPeak
317 | ControlMethod::WalletSyncStatus
318 | ControlMethod::PeerCounts
319 )
320 }
321
322 pub const fn is_pairing_admin(self) -> bool {
328 matches!(
329 self,
330 ControlMethod::PairingList
331 | ControlMethod::PairingApprove
332 | ControlMethod::PairingRevoke
333 )
334 }
335
336 pub const fn routing(self) -> Routing {
338 match self {
339 ControlMethod::PeerStatus
340 | ControlMethod::PeerCounts
341 | ControlMethod::PeersConnect
342 | ControlMethod::PeersDisconnect
343 | ControlMethod::Subscribe
344 | ControlMethod::Unsubscribe
345 | ControlMethod::ListSubscriptions
346 | ControlMethod::WalletBalance
347 | ControlMethod::WalletCoins
348 | ControlMethod::WalletCoinById
349 | ControlMethod::WalletCoinSpend
350 | ControlMethod::WalletCoinsByParent
351 | ControlMethod::WalletArrivals
352 | ControlMethod::WalletPeak
353 | ControlMethod::WalletSyncStatus
354 | ControlMethod::WalletBroadcast
355 | ControlMethod::WalletWatch
356 | ControlMethod::WalletUnwatch
357 | ControlMethod::WalletWatched
358 | ControlMethod::ProfilePutBody
359 | ControlMethod::ProfileGetBody => Routing::Delegated,
360 ControlMethod::PairingRequest | ControlMethod::PairingPoll => Routing::OpenBootstrap,
361 _ => Routing::Owned,
362 }
363 }
364
365 pub const fn category(self) -> Category {
367 match self {
368 ControlMethod::Status => Category::Status,
369 ControlMethod::ConfigGet | ControlMethod::ConfigSetUpstream => Category::Config,
370 ControlMethod::LogSetLevel => Category::Log,
371 ControlMethod::CacheGet | ControlMethod::CacheSetCap | ControlMethod::CacheClear => {
372 Category::Cache
373 }
374 ControlMethod::HostedStoresList
375 | ControlMethod::HostedStoresPin
376 | ControlMethod::HostedStoresUnpin
377 | ControlMethod::HostedStoresStatus => Category::HostedStores,
378 ControlMethod::SyncStatus | ControlMethod::SyncTrigger => Category::Sync,
379 ControlMethod::UpdaterStatus
380 | ControlMethod::UpdaterSetChannel
381 | ControlMethod::UpdaterPause
382 | ControlMethod::UpdaterResume
383 | ControlMethod::UpdaterCheckNow => Category::Updater,
384 ControlMethod::PairingList
385 | ControlMethod::PairingApprove
386 | ControlMethod::PairingRevoke
387 | ControlMethod::PairingRequest
388 | ControlMethod::PairingPoll => Category::Pairing,
389 ControlMethod::PeerStatus
390 | ControlMethod::PeerCounts
391 | ControlMethod::PeersConnect
392 | ControlMethod::PeersDisconnect => Category::Peers,
393 ControlMethod::Subscribe
394 | ControlMethod::Unsubscribe
395 | ControlMethod::ListSubscriptions => Category::Subscriptions,
396 ControlMethod::WalletBalance
397 | ControlMethod::WalletCoins
398 | ControlMethod::WalletCoinById
399 | ControlMethod::WalletCoinSpend
400 | ControlMethod::WalletCoinsByParent
401 | ControlMethod::WalletArrivals
402 | ControlMethod::WalletPeak
403 | ControlMethod::WalletSyncStatus
404 | ControlMethod::WalletBroadcast
405 | ControlMethod::WalletWatch
406 | ControlMethod::WalletUnwatch
407 | ControlMethod::WalletWatched => Category::Wallet,
408 ControlMethod::ProfilePutBody | ControlMethod::ProfileGetBody => Category::Profile,
409 }
410 }
411
412 pub const fn summary(self) -> &'static str {
414 match self {
415 ControlMethod::Status => "A rich node status snapshot (version, uptime, addr, cache, hosted/pinned counts, sync availability).",
416 ControlMethod::ConfigGet => "The node's effective configuration (addr/port, upstream + override, cache dir/shared, config path, sync availability).",
417 ControlMethod::ConfigSetUpstream => "Persist an upstream-RPC override; takes effect on next node start (requires_restart).",
418 ControlMethod::LogSetLevel => "Live-swap the running node's tracing EnvFilter directive (not persisted).",
419 ControlMethod::CacheGet => "The on-disk content-cache view: cap_bytes, used_bytes, dir, shared.",
420 ControlMethod::CacheSetCap => "Set the on-disk cache size cap in bytes (floored at 64 MiB).",
421 ControlMethod::CacheClear => "Delete all locally cached DIG content.",
422 ControlMethod::HostedStoresList => "Every held/pinned store, merged, with each store's cached capsules and a pinned flag.",
423 ControlMethod::HostedStoresPin => "Pin a store (storeId[:rootHash]); pre-fetches the capsule when a root is given and §21 sync is available.",
424 ControlMethod::HostedStoresUnpin => "Unpin a store and evict its cached capsules.",
425 ControlMethod::HostedStoresStatus => "Per-store status: pinned flag, cached capsules, total bytes.",
426 ControlMethod::SyncStatus => "Whether authenticated §21 whole-store sync is available, plus pinned-store cache coverage.",
427 ControlMethod::SyncTrigger => "Trigger a §21 sync for one capsule (storeId + root).",
428 ControlMethod::UpdaterStatus => "The DIG auto-update beacon's current status (proxied from dig-updater).",
429 ControlMethod::UpdaterSetChannel => "Set the beacon's update channel (\"nightly\" | \"stable\").",
430 ControlMethod::UpdaterPause => "Suspend the beacon's auto-updates (optionally until a unix time).",
431 ControlMethod::UpdaterResume => "Resume the beacon's auto-updates.",
432 ControlMethod::UpdaterCheckNow => "Force an immediate beacon update check.",
433 ControlMethod::PairingList => "List pending pairing requests and issued paired tokens (MASTER token only).",
434 ControlMethod::PairingApprove => "Approve a pending pairing, minting a scoped token (MASTER token only).",
435 ControlMethod::PairingRevoke => "Revoke an issued paired token by token_id (MASTER token only).",
436 ControlMethod::PeerStatus => "Live peer-pool + relay-reservation snapshot, including the per-peer connected array; each entry carries an always-present `software` field (the peer's advertised build). Its `relay.peer_count` counts peers connected to THE RELAY, not to this node, and is never the answer to \"how many peers does this node have\" -- that is control.peerCounts.",
437 ControlMethod::PeerCounts => "READ-only: how many peers this node holds on EACH network -- dig_peer_count (DIG content/gossip, port 9445) and chia_peer_count (Chia full nodes serving the wallet chain sync). Two unrelated numbers, each named for its network.",
438 ControlMethod::PeersConnect => "Dial a peer by address, or resolve an already-connected peer_id, via the live gossip pool.",
439 ControlMethod::PeersDisconnect => "Drop a pooled peer by peer_id, closing its mTLS link (idempotent).",
440 ControlMethod::Subscribe => "Subscribe the node to a store it actively watches and gap-fills.",
441 ControlMethod::Unsubscribe => "Stop watching a store.",
442 ControlMethod::ListSubscriptions => "The node's persisted subscription set + count.",
443 ControlMethod::WalletCoins => "READ-only: the spendable coin records for an address + asset, with the tier that answered and the height they reflect.",
444 ControlMethod::WalletCoinById => "READ-only: ONE coin record by coin id, spent or unspent, with no address and no asset scope; `coin: null` means the chain holds no such coin.",
445 ControlMethod::WalletCoinSpend => "READ-only: the SPEND that spent a coin -- its puzzle reveal, its solution and the coin itself -- named by the coin's own id. `spend: null` means the consulted chain shows that coin as unspent or unknown; it NEVER means the chain could not be reached, which is an error.",
446 ControlMethod::WalletCoinsByParent => "READ-only: the DIRECT children created by spending one coin, named by that parent's coin id. ONE hop, never a recursive walk: an empty list means the parent created no known children, and a caller wanting a lineage composes hops itself.",
447 ControlMethod::WalletArrivals => "READ-only: confirmed INCOMING funds recorded since a cursor position, oldest first -- the answer to `was I just paid?`, which no balance or coin list can give. Each row is a coin the node determined ARRIVED: confirmed on chain, above the wallet's arrival baseline, not previously reported, and not the wallet's own change. Resume from `cursor` (the last row you were handed), never from `latest`.",
448 ControlMethod::WalletPeak => "READ-only: the node's current chain peak height, independent of any address.",
449 ControlMethod::WalletSyncStatus => "READ-only: whether the wallet's CHAIN replica is being kept current (not_started/syncing/synced/no_wallet_enrolled/wallet_not_unlocked), the replica's own height, and its CHIA full-node peer count -- unrelated to control.sync.status (DIG stores) and to control.peerStatus (DIG peers).",
450 ControlMethod::WalletBroadcast => "Push an ALREADY-SIGNED spend bundle to the network; the node never signs. TOKEN-GATED.",
451 ControlMethod::WalletBalance => "READ-only: the confirmed spendable balance for an address + asset (plus pending, sync freshness, and the peak height it reflects).",
452 ControlMethod::WalletWatch => "Enrol PUBLIC keys (48-byte G1, lowercase 96-hex) for the node's chain replica to follow, so their addresses are synced and readable. IDEMPOTENT: re-enrolling a key already enrolled succeeds and changes nothing. Keys, never puzzle hashes -- the node derives the addresses itself, so one derivation serves every client. TOKEN-GATED.",
453 ControlMethod::WalletUnwatch => "Deregister enrolled public keys, so the node stops following their addresses. IDEMPOTENT: a key that was never enrolled is not an error. TOKEN-GATED.",
454 ControlMethod::ProfilePutBody => "Hand the node the dig-profile BODY that a chain root commits to. The node INDEPENDENTLY resolves that root on chain and REFUSES any body whose recomputed root is not the confirmed one -- the caller's `root` is a claim to be checked, never a fact to be trusted, and dig-app is a caller like any other. Bodies are capped at MAX_BODY_BYTES (4 MiB). TOKEN-GATED.",
455 ControlMethod::ProfileGetBody => "READ-only: the dig-profile body this node holds at a given store id + root, or `body: null` when it holds none. `null` NEVER means the body could not be read, which is an error. TOKEN-GATED.",
456 ControlMethod::WalletWatched => "READ-only: the public keys currently enrolled, so a client can reconcile what it asked for against what the node holds. TOKEN-GATED although it is a read -- the caller supplies nothing, so the answer is this node's OWN key set.",
457 ControlMethod::PairingRequest => "OPEN: request a control-token pairing; returns a pairing_id + pairing_code to compare.",
458 ControlMethod::PairingPoll => "OPEN: poll a pairing by id; once the operator approves, returns the scoped token once.",
459 }
460 }
461
462 pub const ALL: &'static [ControlMethod] = &[
465 ControlMethod::Status,
466 ControlMethod::ConfigGet,
467 ControlMethod::ConfigSetUpstream,
468 ControlMethod::LogSetLevel,
469 ControlMethod::CacheGet,
470 ControlMethod::CacheSetCap,
471 ControlMethod::CacheClear,
472 ControlMethod::HostedStoresList,
473 ControlMethod::HostedStoresPin,
474 ControlMethod::HostedStoresUnpin,
475 ControlMethod::HostedStoresStatus,
476 ControlMethod::SyncStatus,
477 ControlMethod::SyncTrigger,
478 ControlMethod::UpdaterStatus,
479 ControlMethod::UpdaterSetChannel,
480 ControlMethod::UpdaterPause,
481 ControlMethod::UpdaterResume,
482 ControlMethod::UpdaterCheckNow,
483 ControlMethod::PairingList,
484 ControlMethod::PairingApprove,
485 ControlMethod::PairingRevoke,
486 ControlMethod::PeerStatus,
487 ControlMethod::PeerCounts,
488 ControlMethod::PeersConnect,
489 ControlMethod::PeersDisconnect,
490 ControlMethod::Subscribe,
491 ControlMethod::Unsubscribe,
492 ControlMethod::ListSubscriptions,
493 ControlMethod::WalletBalance,
494 ControlMethod::WalletCoins,
495 ControlMethod::WalletCoinById,
496 ControlMethod::WalletCoinSpend,
497 ControlMethod::WalletCoinsByParent,
498 ControlMethod::WalletArrivals,
499 ControlMethod::WalletPeak,
500 ControlMethod::WalletSyncStatus,
501 ControlMethod::WalletBroadcast,
502 ControlMethod::WalletWatch,
503 ControlMethod::WalletUnwatch,
504 ControlMethod::WalletWatched,
505 ControlMethod::ProfilePutBody,
506 ControlMethod::ProfileGetBody,
507 ControlMethod::PairingRequest,
508 ControlMethod::PairingPoll,
509 ];
510}
511
512#[cfg(test)]
513mod tests {
514 use super::*;
515 use std::collections::BTreeSet;
516
517 #[test]
518 fn every_method_has_a_unique_wire_name() {
519 let names: BTreeSet<&str> = ControlMethod::ALL.iter().map(|m| m.name()).collect();
520 assert_eq!(
521 names.len(),
522 ControlMethod::ALL.len(),
523 "duplicate or missing wire names in the catalog"
524 );
525 }
526
527 #[test]
528 fn from_name_round_trips_every_method() {
529 for &m in ControlMethod::ALL {
530 assert_eq!(ControlMethod::from_name(m.name()), Some(m));
531 }
532 assert_eq!(ControlMethod::from_name("control.nope"), None);
533 assert_eq!(ControlMethod::from_name(""), None);
534 }
535
536 #[test]
537 fn the_token_less_surface_is_exactly_the_bootstrap_plus_the_chain_reads() {
538 let expected_open: BTreeSet<&str> = [
542 "pairing.request",
543 "pairing.poll",
544 "control.wallet.balance",
545 "control.wallet.coins",
546 "control.wallet.coinById",
547 "control.wallet.coinSpend",
548 "control.wallet.coinsByParent",
549 "control.wallet.peak",
550 "control.wallet.syncStatus",
551 "control.peerCounts",
552 ]
553 .into_iter()
554 .collect();
555 assert_eq!(
556 expected_open.len(),
557 10,
558 "the open surface is ten named methods"
559 );
560 let actual_open: BTreeSet<&str> = ControlMethod::ALL
561 .iter()
562 .filter(|m| !m.requires_auth())
563 .map(|m| m.name())
564 .collect();
565 assert_eq!(actual_open, expected_open);
566 }
567
568 #[test]
577 fn the_gated_wallet_methods_are_the_push_the_cursor_and_enrolment() {
578 let gated: Vec<&str> = ControlMethod::ALL
579 .iter()
580 .filter(|m| m.category() == Category::Wallet && m.requires_auth())
581 .map(|m| m.name())
582 .collect();
583 assert_eq!(
584 gated,
585 vec![
586 "control.wallet.arrivals",
587 "control.wallet.broadcast",
588 "control.wallet.watch",
589 "control.wallet.unwatch",
590 "control.wallet.watched",
591 ]
592 );
593 assert!(!ControlMethod::WalletBroadcast.is_open_read());
594 }
595
596 #[test]
608 fn the_arrival_cursor_is_not_an_open_read() {
609 assert!(
610 !ControlMethod::WalletArrivals.is_open_read(),
611 "control.wallet.arrivals discloses this node's OWN watched puzzle hashes to a caller \
612 that supplied nothing, so it MUST NOT be served token-less"
613 );
614 assert!(ControlMethod::WalletArrivals.requires_auth());
615 assert!(
616 ControlMethod::WalletCoinById.is_open_read(),
617 "the caller-addressed reads stay open -- the fix is the membership rule, not gating \
618 the wallet category"
619 );
620 }
621
622 #[test]
641 fn the_catalog_serves_every_chain_source_primitive() {
642 for wire in [
643 "control.wallet.coinById", "control.wallet.coins", "control.wallet.peak", "control.wallet.coinsByParent", "control.wallet.coinSpend", ] {
649 assert!(
650 ControlMethod::from_name(wire).is_some(),
651 "{wire} is required to implement ChainSource over the control plane"
652 );
653 }
654 }
655
656 #[test]
663 fn the_chain_primitives_are_caller_named_open_reads() {
664 for method in [
665 ControlMethod::WalletCoinSpend,
666 ControlMethod::WalletCoinsByParent,
667 ] {
668 assert!(
669 method.is_open_read(),
670 "{} names its subject in the request and discloses no node-to-address \
671 association, exactly like control.wallet.coinById",
672 method.name()
673 );
674 assert!(!method.requires_auth());
675 }
676 assert!(
677 ControlMethod::WalletArrivals.requires_auth(),
678 "the caller-supplies-nothing read stays gated -- the rule is who names the subject, \
679 not whether the bytes are on chain"
680 );
681 assert!(ControlMethod::WalletBroadcast.requires_auth());
682 }
683
684 #[test]
698 fn the_enrolment_methods_are_gated_including_the_read() {
699 for wire in [
700 "control.wallet.watch",
701 "control.wallet.unwatch",
702 "control.wallet.watched",
703 ] {
704 let method = ControlMethod::from_name(wire)
705 .unwrap_or_else(|| panic!("{wire} must be in the catalog"));
706 assert!(
707 !method.is_open_read(),
708 "{wire} either aims this node's subscriptions or names the keys it already \
709 follows, so it MUST NOT be served token-less"
710 );
711 assert!(method.requires_auth(), "{wire} must require the token");
712 assert_eq!(method.category(), Category::Wallet);
713 assert_eq!(method.routing(), Routing::Delegated);
714 }
715 assert!(
716 ControlMethod::WalletCoinById.is_open_read(),
717 "the caller-addressed reads stay open -- enrolment is gated by the membership rule, \
718 not by gating the wallet category"
719 );
720 }
721
722 #[test]
723 fn only_pairing_bootstrap_is_open_bootstrap_routed() {
724 for &m in ControlMethod::ALL {
725 let open_bootstrap = matches!(
726 m,
727 ControlMethod::PairingRequest | ControlMethod::PairingPoll
728 );
729 assert_eq!(
730 m.routing() == Routing::OpenBootstrap,
731 open_bootstrap,
732 "{} routing mismatch",
733 m.name()
734 );
735 }
736 }
737
738 #[test]
739 fn pairing_admin_methods_are_exactly_three() {
740 let admin: Vec<&str> = ControlMethod::ALL
741 .iter()
742 .filter(|m| m.is_pairing_admin())
743 .map(|m| m.name())
744 .collect();
745 assert_eq!(
746 admin,
747 vec![
748 "control.pairing.list",
749 "control.pairing.approve",
750 "control.pairing.revoke"
751 ]
752 );
753 }
754
755 #[test]
756 fn delegated_set_matches_the_engine_surface() {
757 let delegated: BTreeSet<&str> = ControlMethod::ALL
758 .iter()
759 .filter(|m| m.routing() == Routing::Delegated)
760 .map(|m| m.name())
761 .collect();
762 let expected: BTreeSet<&str> = [
763 "control.wallet.coins",
764 "control.wallet.coinById",
765 "control.wallet.coinSpend",
766 "control.wallet.coinsByParent",
767 "control.wallet.arrivals",
768 "control.wallet.peak",
769 "control.wallet.syncStatus",
770 "control.wallet.broadcast",
771 "control.wallet.watch",
772 "control.wallet.unwatch",
773 "control.wallet.watched",
774 "control.profile.putBody",
775 "control.profile.getBody",
776 "control.peerStatus",
777 "control.peerCounts",
778 "control.peers.connect",
779 "control.peers.disconnect",
780 "control.subscribe",
781 "control.unsubscribe",
782 "control.listSubscriptions",
783 "control.wallet.balance",
784 ]
785 .into_iter()
786 .collect();
787 assert_eq!(delegated, expected);
788 }
789
790 #[test]
791 fn every_method_has_a_nonempty_summary() {
792 for &m in ControlMethod::ALL {
793 assert!(!m.summary().is_empty(), "{} has no summary", m.name());
794 }
795 }
796}