From b2266d04ef52d17890547c1995d941bb5cb20a15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Fri, 29 Aug 2025 11:41:22 +0100 Subject: [PATCH] chore: internal hidden command to force advance nyx epoch (#5964) --- common/commands/src/internal/mod.rs | 3 + .../src/internal/nyx/force_advance_epoch.rs | 116 ++++++++++++++++++ common/commands/src/internal/nyx/mod.rs | 19 +++ tools/nym-cli/src/internal/ecash/mod.rs | 2 +- tools/nym-cli/src/internal/mod.rs | 6 +- tools/nym-cli/src/internal/nyx/mod.rs | 22 ++++ 6 files changed, 166 insertions(+), 2 deletions(-) create mode 100644 common/commands/src/internal/nyx/force_advance_epoch.rs create mode 100644 common/commands/src/internal/nyx/mod.rs create mode 100644 tools/nym-cli/src/internal/nyx/mod.rs diff --git a/common/commands/src/internal/mod.rs b/common/commands/src/internal/mod.rs index 44fa4d2659..fc929a2e4e 100644 --- a/common/commands/src/internal/mod.rs +++ b/common/commands/src/internal/mod.rs @@ -4,6 +4,7 @@ use clap::{Args, Subcommand}; pub mod ecash; +pub mod nyx; #[derive(Debug, Args)] #[clap(args_conflicts_with_subcommands = true, subcommand_required = true)] @@ -16,4 +17,6 @@ pub struct Internal { pub enum InternalCommands { /// Ecash related internal commands Ecash(ecash::InternalEcash), + + Nyx(nyx::InternalNyx), } diff --git a/common/commands/src/internal/nyx/force_advance_epoch.rs b/common/commands/src/internal/nyx/force_advance_epoch.rs new file mode 100644 index 0000000000..3c797ff01c --- /dev/null +++ b/common/commands/src/internal/nyx/force_advance_epoch.rs @@ -0,0 +1,116 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::context::SigningClient; +use anyhow::bail; +use clap::Parser; +use nym_mixnet_contract_common::nym_node::Role; +use nym_mixnet_contract_common::reward_params::NodeRewardingParameters; +use nym_mixnet_contract_common::{ + EpochRewardedSet, EpochState, NodeId, RewardingParams, RoleAssignment, +}; +use nym_validator_client::nyxd::contract_traits::mixnet_query_client::MixnetQueryClientExt; +use nym_validator_client::nyxd::contract_traits::{MixnetQueryClient, MixnetSigningClient}; +use rand::prelude::*; +use rand::thread_rng; + +#[derive(Debug, Parser)] +pub struct Args {} + +fn choose_new_nodes( + params: &RewardingParams, + rewarded_set: &EpochRewardedSet, + role: Role, +) -> Vec { + let mut rng = thread_rng(); + + match role { + Role::EntryGateway => rewarded_set + .assignment + .entry_gateways + .choose_multiple(&mut rng, params.rewarded_set.entry_gateways as usize) + .copied() + .collect(), + Role::Layer1 => rewarded_set + .assignment + .layer1 + .choose_multiple(&mut rng, params.rewarded_set.mixnodes as usize / 3) + .copied() + .collect(), + Role::Layer2 => rewarded_set + .assignment + .layer2 + .choose_multiple(&mut rng, params.rewarded_set.mixnodes as usize / 3) + .copied() + .collect(), + Role::Layer3 => rewarded_set + .assignment + .layer3 + .choose_multiple(&mut rng, params.rewarded_set.mixnodes as usize / 3) + .copied() + .collect(), + Role::ExitGateway => rewarded_set + .assignment + .exit_gateways + .choose_multiple(&mut rng, params.rewarded_set.exit_gateways as usize) + .copied() + .collect(), + Role::Standby => rewarded_set + .assignment + .standby + .choose_multiple(&mut rng, params.rewarded_set.standby as usize) + .copied() + .collect(), + } +} + +pub async fn force_advance_epoch(_: Args, client: SigningClient) -> anyhow::Result<()> { + let current_epoch = client.get_current_interval_details().await?; + let epoch_status = client.get_current_epoch_status().await?; + if epoch_status.being_advanced_by.as_str() != client.address().to_string() { + bail!( + "this client is not authorised to perform any epoch operations. we need {}", + client.address() + ) + } + + let rewarding_params = client.get_rewarding_parameters().await?; + let current_rewarded_set = client.get_rewarded_set().await?; + + if !current_epoch.is_current_epoch_over { + println!("the current epoch is not over yet - there's nothing to do") + } + + // is this most efficient? no. but it's simple + loop { + let epoch_status = client.get_current_epoch_status().await?; + + match epoch_status.state { + EpochState::InProgress => break, + EpochState::Rewarding { final_node_id, .. } => { + println!("rewarding {final_node_id} with big fat 0..."); + client + .reward_node( + final_node_id, + NodeRewardingParameters::new(Default::default(), Default::default()), + None, + ) + .await?; + } + EpochState::ReconcilingEvents => { + println!("trying to reconcile events..."); + client.reconcile_epoch_events(None, None).await?; + } + EpochState::RoleAssignment { next } => { + let nodes = choose_new_nodes(&rewarding_params, ¤t_rewarded_set, next); + println!("assigning {nodes:?} as {next}"); + + client + .assign_roles(RoleAssignment { role: next, nodes }, None) + .await?; + } + } + } + + Ok(()) +} diff --git a/common/commands/src/internal/nyx/mod.rs b/common/commands/src/internal/nyx/mod.rs new file mode 100644 index 0000000000..0ffb24f3a3 --- /dev/null +++ b/common/commands/src/internal/nyx/mod.rs @@ -0,0 +1,19 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use clap::{Args, Subcommand}; + +pub mod force_advance_epoch; + +#[derive(Debug, Args)] +#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)] +pub struct InternalNyx { + #[clap(subcommand)] + pub command: InternalNyxCommands, +} + +#[derive(Debug, Subcommand)] +pub enum InternalNyxCommands { + /// Attempt to force advance the current epoch + ForceAdvanceEpoch(force_advance_epoch::Args), +} diff --git a/tools/nym-cli/src/internal/ecash/mod.rs b/tools/nym-cli/src/internal/ecash/mod.rs index ee9f8ef47c..8973925500 100644 --- a/tools/nym-cli/src/internal/ecash/mod.rs +++ b/tools/nym-cli/src/internal/ecash/mod.rs @@ -7,8 +7,8 @@ use nym_network_defaults::NymNetworkDetails; pub(super) async fn execute( global_args: ClientArgs, - ecash: nym_cli_commands::internal::ecash::InternalEcash, nym_network_details: &NymNetworkDetails, + ecash: nym_cli_commands::internal::ecash::InternalEcash, ) -> anyhow::Result<()> { // I reckon those will be needed later let _ = global_args; diff --git a/tools/nym-cli/src/internal/mod.rs b/tools/nym-cli/src/internal/mod.rs index 253c45b227..5bdb26f78d 100644 --- a/tools/nym-cli/src/internal/mod.rs +++ b/tools/nym-cli/src/internal/mod.rs @@ -6,6 +6,7 @@ use nym_cli_commands::internal::InternalCommands; use nym_network_defaults::NymNetworkDetails; mod ecash; +mod nyx; pub(super) async fn execute( global_args: ClientArgs, @@ -14,7 +15,10 @@ pub(super) async fn execute( ) -> anyhow::Result<()> { match internal.command { InternalCommands::Ecash(ecash_commands) => { - ecash::execute(global_args, ecash_commands, nym_network_details).await + ecash::execute(global_args, nym_network_details, ecash_commands).await + } + InternalCommands::Nyx(nyx_commands) => { + nyx::execute(global_args, nym_network_details, nyx_commands).await } } } diff --git a/tools/nym-cli/src/internal/nyx/mod.rs b/tools/nym-cli/src/internal/nyx/mod.rs new file mode 100644 index 0000000000..ac7bb500fc --- /dev/null +++ b/tools/nym-cli/src/internal/nyx/mod.rs @@ -0,0 +1,22 @@ +// Copyright 2025 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nym_cli_commands::context::{create_signing_client, ClientArgs}; +use nym_cli_commands::internal::nyx::InternalNyxCommands; +use nym_network_defaults::NymNetworkDetails; + +pub(super) async fn execute( + global_args: ClientArgs, + nym_network_details: &NymNetworkDetails, + nyx: nym_cli_commands::internal::nyx::InternalNyx, +) -> anyhow::Result<()> { + match nyx.command { + InternalNyxCommands::ForceAdvanceEpoch(args) => { + nym_cli_commands::internal::nyx::force_advance_epoch::force_advance_epoch( + args, + create_signing_client(global_args, nym_network_details)?, + ) + .await + } + } +}