Skip to content

Commit 340f17d

Browse files
authored
test(sns-cli): Port UpgradeSnsControlledCanister with Large Wasm integration test to use SNS CLI with PocketIc (dfinity#3696)
This PR leverages the SNS CLI functionality to prepare and submit UpgradeSnsControlledCanister proposals for large Wasms as part of the existing integration test scenario. The goal is to have the CLI functionality tested in CI. This entails the need to generalize the CLI command upgrade-sns-controlled-canister to work with a CallCanister instance which can be instantiated not only with ic-agent, but also with PocketIc instances.
1 parent f4450eb commit 340f17d

File tree

20 files changed

+818
-371
lines changed

20 files changed

+818
-371
lines changed

Cargo.lock

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/pocket-ic/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1616
- The function `PocketIc::await_call_no_ticks` to await the status of an update call (submitted through an ingress message) becoming known without triggering round execution
1717
(round execution must be triggered separarely, e.g., on a "live" instance or by separate PocketIC library calls).
1818
- The function `PocketIc::set_certified_time` to set the current certified time on all subnets of the PocketIC instance.
19+
- The function `PocketIc::update_call_with_effective_principal` is made public. It is helpful, e.g., for
20+
modeling management canister calls that need to be routed to the right subnet using effective principals.
1921

2022
### Changed
2123
- The response types `pocket_ic::WasmResult`, `pocket_ic::UserError`, and `pocket_ic::CallError` are replaced by a single reject response type `pocket_ic::RejectResponse`.

packages/pocket-ic/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1129,7 +1129,7 @@ impl PocketIc {
11291129
runtime.block_on(async { self.pocket_ic.get_subnet_metrics(subnet_id).await })
11301130
}
11311131

1132-
fn update_call_with_effective_principal(
1132+
pub fn update_call_with_effective_principal(
11331133
&self,
11341134
canister_id: CanisterId,
11351135
effective_principal: RawEffectivePrincipal,

packages/pocket-ic/src/nonblocking.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1529,7 +1529,7 @@ impl PocketIc {
15291529
result.into()
15301530
}
15311531

1532-
pub(crate) async fn update_call_with_effective_principal(
1532+
pub async fn update_call_with_effective_principal(
15331533
&self,
15341534
canister_id: CanisterId,
15351535
effective_principal: RawEffectivePrincipal,

rs/nervous_system/agent/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ DEPENDENCIES = [
1919
"@crate_index//:anyhow",
2020
"@crate_index//:candid",
2121
"@crate_index//:ic-agent",
22+
"@crate_index//:itertools",
2223
"@crate_index//:serde",
24+
"@crate_index//:serde_cbor",
2325
"@crate_index//:tempfile",
2426
"@crate_index//:thiserror",
2527
"@crate_index//:tokio",

rs/nervous_system/agent/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ pocket-ic = { path = "../../../packages/pocket-ic" }
2323
registry-canister = { path = "../../registry/canister" }
2424
ic-sns-root = { path = "../../sns/root" }
2525
ic-sns-swap = { path = "../../sns/swap" }
26+
itertools = { workspace = true }
2627
serde = { workspace = true }
28+
serde_cbor = { workspace = true }
2729
tempfile = { workspace = true }
2830
thiserror = { workspace = true }
2931
tokio = { workspace = true }

rs/nervous_system/agent/src/agent_impl.rs

Lines changed: 92 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,31 @@
1-
use crate::Request;
1+
use crate::CallCanisters;
2+
use crate::{CanisterInfo, Request};
23
use candid::Principal;
34
use ic_agent::Agent;
5+
use itertools::{Either, Itertools};
6+
use serde_cbor::Value;
7+
use std::collections::BTreeSet;
48
use thiserror::Error;
59

6-
use crate::CallCanisters;
7-
810
#[derive(Error, Debug)]
911
pub enum AgentCallError {
12+
#[error("agent identity error: {0}")]
13+
Identity(String),
1014
#[error("agent error: {0}")]
1115
Agent(#[from] ic_agent::AgentError),
1216
#[error("canister request could not be encoded: {0}")]
1317
CandidEncode(candid::Error),
1418
#[error("canister did not respond with the expected response type: {0}")]
1519
CandidDecode(candid::Error),
20+
#[error("invalid canister controllers: {0}")]
21+
CanisterControllers(String),
1622
}
1723

1824
impl crate::sealed::Sealed for Agent {}
1925

2026
impl CallCanisters for Agent {
2127
type Error = AgentCallError;
28+
2229
async fn call<R: Request>(
2330
&self,
2431
canister_id: impl Into<Principal> + Send,
@@ -50,4 +57,86 @@ impl CallCanisters for Agent {
5057
candid::decode_one(response.as_slice()).map_err(AgentCallError::CandidDecode)?;
5158
Ok(response)
5259
}
60+
61+
async fn canister_info(
62+
&self,
63+
canister_id: impl Into<Principal> + Send,
64+
) -> Result<CanisterInfo, Self::Error> {
65+
let canister_id = canister_id.into();
66+
67+
let read_state_result = self
68+
.read_state_canister_info(canister_id, "module_hash")
69+
.await;
70+
71+
let module_hash = match read_state_result {
72+
Ok(module_hash) => Some(module_hash),
73+
Err(ic_agent::AgentError::LookupPathAbsent(_)) => {
74+
// https://internetcomputer.org/docs/current/references/ic-interface-spec#state-tree-canister-information
75+
None
76+
}
77+
Err(err) => {
78+
return Err(Self::Error::Agent(err));
79+
}
80+
};
81+
82+
let controllers_blob = self
83+
.read_state_canister_info(canister_id, "controllers")
84+
.await
85+
.map_err(AgentCallError::Agent)?;
86+
87+
let cbor: Value = serde_cbor::from_slice(&controllers_blob).map_err(|err| {
88+
Self::Error::CanisterControllers(format!("Failed decoding CBOR data: {:?}", err))
89+
})?;
90+
91+
let Value::Array(controllers) = cbor else {
92+
return Err(Self::Error::CanisterControllers(format!(
93+
"Expected controllers to be an array, but got {:?}",
94+
cbor
95+
)));
96+
};
97+
98+
let (controllers, errors): (Vec<_>, Vec<_>) =
99+
controllers.into_iter().partition_map(|value| {
100+
let Value::Bytes(bytes) = value else {
101+
let err = format!(
102+
"Expected canister controller to be of type bytes, got {:?}",
103+
value
104+
);
105+
return Either::Right(err);
106+
};
107+
match Principal::try_from(&bytes) {
108+
Err(err) => {
109+
let err =
110+
format!("Cannot interpret canister controller principal: {}", err);
111+
Either::Right(err)
112+
}
113+
Ok(principal) => Either::Left(principal),
114+
}
115+
});
116+
117+
if !errors.is_empty() {
118+
return Err(Self::Error::CanisterControllers(format!(
119+
"\n - {}",
120+
errors.join("\n - ")
121+
)));
122+
}
123+
124+
let unique_controllers = BTreeSet::from_iter(controllers.iter().copied());
125+
126+
if unique_controllers.len() != controllers.len() {
127+
return Err(Self::Error::CanisterControllers(format!(
128+
"Canister controllers have duplicates: {}",
129+
controllers.into_iter().join(", ")
130+
)));
131+
}
132+
133+
Ok(CanisterInfo {
134+
module_hash,
135+
controllers: unique_controllers,
136+
})
137+
}
138+
139+
fn caller(&self) -> Result<Principal, Self::Error> {
140+
self.get_principal().map_err(Self::Error::Identity)
141+
}
53142
}

rs/nervous_system/agent/src/lib.rs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1+
use candid::{CandidType, Principal};
2+
use serde::de::DeserializeOwned;
3+
use std::collections::BTreeSet;
4+
use std::fmt::Display;
5+
16
pub mod agent_impl;
27
pub mod management_canister;
38
pub mod nns;
49
mod null_request;
510
pub mod pocketic_impl;
611
pub mod sns;
712

8-
use candid::{CandidType, Principal};
9-
use serde::de::DeserializeOwned;
10-
use std::fmt::Display;
11-
1213
// This is used to "seal" the CallCanisters trait so that it cannot be implemented outside of this crate.
1314
// This is useful because it means we can modify the trait in the future without worrying about
1415
// breaking backwards compatibility with implementations outside of this crate.
@@ -27,11 +28,24 @@ pub trait Request: Send {
2728
type Response: CandidType + DeserializeOwned;
2829
}
2930

31+
pub struct CanisterInfo {
32+
pub module_hash: Option<Vec<u8>>,
33+
pub controllers: BTreeSet<Principal>,
34+
}
35+
3036
pub trait CallCanisters: sealed::Sealed {
3137
type Error: Display + Send + std::error::Error + 'static;
38+
39+
fn caller(&self) -> Result<Principal, Self::Error>;
40+
3241
fn call<R: Request>(
3342
&self,
3443
canister_id: impl Into<Principal> + Send,
3544
request: R,
3645
) -> impl std::future::Future<Output = Result<R::Response, Self::Error>> + Send;
46+
47+
fn canister_info(
48+
&self,
49+
canister_id: impl Into<Principal> + Send,
50+
) -> impl std::future::Future<Output = Result<CanisterInfo, Self::Error>> + Send;
3751
}

0 commit comments

Comments
 (0)