canic_core/ops/storage/
pool.rs

1use crate::{
2    Error,
3    cdk::utils::time::now_secs,
4    dto::pool::{CanisterPoolEntryView, CanisterPoolView},
5    model::memory::pool::{
6        CanisterPool, CanisterPoolData, CanisterPoolEntry, CanisterPoolState, CanisterPoolStatus,
7    },
8    ops::{
9        adapter::pool::{canister_pool_entry_to_view, canister_pool_to_view},
10        config::ConfigOps,
11        prelude::*,
12    },
13};
14
15///
16/// PoolOps
17/// Stable storage wrapper for the canister pool registry.
18///
19/// This module contains *storage* operations:
20/// - register entries
21/// - mutate existing entry state (without reordering)
22/// - export and basic lookups
23///
24
25pub struct PoolOps;
26
27impl PoolOps {
28    //
29    // ---- Registration ----
30    //
31
32    pub fn register_ready(
33        pid: Principal,
34        cycles: Cycles,
35        role: Option<CanisterRole>,
36        parent: Option<Principal>,
37        module_hash: Option<Vec<u8>>,
38    ) {
39        let created_at = now_secs();
40        CanisterPool::register(
41            pid,
42            cycles,
43            CanisterPoolStatus::Ready,
44            role,
45            parent,
46            module_hash,
47            created_at,
48        );
49    }
50
51    //
52    // ---- State transitions ----
53    //
54
55    pub fn mark_pending_reset(pid: Principal) {
56        Self::register_or_update_state(
57            pid,
58            Cycles::default(),
59            CanisterPoolStatus::PendingReset,
60            None,
61        );
62    }
63
64    pub fn mark_ready(pid: Principal, cycles: Cycles) {
65        Self::register_or_update_state(pid, cycles, CanisterPoolStatus::Ready, None);
66    }
67
68    pub fn mark_failed(pid: Principal, err: &Error) {
69        let status = CanisterPoolStatus::Failed {
70            reason: err.to_string(),
71        };
72        Self::register_or_update_state(pid, Cycles::default(), status, None);
73    }
74
75    //
76    // ---- Views ----
77    //
78
79    #[must_use]
80    pub fn get_view(pid: Principal) -> Option<CanisterPoolEntryView> {
81        CanisterPool::get(pid).map(|entry| canister_pool_entry_to_view(&entry.header, &entry.state))
82    }
83
84    // ------- Export ------------------------
85
86    #[must_use]
87    pub fn export() -> CanisterPoolData {
88        CanisterPool::export()
89    }
90
91    #[must_use]
92    pub fn export_view() -> CanisterPoolView {
93        let data = CanisterPool::export();
94        canister_pool_to_view(data)
95    }
96
97    //
98    // ---- Mechanical storage access ----
99    //
100
101    #[must_use]
102    pub(crate) fn pop_ready() -> Option<(Principal, CanisterPoolEntry)> {
103        CanisterPool::pop_ready()
104    }
105
106    #[must_use]
107    pub fn contains(pid: &Principal) -> bool {
108        CanisterPool::contains(pid)
109    }
110
111    #[must_use]
112    pub(crate) fn take(pid: &Principal) -> Option<CanisterPoolEntry> {
113        CanisterPool::take(pid)
114    }
115
116    #[must_use]
117    pub fn len() -> u64 {
118        CanisterPool::len()
119    }
120
121    //
122    // ---- Internal helper ----
123    //
124
125    fn register_or_update_state(
126        pid: Principal,
127        cycles: Cycles,
128        status: CanisterPoolStatus,
129        role: Option<CanisterRole>,
130    ) {
131        // Try update first: preserves header/created_at by construction.
132        let updated = CanisterPool::update_state_with(pid, |mut state: CanisterPoolState| {
133            state.cycles = cycles.clone();
134            state.status = status.clone();
135
136            // Preserve existing role unless caller supplies a replacement.
137            if role.is_some() {
138                state.role.clone_from(&role);
139            }
140
141            state
142        });
143
144        if !updated {
145            // For new entries, we don’t know parent/module_hash here (same as before).
146            let created_at = now_secs();
147            CanisterPool::register(pid, cycles, status, role, None, None, created_at);
148        }
149    }
150}
151
152/// Return the controller set for pool canisters.
153///
154/// Mechanical helper used by workflow when creating or resetting
155/// pool canisters.
156///
157/// Guarantees:
158/// - Includes all configured controllers from `Config`
159/// - Always includes the root canister as a controller
160/// - Deduplicates the root if already present
161///
162/// This function:
163/// - Does NOT perform authorization checks
164/// - Does NOT mutate state
165/// - Does NOT make IC calls
166///
167/// Policy decisions about *who* should control pool canisters
168/// are assumed to be encoded in configuration.
169pub fn pool_controllers() -> Result<Vec<Principal>, Error> {
170    let mut controllers = ConfigOps::controllers()?;
171
172    let root = canister_self();
173    if !controllers.contains(&root) {
174        controllers.push(root);
175    }
176
177    Ok(controllers)
178}