Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
2bc9b2f
DEFI-2677: General VisibilitySettings
gregorydemay Mar 3, 2026
5c7072f
DEFI-2677: Snapshot visibility settings
gregorydemay Mar 3, 2026
938314a
DEFI-2677: fix Pocket IC
gregorydemay Mar 4, 2026
7c4f84e
DEFI-2677: fix registry admin
gregorydemay Mar 4, 2026
507e627
DEFI-2677: fix state_layout
gregorydemay Mar 4, 2026
469bdd8
DEFI-2677: fix governance
gregorydemay Mar 4, 2026
33efe61
DEFI-2677: fix SNS
gregorydemay Mar 4, 2026
5409fde
DEFI-2677: fix NNS
gregorydemay Mar 4, 2026
198f0a8
DEFI-2677: fix replica_tests
gregorydemay Mar 4, 2026
1cc4d3e
DEFI-2677: fix tests
gregorydemay Mar 6, 2026
3dc83f3
DEFI-2677: fix `governance-canister.wasm.gz_size_check`. Current size…
gregorydemay Mar 6, 2026
c3fd07d
DEFI-2667: permission for list_canister_snapshot
gregorydemay Mar 6, 2026
2ca1c6e
DEFI-2677: StateMachine list canister snapshots
gregorydemay Mar 6, 2026
c65cb73
DEFI-2677: failing test
gregorydemay Mar 6, 2026
e6cd2ec
DEFI-2677: fix allow ingress messages to list canister snapshots
gregorydemay Mar 6, 2026
79a640d
Typos and renaming
gregorydemay Mar 9, 2026
5f16df9
DEFI-2667: fix renaming
gregorydemay Mar 9, 2026
fced026
DEFI-2667: add test cases
gregorydemay Mar 9, 2026
d7658e1
Merge branch 'master' into gdemay/DEFI-2667-snapshot-visibility-logic
gregorydemay Mar 12, 2026
a0af6bd
DEFI-2667: undo governance changes
gregorydemay Mar 12, 2026
86a6272
DEFI-2667: Implement rest of logic for `read_canister_snapshot_metada…
gregorydemay Mar 12, 2026
8ee1344
DEFI-2667: refactor test
gregorydemay Mar 12, 2026
6d594d5
DEFI-2667: fix tests
gregorydemay Mar 12, 2026
b74370e
Merge branch 'master' into gdemay/DEFI-2667-snapshot-visibility-logic
mraszyk Mar 13, 2026
af4043f
Update rs/execution_environment/src/canister_manager.rs
gregorydemay Mar 13, 2026
449e1c8
Merge branch 'master' into gdemay/DEFI-2667-snapshot-visibility-logic
gregorydemay Mar 16, 2026
4451041
lint
gregorydemay Mar 16, 2026
c8cd64a
Merge remote-tracking branch 'origin/gdemay/DEFI-2667-snapshot-visibi…
gregorydemay Mar 16, 2026
692f824
Automatically fixing code for linting and formatting issues
Mar 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 50 additions & 23 deletions rs/execution_environment/src/canister_manager.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::as_round_instructions;
use crate::execution::common::{
validate_controller, validate_controller_or_subnet_admin, validate_subnet_admin,
validate_controller, validate_controller_or_subnet_admin, validate_snapshot_visibility,
validate_subnet_admin,
};
use crate::execution::install_code::OriginalContext;
use crate::execution::{install::execute_install, upgrade::execute_upgrade};
Expand Down Expand Up @@ -229,10 +230,7 @@ impl CanisterManager {
| Ok(Ic00Method::ClearChunkStore)
| Ok(Ic00Method::TakeCanisterSnapshot)
| Ok(Ic00Method::LoadCanisterSnapshot)
| Ok(Ic00Method::ListCanisterSnapshots)
| Ok(Ic00Method::DeleteCanisterSnapshot)
| Ok(Ic00Method::ReadCanisterSnapshotMetadata)
| Ok(Ic00Method::ReadCanisterSnapshotData)
| Ok(Ic00Method::UploadCanisterSnapshotMetadata)
| Ok(Ic00Method::UploadCanisterSnapshotData) => {
match effective_canister_id {
Expand All @@ -258,6 +256,25 @@ impl CanisterManager {
}
},

Ok(Ic00Method::ListCanisterSnapshots)
| Ok(Ic00Method::ReadCanisterSnapshotMetadata)
| Ok(Ic00Method::ReadCanisterSnapshotData) => {
match effective_canister_id {
Some(canister_id) => {
let canister_state = state.canister_state(&canister_id).ok_or_else(|| UserError::new(
ErrorCode::CanisterNotFound,
format!("Canister {canister_id} not found"),
))?;
validate_snapshot_visibility(canister_state, &sender.get(), method_name)?;
Ok(())
},
None => Err(UserError::new(
ErrorCode::InvalidManagementPayload,
format!("Failed to decode payload for ic00 method: {method_name}"),
)),
}
},

Ok(Ic00Method::FetchCanisterLogs) => Err(UserError::new(
ErrorCode::CanisterRejectedMessage,
format!(
Expand Down Expand Up @@ -2493,14 +2510,14 @@ impl CanisterManager {
/// Returns the canister snapshots list, or
/// an error if it failed to retrieve the information.
///
/// Retrieving the canister snapshots list can only be initiated by the controllers.
/// Retrieving the canister snapshots list can only be initiated by a principal
/// allowed by the canister snapshot visibility settings.
pub(crate) fn list_canister_snapshot(
&self,
sender: PrincipalId,
canister: &CanisterState,
) -> Result<Vec<CanisterSnapshotResponse>, CanisterManagerError> {
// Check sender is a controller.
validate_controller(canister, &sender)?;
) -> Result<Vec<CanisterSnapshotResponse>, UserError> {
validate_snapshot_visibility(canister, &sender, "list_canister_snapshots")?;

let mut responses = vec![];
for (snapshot_id, snapshot) in canister.canister_snapshots.list_snapshots() {
Expand Down Expand Up @@ -2570,10 +2587,15 @@ impl CanisterManager {
sender: PrincipalId,
snapshot_id: SnapshotId,
canister: &CanisterState,
) -> Result<ReadCanisterSnapshotMetadataResponse, CanisterManagerError> {
// Check sender is a controller.
validate_controller(canister, &sender)?;
let snapshot = self.get_snapshot(canister, snapshot_id)?;
) -> Result<ReadCanisterSnapshotMetadataResponse, UserError> {
validate_snapshot_visibility(
canister,
&sender,
"read read_canister_snapshot_metadata snapshot metadata",
)?;
let snapshot = self
.get_snapshot(canister, snapshot_id)
.map_err(UserError::from)?;
// A snapshot also contains the instruction counter as the last global
// (because it is *appended* during WASM instrumentation).
// We pop that last global (which is merely an implementation detail)
Expand Down Expand Up @@ -2618,13 +2640,14 @@ impl CanisterManager {
state: &ReplicatedState,
subnet_size: usize,
round_limits: &mut RoundLimits,
) -> Result<ReadCanisterSnapshotDataResponse, CanisterManagerError> {
// Check sender is a controller.
validate_controller(canister, &sender)?;
let snapshot = self.get_snapshot(canister, snapshot_id)?;
) -> Result<ReadCanisterSnapshotDataResponse, UserError> {
validate_snapshot_visibility(canister, &sender, "read_canister_snapshot_data")?;
let snapshot = self
.get_snapshot(canister, snapshot_id)
.map_err(UserError::from)?;

// Charge upfront for the baseline plus the maximum possible size of the returned slice or fail.
let num_response_bytes = get_response_size(&kind)?;
let num_response_bytes = get_response_size(&kind).map_err(UserError::from)?;
let num_instructions = self
.config
.canister_snapshot_data_baseline_instructions
Expand All @@ -2639,10 +2662,12 @@ impl CanisterManager {
state.get_own_cost_schedule(),
)
{
return Err(CanisterManagerError::CanisterSnapshotNotEnoughCycles(err));
return Err(UserError::from(
CanisterManagerError::CanisterSnapshotNotEnoughCycles(err),
));
};
round_limits.instructions -= as_round_instructions(num_instructions);
let res = match kind {
let res: Result<Vec<u8>, CanisterManagerError> = match kind {
CanisterSnapshotDataKind::StableMemory { offset, size } => {
let stable_memory = snapshot.execution_snapshot().stable_memory.clone();
match CanisterSnapshot::get_memory_chunk(stable_memory, offset, size) {
Expand All @@ -2665,19 +2690,21 @@ impl CanisterManager {
}
CanisterSnapshotDataKind::WasmChunk { hash } => {
let Ok(hash) = <WasmChunkHash>::try_from(hash.clone()) else {
return Err(CanisterManagerError::WasmChunkStoreError {
return Err(UserError::from(CanisterManagerError::WasmChunkStoreError {
message: format!("Bytes {hash:02x?} are not a valid WasmChunkHash."),
});
}));
};
let Some(chunk) = snapshot.chunk_store().get_chunk_complete(&hash) else {
return Err(CanisterManagerError::WasmChunkStoreError {
return Err(UserError::from(CanisterManagerError::WasmChunkStoreError {
message: format!("WasmChunkHash {hash:02x?} not found."),
});
}));
};
Ok(chunk)
}
};

res.map(ReadCanisterSnapshotDataResponse::new)
.map_err(UserError::from)
}

/// Creates a new snapshot based on the provided metadata and returns the new snapshot ID.
Expand Down
16 changes: 16 additions & 0 deletions rs/execution_environment/src/execution/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,22 @@ pub(crate) fn validate_controller(
Ok(())
}

pub(crate) fn validate_snapshot_visibility(
canister: &CanisterState,
caller: &PrincipalId,
method_name: &str,
) -> Result<(), UserError> {
if !crate::canister_settings::VisibilitySettings::from(canister.snapshot_visibility())
.has_access(caller, canister.controllers())
{
return Err(UserError::new(
ErrorCode::CanisterRejectedMessage,
format!("Caller {caller} is not allowed to call {method_name}"),
));
}
Ok(())
}

pub(crate) fn validate_subnet_admin(
subnet_admins: &BTreeSet<PrincipalId>,
sender: &PrincipalId,
Expand Down
39 changes: 16 additions & 23 deletions rs/execution_environment/src/execution_environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2622,12 +2622,9 @@ impl ExecutionEnvironment {
) -> Result<Vec<u8>, UserError> {
let canister = get_canister(args.get_canister_id(), state)?;

let result = self
.canister_manager
self.canister_manager
.list_canister_snapshot(sender, canister)
.map_err(UserError::from)?;

Ok(Encode!(&result).unwrap())
.map(|result| Encode!(&result).unwrap())
}

/// Deletes the specified canister snapshot if it exists.
Expand Down Expand Up @@ -2691,18 +2688,18 @@ impl ExecutionEnvironment {
Some(canister) => canister,
};

let result = match self.canister_manager.read_snapshot_data(
sender,
Arc::make_mut(&mut canister),
args.get_snapshot_id(),
args.kind,
state,
subnet_size,
round_limits,
) {
Ok(result) => Ok(Encode!(&result).unwrap()),
Err(err) => Err(UserError::from(err)),
};
let result = self
.canister_manager
.read_snapshot_data(
sender,
Arc::make_mut(&mut canister),
args.get_snapshot_id(),
args.kind,
state,
subnet_size,
round_limits,
)
.map(|response| Encode!(&response).unwrap());

// Put canister back.
state.put_canister_state(canister);
Expand Down Expand Up @@ -2764,13 +2761,9 @@ impl ExecutionEnvironment {
) -> Result<Vec<u8>, UserError> {
let canister = get_canister(args.get_canister_id(), state)?;
let snapshot_id = args.get_snapshot_id();
match self
.canister_manager
self.canister_manager
.read_snapshot_metadata(sender, snapshot_id, canister)
{
Ok(response) => Ok(Encode!(&response).unwrap()),
Err(e) => Err(UserError::from(e)),
}
.map(|response| Encode!(&response).unwrap())
}

fn create_snapshot_from_metadata(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1155,13 +1155,9 @@ fn list_canister_snapshot_fails_invalid_controller() {
if let RequestOrResponse::Response(res) = response {
assert_eq!(res.originator, *receiver);
res.response_payload.assert_contains_reject(
RejectCode::CanisterError,
RejectCode::CanisterReject,
Comment thread
mraszyk marked this conversation as resolved.
&format!(
"Only the controllers of the canister {} can control it.\n\
Canister's controllers: {}\n\
Sender's ID: {}",
Comment thread
mraszyk marked this conversation as resolved.
canister_id,
test.user_id().get(),
"Caller {} is not allowed to call list_canister_snapshots",
caller_canister.get(),
),
);
Expand Down Expand Up @@ -2103,7 +2099,7 @@ fn read_canister_snapshot_metadata_fails_invalid_controller() {
let error = test
.subnet_message("read_canister_snapshot_metadata", args.encode())
.unwrap_err();
assert_eq!(error.code(), ErrorCode::CanisterInvalidController);
assert_eq!(error.code(), ErrorCode::CanisterRejectedMessage);
}

fn read_canister_snapshot_data(
Expand Down Expand Up @@ -2473,7 +2469,7 @@ fn read_canister_snapshot_data_fails_invalid_controller() {
let error = test
.subnet_message("read_canister_snapshot_data", args.encode())
.unwrap_err();
assert_eq!(error.code(), ErrorCode::CanisterInvalidController);
assert_eq!(error.code(), ErrorCode::CanisterRejectedMessage);
}

#[test]
Expand Down
Loading
Loading