From fc6351c2ef009771d24981f0dbc20263c523669a Mon Sep 17 00:00:00 2001 From: Gauthier Leonard Date: Fri, 2 Jun 2023 19:29:27 +0200 Subject: [PATCH] feat: allow to get pending validators --- Cargo.lock | 20 +++---- crates/ash_cli/src/avalanche.rs | 11 ++++ crates/ash_cli/src/avalanche/validator.rs | 58 ++++++++++++++----- crates/ash_sdk/src/avalanche.rs | 28 +++++++++ .../src/avalanche/jsonrpc/platformvm.rs | 44 +++++++++++++- crates/ash_sdk/src/avalanche/subnets.rs | 16 ++++- 6 files changed, 152 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 93502de..f937733 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -837,9 +837,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.25" +version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdbc37d37da9e5bce8173f3a41b71d9bf3c674deebbaceacd0ebdabde76efb03" +checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" dependencies = [ "android-tzdata", "iana-time-zone", @@ -863,9 +863,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.3.0" +version = "4.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93aae7a4192245f70fe75dd9157fc7b4a5bf53e88d30bd4396f7d8f9284d5acc" +checksum = "b4ed2379f8603fa2b7509891660e802b88c70a79a6427a70abb5968054de2c28" dependencies = [ "clap_builder", "clap_derive", @@ -874,9 +874,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.3.0" +version = "4.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f423e341edefb78c9caba2d9c7f7687d0e72e89df3ce3394554754393ac3990" +checksum = "72394f3339a76daf211e57d4bcb374410f3965dcc606dd0e03738c7888766980" dependencies = [ "anstream", "anstyle", @@ -887,9 +887,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.3.0" +version = "4.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "191d9573962933b4027f932c600cd252ce27a8ad5979418fe78e43c07996f27b" +checksum = "59e9ef9a08ee1c0e1f2e162121665ac45ac3783b0f897db7244ae75ad9a8f65b" dependencies = [ "heck", "proc-macro2", @@ -2934,9 +2934,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.53" +version = "0.10.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12df40a956736488b7b44fe79fe12d4f245bb5b3f5a1f6095e499760015be392" +checksum = "69b3f656a17a6cbc115b5c7a40c616947d213ba182135b014d6051b73ab6f019" dependencies = [ "bitflags", "cfg-if", diff --git a/crates/ash_cli/src/avalanche.rs b/crates/ash_cli/src/avalanche.rs index bbde167..f1fc215 100644 --- a/crates/ash_cli/src/avalanche.rs +++ b/crates/ash_cli/src/avalanche.rs @@ -64,6 +64,17 @@ fn update_subnet_validators( Ok(()) } +// Update a Subnet's pending validators +fn update_subnet_pending_validators( + network: &mut AvalancheNetwork, + subnet_id: &str, +) -> Result<(), CliError> { + network + .update_subnet_pending_validators(parse_id(subnet_id)?) + .map_err(|e| CliError::dataerr(format!("Error updating pending validators: {e}")))?; + Ok(()) +} + // Parse avalanche subcommand pub(crate) fn parse( avalanche: AvalancheCommand, diff --git a/crates/ash_cli/src/avalanche/validator.rs b/crates/ash_cli/src/avalanche/validator.rs index 9a4a023..a56b82a 100644 --- a/crates/ash_cli/src/avalanche/validator.rs +++ b/crates/ash_cli/src/avalanche/validator.rs @@ -71,7 +71,11 @@ enum ValidatorSubcommands { }, /// List the Subnet's validators #[command()] - List, + List { + /// List pending validators + #[arg(long, short = 'p')] + pending: bool, + }, /// Show validator information #[command()] Info { @@ -84,28 +88,50 @@ enum ValidatorSubcommands { fn list( network_name: &str, subnet_id: &str, + pending: bool, config: Option<&str>, json: bool, ) -> Result<(), CliError> { let mut network = load_network(network_name, config)?; update_network_subnets(&mut network)?; - update_subnet_validators(&mut network, subnet_id)?; + let subnet; + let validators; + let first_line; - let subnet = network - .get_subnet(parse_id(subnet_id)?) - .map_err(|e| CliError::dataerr(format!("Error listing validators: {e}")))?; + match pending { + true => { + update_subnet_pending_validators(&mut network, subnet_id)?; + subnet = network + .get_subnet(parse_id(subnet_id)?) + .map_err(|e| CliError::dataerr(format!("Error listing validators: {e}")))?; + validators = subnet.pending_validators.clone(); + first_line = format!( + "Found {} pending validator(s) on Subnet '{}':", + type_colorize(&subnet.validators.len()), + type_colorize(&subnet_id) + ) + } + false => { + update_subnet_validators(&mut network, subnet_id)?; + subnet = network + .get_subnet(parse_id(subnet_id)?) + .map_err(|e| CliError::dataerr(format!("Error listing validators: {e}")))?; + validators = subnet.validators.clone(); + first_line = format!( + "Found {} validator(s) on Subnet '{}':", + type_colorize(&subnet.validators.len()), + type_colorize(&subnet_id) + ) + } + } if json { - println!("{}", serde_json::to_string(&subnet.validators).unwrap()); + println!("{}", serde_json::to_string(&validators).unwrap()); return Ok(()); } - println!( - "Found {} validator(s) on Subnet '{}':", - type_colorize(&subnet.validators.len()), - type_colorize(&subnet_id) - ); - for validator in subnet.validators.iter() { + println!("{}", first_line); + for validator in validators.iter() { println!( "{}", template_validator_info(validator, subnet, true, true, 2) @@ -254,6 +280,12 @@ pub(crate) fn parse( ValidatorSubcommands::Info { id } => { info(&validator.network, &validator.subnet_id, &id, config, json) } - ValidatorSubcommands::List => list(&validator.network, &validator.subnet_id, config, json), + ValidatorSubcommands::List { pending } => list( + &validator.network, + &validator.subnet_id, + pending, + config, + json, + ), } } diff --git a/crates/ash_sdk/src/avalanche.rs b/crates/ash_sdk/src/avalanche.rs index 1a09db1..2d8513b 100644 --- a/crates/ash_sdk/src/avalanche.rs +++ b/crates/ash_sdk/src/avalanche.rs @@ -266,6 +266,34 @@ impl AvalancheNetwork { Ok(()) } + /// Update the pending validators of a Subnet by querying an API endpoint + pub fn update_subnet_pending_validators(&mut self, subnet_id: Id) -> Result<(), AshError> { + let rpc_url = &self.get_pchain()?.rpc_url; + + let validators = platformvm::get_pending_validators(rpc_url, subnet_id)?; + + // Replace the pending validators of the Subnet + let mut subnet = self.get_subnet(subnet_id)?.clone(); + + subnet.pending_validators = validators; + + // Get the index of the Subnet + let subnet_index = self + .subnets + .iter() + .position(|subnet| subnet.id == subnet_id) + .ok_or(AvalancheNetworkError::NotFound { + network: self.name.clone(), + target_type: "Subnet".to_string(), + target_value: subnet_id.to_string(), + })?; + + // Replace the Subnet + self.subnets[subnet_index] = subnet; + + Ok(()) + } + /// Check if the operation is allowed on the network /// If not, return an error fn check_operation_allowed( diff --git a/crates/ash_sdk/src/avalanche/jsonrpc/platformvm.rs b/crates/ash_sdk/src/avalanche/jsonrpc/platformvm.rs index c7c7c06..7cccb3a 100644 --- a/crates/ash_sdk/src/avalanche/jsonrpc/platformvm.rs +++ b/crates/ash_sdk/src/avalanche/jsonrpc/platformvm.rs @@ -6,7 +6,7 @@ use crate::avalanche::{ blockchains::AvalancheBlockchain, jsonrpc::{get_json_rpc_req_result, JsonRpcResponse}, - subnets::{AvalancheSubnet, AvalancheSubnetValidator}, + subnets::{AvalancheSubnet, AvalancheSubnetDelegator, AvalancheSubnetValidator}, }; use crate::{errors::*, impl_json_rpc_response}; use avalanche_types::{ @@ -51,6 +51,7 @@ impl_json_rpc_response!( ); impl_json_rpc_response!(GetBlockchainsResponse, GetBlockchainsResult); impl_json_rpc_response!(GetCurrentValidatorsResponse, GetCurrentValidatorsResult); +impl_json_rpc_response!(GetPendingValidatorsResponse, GetPendingValidatorsResult); /// Get the Subnets of the network by querying the P-Chain API pub fn get_network_subnets( @@ -123,6 +124,47 @@ pub fn get_current_validators( Ok(current_validators) } +/// Get the pending validators of a Subnet by querying the P-Chain API +pub fn get_pending_validators( + rpc_url: &str, + subnet_id: Id, +) -> Result, RpcError> { + let pending_validators_result: GetPendingValidatorsResult = + get_json_rpc_req_result::( + rpc_url, + "platform.getPendingValidators", + Some(ureq::json!({ "subnetID": subnet_id.to_string() })), + )?; + + let mut pending_validators: Vec = pending_validators_result + .validators + .iter() + .map(|validator| AvalancheSubnetValidator::from_api_primary_validator(validator, subnet_id)) + .collect(); + let pending_validators_iter = pending_validators.clone(); + + // For each pending validator, add related delegators + for pending_validator in pending_validators_iter.iter() { + let delegators: Vec = pending_validators_result + .delegators + .iter() + .filter(|delegator| delegator.node_id == pending_validator.node_id) + .cloned() + .map(Into::into) + .collect(); + + if !delegators.is_empty() { + pending_validators + .iter_mut() + .find(|validator| validator.node_id == pending_validator.node_id) + .unwrap() + .delegators = Some(delegators); + } + } + + Ok(pending_validators) +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/ash_sdk/src/avalanche/subnets.rs b/crates/ash_sdk/src/avalanche/subnets.rs index 3da06fc..0145bd5 100644 --- a/crates/ash_sdk/src/avalanche/subnets.rs +++ b/crates/ash_sdk/src/avalanche/subnets.rs @@ -41,8 +41,10 @@ pub struct AvalancheSubnet { /// List of the Subnet's blockchains pub blockchains: Vec, /// List of the Subnet's validators - #[serde(default, skip_serializing_if = "Vec::is_empty")] + #[serde(default)] pub validators: Vec, + #[serde(default)] + pub pending_validators: Vec, } impl AvalancheSubnet { @@ -241,16 +243,26 @@ pub struct AvalancheSubnetValidator { // TODO: Store as DateTime::? pub start_time: u64, pub end_time: u64, + #[serde(skip_serializing_if = "Option::is_none")] pub stake_amount: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub weight: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub potential_reward: Option, pub connected: bool, + #[serde(skip_serializing_if = "Option::is_none")] pub uptime: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub validation_reward_owner: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub delegator_count: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub delegator_weight: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub delegators: Option>, + #[serde(skip_serializing_if = "Option::is_none")] pub delegation_fee: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub delegation_reward_owner: Option, } @@ -299,7 +311,9 @@ pub struct AvalancheSubnetDelegator { pub start_time: u64, pub end_time: u64, pub stake_amount: u64, + #[serde(skip_serializing_if = "Option::is_none")] pub potential_reward: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub reward_owner: Option, }