From 14cac9997b1fba57614ee6c53999679a9d1d673a Mon Sep 17 00:00:00 2001 From: Pietro Date: Fri, 10 Jul 2026 15:00:40 +0200 Subject: [PATCH 1/3] feat(release-runscript): set engine-controller authorized_caller on upgrade The release-runscript hardcoded `()` as the engine-controller upgrade argument, which cannot set the canister's authorized caller. Change the Create Proposal Texts step to prompt for the authorized caller principal and build the upgrade arg: (opt record { authorized_caller = opt principal ""; initial_dkg_subnet_id = null }) This candid string is passed as CANDID_ARGS to prepare-nns-upgrade-proposal-text.sh, which encodes it with `didc encode | xxd -r -p` to compute the proposal's arg_hash and verification section. An empty answer emits `authorized_caller = null`, making the canister fall back to its built-in default caller (rather than the previous behavior of producing an invalid `opt principal ""`). The canister re-applies args on every post_upgrade, so the caller must be supplied each release to retain it. Co-Authored-By: Claude Opus 4.8 --- .../tools/release-runscript/src/main.rs | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/rs/nervous_system/tools/release-runscript/src/main.rs b/rs/nervous_system/tools/release-runscript/src/main.rs index 8fd4e3b400a9..bb0ebeeb4524 100644 --- a/rs/nervous_system/tools/release-runscript/src/main.rs +++ b/rs/nervous_system/tools/release-runscript/src/main.rs @@ -318,16 +318,36 @@ fn run_create_proposal_texts(cmd: CreateProposalTexts) -> Result<()> { // Some canisters require an explicit upgrade arg. cycles-minting // needs a hand-provided one (usually '()'). engine-controller's // post_upgrade traps when decoding an empty arg, so it must be - // given at least '()'; otherwise the upgrade silently rolls back - // while the proposal still reports as executed. + // given a non-empty record; otherwise the upgrade silently rolls + // back while the proposal still reports as executed. We prompt for + // the authorized caller principal and build the record around it. let output = match canister.as_str() { "cycles-minting" => { let upgrade_arg = input_with_default("Upgrade arg for CMC?", "()")?; run_script(script, &[canister, &commit, &upgrade_arg], &ic) .expect("Failed to run NNS proposal text script") } - "engine-controller" => run_script(script, &[canister, &commit, "()"], &ic) - .expect("Failed to run NNS proposal text script"), + "engine-controller" => { + // An empty answer emits `null`, which makes the canister + // fall back to its built-in default authorized caller; a + // non-empty value is wrapped in `opt principal "..."`. + // The canister re-applies this on every post_upgrade, so + // the caller must be supplied each release to retain it + // (otherwise it reverts to the default). + let new_caller = input( + "Authorized caller principal for engine-controller? (leave empty for canister default)", + )?; + let authorized_caller = if new_caller.is_empty() { + "null".to_string() + } else { + format!("opt principal \"{new_caller}\"") + }; + let upgrade_arg = format!( + "(opt record {{ authorized_caller = {authorized_caller}; initial_dkg_subnet_id = null }})" + ); + run_script(script, &[canister, &commit, &upgrade_arg], &ic) + .expect("Failed to run NNS proposal text script") + } _ => run_script(script, &[canister, &commit], &ic) .expect("Failed to run NNS proposal text script"), }; From ee12dd214bd2264655c4089f28c99dee21ec4a5b Mon Sep 17 00:00:00 2001 From: pietrodimarco-dfinity <124565147+pietrodimarco-dfinity@users.noreply.github.com> Date: Wed, 15 Jul 2026 09:30:16 +0200 Subject: [PATCH 2/3] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../tools/release-runscript/src/main.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/rs/nervous_system/tools/release-runscript/src/main.rs b/rs/nervous_system/tools/release-runscript/src/main.rs index bb0ebeeb4524..f6d9c8a408d3 100644 --- a/rs/nervous_system/tools/release-runscript/src/main.rs +++ b/rs/nervous_system/tools/release-runscript/src/main.rs @@ -334,14 +334,17 @@ fn run_create_proposal_texts(cmd: CreateProposalTexts) -> Result<()> { // The canister re-applies this on every post_upgrade, so // the caller must be supplied each release to retain it // (otherwise it reverts to the default). - let new_caller = input( - "Authorized caller principal for engine-controller? (leave empty for canister default)", - )?; - let authorized_caller = if new_caller.is_empty() { - "null".to_string() - } else { - format!("opt principal \"{new_caller}\"") - }; +let new_caller = input( + "Authorized caller principal for engine-controller? (leave empty for canister default)", +)?; +let authorized_caller = if new_caller.is_empty() { + "null".to_string() +} else { + let principal = candid::Principal::from_text(&new_caller).map_err(|e| { + anyhow::anyhow!("Invalid principal '{new_caller}': {e}") + })?; + format!("opt principal \"{}\"", principal.to_text()) +}; let upgrade_arg = format!( "(opt record {{ authorized_caller = {authorized_caller}; initial_dkg_subnet_id = null }})" ); From 4238ddec7727135affab48af6cc0620aa7543019 Mon Sep 17 00:00:00 2001 From: IDX GitHub Automation Date: Wed, 15 Jul 2026 07:35:13 +0000 Subject: [PATCH 3/3] Automatically fixing code for linting and formatting issues --- .../tools/release-runscript/src/main.rs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/rs/nervous_system/tools/release-runscript/src/main.rs b/rs/nervous_system/tools/release-runscript/src/main.rs index f6d9c8a408d3..138440aaade9 100644 --- a/rs/nervous_system/tools/release-runscript/src/main.rs +++ b/rs/nervous_system/tools/release-runscript/src/main.rs @@ -334,17 +334,17 @@ fn run_create_proposal_texts(cmd: CreateProposalTexts) -> Result<()> { // The canister re-applies this on every post_upgrade, so // the caller must be supplied each release to retain it // (otherwise it reverts to the default). -let new_caller = input( - "Authorized caller principal for engine-controller? (leave empty for canister default)", -)?; -let authorized_caller = if new_caller.is_empty() { - "null".to_string() -} else { - let principal = candid::Principal::from_text(&new_caller).map_err(|e| { - anyhow::anyhow!("Invalid principal '{new_caller}': {e}") - })?; - format!("opt principal \"{}\"", principal.to_text()) -}; + let new_caller = input( + "Authorized caller principal for engine-controller? (leave empty for canister default)", + )?; + let authorized_caller = if new_caller.is_empty() { + "null".to_string() + } else { + let principal = candid::Principal::from_text(&new_caller).map_err(|e| { + anyhow::anyhow!("Invalid principal '{new_caller}': {e}") + })?; + format!("opt principal \"{}\"", principal.to_text()) + }; let upgrade_arg = format!( "(opt record {{ authorized_caller = {authorized_caller}; initial_dkg_subnet_id = null }})" );