1use bity_ic_utils::retry_async::retry_async;
38use candid::{CandidType, Encode, Nat, Principal};
39use canfund::{
40 manager::{options::FundManagerOptions, RegisterOpts},
41 operations::fetch::FetchCyclesBalanceFromCanisterStatus,
42 FundManager,
43};
44use ic_cdk::management_canister::create_canister_with_extra_cycles;
45use ic_cdk::management_canister::{
46 canister_status, install_code, start_canister, stop_canister, CanisterIdRecord,
47 CanisterInstallMode, CanisterSettings, CreateCanisterArgs, InstallCodeArgs, LogVisibility,
48};
49use serde::{Deserialize, Serialize};
50use std::sync::Arc;
51use std::{any::Any, collections::HashMap, fmt::Debug};
52
53#[derive(Debug)]
55pub enum NewStorageError {
56 CreateCanisterError(String),
58 InstallCodeError(String),
60 FailedToSerializeInitArgs(String),
62}
63
64#[derive(Debug)]
66pub enum NewCanisterError {
67 CreateCanisterError(String),
69 InstallCodeError(String),
71 FailedToSerializeInitArgs(String),
73}
74
75#[derive(Serialize, Deserialize, Clone)]
77pub enum CanisterError {
78 CantFindControllers(String),
80}
81
82#[derive(CandidType, Serialize, Deserialize, Clone, PartialEq, Debug)]
84pub enum CanisterState {
85 Created,
87 Installed,
89 Stopped,
91}
92
93pub trait Canister {
95 type ParamType: CandidType + Serialize + Clone + Send;
97
98 fn new(canister_id: Principal, state: CanisterState, canister_param: Self::ParamType) -> Self;
100
101 fn canister_param(&self) -> Self::ParamType;
103
104 fn canister_id(&self) -> Principal;
106
107 fn state(&self) -> CanisterState;
109
110 fn as_any(&self) -> &dyn Any;
112
113 fn get_canister_controllers(
115 &self,
116 ) -> impl std::future::Future<Output = Result<Vec<Principal>, CanisterError>> + Send
117 where
118 Self: Sync + Send,
119 {
120 async {
121 match retry_async(
122 async || {
123 canister_status(&CanisterIdRecord {
124 canister_id: self.canister_id(),
125 })
126 .await
127 },
128 3,
129 )
130 .await
131 {
132 Ok(res) => Ok(res.settings.controllers),
133 Err(e) => Err(CanisterError::CantFindControllers(format!("{e:?}"))),
134 }
135 }
136 }
137}
138
139#[derive(Serialize, Deserialize)]
141pub struct SubCanisterManager<T>
142where
143 T: Canister + Clone + Send,
144{
145 pub master_canister_id: Principal,
147 pub sub_canisters: HashMap<Principal, Box<T>>,
149 pub controllers: Vec<Principal>,
151 pub authorized_principal: Vec<Principal>,
153 pub initial_cycles: u128,
155 pub reserved_cycles: u128,
157 pub test_mode: bool,
159 pub commit_hash: String,
161 pub wasm: Vec<u8>,
163 #[serde(skip)]
165 pub fund_manager: FundManager,
166 #[serde(skip)]
168 pub funding_config: FundManagerOptions,
169}
170
171impl<T> SubCanisterManager<T>
172where
173 T: Canister + Clone + Send,
174{
175 pub fn new(
176 master_canister_id: Principal,
177 sub_canisters: HashMap<Principal, Box<T>>,
178 mut controllers: Vec<Principal>,
179 mut authorized_principal: Vec<Principal>,
180 initial_cycles: u128,
181 reserved_cycles: u128,
182 test_mode: bool,
183 commit_hash: String,
184 wasm: Vec<u8>,
185 funding_config: FundManagerOptions,
186 ) -> Self {
187 controllers.push(master_canister_id);
188 authorized_principal.push(master_canister_id);
189
190 Self {
191 master_canister_id,
192 sub_canisters,
193 controllers,
194 authorized_principal,
195 initial_cycles,
196 reserved_cycles,
197 test_mode,
198 commit_hash,
199 wasm,
200 fund_manager: FundManager::new(),
201 funding_config: funding_config,
202 }
203 }
204
205 pub async fn create_canister(
206 &mut self,
207 init_args: <T as Canister>::ParamType,
208 ) -> Result<Box<T>, NewCanisterError> {
209 let mut canister_id = Principal::anonymous();
210
211 for (_canister_id, canister) in self.sub_canisters.iter() {
212 if canister.state() == CanisterState::Created {
213 canister_id = *_canister_id;
214 break;
215 }
216 }
217
218 if canister_id == Principal::anonymous() {
219 let settings = CanisterSettings {
220 controllers: Some(self.controllers.clone()),
221 compute_allocation: None,
222 memory_allocation: None,
223 freezing_threshold: None,
224 reserved_cycles_limit: Some(Nat::from(self.reserved_cycles)),
225 log_visibility: Some(LogVisibility::Public),
226 wasm_memory_limit: None,
227 wasm_memory_threshold: None,
228 environment_variables: None,
229 };
230
231 canister_id = match retry_async(
232 async || {
233 create_canister_with_extra_cycles(
234 &CreateCanisterArgs {
235 settings: Some(settings.clone()),
236 },
237 self.initial_cycles,
238 )
239 .await
240 },
241 3,
242 )
243 .await
244 {
245 Ok(canister) => canister.canister_id,
246 Err(e) => {
247 return Err(NewCanisterError::CreateCanisterError(format!("{e:?}")));
248 }
249 };
250
251 add_canisters_to_fund_manager(
252 &mut self.fund_manager,
253 self.funding_config.clone(),
254 vec![canister_id],
255 );
256
257 self.sub_canisters.insert(
258 canister_id,
259 Box::new(T::new(
260 canister_id,
261 CanisterState::Created,
262 init_args.clone(),
263 )),
264 );
265 }
266
267 let encoded_init_args = match Encode!(&init_args) {
268 Ok(encoded_init_args) => encoded_init_args,
269 Err(e) => {
270 return Err(NewCanisterError::FailedToSerializeInitArgs(format!("{e}")));
271 }
272 };
273
274 let install_args = InstallCodeArgs {
275 mode: CanisterInstallMode::Install,
276 canister_id,
277 wasm_module: self.wasm.clone(),
278 arg: encoded_init_args.clone(),
279 };
280
281 match install_code(&install_args).await {
282 Ok(_) => {}
283 Err(e) => {
284 return Err(NewCanisterError::InstallCodeError(format!("{:?}", e)));
285 }
286 }
287
288 let canister = Box::new(T::new(
289 canister_id,
290 CanisterState::Installed,
291 init_args.clone(),
292 ));
293
294 self.sub_canisters.insert(canister_id, canister);
295
296 Ok(self
297 .sub_canisters
298 .get(&canister_id)
299 .expect("Canister was just inserted")
300 .clone())
301 }
302
303 pub async fn update_canisters(
304 &mut self,
305 update_args: <T as Canister>::ParamType,
306 ) -> Result<(), Vec<String>> {
307 let init_args = match Encode!(&update_args.clone()) {
308 Ok(encoded_init_args) => encoded_init_args,
309 Err(e) => {
310 return Err(vec![format!(
311 "ERROR : failed to create init args with error - {e}"
312 )]);
313 }
314 };
315
316 let mut canister_upgrade_errors = vec![];
317
318 for (canister_id, _canister) in self.sub_canisters.clone().iter() {
319 match retry_async(
320 async || {
321 stop_canister(&CanisterIdRecord {
322 canister_id: *canister_id,
323 })
324 .await
325 },
326 3,
327 )
328 .await
329 {
330 Ok(_) => {
331 self.sub_canisters.insert(
332 *canister_id,
333 Box::new(T::new(
334 *canister_id,
335 CanisterState::Stopped,
336 update_args.clone(),
337 )),
338 );
339 }
340 Err(e) => {
341 canister_upgrade_errors.push(format!(
342 "ERROR: storage upgrade :: storage with principal : {} failed to stop with error {:?}",
343 *canister_id, e
344 ));
345 continue;
346 }
347 }
348
349 let result = {
350 let init_args = init_args.clone();
351 let wasm_module = self.wasm.clone();
352
353 let install_args = InstallCodeArgs {
354 mode: CanisterInstallMode::Upgrade(None),
355 canister_id: *canister_id,
356 wasm_module,
357 arg: init_args,
358 };
359 retry_async(|| install_code(&install_args), 3).await
360 };
361
362 match result {
363 Ok(_) => {
364 match retry_async(
365 async || {
366 start_canister(&CanisterIdRecord {
367 canister_id: *canister_id,
368 })
369 .await
370 },
371 3,
372 )
373 .await
374 {
375 Ok(_) => {
376 self.sub_canisters.insert(
377 *canister_id,
378 Box::new(T::new(
379 *canister_id,
380 CanisterState::Installed,
381 update_args.clone(),
382 )),
383 );
384 }
385 Err(e) => {
386 canister_upgrade_errors.push(format!(
387 "ERROR: storage upgrade :: storage with principal : {} failed to start with error {:?}",
388 *canister_id, e
389 ));
390 }
391 }
392 }
393 Err(e) => {
394 canister_upgrade_errors.push(format!(
395 "ERROR: storage upgrade :: storage with principal : {} failed to install upgrade {:?}",
396 *canister_id, e
397 ));
398 }
399 }
400 }
401
402 if !canister_upgrade_errors.is_empty() {
403 Err(canister_upgrade_errors)
404 } else {
405 Ok(())
406 }
407 }
408
409 pub fn list_canisters(&self) -> Vec<Box<impl Canister>> {
410 self.sub_canisters.values().cloned().collect()
411 }
412
413 pub fn list_canisters_ids(&self) -> Vec<Principal> {
414 self.sub_canisters.clone().into_keys().collect()
415 }
416}
417
418impl<T> Clone for SubCanisterManager<T>
419where
420 T: Canister + Clone + Send,
421{
422 fn clone(&self) -> Self {
423 let mut fund_manager = FundManager::new();
424
425 add_canisters_to_fund_manager(
426 &mut fund_manager,
427 self.funding_config.clone(),
428 self.sub_canisters.clone().into_keys().collect(),
429 );
430
431 Self {
432 master_canister_id: self.master_canister_id,
433 sub_canisters: self.sub_canisters.clone(),
434 controllers: self.controllers.clone(),
435 authorized_principal: self.authorized_principal.clone(),
436 initial_cycles: self.initial_cycles,
437 reserved_cycles: self.reserved_cycles,
438 test_mode: self.test_mode,
439 commit_hash: self.commit_hash.clone(),
440 wasm: self.wasm.clone(),
441 fund_manager: fund_manager,
442 funding_config: self.funding_config.clone(),
443 }
444 }
445}
446
447pub fn add_canisters_to_fund_manager(
448 fund_manager: &mut FundManager,
449 funding_config: FundManagerOptions,
450 canister_id_lst: Vec<Principal>,
451) {
452 fund_manager.stop();
453
454 fund_manager.with_options(funding_config);
455
456 for canister_id in canister_id_lst {
457 fund_manager.register(
458 canister_id,
459 RegisterOpts::new()
460 .with_cycles_fetcher(Arc::new(FetchCyclesBalanceFromCanisterStatus::new())),
461 );
462 }
463
464 fund_manager.start();
465}