Skip to content

Commit c8acdfb

Browse files
daniel-wong-dfinity-org-twindaniel-wong-dfinity-orgIDX GitHub Automation
authored
feat(sns): Added upgrade options to sns cli, and end to end test. (#11379)
In particular, added * --skip-pre-upgrade * --wasm-memory-persistance --------- Co-authored-by: Daniel Wong <daniel.wong@dfinity.org> Co-authored-by: IDX GitHub Automation <infra+github-automation@dfinity.org>
1 parent 11f08eb commit c8acdfb

8 files changed

Lines changed: 424 additions & 3 deletions

File tree

Cargo.lock

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

rs/nervous_system/integration_tests/BUILD.bazel

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ BASE_DEPENDENCIES = [
6666
"@crate_index//:num-traits",
6767
"@crate_index//:rustc-hash",
6868
"@crate_index//:serde",
69+
"@crate_index//:wat",
6970
],
7071
})
7172

@@ -192,6 +193,7 @@ rust_test_suite_with_extra_srcs(
192193
"tests/sns_release_qualification_legacy.rs",
193194
"tests/sns_upgrade_test_utils_legacy.rs",
194195
"tests/upgrade_sns_controlled_canister_with_large_wasm.rs",
196+
"tests/upgrade_sns_controlled_canister_with_options.rs",
195197
"tests/custom_upgrade_path.rs",
196198
"tests/sns_topics.rs",
197199
"tests/sns_extension_test.rs",
@@ -331,6 +333,22 @@ rust_test(
331333
deps = [":nervous_system_integration_tests"] + DEPENDENCIES_WITH_TEST_FEATURES,
332334
)
333335

336+
rust_test(
337+
name = "upgrade_sns_controlled_canister_with_options",
338+
timeout = "long",
339+
srcs = [
340+
"tests/upgrade_sns_controlled_canister_with_options.rs",
341+
],
342+
data = DEV_DATA,
343+
env = DEV_ENV | {"RUST_TEST_NOCAPTURE": "1"},
344+
exec_properties = {"cpu": "4"},
345+
proc_macro_deps = [
346+
# Keep sorted.
347+
"@crate_index//:rust_decimal_macros",
348+
],
349+
deps = [":nervous_system_integration_tests"] + DEPENDENCIES_WITH_TEST_FEATURES,
350+
)
351+
334352
rust_test(
335353
name = "custom_upgrade_path",
336354
timeout = "long",

rs/nervous_system/integration_tests/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,4 +84,5 @@ num-traits = { workspace = true }
8484
registry-canister = { path = "../../registry/canister" }
8585
rustc-hash = { workspace = true }
8686
serde = { workspace = true }
87+
wat = { workspace = true }
8788
xrc-mock = { path = "../../rust_canisters/xrc_mock" }

rs/nervous_system/integration_tests/tests/upgrade_sns_controlled_canister_with_large_wasm.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,8 @@ async fn upgrade_sns_controlled_canister_with_large_wasm() {
158158
)
159159
.unwrap(),
160160
summary: "Upgrade Image Classification canister.".to_string(),
161+
skip_pre_upgrade: false,
162+
wasm_memory_persistence: None,
161163
};
162164

163165
// 2. Submit the upgrade proposal.
Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
use canister_test::Wasm;
2+
use ic_management_canister_types_private::WasmMemoryPersistence;
3+
use ic_nervous_system_agent::{helpers::await_with_timeout, pocketic_impl::PocketIcAgent};
4+
use ic_nervous_system_integration_tests::{
5+
create_service_nervous_system_builder::CreateServiceNervousSystemBuilder,
6+
pocket_ic_helpers::{
7+
NnsInstaller, add_wasms_to_sns_wasm, cycles_ledger, install_canister_on_subnet,
8+
load_registry_mutations, nns, sns,
9+
sns::governance::{find_neuron_with_majority_voting_power, wait_for_proposal_execution},
10+
},
11+
};
12+
use ic_nns_constants::ROOT_CANISTER_ID;
13+
use ic_protobuf::types::v1::{
14+
CanisterInstallMode as CanisterInstallModeProto,
15+
WasmMemoryPersistence as WasmMemoryPersistenceProto,
16+
};
17+
use ic_sns_cli::{
18+
neuron_id_to_candid_subaccount::ParsedSnsNeuron,
19+
upgrade_sns_controlled_canister::{
20+
self, UpgradeSnsControlledCanisterArgs, UpgradeSnsControlledCanisterInfo,
21+
},
22+
};
23+
use ic_sns_governance_api::pb::v1::{UpgradeSnsControlledCanister, proposal};
24+
use ic_sns_swap::pb::v1::Lifecycle;
25+
use icp_ledger::Tokens;
26+
use lazy_static::lazy_static;
27+
use pocket_ic::PocketIcBuilder;
28+
use std::io::Write;
29+
use tempfile::{NamedTempFile, TempDir};
30+
use url::Url;
31+
32+
const MIN_UPGRADE_TIME_SECONDS: u64 = 5;
33+
const MAX_UPGRADE_TIME_SECONDS: u64 = 5 * 60;
34+
35+
lazy_static! {
36+
/// Features:
37+
///
38+
/// 1. Writes a sentinel value to main memory when installed. This is used
39+
/// to verify the wasm_memory_persistence upgrade option.
40+
///
41+
/// 2. Traps during pre_upgrade. This is to verify the skip_pre_upgrade
42+
/// upgrade option.
43+
///
44+
/// 3. Declares Enhanced Orthogonal Persistence (EOP) via
45+
/// `icp:private enhanced-orthogonal-persistence` custom section.
46+
/// This is also to test wasm_memory_persistence.
47+
static ref OLD_CANISTER_CODE: Vec<u8> = wat::parse_str(
48+
r#"
49+
(module
50+
(memory 1)
51+
52+
;; 1. Write sentinel.
53+
(func $initialize
54+
(i32.store (i32.const 0) (i32.const 0xCAFE_BABE))
55+
)
56+
(start $initialize)
57+
58+
;; 2. Trap during pre_upgrade.
59+
(func $pre_upgrade
60+
unreachable
61+
)
62+
(export "canister_pre_upgrade" (func $pre_upgrade))
63+
64+
;; 3. Declare EOP.
65+
(@custom "icp:private enhanced-orthogonal-persistence" "")
66+
)
67+
"#,
68+
)
69+
.unwrap();
70+
71+
/// Traps during upgrade if the value written by OLD_CANISTER_CODE is not found.
72+
static ref NEW_CANISTER_CODE: Vec<u8> = wat::parse_str(
73+
r#"
74+
(module
75+
(memory 1)
76+
77+
(func $check
78+
(if (i32.ne (i32.load (i32.const 0)) (i32.const 0xCAFE_BABE))
79+
(then unreachable)
80+
)
81+
)
82+
(export "canister_post_upgrade" (func $check))
83+
84+
(@custom "icp:private enhanced-orthogonal-persistence" "")
85+
)
86+
"#,
87+
)
88+
.unwrap();
89+
}
90+
91+
/// This exercises the skip_pre_upgrade and wasm_memory_persistence upgrade
92+
/// options, end-to-end, via the SNS CLI's upgrade_sns_controlled_canister
93+
/// command.
94+
#[tokio::test]
95+
async fn upgrade_sns_controlled_canister_with_options() {
96+
// Step 1: Prepare the world.
97+
98+
let state_dir = TempDir::new().unwrap();
99+
let state_dir = state_dir.path().to_path_buf();
100+
101+
let pocket_ic = PocketIcBuilder::new()
102+
.with_state_dir(state_dir.clone())
103+
.with_nns_subnet()
104+
.with_sns_subnet()
105+
.with_ii_subnet()
106+
.with_application_subnet()
107+
.build_async()
108+
.await;
109+
110+
// Install NNS.
111+
{
112+
let registry_proto_path = state_dir.join("registry.proto");
113+
let initial_mutations = load_registry_mutations(registry_proto_path);
114+
115+
let mut nns_installer = NnsInstaller::default();
116+
nns_installer
117+
.with_current_nns_canister_versions()
118+
.with_cycles_minting_canister()
119+
.with_cycles_ledger()
120+
.with_custom_registry_mutations(vec![initial_mutations]);
121+
nns_installer.install(&pocket_ic).await;
122+
}
123+
124+
// Publish SNS WASMs (to SNS-W).
125+
let with_mainnet_sns_canisters = false;
126+
add_wasms_to_sns_wasm(&pocket_ic, with_mainnet_sns_canisters)
127+
.await
128+
.unwrap();
129+
130+
// Create dapp canister and install OLD_CANISTER_CODE into it.
131+
let app_subnet = pocket_ic.topology().await.get_app_subnets()[0];
132+
let original_wasm = Wasm::from_bytes(OLD_CANISTER_CODE.clone());
133+
let original_wasm_hash = original_wasm.sha256_hash().to_vec();
134+
let target_canister_id = install_canister_on_subnet(
135+
&pocket_ic,
136+
app_subnet,
137+
vec![],
138+
Some(original_wasm),
139+
vec![ROOT_CANISTER_ID.into()],
140+
)
141+
.await;
142+
143+
let sns = {
144+
let create_service_nervous_system = CreateServiceNervousSystemBuilder::default()
145+
.with_dapp_canisters(vec![target_canister_id])
146+
.build();
147+
148+
let swap_parameters = create_service_nervous_system
149+
.swap_parameters
150+
.clone()
151+
.unwrap();
152+
153+
let sns_instance_label = "1";
154+
let (sns, _) = nns::governance::propose_to_deploy_sns_and_wait(
155+
&pocket_ic,
156+
create_service_nervous_system,
157+
sns_instance_label,
158+
)
159+
.await;
160+
161+
sns::swap::await_swap_lifecycle(&pocket_ic, sns.swap.canister_id, Lifecycle::Open)
162+
.await
163+
.unwrap();
164+
165+
sns::swap::smoke_test_participate_and_finalize(
166+
&pocket_ic,
167+
sns.swap.canister_id,
168+
swap_parameters,
169+
)
170+
.await;
171+
172+
sns
173+
};
174+
175+
// Get an ID of an SNS neuron that can submit proposals. We rely on the fact that this
176+
// neuron either holds the majority of the voting power or the follow graph is set up
177+
// s.t. when this neuron submits a proposal, that proposal gets through without the need
178+
// for any voting.
179+
let (sns_neuron_id, sender) =
180+
find_neuron_with_majority_voting_power(&pocket_ic, sns.governance.canister_id)
181+
.await
182+
.expect("cannot find SNS neuron with dissolve delay over 6 months.");
183+
184+
// Give the user some cycles so that he can store chunks of the new code in
185+
// a store canister. The dapp upgrade proposal will point to this canister
186+
// as the place where the code can be sourced.
187+
let icp = Tokens::from_tokens(10).unwrap();
188+
cycles_ledger::mint_icp_and_convert_to_cycles(&pocket_ic, sender, icp).await;
189+
190+
// The sns cli will upload the new code to the store canister from here.
191+
let mut new_wasm_file = NamedTempFile::new().unwrap();
192+
new_wasm_file.write_all(&NEW_CANISTER_CODE).unwrap();
193+
194+
// Step 2: Run the code under test.
195+
196+
// Step 2.1: Prepare command to submit proposal to upgrade the dapp canister.
197+
let cli_arg = UpgradeSnsControlledCanisterArgs {
198+
// The new upgrade flags that we are trying to test.
199+
skip_pre_upgrade: true,
200+
wasm_memory_persistence: Some(WasmMemoryPersistence::Keep),
201+
202+
// Basic upgrade parameters.
203+
target_canister_id,
204+
wasm_path: new_wasm_file.path().to_path_buf(),
205+
candid_arg: None,
206+
207+
// Description.
208+
summary: "Upgrade the Dapp".to_string(),
209+
proposal_url: Url::try_from("https://forum.dfinity.org").unwrap(),
210+
211+
// Who is proposing.
212+
sns_neuron_id: Some(ParsedSnsNeuron(sns_neuron_id)),
213+
};
214+
215+
// Step 2.2: Submit proposal to upgrade the dapp canister.
216+
let pocket_ic_agent = PocketIcAgent {
217+
pocket_ic: &pocket_ic,
218+
sender: sender.into(),
219+
};
220+
let UpgradeSnsControlledCanisterInfo {
221+
wasm_module_hash,
222+
proposal_id,
223+
} = upgrade_sns_controlled_canister::exec(cli_arg, &pocket_ic_agent)
224+
.await
225+
.unwrap();
226+
let proposal_id = proposal_id.unwrap();
227+
assert_ne!(wasm_module_hash, original_wasm_hash);
228+
229+
// Step 2.3: Wait for the proposal to execute (successfully).
230+
let action = wait_for_proposal_execution(&pocket_ic, sns.governance.canister_id, proposal_id)
231+
.await
232+
.unwrap()
233+
.proposal
234+
.unwrap()
235+
.action
236+
.unwrap();
237+
238+
// Step 3: Verify result(s).
239+
240+
// Step 3.1: Inspect proposal. This is less interesting than what happens to
241+
// the dapp canister. The main reason this might fail is if somehow the sns
242+
// cli flags did not survive the whole way through, if they somehow got
243+
// dropped along the way.
244+
let proposal::Action::UpgradeSnsControlledCanister(UpgradeSnsControlledCanister {
245+
mode,
246+
canister_upgrade_options,
247+
..
248+
}) = action
249+
else {
250+
panic!("unexpected proposal action {action:?}");
251+
};
252+
assert_eq!(mode, Some(CanisterInstallModeProto::Upgrade as i32));
253+
let canister_upgrade_options = canister_upgrade_options.unwrap();
254+
assert_eq!(canister_upgrade_options.skip_pre_upgrade, Some(true));
255+
assert_eq!(
256+
canister_upgrade_options.wasm_memory_persistence,
257+
Some(WasmMemoryPersistenceProto::Keep as i32),
258+
);
259+
260+
// Step 3.2: Verify that the dapp canister has the new code. This allows us
261+
// to deduce that the upgrade options under test did what they are supposed
262+
// to:
263+
//
264+
// 1. skip_pre_upgrade: If pre-upgrade were not skipped, pre-upgrade
265+
// function in OLD_CANISTER_CODE would have trapped.
266+
//
267+
// 2. wasm_memory_persistence: If main memory were not retained, the check
268+
// in NEW_CANISTER_CODE would have trapped due to not seeing the value
269+
// written to main memory by OLD_CANISTER_CODE.
270+
await_with_timeout(
271+
&pocket_ic,
272+
MIN_UPGRADE_TIME_SECONDS..MAX_UPGRADE_TIME_SECONDS,
273+
|pocket_ic| async {
274+
let status = pocket_ic
275+
.canister_status(target_canister_id.into(), Some(sns.root.canister_id.into()))
276+
.await;
277+
status
278+
.expect("canister status must be available")
279+
.module_hash
280+
},
281+
&Some(wasm_module_hash),
282+
)
283+
.await
284+
.unwrap();
285+
}

rs/sns/cli/BUILD.bazel

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ rust_library(
2929
"//rs/nns/constants",
3030
"//rs/nns/governance/api",
3131
"//rs/nns/sns-wasm",
32+
"//rs/protobuf",
3233
"//rs/sns/dfx-core-vendored",
3334
"//rs/sns/governance",
3435
"//rs/sns/governance/api",
@@ -78,6 +79,7 @@ rust_binary(
7879
"//rs/nns/constants",
7980
"//rs/nns/governance/api",
8081
"//rs/nns/sns-wasm",
82+
"//rs/protobuf",
8183
"//rs/sns/dfx-core-vendored",
8284
"//rs/sns/governance",
8385
"//rs/sns/governance/api",
@@ -144,6 +146,7 @@ rust_test(
144146
"//rs/nns/constants",
145147
"//rs/nns/governance/api",
146148
"//rs/nns/sns-wasm",
149+
"//rs/protobuf",
147150
"//rs/sns/dfx-core-vendored",
148151
"//rs/sns/governance",
149152
"//rs/sns/governance/api",

rs/sns/cli/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ ic-nervous-system-common = { path = "../../nervous_system/common" }
3131
ic-nervous-system-common-test-keys = { path = "../../nervous_system/common/test_keys" }
3232
ic-nervous-system-humanize = { path = "../../nervous_system/humanize" }
3333
ic-nervous-system-proto = { path = "../../nervous_system/proto" }
34+
ic-protobuf = { path = "../../protobuf" }
3435
cycles-minting-canister = { path = "../../nns/cmc" }
3536
ic-nns-common = { path = "../../nns/common" }
3637
ic-nns-constants = { path = "../../nns/constants" }

0 commit comments

Comments
 (0)