diff --git a/contracts/contracts/lib/LibStaking.sol b/contracts/contracts/lib/LibStaking.sol index cfb1f7a796..1cd902343a 100644 --- a/contracts/contracts/lib/LibStaking.sol +++ b/contracts/contracts/lib/LibStaking.sol @@ -147,6 +147,18 @@ library LibValidatorSet { return addresses; } + function listWaitingValidators(ValidatorSet storage validators) internal view returns (address[] memory addresses) { + uint16 size = validators.waitingValidators.getSize(); + addresses = new address[](size); + for (uint16 i = 1; i <= size; ) { + addresses[i - 1] = validators.waitingValidators.getAddress(i); + unchecked { + ++i; + } + } + return addresses; + } + /// @notice Get the total collateral of *active* validators. function getTotalActivePower(ValidatorSet storage validators) internal view returns (uint256 collateral) { uint16 size = validators.activeValidators.getSize(); @@ -431,6 +443,18 @@ library LibStaking { return s.validatorSet.waitingValidators.getSize() + s.validatorSet.activeValidators.getSize(); } + /// @notice Returns all active validators. + function listActiveValidators() internal view returns (address[] memory addresses) { + SubnetActorStorage storage s = LibSubnetActorStorage.appStorage(); + return s.validatorSet.listActiveValidators(); + } + + /// @notice Returns all waiting validators. + function listWaitingValidators() internal view returns (address[] memory addresses) { + SubnetActorStorage storage s = LibSubnetActorStorage.appStorage(); + return s.validatorSet.listWaitingValidators(); + } + function getTotalConfirmedCollateral() internal view returns (uint256) { SubnetActorStorage storage s = LibSubnetActorStorage.appStorage(); return s.validatorSet.getTotalConfirmedCollateral(); diff --git a/contracts/contracts/subnet/SubnetActorGetterFacet.sol b/contracts/contracts/subnet/SubnetActorGetterFacet.sol index 415064ab19..e7be4f36ef 100644 --- a/contracts/contracts/subnet/SubnetActorGetterFacet.sol +++ b/contracts/contracts/subnet/SubnetActorGetterFacet.sol @@ -118,6 +118,16 @@ contract SubnetActorGetterFacet { validator = s.validatorSet.validators[validatorAddress]; } + /// @notice Returns detailed information about all active validators. + function getActiveValidators() external view returns (address[] memory) { + return LibStaking.listActiveValidators(); + } + + /// @notice Returns detailed information about all waiting validators. + function getWaitingValidators() external view returns (address[] memory) { + return LibStaking.listWaitingValidators(); + } + /// @notice Returns the total number of validators (active and waiting). function getTotalValidatorsNumber() external view returns (uint16) { return LibStaking.totalValidators(); diff --git a/contracts/test/helpers/SelectorLibrary.sol b/contracts/test/helpers/SelectorLibrary.sol index adcdc5e965..240c64f0e3 100644 --- a/contracts/test/helpers/SelectorLibrary.sol +++ b/contracts/test/helpers/SelectorLibrary.sol @@ -69,7 +69,7 @@ library SelectorLibrary { if (keccak256(abi.encodePacked(facetName)) == keccak256(abi.encodePacked("SubnetActorGetterFacet"))) { return abi.decode( - hex"000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000203354c3e10000000000000000000000000000000000000000000000000000000035142c8c0000000000000000000000000000000000000000000000000000000006c46853000000000000000000000000000000000000000000000000000000004b27aa72000000000000000000000000000000000000000000000000000000004b0694e200000000000000000000000000000000000000000000000000000000b6797d3c000000000000000000000000000000000000000000000000000000008ef3f76100000000000000000000000000000000000000000000000000000000e02d971b00000000000000000000000000000000000000000000000000000000903e693000000000000000000000000000000000000000000000000000000000948628a900000000000000000000000000000000000000000000000000000000d92e8f1200000000000000000000000000000000000000000000000000000000c7cda762000000000000000000000000000000000000000000000000000000009754b29e0000000000000000000000000000000000000000000000000000000038a210b30000000000000000000000000000000000000000000000000000000080f76021000000000000000000000000000000000000000000000000000000005dd9147c00000000000000000000000000000000000000000000000000000000d6eb591000000000000000000000000000000000000000000000000000000000332a5ac9000000000000000000000000000000000000000000000000000000001597bf7e0000000000000000000000000000000000000000000000000000000052d182d1000000000000000000000000000000000000000000000000000000001904bb2e00000000000000000000000000000000000000000000000000000000cfca28240000000000000000000000000000000000000000000000000000000040550a1c00000000000000000000000000000000000000000000000000000000d081be03000000000000000000000000000000000000000000000000000000001f3a0e410000000000000000000000000000000000000000000000000000000072d0a0e000000000000000000000000000000000000000000000000000000000599c7bd1000000000000000000000000000000000000000000000000000000009e33bd0200000000000000000000000000000000000000000000000000000000c5ab224100000000000000000000000000000000000000000000000000000000f0cf6c9600000000000000000000000000000000000000000000000000000000ad81e4d60000000000000000000000000000000000000000000000000000000080875df700000000000000000000000000000000000000000000000000000000", + hex"000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000223354c3e10000000000000000000000000000000000000000000000000000000035142c8c0000000000000000000000000000000000000000000000000000000006c46853000000000000000000000000000000000000000000000000000000004b27aa72000000000000000000000000000000000000000000000000000000004b0694e200000000000000000000000000000000000000000000000000000000b6797d3c000000000000000000000000000000000000000000000000000000008ef3f76100000000000000000000000000000000000000000000000000000000e02d971b00000000000000000000000000000000000000000000000000000000903e693000000000000000000000000000000000000000000000000000000000948628a900000000000000000000000000000000000000000000000000000000d92e8f12000000000000000000000000000000000000000000000000000000009de7025800000000000000000000000000000000000000000000000000000000c7cda762000000000000000000000000000000000000000000000000000000009754b29e0000000000000000000000000000000000000000000000000000000038a210b30000000000000000000000000000000000000000000000000000000080f76021000000000000000000000000000000000000000000000000000000005dd9147c00000000000000000000000000000000000000000000000000000000d6eb591000000000000000000000000000000000000000000000000000000000332a5ac9000000000000000000000000000000000000000000000000000000001597bf7e0000000000000000000000000000000000000000000000000000000052d182d1000000000000000000000000000000000000000000000000000000001904bb2e000000000000000000000000000000000000000000000000000000006ad04c7900000000000000000000000000000000000000000000000000000000cfca28240000000000000000000000000000000000000000000000000000000040550a1c00000000000000000000000000000000000000000000000000000000d081be03000000000000000000000000000000000000000000000000000000001f3a0e410000000000000000000000000000000000000000000000000000000072d0a0e000000000000000000000000000000000000000000000000000000000599c7bd1000000000000000000000000000000000000000000000000000000009e33bd0200000000000000000000000000000000000000000000000000000000c5ab224100000000000000000000000000000000000000000000000000000000f0cf6c9600000000000000000000000000000000000000000000000000000000ad81e4d60000000000000000000000000000000000000000000000000000000080875df700000000000000000000000000000000000000000000000000000000", (bytes4[]) ); } diff --git a/contracts/test/integration/SubnetActorDiamond.t.sol b/contracts/test/integration/SubnetActorDiamond.t.sol index 85f57e1356..1de996148b 100644 --- a/contracts/test/integration/SubnetActorDiamond.t.sol +++ b/contracts/test/integration/SubnetActorDiamond.t.sol @@ -75,6 +75,9 @@ contract SubnetActorDiamondTest is Test, IntegrationTestBase { require(saDiamond.getter().bottomUpCheckPeriod() == params.bottomUpCheckPeriod, "unexpected bottom-up period"); require(saDiamond.getter().majorityPercentage() == params.majorityPercentage, "unexpected majority percentage"); require(saDiamond.getter().getParent().toHash() == _parentId.toHash(), "unexpected parent subnetID hash"); + require(saDiamond.getter().genesisValidators().length == 0, "unexpected genesis validators"); + require(saDiamond.getter().getActiveValidators().length == 0, "unexpected active validators"); + require(saDiamond.getter().getWaitingValidators().length == 0, "unexpected waiting validators"); } function testSubnetActorDiamondReal_LoupeFunction() public view { @@ -131,7 +134,9 @@ contract SubnetActorDiamondTest is Test, IntegrationTestBase { TestUtils.ensureBytesEqual(v.metadata, publicKey1); require(saDiamond.getter().bootstrapped(), "subnet not bootstrapped"); require(!saDiamond.getter().killed(), "subnet killed"); - require(saDiamond.getter().genesisValidators().length == 1, "not one validator in genesis"); + require(saDiamond.getter().genesisValidators().length == 1, "not 1 genesis validator"); + require(saDiamond.getter().getActiveValidators().length == 1, "not 1 active validator"); + require(saDiamond.getter().getWaitingValidators().length == 0, "not 0 waiting validator"); (uint64 nextConfigNum, uint64 startConfigNum) = saDiamond.getter().getConfigurationNumbers(); require(nextConfigNum == LibStaking.INITIAL_CONFIGURATION_NUMBER, "next config num not 1"); @@ -177,6 +182,8 @@ contract SubnetActorDiamondTest is Test, IntegrationTestBase { require(!saDiamond.getter().isWaitingValidator(validator1), "waiting validator1"); require(saDiamond.getter().isActiveValidator(validator2), "not active validator2"); require(!saDiamond.getter().isWaitingValidator(validator2), "waiting validator2"); + require(saDiamond.getter().getActiveValidators().length == 2, "not 2 active validators"); + require(saDiamond.getter().getWaitingValidators().length == 0, "not 0 waiting validators"); (nextConfigNum, startConfigNum) = saDiamond.getter().getConfigurationNumbers(); require( diff --git a/ipc/cli/src/commands/subnet/list_validators.rs b/ipc/cli/src/commands/subnet/list_validators.rs new file mode 100644 index 0000000000..ae7fe34953 --- /dev/null +++ b/ipc/cli/src/commands/subnet/list_validators.rs @@ -0,0 +1,42 @@ +// Copyright 2022-2024 Protocol Labs +// SPDX-License-Identifier: MIT +//! List subnets cli command + +use crate::{get_ipc_provider, CommandLineHandler, GlobalArguments}; +use async_trait::async_trait; +use clap::Args; +use ipc_api::subnet_id::SubnetID; +use std::fmt::Debug; +use std::str::FromStr; + +/// The command to create a new subnet actor. +pub(crate) struct ListValidators; + +#[async_trait] +impl CommandLineHandler for ListValidators { + type Arguments = ListValidatorsArgs; + + async fn handle(global: &GlobalArguments, arguments: &Self::Arguments) -> anyhow::Result<()> { + log::debug!("list validators with args: {:?}", arguments); + + let provider = get_ipc_provider(global)?; + let subnet = SubnetID::from_str(&arguments.subnet)?; + + let validators = provider.list_validators(&subnet).await?; + + for (addr, info) in validators { + println!("{}: {}", addr, info); + } + Ok(()) + } +} + +#[derive(Debug, Args)] +#[command( + name = "list validators", + about = "List the info of all the validators in the subnet, as viewed by the parent" +)] +pub(crate) struct ListValidatorsArgs { + #[arg(long, help = "The target subnet to perform query")] + pub subnet: String, +} diff --git a/ipc/cli/src/commands/subnet/mod.rs b/ipc/cli/src/commands/subnet/mod.rs index c7597615e6..55414e6384 100644 --- a/ipc/cli/src/commands/subnet/mod.rs +++ b/ipc/cli/src/commands/subnet/mod.rs @@ -1,12 +1,17 @@ // Copyright 2022-2024 Protocol Labs // SPDX-License-Identifier: MIT +use self::bootstrap::{AddBootstrap, AddBootstrapArgs, ListBootstraps, ListBootstrapsArgs}; +use self::join::{StakeSubnet, StakeSubnetArgs, UnstakeSubnet, UnstakeSubnetArgs}; +use self::leave::{Claim, ClaimArgs}; +use self::rpc::{ChainIdSubnet, ChainIdSubnetArgs}; pub use crate::commands::subnet::create::{CreateSubnet, CreateSubnetArgs}; use crate::commands::subnet::genesis_epoch::{GenesisEpoch, GenesisEpochArgs}; pub use crate::commands::subnet::join::{JoinSubnet, JoinSubnetArgs}; pub use crate::commands::subnet::kill::{KillSubnet, KillSubnetArgs}; pub use crate::commands::subnet::leave::{LeaveSubnet, LeaveSubnetArgs}; use crate::commands::subnet::list_subnets::{ListSubnets, ListSubnetsArgs}; +use crate::commands::subnet::list_validators::{ListValidators, ListValidatorsArgs}; use crate::commands::subnet::rpc::{RPCSubnet, RPCSubnetArgs}; use crate::commands::subnet::send_value::{SendValue, SendValueArgs}; use crate::commands::subnet::set_federated_power::{SetFederatedPower, SetFederatedPowerArgs}; @@ -17,11 +22,6 @@ use crate::commands::subnet::validator::{ValidatorInfo, ValidatorInfoArgs}; use crate::{CommandLineHandler, GlobalArguments}; use clap::{Args, Subcommand}; -use self::bootstrap::{AddBootstrap, AddBootstrapArgs, ListBootstraps, ListBootstrapsArgs}; -use self::join::{StakeSubnet, StakeSubnetArgs, UnstakeSubnet, UnstakeSubnetArgs}; -use self::leave::{Claim, ClaimArgs}; -use self::rpc::{ChainIdSubnet, ChainIdSubnetArgs}; - pub mod bootstrap; pub mod create; mod genesis_epoch; @@ -29,6 +29,7 @@ pub mod join; pub mod kill; pub mod leave; pub mod list_subnets; +pub mod list_validators; pub mod rpc; pub mod send_value; mod set_federated_power; @@ -53,6 +54,7 @@ impl SubnetCommandsArgs { match &self.command { Commands::Create(args) => CreateSubnet::handle(global, args).await, Commands::List(args) => ListSubnets::handle(global, args).await, + Commands::ListValidators(args) => ListValidators::handle(global, args).await, Commands::Join(args) => JoinSubnet::handle(global, args).await, Commands::Rpc(args) => RPCSubnet::handle(global, args).await, Commands::ChainId(args) => ChainIdSubnet::handle(global, args).await, @@ -78,6 +80,7 @@ impl SubnetCommandsArgs { pub(crate) enum Commands { Create(CreateSubnetArgs), List(ListSubnetsArgs), + ListValidators(ListValidatorsArgs), Join(JoinSubnetArgs), Rpc(RPCSubnetArgs), ChainId(ChainIdSubnetArgs), diff --git a/ipc/provider/src/lib.rs b/ipc/provider/src/lib.rs index 5fa469f309..62f12edcb4 100644 --- a/ipc/provider/src/lib.rs +++ b/ipc/provider/src/lib.rs @@ -602,6 +602,16 @@ impl IpcProvider { conn.manager().get_validator_info(subnet, validator).await } + pub async fn list_validators( + &self, + subnet: &SubnetID, + ) -> anyhow::Result> { + let parent = subnet.parent().ok_or_else(|| anyhow!("no parent found"))?; + let conn = self.get_connection(&parent)?; + + conn.manager().list_validators(subnet).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 ac9b0766f4..a31e20da4e 100644 --- a/ipc/provider/src/manager/evm/manager.rs +++ b/ipc/provider/src/manager/evm/manager.rs @@ -877,6 +877,50 @@ impl SubnetManager for EthSubnetManager { }) } + async fn list_validators(&self, subnet: &SubnetID) -> 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 mut addresses: Vec
= vec![]; + let mut validators: Vec = vec![]; + + let active = contract.get_active_validators().call().await?; + addresses.extend( + active + .iter() + .map(ethers_address_to_fil_address) + .collect::, _>>()?, + ); + for addr in active { + let info = contract.get_validator(addr).call().await?; + validators.push(ValidatorInfo { + staking: ValidatorStakingInfo::try_from(info)?, + is_active: true, + is_waiting: false, + }); + } + let waiting = contract.get_waiting_validators().call().await?; + addresses.extend( + waiting + .iter() + .map(ethers_address_to_fil_address) + .collect::, _>>()?, + ); + for addr in waiting { + let info = contract.get_validator(addr).call().await?; + validators.push(ValidatorInfo { + staking: ValidatorStakingInfo::try_from(info)?, + is_active: false, + is_waiting: true, + }); + } + + Ok(addresses.into_iter().zip(validators).collect()) + } + async fn set_federated_power( &self, from: &Address, diff --git a/ipc/provider/src/manager/subnet.rs b/ipc/provider/src/manager/subnet.rs index cc47ab093a..dad461223f 100644 --- a/ipc/provider/src/manager/subnet.rs +++ b/ipc/provider/src/manager/subnet.rs @@ -1,8 +1,6 @@ // Copyright 2022-2024 Protocol Labs // SPDX-License-Identifier: MIT -use std::collections::{BTreeMap, HashMap}; - use anyhow::Result; use async_trait::async_trait; use fvm_shared::clock::ChainEpoch; @@ -15,6 +13,7 @@ use ipc_api::staking::{StakingChangeRequest, ValidatorInfo}; use ipc_api::subnet::{Asset, ConstructParams, PermissionMode}; use ipc_api::subnet_id::SubnetID; use ipc_api::validator::Validator; +use std::collections::{BTreeMap, HashMap}; use crate::lotus::message::ipc::SubnetInfo; @@ -189,6 +188,9 @@ pub trait SubnetManager: Send + Sync + TopDownFinalityQuery + BottomUpCheckpoint validator: &Address, ) -> Result; + /// Lists all the validators + async fn list_validators(&self, subnet: &SubnetID) -> Result>; + async fn set_federated_power( &self, from: &Address,