diff --git a/.changelog/unreleased/improvements/2368-lookup-validator-consensus-key.md b/.changelog/unreleased/improvements/2368-lookup-validator-consensus-key.md new file mode 100644 index 0000000000..a72d092083 --- /dev/null +++ b/.changelog/unreleased/improvements/2368-lookup-validator-consensus-key.md @@ -0,0 +1,3 @@ +- Added validator's consensus key look-up to `client find-validator` + command, which now also accepts a native validator address. + ([\#2368](https://github.com/anoma/namada/pull/2368)) \ No newline at end of file diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index fca330ec48..eb57280794 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1757,7 +1757,10 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) - .about("Find a PoS validator by its Tendermint address.") + .about( + "Find a PoS validator and its consensus key by its native \ + address or Tendermint address.", + ) .add_args::>() } } @@ -3031,7 +3034,7 @@ pub mod args { pub const TEMPLATES_PATH: Arg = arg("templates-path"); pub const TIMEOUT_HEIGHT: ArgOpt = arg_opt("timeout-height"); pub const TIMEOUT_SEC_OFFSET: ArgOpt = arg_opt("timeout-sec-offset"); - pub const TM_ADDRESS: Arg = arg("tm-address"); + pub const TM_ADDRESS: ArgOpt = arg_opt("tm-address"); pub const TOKEN_OPT: ArgOpt = TOKEN.opt(); pub const TOKEN: Arg = arg("token"); pub const TOKEN_STR: Arg = arg("token"); @@ -5733,15 +5736,26 @@ pub mod args { fn parse(matches: &ArgMatches) -> Self { let query = Query::parse(matches); let tm_addr = TM_ADDRESS.parse(matches); - Self { query, tm_addr } + let validator_addr = VALIDATOR_OPT.parse(matches); + Self { + query, + tm_addr, + validator_addr, + } } fn def(app: App) -> App { - app.add_args::>().arg( - TM_ADDRESS - .def() - .help("The address of the validator in Tendermint."), - ) + app.add_args::>() + .arg( + TM_ADDRESS + .def() + .help("The address of the validator in Tendermint."), + ) + .arg( + VALIDATOR_OPT + .def() + .help("The native address of the validator."), + ) } } @@ -5750,6 +5764,9 @@ pub mod args { QueryFindValidator:: { query: self.query.to_sdk(ctx), tm_addr: self.tm_addr, + validator_addr: self + .validator_addr + .map(|x| ctx.borrow_chain_or_exit().get(&x)), } } } diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index bc57f58f14..d41917b1dc 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -2245,33 +2245,65 @@ pub async fn query_find_validator( context: &N, args: args::QueryFindValidator, ) { - let args::QueryFindValidator { query: _, tm_addr } = args; - if tm_addr.len() != 40 { - edisplay_line!( - context.io(), - "Expected 40 characters in Tendermint address, got {}", - tm_addr.len() + let args::QueryFindValidator { + query: _, + tm_addr, + mut validator_addr, + } = args; + if let Some(tm_addr) = tm_addr { + if tm_addr.len() != 40 { + edisplay_line!( + context.io(), + "Expected 40 characters in Tendermint address, got {}", + tm_addr.len() + ); + cli::safe_exit(1); + } + let tm_addr = tm_addr.to_ascii_uppercase(); + let validator = unwrap_client_response::( + RPC.vp() + .pos() + .validator_by_tm_addr(context.client(), &tm_addr) + .await, ); - cli::safe_exit(1); + match validator { + Some(address) => { + display_line!( + context.io(), + "Found validator address \"{address}\"." + ); + if validator_addr.is_none() { + validator_addr = Some(address); + } + } + None => { + display_line!( + context.io(), + "No validator with Tendermint address {tm_addr} found." + ) + } + } } - let tm_addr = tm_addr.to_ascii_uppercase(); - let validator = unwrap_client_response::( - RPC.vp() - .pos() - .validator_by_tm_addr(context.client(), &tm_addr) - .await, - ); - match validator { - Some(address) => { + if let Some(validator_addr) = validator_addr { + if let Some(consensus_key) = unwrap_client_response::( + RPC.vp() + .pos() + .consensus_key(context.client(), &validator_addr) + .await, + ) { + let pkh: PublicKeyHash = (&consensus_key).into(); + display_line!(context.io(), "Consensus key: {consensus_key}"); display_line!( context.io(), - "Found validator address \"{address}\"." - ) - } - None => { + "Tendermint key: {}", + tm_consensus_key_raw_hash(&consensus_key) + ); + display_line!(context.io(), "Consensus key hash: {}", pkh); + } else { display_line!( context.io(), - "No validator with Tendermint address {tm_addr} found." + "Consensus key for validator {validator_addr} could not be \ + found." ) } } diff --git a/proof_of_stake/src/storage.rs b/proof_of_stake/src/storage.rs index bab677b3a0..94fa374461 100644 --- a/proof_of_stake/src/storage.rs +++ b/proof_of_stake/src/storage.rs @@ -846,3 +846,16 @@ where let handle = LazySet::open(key); handle.contains(storage, consensus_key) } + +/// Find a consensus key of a validator account. +pub fn get_consensus_key( + storage: &S, + addr: &Address, + epoch: Epoch, +) -> storage_api::Result> +where + S: StorageRead, +{ + let params = read_pos_params(storage)?; + validator_consensus_key_handle(addr).get(storage, epoch, ¶ms) +} diff --git a/sdk/src/args.rs b/sdk/src/args.rs index c12ecdf537..d754ff8332 100644 --- a/sdk/src/args.rs +++ b/sdk/src/args.rs @@ -1812,7 +1812,9 @@ pub struct QueryFindValidator { /// Common query args pub query: Query, /// Tendermint address - pub tm_addr: String, + pub tm_addr: Option, + /// Native validator address + pub validator_addr: Option, } /// Query the raw bytes of given storage key diff --git a/sdk/src/queries/vp/pos.rs b/sdk/src/queries/vp/pos.rs index ff1b073dc4..91f8d10cd9 100644 --- a/sdk/src/queries/vp/pos.rs +++ b/sdk/src/queries/vp/pos.rs @@ -42,6 +42,8 @@ router! {POS, ( "validator" ) = { ( "is_validator" / [addr: Address] ) -> bool = is_validator, + ( "consensus_key" / [addr: Address] ) -> Option = consensus_key, + ( "addresses" / [epoch: opt Epoch] ) -> HashSet
= validator_addresses, @@ -194,6 +196,23 @@ where namada_proof_of_stake::is_validator(ctx.wl_storage, &addr) } +/// Find a consensus key of a validator account. +fn consensus_key( + ctx: RequestCtx<'_, D, H, V, T>, + addr: Address, +) -> storage_api::Result> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + let current_epoch = ctx.wl_storage.storage.last_epoch; + namada_proof_of_stake::storage::get_consensus_key( + ctx.wl_storage, + &addr, + current_epoch, + ) +} + /// Find if the given address is a delegator fn is_delegator( ctx: RequestCtx<'_, D, H, V, T>, diff --git a/sdk/src/rpc.rs b/sdk/src/rpc.rs index 2bb7192a89..0c237a6d52 100644 --- a/sdk/src/rpc.rs +++ b/sdk/src/rpc.rs @@ -15,6 +15,7 @@ use namada_core::ledger::governance::utils::Vote; use namada_core::ledger::ibc::storage::{ ibc_denom_key, ibc_denom_key_prefix, is_ibc_denom_key, }; +use namada_core::ledger::pgf::storage::steward::StewardDetail; use namada_core::ledger::storage::LastBlock; use namada_core::types::account::Account; use namada_core::types::address::{Address, InternalAddress}; @@ -223,6 +224,27 @@ pub async fn has_bonds( convert_response::(RPC.vp().pos().has_bonds(client, source).await) } +/// Get the set of pgf stewards +pub async fn query_pgf_stewards( + client: &C, +) -> Result, error::Error> { + convert_response::>( + RPC.vp().pgf().stewards(client).await, + ) +} + +/// Query the consensus key by validator address +pub async fn query_validator_consensus_keys< + C: crate::queries::Client + Sync, +>( + client: &C, + address: &Address, +) -> Result, error::Error> { + convert_response::>( + RPC.vp().pos().consensus_key(client, address).await, + ) +} + /// Get the set of consensus keys registered in the network pub async fn get_consensus_keys( client: &C,