Skip to content

Commit 956669b

Browse files
author
Frederik Rothenberger
authored
Refactor archive deployment code (#1017)
This PR introduces a new type to express the verification status of the wasm. Additionally, it makes the deploy_archive more clear by splitting the big match statement into smaller pieces.
1 parent e6007cc commit 956669b

File tree

1 file changed

+90
-86
lines changed

1 file changed

+90
-86
lines changed

src/internet_identity/src/archive.rs

Lines changed: 90 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ use sha2::Sha256;
1919
use std::time::Duration;
2020
use ArchiveState::{Created, CreationInProgress, NotCreated};
2121
use CanisterInstallMode::Upgrade;
22-
use DeployArchiveResult::{Failed, Success};
2322

2423
#[derive(Clone, Debug, Default, CandidType, Deserialize, Eq, PartialEq)]
2524
pub struct ArchiveInfo {
@@ -60,94 +59,86 @@ pub struct ArchiveStatusCache {
6059
pub status: CanisterStatusResponse,
6160
}
6261

63-
/// Wrapper for wasm to carry verification state.
64-
struct VerifiableWasm {
65-
wasm: Vec<u8>,
66-
verified: bool,
67-
}
62+
struct VerifiedWasm(Vec<u8>);
6863

69-
impl VerifiableWasm {
70-
pub fn from_unverified_wasm(wasm: ByteBuf) -> Self {
71-
VerifiableWasm {
72-
wasm: wasm.into_vec(),
73-
verified: false,
74-
}
64+
pub async fn deploy_archive(wasm: ByteBuf) -> DeployArchiveResult {
65+
// archive state without creation_in_progress
66+
// if creation is in progress we exit early since we do not want to deploy the archive twice
67+
enum ReducedArchiveState {
68+
NotCreated,
69+
Created(ArchiveData),
7570
}
7671

77-
pub fn verify_wasm_hash(&mut self) -> Result<(), DeployArchiveResult> {
78-
let expected_hash =
79-
state::expected_archive_hash().expect("bug: no wasm hash to check against");
72+
let Some(expected_hash) = state::expected_archive_hash() else {
73+
return DeployArchiveResult::Failed("archive deployment disabled".to_string());
74+
};
8075

81-
let mut hasher = Sha256::new();
82-
hasher.update(&self.wasm);
83-
let wasm_hash: [u8; 32] = hasher.finalize().into();
76+
unlock_archive_if_stuck();
77+
// exit early if archive is being created by another call to deploy_archive
78+
let reduced_state = match state::archive_state() {
79+
NotCreated => ReducedArchiveState::NotCreated,
80+
CreationInProgress(_) => return DeployArchiveResult::CreationInProgress,
81+
Created(data) => ReducedArchiveState::Created(data),
82+
};
8483

85-
if wasm_hash != expected_hash {
86-
return Err(Failed("invalid wasm module".to_string()));
84+
// exit early if the expected wasm module is already installed
85+
if let ReducedArchiveState::Created(ref data) = reduced_state {
86+
let status = archive_status(data.archive_canister).await;
87+
match status.module_hash {
88+
Some(hash) if hash == expected_hash.to_vec() => {
89+
// we already have an archive with the expected module and don't need to do anything
90+
return DeployArchiveResult::Success(data.archive_canister);
91+
}
92+
None | Some(_) => {}
8793
}
88-
89-
self.verified = true;
90-
Ok(())
91-
}
92-
93-
pub fn get_verified_wasm(mut self) -> Result<Vec<u8>, DeployArchiveResult> {
94-
if !self.verified {
95-
self.verify_wasm_hash()?;
96-
};
97-
Ok(self.wasm)
9894
}
99-
}
10095

101-
pub async fn deploy_archive(wasm: ByteBuf) -> DeployArchiveResult {
102-
let mut wasm = VerifiableWasm::from_unverified_wasm(wasm);
103-
let expected_hash = if let Some(hash) = state::expected_archive_hash() {
104-
hash
105-
} else {
106-
return Failed("archive deployment disabled".to_string());
96+
// all early checks passed, we need to make changes to the archive --> verify wasm
97+
let verified_wasm = match verify_wasm(wasm.into_vec()) {
98+
Ok(verified_wasm) => verified_wasm,
99+
Err(err) => return DeployArchiveResult::Failed(err),
107100
};
108101

109-
let (archive_canister, install_mode) = match state::archive_state() {
110-
NotCreated => match create_archive(&mut wasm).await {
111-
Ok(archive) => (archive, Install),
112-
Err(result) => return result,
113-
},
114-
CreationInProgress(timestamp) => {
115-
if time() - timestamp > Duration::from_secs(24 * 60 * 60).as_nanos() as u64 {
116-
// The archive has been in creation for more than a day and the creation process
117-
// has likely failed thus another attempt should be made.
118-
match create_archive(&mut wasm).await {
119-
Ok(archive) => (archive, Install),
120-
Err(result) => return result,
121-
}
122-
} else {
123-
return DeployArchiveResult::CreationInProgress;
124-
}
125-
}
126-
Created(archive_data) => {
127-
let status = match archive_status(archive_data.archive_canister).await {
128-
Ok(status) => status,
129-
Err(message) => return Failed(message),
102+
// create if not exists and determine install mode
103+
let (archive_canister, install_mode) = match reduced_state {
104+
ReducedArchiveState::NotCreated => {
105+
let archive = match create_archive().await {
106+
Ok(archive) => archive,
107+
Err(err) => return DeployArchiveResult::Failed(err),
130108
};
109+
(archive, Install)
110+
}
111+
ReducedArchiveState::Created(data) => {
112+
let status = archive_status(data.archive_canister).await;
131113
match status.module_hash {
132-
None => (archive_data.archive_canister, Install),
133-
Some(hash) if hash == expected_hash.to_vec() => {
134-
// we already have an archive with the expected module and don't need to do anything
135-
return Success(archive_data.archive_canister);
136-
}
137-
Some(_) => (archive_data.archive_canister, Upgrade),
114+
None => (data.archive_canister, Install),
115+
Some(_) => (data.archive_canister, Upgrade),
138116
}
139117
}
140118
};
141119

142-
match install_archive(archive_canister, wasm, install_mode).await {
143-
Ok(()) => Success(archive_canister),
144-
Err(err) => err,
120+
match install_archive(archive_canister, verified_wasm, install_mode).await {
121+
Ok(()) => DeployArchiveResult::Success(archive_canister),
122+
Err(err) => DeployArchiveResult::Failed(err),
145123
}
146124
}
147125

148-
async fn create_archive(wasm: &mut VerifiableWasm) -> Result<Principal, DeployArchiveResult> {
149-
wasm.verify_wasm_hash()?;
126+
fn unlock_archive_if_stuck() {
127+
match state::archive_state() {
128+
NotCreated | Created(_) => {}
129+
CreationInProgress(timestamp) => {
130+
// The archive has been in creation for more than a day and the creation process
131+
// has likely failed thus another attempt should be made.
132+
if time() - timestamp > Duration::from_secs(24 * 60 * 60).as_nanos() as u64 {
133+
state::persistent_state_mut(|persistent_state| {
134+
persistent_state.archive_info.state = NotCreated
135+
})
136+
}
137+
}
138+
}
139+
}
150140

141+
async fn create_archive() -> Result<Principal, String> {
151142
// lock the archive
152143
state::persistent_state_mut(|persistent_state| {
153144
persistent_state.archive_info.state = CreationInProgress(time());
@@ -171,10 +162,10 @@ async fn create_archive(wasm: &mut VerifiableWasm) -> Result<Principal, DeployAr
171162
state::persistent_state_mut(|persistent_state| {
172163
persistent_state.archive_info.state = NotCreated
173164
});
174-
Err(Failed(format!(
165+
Err(format!(
175166
"failed to create archive! error code: {:?}, message: {}",
176167
reject_code, message
177-
)))
168+
))
178169
}
179170
}
180171
}
@@ -201,52 +192,65 @@ async fn create_canister(arg: CreateCanisterArgument) -> CallResult<(CanisterIdR
201192

202193
async fn install_archive(
203194
archive_canister: Principal,
204-
wasm: VerifiableWasm,
195+
wasm: VerifiedWasm,
205196
install_mode: CanisterInstallMode,
206-
) -> Result<(), DeployArchiveResult> {
197+
) -> Result<(), String> {
207198
let settings = ArchiveInit {
208199
ii_canister: id(),
209200
max_entries_per_call: 1000,
210201
};
211-
let encoded_arg = candid::encode_one(settings).map_err(|err| {
212-
Failed(format!(
213-
"failed to encode archive install argument: {:?}",
214-
err
215-
))
216-
})?;
202+
let encoded_arg = candid::encode_one(settings)
203+
.map_err(|err| format!("failed to encode archive install argument: {:?}", err))?;
217204

218205
install_code(InstallCodeArgument {
219206
mode: install_mode,
220207
canister_id: archive_canister,
221-
wasm_module: wasm.get_verified_wasm()?,
208+
wasm_module: wasm.0,
222209
arg: encoded_arg,
223210
})
224211
.await
225212
.map_err(|(code, message)| {
226-
Failed(format!(
213+
format!(
227214
"failed to install archive canister! error code: {:?}, message: {}",
228215
code, message
229-
))
216+
)
230217
})
231218
}
232219

233-
async fn archive_status(archive_canister: Principal) -> Result<CanisterStatusResponse, String> {
220+
fn verify_wasm(wasm: Vec<u8>) -> Result<VerifiedWasm, String> {
221+
let expected_hash = match state::expected_archive_hash() {
222+
None => return Err("archive deployment disabled".to_string()),
223+
Some(hash) => hash,
224+
};
225+
226+
let mut hasher = Sha256::new();
227+
hasher.update(&wasm);
228+
let actual_hash: [u8; 32] = hasher.finalize().into();
229+
230+
if actual_hash != expected_hash {
231+
return Err("invalid wasm module".to_string());
232+
}
233+
234+
Ok(VerifiedWasm(wasm))
235+
}
236+
237+
async fn archive_status(archive_canister: Principal) -> CanisterStatusResponse {
234238
let status_opt = state::cached_archive_status();
235239
match status_opt {
236240
None => {
237241
let (archive_status,) = canister_status(CanisterIdRecord {
238242
canister_id: archive_canister,
239243
})
240244
.await
241-
.map_err(|err| format!("failed to retrieve archive status: {:?}", err))?;
245+
.expect(&format!("failed to retrieve archive status"));
242246

243247
state::cache_archive_status(ArchiveStatusCache {
244248
timestamp: time(),
245249
status: archive_status.clone(),
246250
});
247-
Ok(archive_status)
251+
archive_status
248252
}
249-
Some(status) => Ok(status),
253+
Some(status) => status,
250254
}
251255
}
252256

0 commit comments

Comments
 (0)