ic_query/sns/report/model/reports/
upgrade.rs1use super::invocation::SnsCanisterMethod;
8use crate::hex::is_canonical_lowercase_hex;
9use serde::{Deserialize as SerdeDeserialize, Serialize};
10
11#[derive(Clone, Debug, Eq, PartialEq, SerdeDeserialize, Serialize)]
18#[serde(deny_unknown_fields)]
19pub struct SnsVersion {
20 pub archive_wasm_hash_hex: String,
22 pub root_wasm_hash_hex: String,
24 pub swap_wasm_hash_hex: String,
26 pub ledger_wasm_hash_hex: String,
28 pub governance_wasm_hash_hex: String,
30 pub index_wasm_hash_hex: String,
32}
33
34impl SnsVersion {
35 pub(in crate::sns::report) fn validate_hashes(
36 &self,
37 field: &str,
38 expected_bytes: Option<usize>,
39 ) -> Result<(), String> {
40 for (role, hash) in [
41 ("archive", self.archive_wasm_hash_hex.as_str()),
42 ("root", self.root_wasm_hash_hex.as_str()),
43 ("swap", self.swap_wasm_hash_hex.as_str()),
44 ("ledger", self.ledger_wasm_hash_hex.as_str()),
45 ("governance", self.governance_wasm_hash_hex.as_str()),
46 ("index", self.index_wasm_hash_hex.as_str()),
47 ] {
48 if !is_canonical_lowercase_hex(hash)
49 || expected_bytes.is_some_and(|bytes| hash.len() / 2 != bytes)
50 {
51 let expectation = expected_bytes.map_or_else(
52 || "lowercase even-length hexadecimal text".to_string(),
53 |bytes| format!("a {bytes}-byte lowercase hexadecimal hash"),
54 );
55 return Err(format!("{field}.{role}_wasm_hash_hex is not {expectation}"));
56 }
57 }
58 Ok(())
59 }
60}
61
62#[derive(Clone, Debug, Eq, PartialEq, SerdeDeserialize, Serialize)]
69#[serde(deny_unknown_fields)]
70pub struct SnsPendingUpgrade {
71 pub mark_failed_at_seconds: u64,
73 pub checking_upgrade_lock: u64,
75 pub proposal_id: u64,
77 pub target_version: Option<SnsVersion>,
79}
80
81#[derive(Clone, Debug, Eq, PartialEq, SerdeDeserialize, Serialize)]
88#[serde(deny_unknown_fields)]
89pub struct SnsRunningVersionResponse {
90 pub deployed_version: Option<SnsVersion>,
92 pub pending_version: Option<SnsPendingUpgrade>,
94}
95
96#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
103pub struct SnsUpgradeQueryGap {
104 pub method: SnsCanisterMethod,
106 pub reason: String,
108}
109
110#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
117pub struct SnsUpgradeReport {
118 pub schema_version: u32,
120 pub network: String,
122 pub sns_wasm_canister_id: String,
124 pub fetched_at: String,
126 pub source_endpoint: String,
128 pub fetched_by: String,
130 pub id: usize,
132 pub name: String,
134 pub root_canister_id: String,
136 pub governance_canister_id: String,
138 pub running_version_method: SnsCanisterMethod,
140 pub next_version_method: SnsCanisterMethod,
142 pub point_in_time_guaranteed: bool,
144 pub component_query_count: usize,
146 pub successful_component_query_count: usize,
148 pub component_gap_count: usize,
150 pub deployed_version: SnsVersion,
152 pub pending_upgrade: Option<SnsPendingUpgrade>,
154 pub next_version: Option<SnsVersion>,
156 pub next_version_gap: Option<SnsUpgradeQueryGap>,
158}
159
160#[cfg(test)]
161mod tests {
162 use super::SnsVersion;
163
164 #[test]
165 fn version_hash_admission_preserves_upgrade_and_checkpoint_policies() {
166 for (hash, upgrade_valid, checkpoint_valid) in [
167 ("0a".repeat(32), true, true),
168 ("0a".to_string(), true, false),
169 ("0a".repeat(33), true, false),
170 (String::new(), false, false),
171 ("a".to_string(), false, false),
172 ("AB".repeat(32), false, false),
173 ("zz".repeat(32), false, false),
174 ] {
175 let version = SnsVersion {
176 archive_wasm_hash_hex: hash.clone(),
177 root_wasm_hash_hex: hash.clone(),
178 swap_wasm_hash_hex: hash.clone(),
179 ledger_wasm_hash_hex: hash.clone(),
180 governance_wasm_hash_hex: hash.clone(),
181 index_wasm_hash_hex: hash,
182 };
183 assert_eq!(
184 version.validate_hashes("version", None).is_ok(),
185 upgrade_valid
186 );
187 assert_eq!(
188 version.validate_hashes("version", Some(32)).is_ok(),
189 checkpoint_valid
190 );
191 }
192 }
193}