Skip to main content

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}