ic_agent/agent/subnet.rs
1//! Information about IC subnets.
2//!
3//! Fetch subnet information via [`Agent::fetch_subnet_by_id`](crate::Agent::fetch_subnet_by_id) or
4//! [`Agent::get_subnet_by_canister`](crate::Agent::get_subnet_by_canister).
5
6use std::{collections::HashMap, ops::RangeInclusive};
7
8use candid::Principal;
9use rangemap::RangeInclusiveSet;
10
11/// The type of an IC subnet, as reported in the state tree under `/subnet/<subnet_id>/type`.
12///
13/// This leaf is present for certification version V25 and higher.
14#[derive(Clone, Debug, PartialEq, Eq)]
15pub enum SubnetType {
16 /// The NNS subnet and other system-level subnets.
17 System,
18 /// A standard application subnet.
19 Application,
20 /// A verified application subnet.
21 VerifiedApplication,
22 /// A cloud engine subnet.
23 CloudEngine,
24 /// An unrecognized subnet type string, preserved for forward compatibility.
25 Unknown(String),
26}
27
28/// Information about a subnet, including its public key, member nodes, and assigned canister ranges.
29///
30/// Range information may be incomplete depending on how the subnet was fetched. The lack of a canister ID
31/// within assigned ranges should not be treated immediately as an authorization failure without fetching
32/// fresh data with [`Agent::fetch_subnet_by_canister`](crate::Agent::fetch_subnet_by_canister).
33///
34/// Conversely, the *presence* of a canister ID within the ranges is only authoritative when the
35/// subnet was fetched by canister. A subnet fetched by ID via
36/// [`Agent::fetch_subnet_by_id`](crate::Agent::fetch_subnet_by_id) reports ranges that it attested
37/// itself. Use [`Agent::fetch_subnet_by_canister`](crate::Agent::fetch_subnet_by_canister) to
38/// decide whether a subnet may speak for a canister.
39#[derive(Debug, Clone)]
40pub struct Subnet {
41 pub(crate) id: Principal,
42 // This key is just fetched for completeness. Do not actually use this value as it is not authoritative in case of a rogue subnet.
43 // If a future agent needs to know the subnet key then it should fetch /subnet from the *root* subnet.
44 pub(crate) key: Vec<u8>,
45 pub(crate) node_keys: HashMap<Principal, Vec<u8>>,
46 // Only authoritative when this subnet was looked up by canister, in which case the ranges came
47 // from the NNS-root-signed delegation. When looked up by subnet ID they are self-attested, and
48 // so must not be used to decide which subnet may answer for a canister.
49 pub(crate) canister_ranges: RangeInclusiveSet<Principal>,
50 /// Present when the certificate's version is V25 or higher; `None` for older certificates.
51 pub(crate) subnet_type: Option<SubnetType>,
52}
53
54impl Subnet {
55 /// Checks whether the given canister ID is contained within the subnet's assigned canister ranges.
56 ///
57 /// Note that this is not authoritative if the subnet was fetched by subnet ID; see the
58 /// type-level documentation.
59 pub fn contains_canister(&self, canister_id: &Principal) -> bool {
60 self.canister_ranges.contains(canister_id)
61 }
62 /// Returns an iterator over the known canister ID ranges assigned to this subnet.
63 ///
64 /// Note that these are self-reported, and so not authoritative, if the subnet was fetched by
65 /// subnet ID; see the type-level documentation.
66 pub fn iter_canister_ranges(&self) -> CanisterRangesIter<'_> {
67 CanisterRangesIter {
68 inner: self.canister_ranges.iter(),
69 }
70 }
71 /// Returns the self-reported public key of the subnet.
72 ///
73 /// Note that this key is not authoritative if the subnet is rogue.
74 pub fn self_reported_key(&self) -> &[u8] {
75 &self.key
76 }
77 /// Checks whether the given node ID is a member of this subnet.
78 pub fn contains_node(&self, node_id: &Principal) -> bool {
79 self.node_keys.contains_key(node_id)
80 }
81 /// Returns the public key of the given node ID, if it is a member of this subnet.
82 pub fn get_node_key(&self, node_id: &Principal) -> Option<&[u8]> {
83 self.node_keys.get(node_id).map(|k| &k[..])
84 }
85 /// Returns an iterator over the nodes in this subnet.
86 pub fn iter_nodes(&self) -> SubnetNodeIter<'_> {
87 SubnetNodeIter {
88 inner: self.node_keys.keys(),
89 }
90 }
91 /// Returns an iterator over the node IDs and their corresponding public keys in this subnet.
92 pub fn iter_node_keys(&self) -> SubnetKeysIter<'_> {
93 SubnetKeysIter {
94 inner: self.node_keys.iter(),
95 }
96 }
97 /// Returns the subnet's ID.
98 pub fn id(&self) -> Principal {
99 self.id
100 }
101 /// Returns the subnet type if it was present in the state tree certificate.
102 ///
103 /// This is populated when the replica uses certification version V25 or higher.
104 /// Returns `None` for older certificates that do not include the `type` leaf.
105 pub fn subnet_type(&self) -> Option<&SubnetType> {
106 self.subnet_type.as_ref()
107 }
108}
109
110/// Iterator over the canister ID ranges assigned to a subnet.
111pub struct CanisterRangesIter<'a> {
112 inner: rangemap::inclusive_set::Iter<'a, Principal>,
113}
114
115impl Iterator for CanisterRangesIter<'_> {
116 type Item = RangeInclusive<Principal>;
117
118 fn next(&mut self) -> Option<Self::Item> {
119 self.inner.next().cloned()
120 }
121}
122
123/// Iterator over the node IDs in a subnet.
124#[derive(Debug)]
125pub struct SubnetNodeIter<'a> {
126 inner: std::collections::hash_map::Keys<'a, Principal, Vec<u8>>,
127}
128
129impl<'a> Iterator for SubnetNodeIter<'a> {
130 type Item = Principal;
131
132 fn next(&mut self) -> Option<Self::Item> {
133 self.inner.next().copied()
134 }
135}
136
137/// Iterator over the node IDs and their corresponding public keys in a subnet.
138#[derive(Debug)]
139pub struct SubnetKeysIter<'a> {
140 inner: std::collections::hash_map::Iter<'a, Principal, Vec<u8>>,
141}
142
143impl<'a> Iterator for SubnetKeysIter<'a> {
144 type Item = (Principal, &'a [u8]);
145
146 fn next(&mut self) -> Option<Self::Item> {
147 self.inner.next().map(|(k, v)| (*k, &v[..]))
148 }
149}