canic_core/ops/storage/registry/
subnet.rs1use crate::{
2 Error, ThisError,
3 cdk::types::Principal,
4 cdk::utils::time::now_secs,
5 config::schema::CanisterCardinality,
6 dto::{
7 registry::SubnetRegistryView,
8 snapshot::{TopologyChildView, TopologyNodeView},
9 },
10 ids::CanisterRole,
11 model::memory::{CanisterEntry, CanisterSummary, registry::SubnetRegistry},
12 ops::{
13 adapter::{
14 canister::{canister_summary_to_topology_child, canister_summary_to_topology_node},
15 registry::subnet_registry_to_view,
16 },
17 config::ConfigOps,
18 storage::registry::RegistryOpsError,
19 },
20};
21use std::collections::HashSet;
22
23#[derive(Debug, ThisError)]
28pub enum SubnetRegistryOpsError {
29 #[error("canister {0} already registered")]
33 AlreadyRegistered(Principal),
34
35 #[error("role {role} already registered to {pid}")]
36 RoleAlreadyRegistered { role: CanisterRole, pid: Principal },
37
38 #[error("canister {0} not found in subnet registry")]
42 CanisterNotFound(Principal),
43
44 #[error("parent chain contains a cycle at {0}")]
45 ParentChainCycle(Principal),
46
47 #[error("parent chain exceeded registry size ({0})")]
48 ParentChainTooLong(usize),
49
50 #[error("parent chain did not terminate at root (last pid: {0})")]
51 ParentChainNotRootTerminated(Principal),
52}
53
54impl From<SubnetRegistryOpsError> for Error {
55 fn from(err: SubnetRegistryOpsError) -> Self {
56 RegistryOpsError::from(err).into()
57 }
58}
59
60pub struct SubnetRegistryOps;
68
69impl SubnetRegistryOps {
70 pub(crate) fn register(
75 pid: Principal,
76 role: &CanisterRole,
77 parent_pid: Principal,
78 module_hash: Vec<u8>,
79 ) -> Result<(), Error> {
80 if SubnetRegistry::get(pid).is_some() {
81 return Err(SubnetRegistryOpsError::AlreadyRegistered(pid).into());
82 }
83
84 if role_requires_singleton(role)?
85 && let Some((existing_pid, _)) = SubnetRegistry::find_first_by_role(role)
86 {
87 return Err(SubnetRegistryOpsError::RoleAlreadyRegistered {
88 role: role.clone(),
89 pid: existing_pid,
90 }
91 .into());
92 }
93
94 let created_at = now_secs();
95 SubnetRegistry::register(pid, role, parent_pid, module_hash, created_at);
96 Ok(())
97 }
98
99 pub(crate) fn remove(pid: &Principal) -> Option<CanisterEntry> {
100 SubnetRegistry::remove(pid)
101 }
102
103 pub(crate) fn register_root(pid: Principal) {
104 let created_at = now_secs();
105 SubnetRegistry::register_root(pid, created_at);
106 }
107
108 pub(crate) fn update_module_hash(pid: Principal, module_hash: Vec<u8>) -> bool {
109 SubnetRegistry::update_module_hash(pid, module_hash)
110 }
111
112 #[must_use]
117 pub(crate) fn get(pid: Principal) -> Option<CanisterEntry> {
118 SubnetRegistry::get(pid)
119 }
120
121 #[must_use]
122 pub(crate) fn is_registered(pid: Principal) -> bool {
123 SubnetRegistry::get(pid).is_some()
124 }
125
126 #[must_use]
127 pub fn get_parent(pid: Principal) -> Option<Principal> {
128 SubnetRegistry::get_parent(pid)
129 }
130
131 #[must_use]
133 pub(crate) fn children(pid: Principal) -> Vec<(Principal, CanisterSummary)> {
134 SubnetRegistry::children(pid)
135 }
136
137 #[must_use]
139 pub(crate) fn children_view(pid: Principal) -> Vec<TopologyNodeView> {
140 SubnetRegistry::children(pid)
141 .into_iter()
142 .map(|(pid, summary)| canister_summary_to_topology_node(pid, &summary))
143 .collect()
144 }
145
146 #[must_use]
148 pub(crate) fn children_child_view(pid: Principal) -> Vec<TopologyChildView> {
149 SubnetRegistry::children(pid)
150 .into_iter()
151 .map(|(pid, summary)| canister_summary_to_topology_child(pid, &summary))
152 .collect()
153 }
154
155 #[must_use]
157 pub(crate) fn subtree(pid: Principal) -> Vec<(Principal, CanisterSummary)> {
158 SubnetRegistry::subtree(pid)
159 }
160
161 #[must_use]
163 pub(crate) fn export_roles() -> Vec<(Principal, CanisterRole)> {
164 SubnetRegistry::export()
165 .into_iter()
166 .map(|(pid, entry)| (pid, entry.role))
167 .collect()
168 }
169
170 #[must_use]
171 pub fn export_view() -> SubnetRegistryView {
172 let data = SubnetRegistry::export();
173
174 subnet_registry_to_view(data)
175 }
176
177 pub(crate) fn parent_chain(
190 target: Principal,
191 ) -> Result<Vec<(Principal, CanisterSummary)>, Error> {
192 let registry_len = SubnetRegistry::export().len();
193
194 let mut chain: Vec<(Principal, CanisterSummary)> = Vec::new();
195 let mut seen: HashSet<Principal> = HashSet::new();
196 let mut pid = target;
197
198 loop {
199 if !seen.insert(pid) {
200 return Err(SubnetRegistryOpsError::ParentChainCycle(pid).into());
201 }
202
203 let Some(entry) = SubnetRegistry::get(pid) else {
204 return Err(SubnetRegistryOpsError::CanisterNotFound(pid).into());
205 };
206
207 if seen.len() > registry_len {
208 return Err(SubnetRegistryOpsError::ParentChainTooLong(seen.len()).into());
209 }
210
211 let summary = CanisterSummary::from(&entry);
212 let parent = entry.parent_pid;
213
214 chain.push((pid, summary));
215
216 if let Some(parent_pid) = parent {
217 pid = parent_pid;
218 } else {
219 if entry.role != CanisterRole::ROOT {
220 return Err(SubnetRegistryOpsError::ParentChainNotRootTerminated(pid).into());
221 }
222 break;
223 }
224 }
225
226 chain.reverse();
227 Ok(chain)
228 }
229
230 pub(crate) fn parent_chain_view(target: Principal) -> Result<Vec<TopologyNodeView>, Error> {
232 let chain = Self::parent_chain(target)?;
233 Ok(chain
234 .into_iter()
235 .map(|(pid, summary)| canister_summary_to_topology_node(pid, &summary))
236 .collect())
237 }
238}
239
240fn role_requires_singleton(role: &CanisterRole) -> Result<bool, Error> {
245 let cfg = ConfigOps::current_subnet_canister(role)?;
246 Ok(cfg.cardinality == CanisterCardinality::Single)
247}