Skip to content

Commit

Permalink
feat!: canister state snapshots (#504)
Browse files Browse the repository at this point in the history
* snapshot types

* snapshot methods

* types cont.

* update pocket-ic which support nonmainnet features

* e2e test

* add LoadSnapshot variant to CanisterChangeDetails

* changelog

* fix canister_info e2e test

* show canister_info after snapshot operations

* ci test show output

* revert canister_info.rs

* revert ci.yml

* canister_info changes can have load_snapshot records
  • Loading branch information
lwshang authored Aug 8, 2024
1 parent 5979571 commit a8a7d4e
Show file tree
Hide file tree
Showing 8 changed files with 295 additions and 36 deletions.
82 changes: 58 additions & 24 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion e2e-tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,4 @@ path = "canisters/chunk.rs"

[dev-dependencies]
hex.workspace = true
pocket-ic = "3"
pocket-ic = "4"
68 changes: 68 additions & 0 deletions e2e-tests/canisters/management_caller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,72 @@ mod provisional {
}
}

mod snapshot {
use super::*;
use ic_cdk::api::management_canister::main::*;

#[update]
async fn execute_snapshot_methods() {
let arg = CreateCanisterArgument::default();
let canister_id = create_canister(arg, 2_000_000_000_000u128)
.await
.unwrap()
.0
.canister_id;

// Cannot take a snapshot of a canister that is empty.
// So we install a minimal wasm module.
let arg = InstallCodeArgument {
mode: CanisterInstallMode::Install,
canister_id,
// A minimal valid wasm module
// wat2wasm "(module)"
wasm_module: b"\x00asm\x01\x00\x00\x00".to_vec(),
arg: vec![],
};
install_code(arg).await.unwrap();

let arg = TakeCanisterSnapshotArgs {
canister_id,
replace_snapshot: None,
};
let snapshot = take_canister_snapshot(arg).await.unwrap().0;

let arg = LoadCanisterSnapshotArgs {
canister_id,
snapshot_id: snapshot.id.clone(),
sender_canister_version: None,
};
assert!(load_canister_snapshot(arg).await.is_ok());

let canister_id_record = CanisterIdRecord { canister_id };
let snapshots = list_canister_snapshots(canister_id_record).await.unwrap().0;
assert_eq!(snapshots.len(), 1);
assert_eq!(snapshots[0].id, snapshot.id);

let arg = DeleteCanisterSnapshotArgs {
canister_id,
snapshot_id: snapshot.id.clone(),
};
assert!(delete_canister_snapshot(arg).await.is_ok());

let arg = CanisterInfoRequest {
canister_id,
num_requested_changes: Some(1),
};
let canister_info_response = canister_info(arg).await.unwrap().0;
assert_eq!(canister_info_response.total_num_changes, 3);
assert_eq!(canister_info_response.recent_changes.len(), 1);
if let CanisterChange {
details: CanisterChangeDetails::LoadSnapshot(load_snapshot_record),
..
} = &canister_info_response.recent_changes[0]
{
assert_eq!(load_snapshot_record.snapshot_id, snapshot.id);
} else {
panic!("Expected the most recent change to be LoadSnapshot");
}
}
}

fn main() {}
44 changes: 34 additions & 10 deletions e2e-tests/tests/e2e.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::time::Duration;
use std::time::SystemTime;
use std::time::UNIX_EPOCH;

use candid::utils::ArgumentDecoder;
use candid::utils::ArgumentEncoder;
Expand All @@ -13,6 +14,7 @@ use ic_cdk::api::management_canister::main::{
};
use ic_cdk_e2e_tests::cargo_build_canister;
use pocket_ic::common::rest::RawEffectivePrincipal;
use pocket_ic::PocketIcBuilder;
use pocket_ic::{call_candid_as, query_candid, CallError, ErrorCode, PocketIc, WasmResult};

use serde_bytes::ByteBuf;
Expand Down Expand Up @@ -334,7 +336,15 @@ fn test_set_global_timers() {
fn test_canister_info() {
let pic = PocketIc::new();
let wasm = cargo_build_canister("canister_info");
pic.set_time(SystemTime::UNIX_EPOCH);
// As of PocketIC server v5.0.0 and client v4.0.0, the first canister creation happens at (time0+4).
// Each operation advances the Pic by 2 nanos, except for the last operation which advances only by 1 nano.
let time0: u64 = pic
.get_time()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos()
.try_into()
.unwrap();
let canister_id = pic.create_canister();
pic.add_cycles(canister_id, INIT_CYCLES);
pic.install_canister(canister_id, wasm, vec![], None);
Expand Down Expand Up @@ -377,7 +387,7 @@ fn test_canister_info() {
total_num_changes: 9,
recent_changes: vec![
CanisterChange {
timestamp_nanos: 4,
timestamp_nanos: time0 + 4,
canister_version: 0,
origin: CanisterChangeOrigin::FromCanister(FromCanisterRecord {
canister_id,
Expand All @@ -388,7 +398,7 @@ fn test_canister_info() {
}),
},
CanisterChange {
timestamp_nanos: 6,
timestamp_nanos: time0 + 6,
canister_version: 1,
origin: CanisterChangeOrigin::FromCanister(FromCanisterRecord {
canister_id,
Expand All @@ -403,7 +413,7 @@ fn test_canister_info() {
}),
},
CanisterChange {
timestamp_nanos: 8,
timestamp_nanos: time0 + 8,
canister_version: 2,
origin: CanisterChangeOrigin::FromCanister(FromCanisterRecord {
canister_id,
Expand All @@ -412,7 +422,7 @@ fn test_canister_info() {
details: CanisterChangeDetails::CodeUninstall,
},
CanisterChange {
timestamp_nanos: 10,
timestamp_nanos: time0 + 10,
canister_version: 3,
origin: CanisterChangeOrigin::FromCanister(FromCanisterRecord {
canister_id,
Expand All @@ -427,7 +437,7 @@ fn test_canister_info() {
}),
},
CanisterChange {
timestamp_nanos: 12,
timestamp_nanos: time0 + 12,
canister_version: 4,
origin: CanisterChangeOrigin::FromCanister(FromCanisterRecord {
canister_id,
Expand All @@ -442,7 +452,7 @@ fn test_canister_info() {
}),
},
CanisterChange {
timestamp_nanos: 14,
timestamp_nanos: time0 + 14,
canister_version: 5,
origin: CanisterChangeOrigin::FromCanister(FromCanisterRecord {
canister_id,
Expand All @@ -457,7 +467,7 @@ fn test_canister_info() {
}),
},
CanisterChange {
timestamp_nanos: 16,
timestamp_nanos: time0 + 16,
canister_version: 6,
origin: CanisterChangeOrigin::FromCanister(FromCanisterRecord {
canister_id,
Expand All @@ -468,15 +478,15 @@ fn test_canister_info() {
}),
},
CanisterChange {
timestamp_nanos: 18,
timestamp_nanos: time0 + 18,
canister_version: 7,
origin: CanisterChangeOrigin::FromUser(FromUserRecord {
user_id: Principal::anonymous(),
}),
details: CanisterChangeDetails::CodeUninstall,
},
CanisterChange {
timestamp_nanos: 19,
timestamp_nanos: time0 + 19,
canister_version: 8,
origin: CanisterChangeOrigin::FromUser(FromUserRecord {
user_id: Principal::anonymous(),
Expand Down Expand Up @@ -543,6 +553,20 @@ fn test_call_management() {
.expect("Error calling execute_provisional_methods");
}

#[test]
fn test_snapshot() {
let pic = PocketIcBuilder::new()
.with_application_subnet()
.with_nonmainnet_features(true)
.build();
let wasm = cargo_build_canister("management_caller");
let canister_id = pic.create_canister();
pic.add_cycles(canister_id, 300_000_000_000_000_000_000_000_000u128);
pic.install_canister(canister_id, wasm, vec![], None);
let () = call_candid(&pic, canister_id, "execute_snapshot_methods", ())
.expect("Error calling execute_snapshot_methods");
}

#[test]
fn test_chunk() {
let pic = PocketIc::new();
Expand Down
2 changes: 1 addition & 1 deletion scripts/download_pocket_ic.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ cd "$SCRIPTS_DIR/../e2e-tests"
uname_sys=$(uname -s | tr '[:upper:]' '[:lower:]')
echo "uname_sys: $uname_sys"

tag="release-2024-05-22_23-01-base"
tag="release-2024-08-02_01-30-base"

curl -sL "https://github.com/dfinity/ic/releases/download/$tag/pocket-ic-x86_64-$uname_sys.gz" --output pocket-ic.gz
gzip -df pocket-ic.gz
Expand Down
Loading

0 comments on commit a8a7d4e

Please sign in to comment.