ic_query/sns/report/model/reports/
upgrade.rs1use super::invocation::SnsCanisterMethod;
8use crate::hex::is_canonical_lowercase_hex;
9use crate::sns::report::SnsReportContext;
10use serde::{Deserialize as SerdeDeserialize, Serialize};
11
12#[derive(Clone, Debug, Eq, PartialEq, SerdeDeserialize, Serialize)]
19#[serde(deny_unknown_fields)]
20pub struct SnsVersion {
21 pub archive_wasm_hash_hex: String,
23 pub root_wasm_hash_hex: String,
25 pub swap_wasm_hash_hex: String,
27 pub ledger_wasm_hash_hex: String,
29 pub governance_wasm_hash_hex: String,
31 pub index_wasm_hash_hex: String,
33}
34
35impl SnsVersion {
36 pub(in crate::sns::report) fn validate_hashes(
37 &self,
38 field: &str,
39 expected_bytes: Option<usize>,
40 ) -> Result<(), String> {
41 for (role, hash) in [
42 ("archive", self.archive_wasm_hash_hex.as_str()),
43 ("root", self.root_wasm_hash_hex.as_str()),
44 ("swap", self.swap_wasm_hash_hex.as_str()),
45 ("ledger", self.ledger_wasm_hash_hex.as_str()),
46 ("governance", self.governance_wasm_hash_hex.as_str()),
47 ("index", self.index_wasm_hash_hex.as_str()),
48 ] {
49 if !is_canonical_lowercase_hex(hash)
50 || expected_bytes.is_some_and(|bytes| hash.len() / 2 != bytes)
51 {
52 let expectation = expected_bytes.map_or_else(
53 || "lowercase even-length hexadecimal text".to_string(),
54 |bytes| format!("a {bytes}-byte lowercase hexadecimal hash"),
55 );
56 return Err(format!("{field}.{role}_wasm_hash_hex is not {expectation}"));
57 }
58 }
59 Ok(())
60 }
61}
62
63#[derive(Clone, Debug, Eq, PartialEq, SerdeDeserialize, Serialize)]
70#[serde(deny_unknown_fields)]
71pub struct SnsPendingUpgrade {
72 pub mark_failed_at_seconds: u64,
74 pub checking_upgrade_lock: u64,
76 pub proposal_id: u64,
78 pub target_version: Option<SnsVersion>,
80}
81
82#[derive(Clone, Debug, Eq, PartialEq, SerdeDeserialize, Serialize)]
89#[serde(deny_unknown_fields)]
90pub struct SnsRunningVersionResponse {
91 pub deployed_version: Option<SnsVersion>,
93 pub pending_version: Option<SnsPendingUpgrade>,
95}
96
97#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
104pub struct SnsUpgradeQueryGap {
105 pub method: SnsCanisterMethod,
107 pub reason: String,
109}
110
111#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
118pub struct SnsUpgradeReport {
119 pub schema_version: u32,
121 #[serde(flatten)]
123 pub context: SnsReportContext,
124 pub running_version_method: SnsCanisterMethod,
126 pub next_version_method: SnsCanisterMethod,
128 pub point_in_time_guaranteed: bool,
130 pub component_query_count: usize,
132 pub successful_component_query_count: usize,
134 pub component_gap_count: usize,
136 pub deployed_version: SnsVersion,
138 pub pending_upgrade: Option<SnsPendingUpgrade>,
140 pub next_version: Option<SnsVersion>,
142 pub next_version_gap: Option<SnsUpgradeQueryGap>,
144}
145
146#[cfg(test)]
147mod tests {
148 use super::SnsVersion;
149
150 #[test]
151 fn version_hash_admission_preserves_upgrade_and_checkpoint_policies() {
152 for (hash, upgrade_valid, checkpoint_valid) in [
153 ("0a".repeat(32), true, true),
154 ("0a".to_string(), true, false),
155 ("0a".repeat(33), true, false),
156 (String::new(), false, false),
157 ("a".to_string(), false, false),
158 ("AB".repeat(32), false, false),
159 ("zz".repeat(32), false, false),
160 ] {
161 let version = SnsVersion {
162 archive_wasm_hash_hex: hash.clone(),
163 root_wasm_hash_hex: hash.clone(),
164 swap_wasm_hash_hex: hash.clone(),
165 ledger_wasm_hash_hex: hash.clone(),
166 governance_wasm_hash_hex: hash.clone(),
167 index_wasm_hash_hex: hash,
168 };
169 assert_eq!(
170 version.validate_hashes("version", None).is_ok(),
171 upgrade_valid
172 );
173 assert_eq!(
174 version.validate_hashes("version", Some(32)).is_ok(),
175 checkpoint_valid
176 );
177 }
178 }
179}