@@ -19,7 +19,6 @@ use sha2::Sha256;
1919use std:: time:: Duration ;
2020use ArchiveState :: { Created , CreationInProgress , NotCreated } ;
2121use CanisterInstallMode :: Upgrade ;
22- use DeployArchiveResult :: { Failed , Success } ;
2322
2423#[ derive( Clone , Debug , Default , CandidType , Deserialize , Eq , PartialEq ) ]
2524pub 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
202193async 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