diff --git a/ipc/cli/src/commands/subnet/mod.rs b/ipc/cli/src/commands/subnet/mod.rs index c2f00ad7..9bc45c7c 100644 --- a/ipc/cli/src/commands/subnet/mod.rs +++ b/ipc/cli/src/commands/subnet/mod.rs @@ -9,6 +9,7 @@ pub use crate::commands::subnet::leave::{LeaveSubnet, LeaveSubnetArgs}; use crate::commands::subnet::list_subnets::{ListSubnets, ListSubnetsArgs}; use crate::commands::subnet::rpc::{RPCSubnet, RPCSubnetArgs}; use crate::commands::subnet::send_value::{SendValue, SendValueArgs}; +use crate::commands::subnet::validator::{ValidatorInfo, ValidatorInfoArgs}; use crate::{CommandLineHandler, GlobalArguments}; use clap::{Args, Subcommand}; @@ -26,6 +27,7 @@ pub mod leave; pub mod list_subnets; pub mod rpc; pub mod send_value; +mod validator; #[derive(Debug, Args)] #[command( @@ -55,6 +57,7 @@ impl SubnetCommandsArgs { Commands::AddBootstrap(args) => AddBootstrap::handle(global, args).await, Commands::ListBootstraps(args) => ListBootstraps::handle(global, args).await, Commands::GenesisEpoch(args) => GenesisEpoch::handle(global, args).await, + Commands::GetValidator(args) => ValidatorInfo::handle(global, args).await, } } } @@ -75,4 +78,5 @@ pub(crate) enum Commands { AddBootstrap(AddBootstrapArgs), ListBootstraps(ListBootstrapsArgs), GenesisEpoch(GenesisEpochArgs), + GetValidator(ValidatorInfoArgs), } diff --git a/ipc/cli/src/commands/subnet/validator.rs b/ipc/cli/src/commands/subnet/validator.rs new file mode 100644 index 00000000..1b211b31 --- /dev/null +++ b/ipc/cli/src/commands/subnet/validator.rs @@ -0,0 +1,42 @@ +// Copyright 2022-2023 Protocol Labs +// SPDX-License-Identifier: MIT +//! Get the validator information + +use async_trait::async_trait; +use clap::Args; +use fvm_shared::address::Address; +use ipc_sdk::subnet_id::SubnetID; +use std::fmt::Debug; +use std::str::FromStr; + +use crate::{get_ipc_provider, CommandLineHandler, GlobalArguments}; + +/// The command to get the validator information +pub(crate) struct ValidatorInfo; + +#[async_trait] +impl CommandLineHandler for ValidatorInfo { + type Arguments = ValidatorInfoArgs; + + async fn handle(global: &GlobalArguments, arguments: &Self::Arguments) -> anyhow::Result<()> { + log::debug!("get validator info with args: {:?}", arguments); + + let provider = get_ipc_provider(global)?; + let subnet = SubnetID::from_str(&arguments.subnet)?; + let validator = Address::from_str(&arguments.validator)?; + + let validator_info = provider.get_validator_info(&subnet, &validator).await?; + println!("{}", validator_info); + + Ok(()) + } +} + +#[derive(Debug, Args)] +#[command(name = "validator-info", about = "Get the validator info")] +pub(crate) struct ValidatorInfoArgs { + #[arg(long, short, help = "The subnet id to query validator info")] + pub subnet: String, + #[arg(long, short, help = "The validator address")] + pub validator: String, +} diff --git a/ipc/provider/src/lib.rs b/ipc/provider/src/lib.rs index eb4b076a..145023d7 100644 --- a/ipc/provider/src/lib.rs +++ b/ipc/provider/src/lib.rs @@ -16,7 +16,7 @@ use ipc_identity::{ EthKeyAddress, EvmKeyStore, KeyStore, KeyStoreConfig, PersistentKeyStore, Wallet, }; use ipc_sdk::checkpoint::{BottomUpCheckpointBundle, QuorumReachedEvent}; -use ipc_sdk::staking::StakingChangeRequest; +use ipc_sdk::staking::{StakingChangeRequest, ValidatorInfo}; use ipc_sdk::{ cross::CrossMsg, subnet::{ConsensusType, ConstructParams}, @@ -604,6 +604,21 @@ impl IpcProvider { conn.manager().genesis_epoch(subnet).await } + /// Get the validator information. + pub async fn get_validator_info( + &self, + subnet: &SubnetID, + validator: &Address, + ) -> anyhow::Result { + let parent = subnet.parent().ok_or_else(|| anyhow!("no parent found"))?; + let conn = match self.connection(&parent) { + None => return Err(anyhow!("target subnet parent not found")), + Some(conn) => conn, + }; + + conn.manager().get_validator_info(subnet, validator).await + } + /// Get the changes in subnet validators. This is fetched from parent. pub async fn get_validator_changeset( &self, diff --git a/ipc/provider/src/manager/evm/manager.rs b/ipc/provider/src/manager/evm/manager.rs index 4713353f..6144eee8 100644 --- a/ipc/provider/src/manager/evm/manager.rs +++ b/ipc/provider/src/manager/evm/manager.rs @@ -38,7 +38,7 @@ use ipc_identity::{EthKeyAddress, EvmKeyStore, PersistentKeyStore}; use ipc_sdk::checkpoint::{BottomUpCheckpoint, BottomUpCheckpointBundle, QuorumReachedEvent}; use ipc_sdk::cross::CrossMsg; use ipc_sdk::gateway::Status; -use ipc_sdk::staking::StakingChangeRequest; +use ipc_sdk::staking::{StakingChangeRequest, ValidatorInfo, ValidatorStakingInfo}; use ipc_sdk::subnet::ConstructParams; use ipc_sdk::subnet_id::SubnetID; use num_traits::ToPrimitive; @@ -744,6 +744,29 @@ impl SubnetManager for EthSubnetManager { ); Ok(contract.get_bootstrap_nodes().call().await?) } + + async fn get_validator_info( + &self, + subnet: &SubnetID, + validator: &Address, + ) -> Result { + let address = contract_address_from_subnet(subnet)?; + let contract = subnet_actor_getter_facet::SubnetActorGetterFacet::new( + address, + Arc::new(self.ipc_contract_info.provider.clone()), + ); + let validator = payload_to_evm_address(validator.payload())?; + + let validator_info = contract.get_validator(validator).call().await?; + let is_active = contract.is_active_validator(validator).call().await?; + let is_waiting = contract.is_waiting_validator(validator).call().await?; + + Ok(ValidatorInfo { + staking: ValidatorStakingInfo::try_from(validator_info)?, + is_active, + is_waiting, + }) + } } #[async_trait] diff --git a/ipc/provider/src/manager/subnet.rs b/ipc/provider/src/manager/subnet.rs index 43b41e36..64133daa 100644 --- a/ipc/provider/src/manager/subnet.rs +++ b/ipc/provider/src/manager/subnet.rs @@ -9,7 +9,7 @@ use fvm_shared::clock::ChainEpoch; use fvm_shared::{address::Address, econ::TokenAmount}; use ipc_sdk::checkpoint::{BottomUpCheckpointBundle, QuorumReachedEvent}; use ipc_sdk::cross::CrossMsg; -use ipc_sdk::staking::StakingChangeRequest; +use ipc_sdk::staking::{StakingChangeRequest, ValidatorInfo}; use ipc_sdk::subnet::ConstructParams; use ipc_sdk::subnet_id::SubnetID; use ipc_sdk::validator::Validator; @@ -139,6 +139,13 @@ pub trait SubnetManager: Send + Sync + TopDownCheckpointQuery + BottomUpCheckpoi /// Lists the bootstrap nodes of a subnet async fn list_bootstrap_nodes(&self, subnet: &SubnetID) -> Result>; + + /// Get the validator information + async fn get_validator_info( + &self, + subnet: &SubnetID, + validator: &Address, + ) -> Result; } #[derive(Debug)] diff --git a/ipc/sdk/src/staking.rs b/ipc/sdk/src/staking.rs index 09be41dd..6ebfa5ce 100644 --- a/ipc/sdk/src/staking.rs +++ b/ipc/sdk/src/staking.rs @@ -3,9 +3,12 @@ //! Staking module related types and functions -use crate::ethers_address_to_fil_address; +use crate::{eth_to_fil_amount, ethers_address_to_fil_address}; +use ethers::utils::hex; use fvm_shared::address::Address; -use ipc_actors_abis::lib_staking_change_log; +use fvm_shared::econ::TokenAmount; +use ipc_actors_abis::{lib_staking_change_log, subnet_actor_getter_facet}; +use std::fmt::{Display, Formatter}; pub type ConfigurationNumber = u64; @@ -56,3 +59,55 @@ impl TryFrom for StakingC }) } } + +/// The staking validator information +#[derive(Clone, Debug)] +pub struct ValidatorStakingInfo { + confirmed_collateral: TokenAmount, + total_collateral: TokenAmount, + metadata: Vec, +} + +impl Display for ValidatorStakingInfo { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "ValidatorStaking(confirmed_collateral: {}, total_collateral: {}, metadata: 0x{})", + self.confirmed_collateral, + self.total_collateral, + hex::encode(&self.metadata) + ) + } +} + +impl TryFrom for ValidatorStakingInfo { + type Error = anyhow::Error; + + fn try_from(value: subnet_actor_getter_facet::ValidatorInfo) -> Result { + Ok(Self { + confirmed_collateral: eth_to_fil_amount(&value.confirmed_collateral)?, + total_collateral: eth_to_fil_amount(&value.total_collateral)?, + metadata: value.metadata.to_vec(), + }) + } +} + +/// The full validator information with +#[derive(Clone, Debug)] +pub struct ValidatorInfo { + pub staking: ValidatorStakingInfo, + /// If the validator is active in block production + pub is_active: bool, + /// If the validator is current waiting to be promoted to active + pub is_waiting: bool, +} + +impl Display for ValidatorInfo { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "ValidatorInfo(staking: {}, is_active: {}, is_waiting: {})", + self.staking, self.is_active, self.is_waiting + ) + } +}