Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: canister state snapshots #504

Merged
merged 13 commits into from
Aug 8, 2024
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
Loading