Skip to content

Commit

Permalink
feat(upgrader): disaster recovery flow reuses the same upgrader canis…
Browse files Browse the repository at this point in the history
…ter (#293)

Enables the disaster recovery flow to reuse the same upgrader canister
in case the mode of recovery would be set to `install` or `reinstall`.
  • Loading branch information
keplervital authored Jul 4, 2024
1 parent 9497822 commit a2fcdb5
Show file tree
Hide file tree
Showing 10 changed files with 237 additions and 37 deletions.
12 changes: 10 additions & 2 deletions apps/wallet/src/generated/station/station.did
Original file line number Diff line number Diff line change
Expand Up @@ -1986,6 +1986,14 @@ type AdminInitInput = record {
identity : principal;
};

// An input type for configuring the upgrader canister.
type SystemUpgraderInput = variant {
// An existing upgrader canister.
Id : principal;
// Creates and deploys a new canister.
WasmModule : blob;
};

// The init configuration for the canister.
//
// Only used when installing the canister for the first time.
Expand All @@ -1996,8 +2004,8 @@ type SystemInit = record {
admins : vec AdminInitInput;
// Quorum of admins for initial policies.
quorum : opt nat16;
// The wasm module of the station upgrader canister.
upgrader_wasm_module : blob;
// The upgrader configuration.
upgrader : SystemUpgraderInput;
// An optional additional controller of the station and upgrader canisters.
fallback_controller : opt principal;
};
Expand Down
4 changes: 3 additions & 1 deletion apps/wallet/src/generated/station/station.did.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -865,8 +865,8 @@ export type SystemInfoResult = { 'Ok' : { 'system' : SystemInfo } } |
export interface SystemInit {
'name' : string,
'fallback_controller' : [] | [Principal],
'upgrader' : SystemUpgraderInput,
'admins' : Array<AdminInitInput>,
'upgrader_wasm_module' : Uint8Array | number[],
'quorum' : [] | [number],
}
export type SystemInstall = { 'Upgrade' : SystemUpgrade } |
Expand All @@ -875,6 +875,8 @@ export type SystemResourceAction = { 'ManageSystemInfo' : null } |
{ 'SystemInfo' : null } |
{ 'Capabilities' : null };
export interface SystemUpgrade { 'name' : [] | [string] }
export type SystemUpgraderInput = { 'Id' : Principal } |
{ 'WasmModule' : Uint8Array | number[] };
export type TimestampRFC3339 = string;
export interface Transfer {
'id' : UUID,
Expand Down
12 changes: 10 additions & 2 deletions apps/wallet/src/generated/station/station.did.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@ export const idlFactory = ({ IDL }) => {
const RequestPolicyRule = IDL.Rec();
const RequestPolicyRuleResult = IDL.Rec();
const SystemUpgrade = IDL.Record({ 'name' : IDL.Opt(IDL.Text) });
const SystemUpgraderInput = IDL.Variant({
'Id' : IDL.Principal,
'WasmModule' : IDL.Vec(IDL.Nat8),
});
const AdminInitInput = IDL.Record({
'name' : IDL.Text,
'identity' : IDL.Principal,
});
const SystemInit = IDL.Record({
'name' : IDL.Text,
'fallback_controller' : IDL.Opt(IDL.Principal),
'upgrader' : SystemUpgraderInput,
'admins' : IDL.Vec(AdminInitInput),
'upgrader_wasm_module' : IDL.Vec(IDL.Nat8),
'quorum' : IDL.Opt(IDL.Nat16),
});
const SystemInstall = IDL.Variant({
Expand Down Expand Up @@ -1183,15 +1187,19 @@ export const idlFactory = ({ IDL }) => {
};
export const init = ({ IDL }) => {
const SystemUpgrade = IDL.Record({ 'name' : IDL.Opt(IDL.Text) });
const SystemUpgraderInput = IDL.Variant({
'Id' : IDL.Principal,
'WasmModule' : IDL.Vec(IDL.Nat8),
});
const AdminInitInput = IDL.Record({
'name' : IDL.Text,
'identity' : IDL.Principal,
});
const SystemInit = IDL.Record({
'name' : IDL.Text,
'fallback_controller' : IDL.Opt(IDL.Principal),
'upgrader' : SystemUpgraderInput,
'admins' : IDL.Vec(AdminInitInput),
'upgrader_wasm_module' : IDL.Vec(IDL.Nat8),
'quorum' : IDL.Opt(IDL.Nat16),
});
const SystemInstall = IDL.Variant({
Expand Down
2 changes: 1 addition & 1 deletion core/control-panel/impl/src/services/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ impl DeployService {
arg: Encode!(&station_api::SystemInstall::Init(station_api::SystemInit {
name: input.name.clone(),
admins,
upgrader_wasm_module,
upgrader: station_api::SystemUpgraderInput::WasmModule(upgrader_wasm_module),
quorum: Some(1),
fallback_controller: Some(NNS_ROOT_CANISTER_ID),
}))
Expand Down
12 changes: 10 additions & 2 deletions core/station/api/spec.did
Original file line number Diff line number Diff line change
Expand Up @@ -1986,6 +1986,14 @@ type AdminInitInput = record {
identity : principal;
};

// An input type for configuring the upgrader canister.
type SystemUpgraderInput = variant {
// An existing upgrader canister.
Id : principal;
// Creates and deploys a new canister.
WasmModule : blob;
};

// The init configuration for the canister.
//
// Only used when installing the canister for the first time.
Expand All @@ -1996,8 +2004,8 @@ type SystemInit = record {
admins : vec AdminInitInput;
// Quorum of admins for initial policies.
quorum : opt nat16;
// The wasm module of the station upgrader canister.
upgrader_wasm_module : blob;
// The upgrader configuration.
upgrader : SystemUpgraderInput;
// An optional additional controller of the station and upgrader canisters.
fallback_controller : opt principal;
};
Expand Down
11 changes: 8 additions & 3 deletions core/station/api/src/system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ pub struct AdminInitInput {
pub identity: Principal,
}

#[derive(CandidType, serde::Serialize, Deserialize, Clone, Debug)]
pub enum SystemUpgraderInput {
Id(Principal),
WasmModule(#[serde(with = "serde_bytes")] Vec<u8>),
}

#[derive(CandidType, serde::Serialize, Deserialize, Clone, Debug)]
pub struct SystemInit {
/// The station name.
Expand All @@ -40,9 +46,8 @@ pub struct SystemInit {
pub admins: Vec<AdminInitInput>,
/// The quorum of admin approvals required in initial policies.
pub quorum: Option<u16>,
/// The upgrader canister wasm module.
#[serde(with = "serde_bytes")]
pub upgrader_wasm_module: Vec<u8>,
/// The upgrader configuration.
pub upgrader: SystemUpgraderInput,
pub fallback_controller: Option<Principal>,
}

Expand Down
38 changes: 30 additions & 8 deletions core/station/impl/src/services/system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,17 +153,15 @@ impl SystemService {
print("Adding initial canister configurations");
install_canister_handlers::init_post_process(&init).await?;

print("Deploying upgrader canister");
print("Init upgrader canister");
let canister_id = self_canister_id();
let mut upgrader_controllers = vec![canister_id];
if let Some(fallback_controller) = init.fallback_controller {
upgrader_controllers.push(fallback_controller);
}
let upgrader_canister_id = install_canister_handlers::deploy_upgrader(
init.upgrader_wasm_module,
upgrader_controllers,
)
.await?;
let upgrader_canister_id =
install_canister_handlers::init_upgrader(init.upgrader, upgrader_controllers)
.await?;
system_info.set_upgrader_canister_id(upgrader_canister_id);

// sets the upgrader as a controller of the station canister
Expand Down Expand Up @@ -413,8 +411,32 @@ mod install_canister_handlers {
Ok(())
}

pub async fn init_upgrader(
input: station_api::SystemUpgraderInput,
controllers: Vec<Principal>,
) -> Result<Principal, String> {
match input {
station_api::SystemUpgraderInput::Id(upgrader_id) => {
mgmt::update_settings(mgmt::UpdateSettingsArgument {
canister_id: upgrader_id,
settings: mgmt::CanisterSettings {
controllers: Some(controllers),
..Default::default()
},
})
.await
.map_err(|e| format!("Failed to set upgrader controller: {:?}", e))?;

Ok(upgrader_id)
}
station_api::SystemUpgraderInput::WasmModule(upgrader_wasm_module) => {
deploy_upgrader(upgrader_wasm_module, controllers).await
}
}
}

/// Deploys the station upgrader canister and sets the station as the controller of the upgrader.
pub async fn deploy_upgrader(
async fn deploy_upgrader(
upgrader_wasm_module: Vec<u8>,
controllers: Vec<Principal>,
) -> Result<Principal, String> {
Expand Down Expand Up @@ -505,7 +527,7 @@ mod tests {
identity: Principal::from_slice(&[1; 29]),
}],
quorum: Some(1),
upgrader_wasm_module: vec![],
upgrader: station_api::SystemUpgraderInput::WasmModule(vec![]),
fallback_controller: None,
})
.await;
Expand Down
48 changes: 44 additions & 4 deletions core/upgrader/impl/src/services/install_canister.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
use std::sync::Arc;

use crate::model::InstallMode;
use async_trait::async_trait;
use candid::Principal;
use ic_cdk::api::management_canister::main::{self as mgmt, CanisterIdRecord, InstallCodeArgument};
use lazy_static::lazy_static;

use crate::model::InstallMode;
use std::{collections::BTreeSet, sync::Arc};

lazy_static! {
pub static ref INSTALL_CANISTER: Arc<StationDisasterRecoveryInstall> =
Expand Down Expand Up @@ -39,6 +37,48 @@ impl InstallCanister for StationDisasterRecoveryInstall {
arg: Vec<u8>,
mode: InstallMode,
) -> Result<(), String> {
// For install and reinstall, we need to make the station self controlled.
match mode {
InstallMode::Install | InstallMode::Reinstall => {
// Get the current controllers and add the station to it. We preserve the existing controllers
// during this step to avoid losing control in case of a failed install.
let (info,) = mgmt::canister_info(mgmt::CanisterInfoRequest {
canister_id,
num_requested_changes: None,
})
.await
.map_err(|(code, err)| {
format!(
"failed to get canister info for canister: \"{}\", rejection code: {}",
err, code as i32
)
})?;

let mut controllers = BTreeSet::new();
controllers.extend(info.controllers.iter().cloned());
controllers.insert(canister_id);
controllers.insert(ic_cdk::id());

mgmt::update_settings(mgmt::UpdateSettingsArgument {
canister_id,
settings: mgmt::CanisterSettings {
controllers: Some(controllers.into_iter().collect()),
..Default::default()
},
})
.await
.map_err(|(code, err)| {
format!(
"failed to update settings for canister: \"{}\", rejection code: {}",
err, code as i32
)
})?;
}
InstallMode::Upgrade => {
// For upgrade, there are no controller changes needed.
}
}

mgmt::install_code(InstallCodeArgument {
mode: mode.into(),
canister_id,
Expand Down
Loading

0 comments on commit a2fcdb5

Please sign in to comment.