From b0befccc79e7b6e2eaca8b4efd6c39203fea8553 Mon Sep 17 00:00:00 2001 From: Pietro Date: Fri, 21 Jun 2024 18:35:39 +0200 Subject: [PATCH 01/11] Analyze open proposals subnet change --- Cargo.lock | 2 + rs/cli/Cargo.toml | 2 + rs/cli/src/cli.rs | 6 + rs/cli/src/main.rs | 71 +++++------ rs/cli/src/runner.rs | 153 ++++++++++++++++------- rs/ic-management-backend/src/proposal.rs | 35 +----- rs/ic-management-types/src/lib.rs | 30 +++++ 7 files changed, 179 insertions(+), 120 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 66bc4de18..f16830a39 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2214,6 +2214,8 @@ checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" name = "dre" version = "0.4.0" dependencies = [ + "actix-rt", + "actix-web", "anyhow", "async-recursion", "async-trait", diff --git a/rs/cli/Cargo.toml b/rs/cli/Cargo.toml index 73ced9359..7fd4d49df 100644 --- a/rs/cli/Cargo.toml +++ b/rs/cli/Cargo.toml @@ -11,6 +11,7 @@ documentation.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +actix-web = { workspace = true } anyhow = { workspace = true } async-recursion = { workspace = true } async-trait = { workspace = true } @@ -72,6 +73,7 @@ url = { workspace = true } humantime = { workspace = true } [dev-dependencies] +actix-rt = { workspace = true } wiremock = { workspace = true } [build-dependencies] diff --git a/rs/cli/src/cli.rs b/rs/cli/src/cli.rs index 84e6bdc97..be892d425 100644 --- a/rs/cli/src/cli.rs +++ b/rs/cli/src/cli.rs @@ -615,6 +615,12 @@ pub mod proposals { /// Proposal ID proposal_id: u64, }, + + /// Print decentralization for a subnet-change proposal gived ID + Analyze { + /// Proposal ID + proposal_id: u64, + }, } #[derive(ValueEnum, Clone, Debug)] diff --git a/rs/cli/src/main.rs b/rs/cli/src/main.rs index 201b4c3e4..803f93185 100644 --- a/rs/cli/src/main.rs +++ b/rs/cli/src/main.rs @@ -1,5 +1,6 @@ use crate::ic_admin::IcAdminWrapper; use clap::{error::ErrorKind, CommandFactory, Parser}; +use decentralization::network::SubnetChange; use dialoguer::Confirm; use dotenv::dotenv; use dre::detect_neuron::Auth; @@ -10,8 +11,11 @@ use ic_base_types::CanisterId; use ic_canisters::governance::{governance_canister_version, GovernanceCanisterWrapper}; use ic_canisters::CanisterClient; use ic_management_backend::endpoints; +use ic_management_types::filter_map_nns_function_proposals; use ic_management_types::requests::NodesRemoveRequest; -use ic_management_types::{Artifact, MinNakamotoCoefficients, NodeFeature}; +use ic_management_types::{Artifact, MinNakamotoCoefficients, NnsFunctionProposal, NodeFeature}; +use registry_canister::mutations::do_change_subnet_membership::ChangeSubnetMembershipPayload; + use ic_nns_common::pb::v1::ProposalId; use ic_nns_governance::pb::v1::ListProposalInfo; use log::{info, warn}; @@ -70,35 +74,18 @@ async fn async_main() -> Result<(), anyhow::Error> { let governance_canister_version = governance_canister_v.stringified_hash; - let (tx, rx) = mpsc::channel(); - - let backend_port = local_unused_port(); - let target_network_backend = target_network.clone(); - thread::spawn(move || { - let rt = tokio::runtime::Runtime::new().unwrap(); - rt.block_on(async move { - endpoints::run_backend(&target_network_backend, "127.0.0.1", backend_port, true, Some(tx)) - .await - .expect("failed") - }); - }); - - let srv = rx.recv().unwrap(); - - let r = ic_admin::with_ic_admin(governance_canister_version.into(), async { + ic_admin::with_ic_admin(governance_canister_version.into(), async { let dry_run = cli_opts.dry_run; - - let runner_instance = { - let cli = dre::parsed_cli::ParsedCli::from_opts(&cli_opts) + let cli = dre::parsed_cli::ParsedCli::from_opts(&cli_opts) + .await + .expect("Failed to create authenticated CLI"); + let ic_admin_wrapper = IcAdminWrapper::from_cli(cli); + + let runner_instance = runner::Runner::new(ic_admin_wrapper, &target_network) .await - .expect("Failed to create authenticated CLI"); - let ic_admin_wrapper = IcAdminWrapper::from_cli(cli); - runner::Runner::new_with_network_and_backend_port(ic_admin_wrapper, &target_network, backend_port) - .await - .expect("Failed to create a runner") - }; + .expect("Failed to create a runner"); - match &cli_opts.subcommand { + let r = match &cli_opts.subcommand { cli::Commands::DerToPrincipal { path } => { let principal = ic_base_types::PrincipalId::new_self_authenticating(&std::fs::read(path)?); println!("{}", principal); @@ -263,11 +250,6 @@ async fn async_main() -> Result<(), anyhow::Error> { cli::Commands::Propose { args } => runner_instance.ic_admin.run_passthrough_propose(args, dry_run).await, cli::Commands::UpdateUnassignedNodes { nns_subnet_id } => { - let runner_instance = if target_network.is_mainnet() { - runner_instance.as_automation() - } else { - runner_instance - }; let nns_subnet_id = match nns_subnet_id { Some(subnet_id) => subnet_id.to_owned(), None => { @@ -326,11 +308,6 @@ async fn async_main() -> Result<(), anyhow::Error> { }, cli::Commands::Hostos(nodes) => { - let runner_instance = if target_network.is_mainnet() { - runner_instance.as_automation() - } else { - runner_instance - }; match &nodes.subcommand { cli::hostos::Commands::Rollout { version, nodes } => runner_instance.hostos_rollout(nodes.clone(), version, dry_run, None).await, cli::hostos::Commands::RolloutFromNodeGroup { @@ -544,14 +521,22 @@ async fn async_main() -> Result<(), anyhow::Error> { println!("{}", proposal); Ok(()) } + cli::proposals::Commands::Analyze { proposal_id } => { + let nns_url = target_network.get_nns_urls().first().expect("Should have at least one NNS URL"); + let client = GovernanceCanisterWrapper::from(CanisterClient::from_anonymous(nns_url)?); + let proposal = client.get_proposal(*proposal_id).await?; + if let Some((info, change_membership)) = filter_map_nns_function_proposals::(&vec![proposal]).first() + { + runner_instance.decentralization_change(change_membership).await? + } + Ok(()) + } }, - } + }; + runner_instance.stop_backend(); + r }) - .await; - - srv.stop(false).await; - - r + .await } // Construct MinNakamotoCoefficients from an array (slice) of ["key=value"], and diff --git a/rs/cli/src/runner.rs b/rs/cli/src/runner.rs index f87c20afd..1326874c8 100644 --- a/rs/cli/src/runner.rs +++ b/rs/cli/src/runner.rs @@ -3,27 +3,85 @@ use crate::ic_admin::ProposeOptions; use crate::operations::hostos_rollout::{HostosRollout, HostosRolloutResponse, NodeGroupUpdate}; use crate::ops_subnet_node_replace; use crate::{ic_admin, local_unused_port}; +use decentralization::network::{SubnetQuerier, SubnetQueryBy}; use decentralization::SubnetChangeResponse; use futures::future::join_all; use ic_base_types::PrincipalId; +use ic_management_backend::endpoints; use ic_management_backend::proposal::ProposalAgent; use ic_management_backend::public_dashboard::query_ic_dashboard_list; use ic_management_backend::registry::{self, RegistryState}; use ic_management_types::requests::NodesRemoveRequest; -use ic_management_types::{Artifact, Network, Node, NodeFeature, NodeProvidersResponse}; +use ic_management_types::{Artifact, Network, Node, NodeFeature, NodeProvidersResponse, TopologyChangePayload}; use itertools::Itertools; use log::{info, warn}; +use registry_canister::mutations::do_change_subnet_membership::ChangeSubnetMembershipPayload; +use std::cell::RefCell; use std::collections::BTreeMap; +use std::thread; use tabled::builder::Builder; use tabled::settings::Style; +use std::sync::mpsc; +use actix_web::dev::ServerHandle; + pub struct Runner { pub ic_admin: ic_admin::IcAdminWrapper, - dashboard_backend_client: DashboardBackendClient, registry: RegistryState, + dashboard_backend_client: RefCell>, + backend_srv: RefCell>, } impl Runner { + pub async fn get_backend_client(&self) -> anyhow::Result { + if let Some(dashboard_backend_client) = &*self.dashboard_backend_client.borrow() { + return Ok(dashboard_backend_client.clone()); + }; + + let backend_port = local_unused_port(); + let backend_url = format!("http://localhost:{}/", backend_port); + let (tx, rx) = mpsc::channel(); + + let target_network_backend = self.registry.network(); + thread::spawn(move || { + let rt = tokio::runtime::Runtime::new().unwrap(); + rt.block_on(async move { + endpoints::run_backend(&target_network_backend, "127.0.0.1", backend_port, true, Some(tx)) + .await + .expect("failed") + }); + }); + let srv = rx.recv().unwrap(); + let dashboard_backend_client = DashboardBackendClient::new_with_backend_url(backend_url); + + self.dashboard_backend_client.borrow_mut().get_or_insert_with(|| dashboard_backend_client.clone()); + self.backend_srv.borrow_mut().get_or_insert_with(|| srv.clone()); + + Ok(dashboard_backend_client) + } + + pub async fn stop_backend(&self) -> anyhow::Result<()> { + if let Some(backend_srv) = &*self.backend_srv.borrow() { + backend_srv.stop(false).await; + }; + Ok(()) + } + + pub async fn new(ic_admin: ic_admin::IcAdminWrapper, network: &Network) -> anyhow::Result { + let mut registry = registry::RegistryState::new(network, true).await; + let node_providers = query_ic_dashboard_list::("v3/node-providers") + .await? + .node_providers; + registry.update_node_details(&node_providers).await?; + + Ok(Self { + ic_admin, + registry, + dashboard_backend_client: RefCell::new(None), + backend_srv: RefCell::new(None), + }) + } + pub async fn deploy(&self, subnet: &PrincipalId, version: &str, dry_run: bool) -> anyhow::Result<()> { self.ic_admin .propose_run( @@ -59,7 +117,7 @@ impl Runner { dry_run: bool, ) -> anyhow::Result<()> { let subnet = request.subnet; - let change = self.dashboard_backend_client.subnet_resize(request).await?; + let change = self.get_backend_client().await?.subnet_resize(request).await?; if verbose { if let Some(run_log) = &change.run_log { println!("{}\n", run_log.join("\n")); @@ -107,7 +165,7 @@ impl Runner { println!("{}", self.ic_admin.grep_subcommand_arguments("propose-to-create-subnet")); return Ok(()); } - let subnet_creation_data = self.dashboard_backend_client.subnet_create(request).await?; + let subnet_creation_data = self.get_backend_client().await?.subnet_create(request).await?; if verbose { if let Some(run_log) = &subnet_creation_data.run_log { println!("{}\n", run_log.join("\n")); @@ -116,7 +174,7 @@ impl Runner { println!("{}", subnet_creation_data); let replica_version = replica_version.unwrap_or( - self.dashboard_backend_client + self.get_backend_client().await? .get_nns_replica_version() .await .expect("Failed to get a GuestOS version of the NNS subnet"), @@ -146,7 +204,7 @@ impl Runner { verbose: bool, dry_run: bool, ) -> anyhow::Result<()> { - let change = self.dashboard_backend_client.membership_replace(request).await?; + let change = self.get_backend_client().await?.membership_replace(request).await?; if verbose { if let Some(run_log) = &change.run_log { println!("{}\n", run_log.join("\n")); @@ -163,7 +221,7 @@ impl Runner { async fn run_membership_change(&self, change: SubnetChangeResponse, options: ProposeOptions, dry_run: bool) -> anyhow::Result<()> { let subnet_id = change.subnet_id.ok_or_else(|| anyhow::anyhow!("subnet_id is required"))?; - let pending_action = self.dashboard_backend_client.subnet_pending_action(subnet_id).await?; + let pending_action = self.get_backend_client().await?.subnet_pending_action(subnet_id).await?; if let Some(proposal) = pending_action { return Err(anyhow::anyhow!(format!( "There is a pending proposal for this subnet: https://dashboard.internetcomputer.org/proposal/{}", @@ -186,44 +244,8 @@ impl Runner { Ok(()) } - pub async fn new_with_network_and_backend_port(ic_admin: ic_admin::IcAdminWrapper, network: &Network, backend_port: u16) -> anyhow::Result { - let backend_url = format!("http://localhost:{}/", backend_port); - - let dashboard_backend_client = DashboardBackendClient::new_with_backend_url(backend_url); - let mut registry = registry::RegistryState::new(network, true).await; - let node_providers = query_ic_dashboard_list::("v3/node-providers") - .await? - .node_providers; - registry.update_node_details(&node_providers).await?; - - Ok(Self { - ic_admin, - dashboard_backend_client, - // TODO: Remove once DREL-118 completed. - registry, - }) - } - - pub async fn new(ic_admin: ic_admin::IcAdminWrapper, network: &Network) -> anyhow::Result { - // TODO: Remove once DREL-118 completed. - let backend_port = local_unused_port(); - let backend_url = format!("http://localhost:{}/", backend_port); - let dashboard_backend_client = DashboardBackendClient::new_with_backend_url(backend_url); - - let mut registry = registry::RegistryState::new(network, true).await; - let node_providers = query_ic_dashboard_list::("v3/node-providers") - .await? - .node_providers; - registry.update_node_details(&node_providers).await?; - Ok(Self { - ic_admin, - dashboard_backend_client, - registry, - }) - } - pub async fn prepare_versions_to_retire(&self, release_artifact: &Artifact, edit_summary: bool) -> anyhow::Result<(String, Option>)> { - let retireable_versions = self.dashboard_backend_client.get_retireable_versions(release_artifact).await?; + let retireable_versions = self.get_backend_client().await?.get_retireable_versions(release_artifact).await?; let versions = if retireable_versions.is_empty() { Vec::new() @@ -398,7 +420,7 @@ impl Runner { } pub async fn remove_nodes(&self, request: NodesRemoveRequest, dry_run: bool) -> anyhow::Result<()> { - let node_remove_response = self.dashboard_backend_client.remove_nodes(request).await?; + let node_remove_response = self.get_backend_client().await?.remove_nodes(request).await?; let mut node_removals = node_remove_response.removals; node_removals.sort_by_key(|nr| nr.reason.message()); @@ -452,7 +474,7 @@ impl Runner { _verbose: bool, simulate: bool, ) -> Result<(), anyhow::Error> { - let change = self.dashboard_backend_client.network_heal(request).await?; + let change = self.get_backend_client().await?.network_heal(request).await?; println!("{}", change); let errors = join_all(change.subnets_change_response.iter().map(|subnet_change_response| async move { @@ -478,4 +500,43 @@ impl Runner { Ok(()) } + + pub async fn decentralization_change(&self, change: &ChangeSubnetMembershipPayload) -> Result<(), anyhow::Error> { + if let Some(id) = change.get_subnet() { + let subnet_before = self.registry.subnet(SubnetQueryBy::SubnetId(id)).await.map_err(|e| anyhow::anyhow!(e))?; + let added_nodes = self + .registry + .nodes() + .values() + .filter(|n| change.get_added_node_ids().contains(&n.principal)) + .map(decentralization::network::Node::from) + .collect_vec(); + let removed_nodes = self + .registry + .nodes() + .values() + .filter(|n| change.get_removed_node_ids().contains(&n.principal)) + .map(decentralization::network::Node::from) + .collect_vec(); + + let subnet_after = subnet_before + .clone() + .with_nodes(added_nodes) + .without_nodes(removed_nodes) + .map_err(|e| anyhow::anyhow!(e))?; + + let subnet_change: SubnetChangeResponse = SubnetChangeResponse { + added: change.get_added_node_ids(), + removed: change.get_removed_node_ids(), + subnet_id: Some(id), + score_before: subnet_before.nakamoto_score(), + score_after: subnet_after.nakamoto_score(), + ..Default::default() + }; + + println!("{}", subnet_change) + } + + Ok(()) + } } diff --git a/rs/ic-management-backend/src/proposal.rs b/rs/ic-management-backend/src/proposal.rs index 774682984..e3574400b 100644 --- a/rs/ic-management-backend/src/proposal.rs +++ b/rs/ic-management-backend/src/proposal.rs @@ -8,11 +8,12 @@ use candid::{Decode, Encode}; use futures_util::future::try_join_all; use ic_agent::agent::http_transport::ReqwestTransport; use ic_agent::Agent; +use ic_management_types::filter_map_nns_function_proposals; use ic_management_types::UpdateElectedHostosVersionsProposal; use ic_management_types::UpdateElectedReplicaVersionsProposal; use ic_management_types::UpdateNodesHostosVersionsProposal; -use ic_management_types::{NnsFunctionProposal, TopologyChangePayload, TopologyChangeProposal}; -use ic_nns_governance::pb::v1::{proposal::Action, ListProposalInfo, ListProposalInfoResponse, NnsFunction}; +use ic_management_types::{TopologyChangePayload, TopologyChangeProposal}; +use ic_nns_governance::pb::v1::{proposal::Action, ListProposalInfo, ListProposalInfoResponse}; use ic_nns_governance::pb::v1::{ProposalInfo, ProposalStatus, Topic}; use itertools::Itertools; use registry_canister::mutations::do_add_nodes_to_subnet::AddNodesToSubnetPayload; @@ -285,32 +286,4 @@ impl ProposalAgent { }) } } - -fn filter_map_nns_function_proposals(proposals: &[ProposalInfo]) -> Vec<(ProposalInfo, T)> { - proposals - .iter() - .filter(|p| ProposalStatus::try_from(p.status).expect("unknown proposal status") != ProposalStatus::Rejected) - .filter_map(|p| { - p.proposal - .as_ref() - .and_then(|p| p.action.as_ref()) - .ok_or_else(|| anyhow::format_err!("no action")) - .and_then(|a| match a { - Action::ExecuteNnsFunction(function) => { - let func = NnsFunction::try_from(function.nns_function)?; - Ok((func, function.payload.as_slice())) - } - _ => Err(anyhow::format_err!("not an NNS function")), - }) - .and_then(|(function_type, function_payload)| { - if function_type == T::TYPE { - Decode!(function_payload, T).map_err(|e| anyhow::format_err!("failed decoding candid: {}", e)) - } else { - Err(anyhow::format_err!("unsupported NNS function")) - } - }) - .ok() - .map(|payload| (p.clone(), payload)) - }) - .collect() -} + \ No newline at end of file diff --git a/rs/ic-management-types/src/lib.rs b/rs/ic-management-types/src/lib.rs index 89aae7a2c..711b3af59 100644 --- a/rs/ic-management-types/src/lib.rs +++ b/rs/ic-management-types/src/lib.rs @@ -5,6 +5,7 @@ pub use crate::errors::*; use candid::{CandidType, Decode}; use core::hash::Hash; use ic_base_types::NodeId; +use ic_nns_governance::pb::v1::proposal::Action; use ic_nns_governance::pb::v1::NnsFunction; use ic_nns_governance::pb::v1::ProposalInfo; use ic_nns_governance::pb::v1::ProposalStatus; @@ -32,6 +33,35 @@ use strum::VariantNames; use strum_macros::EnumString; use url::Url; +pub fn filter_map_nns_function_proposals(proposals: &[ProposalInfo]) -> Vec<(ProposalInfo, T)> { + proposals + .iter() + .filter(|p| ProposalStatus::try_from(p.status).expect("unknown proposal status") != ProposalStatus::Rejected) + .filter_map(|p| { + p.proposal + .as_ref() + .and_then(|p| p.action.as_ref()) + .ok_or_else(|| anyhow::format_err!("no action")) + .and_then(|a| match a { + Action::ExecuteNnsFunction(function) => { + let func = NnsFunction::try_from(function.nns_function)?; + Ok((func, function.payload.as_slice())) + } + _ => Err(anyhow::format_err!("not an NNS function")), + }) + .and_then(|(function_type, function_payload)| { + if function_type == T::TYPE { + Decode!(function_payload, T).map_err(|e| anyhow::format_err!("failed decoding candid: {}", e)) + } else { + Err(anyhow::format_err!("unsupported NNS function")) + } + }) + .ok() + .map(|payload| (p.clone(), payload)) + }) + .collect() +} + pub trait NnsFunctionProposal: CandidType + serde::de::DeserializeOwned { const TYPE: NnsFunction; fn decode(function_type: NnsFunction, function_payload: &[u8]) -> anyhow::Result { From 77c2106adf48f24258fca0bd29914b9b5e4ca453 Mon Sep 17 00:00:00 2001 From: Pietro Date: Fri, 21 Jun 2024 19:31:01 +0200 Subject: [PATCH 02/11] Update as_automation --- rs/cli/src/main.rs | 18 +++++++++++------- rs/cli/src/runner.rs | 13 ++++++++++--- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/rs/cli/src/main.rs b/rs/cli/src/main.rs index 803f93185..62b84d8d8 100644 --- a/rs/cli/src/main.rs +++ b/rs/cli/src/main.rs @@ -250,19 +250,22 @@ async fn async_main() -> Result<(), anyhow::Error> { cli::Commands::Propose { args } => runner_instance.ic_admin.run_passthrough_propose(args, dry_run).await, cli::Commands::UpdateUnassignedNodes { nns_subnet_id } => { + let ic_admin = if target_network.is_mainnet() { + runner_instance.ic_admin.clone().as_automation() + } else { + runner_instance.ic_admin.clone() + }; let nns_subnet_id = match nns_subnet_id { Some(subnet_id) => subnet_id.to_owned(), None => { - let res = runner_instance - .ic_admin + let res = ic_admin .run_passthrough_get(&["get-subnet-list".to_string()], true) .await?; let subnet_list: Vec = serde_json::from_str(&res)?; subnet_list.first().ok_or_else(|| anyhow::anyhow!("No subnet found"))?.clone() } }; - runner_instance - .ic_admin + ic_admin .update_unassigned_nodes(&nns_subnet_id, &target_network, dry_run) .await } @@ -308,8 +311,9 @@ async fn async_main() -> Result<(), anyhow::Error> { }, cli::Commands::Hostos(nodes) => { + let as_automation = target_network.is_mainnet(); match &nodes.subcommand { - cli::hostos::Commands::Rollout { version, nodes } => runner_instance.hostos_rollout(nodes.clone(), version, dry_run, None).await, + cli::hostos::Commands::Rollout { version, nodes } => runner_instance.hostos_rollout(nodes.clone(), version, dry_run, None, as_automation).await, cli::hostos::Commands::RolloutFromNodeGroup { version, assignment, @@ -319,7 +323,7 @@ async fn async_main() -> Result<(), anyhow::Error> { } => { let update_group = NodeGroupUpdate::new(*assignment, *owner, NumberOfNodes::from_str(nodes_in_group)?); if let Some((nodes_to_update, summary)) = runner_instance.hostos_rollout_nodes(update_group, version, exclude).await? { - return runner_instance.hostos_rollout(nodes_to_update, version, dry_run, Some(summary)).await; + return runner_instance.hostos_rollout(nodes_to_update, version, dry_run, Some(summary), as_automation).await; } Ok(()) } @@ -533,7 +537,7 @@ async fn async_main() -> Result<(), anyhow::Error> { } }, }; - runner_instance.stop_backend(); + let _ = runner_instance.stop_backend().await; r }) .await diff --git a/rs/cli/src/runner.rs b/rs/cli/src/runner.rs index 1326874c8..80437cfb1 100644 --- a/rs/cli/src/runner.rs +++ b/rs/cli/src/runner.rs @@ -1,5 +1,5 @@ use crate::clients::DashboardBackendClient; -use crate::ic_admin::ProposeOptions; +use crate::ic_admin::{IcAdminWrapper, ProposeOptions}; use crate::operations::hostos_rollout::{HostosRollout, HostosRolloutResponse, NodeGroupUpdate}; use crate::ops_subnet_node_replace; use crate::{ic_admin, local_unused_port}; @@ -38,6 +38,7 @@ impl Runner { return Ok(dashboard_backend_client.clone()); }; + // This will be executed just once creating the backend let backend_port = local_unused_port(); let backend_url = format!("http://localhost:{}/", backend_port); let (tx, rx) = mpsc::channel(); @@ -396,9 +397,15 @@ impl Runner { } } } - pub async fn hostos_rollout(&self, nodes: Vec, version: &str, dry_run: bool, maybe_summary: Option) -> anyhow::Result<()> { + pub async fn hostos_rollout(&self, nodes: Vec, version: &str, dry_run: bool, maybe_summary: Option, as_automation: bool) -> anyhow::Result<()> { + let ic_admin = if as_automation { + self.ic_admin.clone().as_automation() + } else { + self.ic_admin.clone() + }; + let title = format!("Set HostOS version: {version} on {} nodes", nodes.clone().len()); - self.ic_admin + ic_admin .propose_run( ic_admin::ProposeCommand::DeployHostosToSomeNodes { nodes: nodes.clone(), From e1084f997b3f1438f662fac2de705622e1e04b2a Mon Sep 17 00:00:00 2001 From: Pietro Date: Fri, 21 Jun 2024 19:41:26 +0200 Subject: [PATCH 03/11] Remove analyze command --- rs/cli/src/main.rs | 10 ------- rs/cli/src/runner.rs | 38 ------------------------ rs/ic-management-backend/src/proposal.rs | 31 +++++++++++++++++-- rs/ic-management-types/src/lib.rs | 30 ------------------- 4 files changed, 29 insertions(+), 80 deletions(-) diff --git a/rs/cli/src/main.rs b/rs/cli/src/main.rs index 62b84d8d8..c84b86578 100644 --- a/rs/cli/src/main.rs +++ b/rs/cli/src/main.rs @@ -525,16 +525,6 @@ async fn async_main() -> Result<(), anyhow::Error> { println!("{}", proposal); Ok(()) } - cli::proposals::Commands::Analyze { proposal_id } => { - let nns_url = target_network.get_nns_urls().first().expect("Should have at least one NNS URL"); - let client = GovernanceCanisterWrapper::from(CanisterClient::from_anonymous(nns_url)?); - let proposal = client.get_proposal(*proposal_id).await?; - if let Some((info, change_membership)) = filter_map_nns_function_proposals::(&vec![proposal]).first() - { - runner_instance.decentralization_change(change_membership).await? - } - Ok(()) - } }, }; let _ = runner_instance.stop_backend().await; diff --git a/rs/cli/src/runner.rs b/rs/cli/src/runner.rs index 80437cfb1..1cb0a8f0f 100644 --- a/rs/cli/src/runner.rs +++ b/rs/cli/src/runner.rs @@ -508,42 +508,4 @@ impl Runner { Ok(()) } - pub async fn decentralization_change(&self, change: &ChangeSubnetMembershipPayload) -> Result<(), anyhow::Error> { - if let Some(id) = change.get_subnet() { - let subnet_before = self.registry.subnet(SubnetQueryBy::SubnetId(id)).await.map_err(|e| anyhow::anyhow!(e))?; - let added_nodes = self - .registry - .nodes() - .values() - .filter(|n| change.get_added_node_ids().contains(&n.principal)) - .map(decentralization::network::Node::from) - .collect_vec(); - let removed_nodes = self - .registry - .nodes() - .values() - .filter(|n| change.get_removed_node_ids().contains(&n.principal)) - .map(decentralization::network::Node::from) - .collect_vec(); - - let subnet_after = subnet_before - .clone() - .with_nodes(added_nodes) - .without_nodes(removed_nodes) - .map_err(|e| anyhow::anyhow!(e))?; - - let subnet_change: SubnetChangeResponse = SubnetChangeResponse { - added: change.get_added_node_ids(), - removed: change.get_removed_node_ids(), - subnet_id: Some(id), - score_before: subnet_before.nakamoto_score(), - score_after: subnet_after.nakamoto_score(), - ..Default::default() - }; - - println!("{}", subnet_change) - } - - Ok(()) - } } diff --git a/rs/ic-management-backend/src/proposal.rs b/rs/ic-management-backend/src/proposal.rs index e3574400b..50a6d97de 100644 --- a/rs/ic-management-backend/src/proposal.rs +++ b/rs/ic-management-backend/src/proposal.rs @@ -8,7 +8,6 @@ use candid::{Decode, Encode}; use futures_util::future::try_join_all; use ic_agent::agent::http_transport::ReqwestTransport; use ic_agent::Agent; -use ic_management_types::filter_map_nns_function_proposals; use ic_management_types::UpdateElectedHostosVersionsProposal; use ic_management_types::UpdateElectedReplicaVersionsProposal; use ic_management_types::UpdateNodesHostosVersionsProposal; @@ -286,4 +285,32 @@ impl ProposalAgent { }) } } - \ No newline at end of file + +fn filter_map_nns_function_proposals(proposals: &[ProposalInfo]) -> Vec<(ProposalInfo, T)> { + proposals + .iter() + .filter(|p| ProposalStatus::try_from(p.status).expect("unknown proposal status") != ProposalStatus::Rejected) + .filter_map(|p| { + p.proposal + .as_ref() + .and_then(|p| p.action.as_ref()) + .ok_or_else(|| anyhow::format_err!("no action")) + .and_then(|a| match a { + Action::ExecuteNnsFunction(function) => { + let func = NnsFunction::try_from(function.nns_function)?; + Ok((func, function.payload.as_slice())) + } + _ => Err(anyhow::format_err!("not an NNS function")), + }) + .and_then(|(function_type, function_payload)| { + if function_type == T::TYPE { + Decode!(function_payload, T).map_err(|e| anyhow::format_err!("failed decoding candid: {}", e)) + } else { + Err(anyhow::format_err!("unsupported NNS function")) + } + }) + .ok() + .map(|payload| (p.clone(), payload)) + }) + .collect() +} diff --git a/rs/ic-management-types/src/lib.rs b/rs/ic-management-types/src/lib.rs index 711b3af59..89aae7a2c 100644 --- a/rs/ic-management-types/src/lib.rs +++ b/rs/ic-management-types/src/lib.rs @@ -5,7 +5,6 @@ pub use crate::errors::*; use candid::{CandidType, Decode}; use core::hash::Hash; use ic_base_types::NodeId; -use ic_nns_governance::pb::v1::proposal::Action; use ic_nns_governance::pb::v1::NnsFunction; use ic_nns_governance::pb::v1::ProposalInfo; use ic_nns_governance::pb::v1::ProposalStatus; @@ -33,35 +32,6 @@ use strum::VariantNames; use strum_macros::EnumString; use url::Url; -pub fn filter_map_nns_function_proposals(proposals: &[ProposalInfo]) -> Vec<(ProposalInfo, T)> { - proposals - .iter() - .filter(|p| ProposalStatus::try_from(p.status).expect("unknown proposal status") != ProposalStatus::Rejected) - .filter_map(|p| { - p.proposal - .as_ref() - .and_then(|p| p.action.as_ref()) - .ok_or_else(|| anyhow::format_err!("no action")) - .and_then(|a| match a { - Action::ExecuteNnsFunction(function) => { - let func = NnsFunction::try_from(function.nns_function)?; - Ok((func, function.payload.as_slice())) - } - _ => Err(anyhow::format_err!("not an NNS function")), - }) - .and_then(|(function_type, function_payload)| { - if function_type == T::TYPE { - Decode!(function_payload, T).map_err(|e| anyhow::format_err!("failed decoding candid: {}", e)) - } else { - Err(anyhow::format_err!("unsupported NNS function")) - } - }) - .ok() - .map(|payload| (p.clone(), payload)) - }) - .collect() -} - pub trait NnsFunctionProposal: CandidType + serde::de::DeserializeOwned { const TYPE: NnsFunction; fn decode(function_type: NnsFunction, function_payload: &[u8]) -> anyhow::Result { From d8b982ff4fbe88617621352949a518d65a192185 Mon Sep 17 00:00:00 2001 From: Pietro Date: Fri, 21 Jun 2024 19:48:07 +0200 Subject: [PATCH 04/11] Allow dashboard backend removal --- rs/cli/src/cli.rs | 6 ------ rs/cli/src/main.rs | 10 ++-------- rs/cli/src/runner.rs | 6 ++---- rs/ic-management-backend/src/proposal.rs | 4 ++-- 4 files changed, 6 insertions(+), 20 deletions(-) diff --git a/rs/cli/src/cli.rs b/rs/cli/src/cli.rs index be892d425..84e6bdc97 100644 --- a/rs/cli/src/cli.rs +++ b/rs/cli/src/cli.rs @@ -615,12 +615,6 @@ pub mod proposals { /// Proposal ID proposal_id: u64, }, - - /// Print decentralization for a subnet-change proposal gived ID - Analyze { - /// Proposal ID - proposal_id: u64, - }, } #[derive(ValueEnum, Clone, Debug)] diff --git a/rs/cli/src/main.rs b/rs/cli/src/main.rs index c84b86578..f9f40aa1a 100644 --- a/rs/cli/src/main.rs +++ b/rs/cli/src/main.rs @@ -1,20 +1,16 @@ use crate::ic_admin::IcAdminWrapper; use clap::{error::ErrorKind, CommandFactory, Parser}; -use decentralization::network::SubnetChange; use dialoguer::Confirm; use dotenv::dotenv; use dre::detect_neuron::Auth; use dre::general::{filter_proposals, get_node_metrics_history, vote_on_proposals}; use dre::operations::hostos_rollout::{NodeGroupUpdate, NumberOfNodes}; -use dre::{cli, ic_admin, local_unused_port, registry_dump, runner}; +use dre::{cli, ic_admin, registry_dump, runner}; use ic_base_types::CanisterId; use ic_canisters::governance::{governance_canister_version, GovernanceCanisterWrapper}; use ic_canisters::CanisterClient; -use ic_management_backend::endpoints; -use ic_management_types::filter_map_nns_function_proposals; use ic_management_types::requests::NodesRemoveRequest; -use ic_management_types::{Artifact, MinNakamotoCoefficients, NnsFunctionProposal, NodeFeature}; -use registry_canister::mutations::do_change_subnet_membership::ChangeSubnetMembershipPayload; +use ic_management_types::{Artifact, MinNakamotoCoefficients, NodeFeature}; use ic_nns_common::pb::v1::ProposalId; use ic_nns_governance::pb::v1::ListProposalInfo; @@ -23,8 +19,6 @@ use regex::Regex; use serde_json::Value; use std::collections::BTreeMap; use std::str::FromStr; -use std::sync::mpsc; -use std::thread; use tokio::runtime::Runtime; const STAGING_NEURON_ID: u64 = 49; diff --git a/rs/cli/src/runner.rs b/rs/cli/src/runner.rs index 1cb0a8f0f..60cd6abac 100644 --- a/rs/cli/src/runner.rs +++ b/rs/cli/src/runner.rs @@ -1,9 +1,8 @@ use crate::clients::DashboardBackendClient; -use crate::ic_admin::{IcAdminWrapper, ProposeOptions}; +use crate::ic_admin::ProposeOptions; use crate::operations::hostos_rollout::{HostosRollout, HostosRolloutResponse, NodeGroupUpdate}; use crate::ops_subnet_node_replace; use crate::{ic_admin, local_unused_port}; -use decentralization::network::{SubnetQuerier, SubnetQueryBy}; use decentralization::SubnetChangeResponse; use futures::future::join_all; use ic_base_types::PrincipalId; @@ -12,10 +11,9 @@ use ic_management_backend::proposal::ProposalAgent; use ic_management_backend::public_dashboard::query_ic_dashboard_list; use ic_management_backend::registry::{self, RegistryState}; use ic_management_types::requests::NodesRemoveRequest; -use ic_management_types::{Artifact, Network, Node, NodeFeature, NodeProvidersResponse, TopologyChangePayload}; +use ic_management_types::{Artifact, Network, Node, NodeFeature, NodeProvidersResponse}; use itertools::Itertools; use log::{info, warn}; -use registry_canister::mutations::do_change_subnet_membership::ChangeSubnetMembershipPayload; use std::cell::RefCell; use std::collections::BTreeMap; use std::thread; diff --git a/rs/ic-management-backend/src/proposal.rs b/rs/ic-management-backend/src/proposal.rs index 50a6d97de..774682984 100644 --- a/rs/ic-management-backend/src/proposal.rs +++ b/rs/ic-management-backend/src/proposal.rs @@ -11,8 +11,8 @@ use ic_agent::Agent; use ic_management_types::UpdateElectedHostosVersionsProposal; use ic_management_types::UpdateElectedReplicaVersionsProposal; use ic_management_types::UpdateNodesHostosVersionsProposal; -use ic_management_types::{TopologyChangePayload, TopologyChangeProposal}; -use ic_nns_governance::pb::v1::{proposal::Action, ListProposalInfo, ListProposalInfoResponse}; +use ic_management_types::{NnsFunctionProposal, TopologyChangePayload, TopologyChangeProposal}; +use ic_nns_governance::pb::v1::{proposal::Action, ListProposalInfo, ListProposalInfoResponse, NnsFunction}; use ic_nns_governance::pb::v1::{ProposalInfo, ProposalStatus, Topic}; use itertools::Itertools; use registry_canister::mutations::do_add_nodes_to_subnet::AddNodesToSubnetPayload; From bb99929b719b33dbcd17781d8f2feb6e7a8a70a1 Mon Sep 17 00:00:00 2001 From: Pietro Date: Fri, 21 Jun 2024 19:54:28 +0200 Subject: [PATCH 05/11] Run repin --- Cargo.Bazel.lock | 22 +++++++++++++++++++--- rs/cli/src/main.rs | 22 +++++++++++----------- rs/cli/src/runner.rs | 22 +++++++++++++++------- 3 files changed, 45 insertions(+), 21 deletions(-) diff --git a/Cargo.Bazel.lock b/Cargo.Bazel.lock index 8e52068ea..c06a2dfac 100644 --- a/Cargo.Bazel.lock +++ b/Cargo.Bazel.lock @@ -1,5 +1,5 @@ { - "checksum": "87e2c8f3dba5c353695777f770c5dd8e59ed40002ef4a441217e73677175c77b", + "checksum": "c3dcea9e1d5b4d6c8e5e6f9ece05a77fcd0901359c76444219a2d1904e54e8c2", "crates": { "actix-codec 0.5.2": { "name": "actix-codec", @@ -11213,6 +11213,10 @@ ], "deps": { "common": [ + { + "id": "actix-web 4.6.0", + "target": "actix_web" + }, { "id": "anyhow 1.0.86", "target": "anyhow" @@ -11434,6 +11438,10 @@ }, "deps_dev": { "common": [ + { + "id": "actix-rt 2.9.0", + "target": "actix_rt" + }, { "id": "wiremock 0.6.0", "target": "wiremock" @@ -28936,29 +28944,35 @@ ], "crate_features": { "common": [ - "elf", - "errno", "general", "ioctl", "no_std" ], "selects": { "aarch64-unknown-linux-gnu": [ + "elf", + "errno", "prctl", "std", "system" ], "arm-unknown-linux-gnueabi": [ + "elf", + "errno", "prctl", "std", "system" ], "armv7-unknown-linux-gnueabi": [ + "elf", + "errno", "prctl", "std", "system" ], "i686-unknown-linux-gnu": [ + "elf", + "errno", "prctl", "std", "system" @@ -28974,6 +28988,8 @@ "system" ], "x86_64-unknown-linux-gnu": [ + "elf", + "errno", "prctl", "std", "system" diff --git a/rs/cli/src/main.rs b/rs/cli/src/main.rs index f0692dbaf..2c07b0317 100644 --- a/rs/cli/src/main.rs +++ b/rs/cli/src/main.rs @@ -74,10 +74,10 @@ async fn async_main() -> Result<(), anyhow::Error> { .await .expect("Failed to create authenticated CLI"); let ic_admin_wrapper = IcAdminWrapper::from_cli(cli); - + let runner_instance = runner::Runner::new(ic_admin_wrapper, &target_network) - .await - .expect("Failed to create a runner"); + .await + .expect("Failed to create a runner"); let r = match &cli_opts.subcommand { cli::Commands::DerToPrincipal { path } => { @@ -257,16 +257,12 @@ async fn async_main() -> Result<(), anyhow::Error> { let nns_subnet_id = match nns_subnet_id { Some(subnet_id) => subnet_id.to_owned(), None => { - let res = ic_admin - .run_passthrough_get(&["get-subnet-list".to_string()], true) - .await?; + let res = ic_admin.run_passthrough_get(&["get-subnet-list".to_string()], true).await?; let subnet_list: Vec = serde_json::from_str(&res)?; subnet_list.first().ok_or_else(|| anyhow::anyhow!("No subnet found"))?.clone() } }; - ic_admin - .update_unassigned_nodes(&nns_subnet_id, &target_network, dry_run) - .await + ic_admin.update_unassigned_nodes(&nns_subnet_id, &target_network, dry_run).await } cli::Commands::Version(version_command) => match &version_command { @@ -312,7 +308,9 @@ async fn async_main() -> Result<(), anyhow::Error> { cli::Commands::Hostos(nodes) => { let as_automation = target_network.is_mainnet(); match &nodes.subcommand { - cli::hostos::Commands::Rollout { version, nodes } => runner_instance.hostos_rollout(nodes.clone(), version, dry_run, None, as_automation).await, + cli::hostos::Commands::Rollout { version, nodes } => { + runner_instance.hostos_rollout(nodes.clone(), version, dry_run, None, as_automation).await + } cli::hostos::Commands::RolloutFromNodeGroup { version, assignment, @@ -322,7 +320,9 @@ async fn async_main() -> Result<(), anyhow::Error> { } => { let update_group = NodeGroupUpdate::new(*assignment, *owner, NumberOfNodes::from_str(nodes_in_group)?); if let Some((nodes_to_update, summary)) = runner_instance.hostos_rollout_nodes(update_group, version, exclude).await? { - return runner_instance.hostos_rollout(nodes_to_update, version, dry_run, Some(summary), as_automation).await; + return runner_instance + .hostos_rollout(nodes_to_update, version, dry_run, Some(summary), as_automation) + .await; } Ok(()) } diff --git a/rs/cli/src/runner.rs b/rs/cli/src/runner.rs index 60cd6abac..707d2820b 100644 --- a/rs/cli/src/runner.rs +++ b/rs/cli/src/runner.rs @@ -3,6 +3,7 @@ use crate::ic_admin::ProposeOptions; use crate::operations::hostos_rollout::{HostosRollout, HostosRolloutResponse, NodeGroupUpdate}; use crate::ops_subnet_node_replace; use crate::{ic_admin, local_unused_port}; +use actix_web::dev::ServerHandle; use decentralization::SubnetChangeResponse; use futures::future::join_all; use ic_base_types::PrincipalId; @@ -16,12 +17,10 @@ use itertools::Itertools; use log::{info, warn}; use std::cell::RefCell; use std::collections::BTreeMap; +use std::sync::mpsc; use std::thread; use tabled::builder::Builder; use tabled::settings::Style; -use std::sync::mpsc; -use actix_web::dev::ServerHandle; - pub struct Runner { pub ic_admin: ic_admin::IcAdminWrapper, @@ -53,7 +52,9 @@ impl Runner { let srv = rx.recv().unwrap(); let dashboard_backend_client = DashboardBackendClient::new_with_backend_url(backend_url); - self.dashboard_backend_client.borrow_mut().get_or_insert_with(|| dashboard_backend_client.clone()); + self.dashboard_backend_client + .borrow_mut() + .get_or_insert_with(|| dashboard_backend_client.clone()); self.backend_srv.borrow_mut().get_or_insert_with(|| srv.clone()); Ok(dashboard_backend_client) @@ -173,7 +174,8 @@ impl Runner { println!("{}", subnet_creation_data); let replica_version = replica_version.unwrap_or( - self.get_backend_client().await? + self.get_backend_client() + .await? .get_nns_replica_version() .await .expect("Failed to get a GuestOS version of the NNS subnet"), @@ -395,7 +397,14 @@ impl Runner { } } } - pub async fn hostos_rollout(&self, nodes: Vec, version: &str, dry_run: bool, maybe_summary: Option, as_automation: bool) -> anyhow::Result<()> { + pub async fn hostos_rollout( + &self, + nodes: Vec, + version: &str, + dry_run: bool, + maybe_summary: Option, + as_automation: bool, + ) -> anyhow::Result<()> { let ic_admin = if as_automation { self.ic_admin.clone().as_automation() } else { @@ -505,5 +514,4 @@ impl Runner { Ok(()) } - } From ab411747b0d87e08ba8dc12061bad19124bc67b3 Mon Sep 17 00:00:00 2001 From: Pietro Date: Fri, 21 Jun 2024 20:00:20 +0200 Subject: [PATCH 06/11] Fix typo --- rs/cli/src/runner.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rs/cli/src/runner.rs b/rs/cli/src/runner.rs index 707d2820b..005bc353c 100644 --- a/rs/cli/src/runner.rs +++ b/rs/cli/src/runner.rs @@ -69,7 +69,7 @@ impl Runner { pub async fn new(ic_admin: ic_admin::IcAdminWrapper, network: &Network) -> anyhow::Result { let mut registry = registry::RegistryState::new(network, true).await; - let node_providers = query_ic_dashboard_list::("v3/node-providers") + let node_providers = query_ic_dashboard_list::(network, "v3/node-providers") .await? .node_providers; registry.update_node_details(&node_providers).await?; From f69028b0db86294df2637f85eccd4db881c1f8f7 Mon Sep 17 00:00:00 2001 From: Pietro Date: Fri, 21 Jun 2024 20:03:53 +0200 Subject: [PATCH 07/11] fix clippy --- rs/cli/src/runner.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rs/cli/src/runner.rs b/rs/cli/src/runner.rs index 005bc353c..4aad77ec0 100644 --- a/rs/cli/src/runner.rs +++ b/rs/cli/src/runner.rs @@ -62,7 +62,7 @@ impl Runner { pub async fn stop_backend(&self) -> anyhow::Result<()> { if let Some(backend_srv) = &*self.backend_srv.borrow() { - backend_srv.stop(false).await; + let _ = backend_srv.stop(false).await; }; Ok(()) } From 431db79f7e1bcfde522644731c30d11070b74887 Mon Sep 17 00:00:00 2001 From: Pietro Date: Fri, 21 Jun 2024 23:01:15 +0200 Subject: [PATCH 08/11] Add clone for refcell --- rs/cli/src/runner.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rs/cli/src/runner.rs b/rs/cli/src/runner.rs index 4aad77ec0..5167bb93a 100644 --- a/rs/cli/src/runner.rs +++ b/rs/cli/src/runner.rs @@ -61,7 +61,7 @@ impl Runner { } pub async fn stop_backend(&self) -> anyhow::Result<()> { - if let Some(backend_srv) = &*self.backend_srv.borrow() { + if let Some(backend_srv) = self.backend_srv.borrow().clone() { let _ = backend_srv.stop(false).await; }; Ok(()) From 0045eb3095018f082e147b5e579b29adc5a4fe27 Mon Sep 17 00:00:00 2001 From: Pietro Date: Fri, 21 Jun 2024 23:04:21 +0200 Subject: [PATCH 09/11] Move borrowing --- rs/cli/src/runner.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rs/cli/src/runner.rs b/rs/cli/src/runner.rs index 5167bb93a..8b325a2d7 100644 --- a/rs/cli/src/runner.rs +++ b/rs/cli/src/runner.rs @@ -61,7 +61,9 @@ impl Runner { } pub async fn stop_backend(&self) -> anyhow::Result<()> { - if let Some(backend_srv) = self.backend_srv.borrow().clone() { + let srv = &*self.backend_srv.borrow(); + + if let Some(backend_srv) = srv { let _ = backend_srv.stop(false).await; }; Ok(()) From d600e3ef947a686db09689a3c8496f238bfb935b Mon Sep 17 00:00:00 2001 From: Pietro Date: Fri, 21 Jun 2024 23:05:55 +0200 Subject: [PATCH 10/11] Fix clippy --- rs/cli/src/runner.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/rs/cli/src/runner.rs b/rs/cli/src/runner.rs index 8b325a2d7..257708915 100644 --- a/rs/cli/src/runner.rs +++ b/rs/cli/src/runner.rs @@ -62,7 +62,6 @@ impl Runner { pub async fn stop_backend(&self) -> anyhow::Result<()> { let srv = &*self.backend_srv.borrow(); - if let Some(backend_srv) = srv { let _ = backend_srv.stop(false).await; }; From 10dd6fcdafed76cd774c7dd936f295161e48eb82 Mon Sep 17 00:00:00 2001 From: Pietro Date: Fri, 21 Jun 2024 23:28:56 +0200 Subject: [PATCH 11/11] Fix clippy --- rs/cli/src/runner.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rs/cli/src/runner.rs b/rs/cli/src/runner.rs index 257708915..f47f5e01a 100644 --- a/rs/cli/src/runner.rs +++ b/rs/cli/src/runner.rs @@ -61,10 +61,10 @@ impl Runner { } pub async fn stop_backend(&self) -> anyhow::Result<()> { - let srv = &*self.backend_srv.borrow(); - if let Some(backend_srv) = srv { - let _ = backend_srv.stop(false).await; - }; + let backend_srv_opt = self.backend_srv.borrow().clone(); + if let Some(backend_srv) = backend_srv_opt { + backend_srv.stop(false).await; + } Ok(()) }