From 9fc3319f73f3f0ac42ef82a7335aeea536c6e055 Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Tue, 31 Dec 2024 14:16:52 +0300 Subject: [PATCH 01/16] save dev state Signed-off-by: onur-ozkan --- mm2src/coins/rpc_command/tendermint/mod.rs | 1 + .../coins/rpc_command/tendermint/staking.rs | 60 +++++++++++++++++++ mm2src/coins/tendermint/tendermint_coin.rs | 46 ++++++++++++++ 3 files changed, 107 insertions(+) create mode 100644 mm2src/coins/rpc_command/tendermint/staking.rs diff --git a/mm2src/coins/rpc_command/tendermint/mod.rs b/mm2src/coins/rpc_command/tendermint/mod.rs index 3e2b664aec..d180c60f9f 100644 --- a/mm2src/coins/rpc_command/tendermint/mod.rs +++ b/mm2src/coins/rpc_command/tendermint/mod.rs @@ -1,4 +1,5 @@ mod ibc_chains; +mod staking; mod ibc_transfer_channels; pub use ibc_chains::*; diff --git a/mm2src/coins/rpc_command/tendermint/staking.rs b/mm2src/coins/rpc_command/tendermint/staking.rs new file mode 100644 index 0000000000..96f1dfb893 --- /dev/null +++ b/mm2src/coins/rpc_command/tendermint/staking.rs @@ -0,0 +1,60 @@ +use common::HttpStatusCode; +use cosmrs::proto::tendermint::types::Validator; +use mm2_core::mm_ctx::MmArc; +use mm2_err_handle::prelude::MmError; + +use crate::tendermint; + +pub type ValidatorsRPCResult = Result>; + +#[derive(Clone, Deserialize)] +pub enum ValidatorStatus { + All, + Active, + Jailed, +} + +impl ToString for ValidatorStatus { + fn to_string(&self) -> String { + match self { + ValidatorStatus::All => "".into(), + ValidatorStatus::Active => "Bonded".into(), + ValidatorStatus::Jailed => "Unbonded".into(), + } + } +} + +#[derive(Clone, Deserialize)] +pub struct ValidatorsRPC { + pub(crate) page: u16, + pub(crate) limit: u8, + pub(crate) filter_by_status: ValidatorStatus, +} + +#[derive(Clone, Serialize)] +pub struct ValidatorsRPCResponse { + pub(crate) chain_registry_list: Vec, +} + +#[derive(Clone, Debug, Display, Serialize, SerializeErrorType, PartialEq)] +#[serde(tag = "error_type", content = "error_data")] +pub enum ValidatorsRPCError { + #[display(fmt = "Transport error: {}", _0)] + Transport(String), + #[display(fmt = "Internal error: {}", _0)] + InternalError(String), +} + +impl HttpStatusCode for ValidatorsRPCError { + fn status_code(&self) -> common::StatusCode { + match self { + ValidatorsRPCError::Transport(_) => common::StatusCode::SERVICE_UNAVAILABLE, + ValidatorsRPCError::InternalError(_) => common::StatusCode::INTERNAL_SERVER_ERROR, + } + } +} + +#[inline(always)] +pub async fn validators_list_rpc(_ctx: MmArc, _req: serde_json::Value) -> ValidatorsRPCResult { + todo!() +} diff --git a/mm2src/coins/tendermint/tendermint_coin.rs b/mm2src/coins/tendermint/tendermint_coin.rs index 323637599d..7ccff2bbc5 100644 --- a/mm2src/coins/tendermint/tendermint_coin.rs +++ b/mm2src/coins/tendermint/tendermint_coin.rs @@ -38,6 +38,7 @@ use common::log::{debug, warn}; use common::{get_utc_timestamp, now_sec, Future01CompatExt, DEX_FEE_ADDR_PUBKEY}; use cosmrs::bank::MsgSend; use cosmrs::crypto::secp256k1::SigningKey; +use cosmrs::proto::cosmos::staking::v1beta1::{QueryValidatorsRequest, QueryValidatorsResponse}; use cosmrs::proto::cosmos::auth::v1beta1::{BaseAccount, QueryAccountRequest, QueryAccountResponse}; use cosmrs::proto::cosmos::bank::v1beta1::{MsgSend as MsgSendProto, QueryBalanceRequest, QueryBalanceResponse}; use cosmrs::proto::cosmos::base::tendermint::v1beta1::{GetBlockByHeightRequest, GetBlockByHeightResponse, @@ -89,6 +90,7 @@ const ABCI_QUERY_ACCOUNT_PATH: &str = "/cosmos.auth.v1beta1.Query/Account"; const ABCI_QUERY_BALANCE_PATH: &str = "/cosmos.bank.v1beta1.Query/Balance"; const ABCI_GET_TX_PATH: &str = "/cosmos.tx.v1beta1.Service/GetTx"; const ABCI_GET_TXS_EVENT_PATH: &str = "/cosmos.tx.v1beta1.Service/GetTxsEvent"; +const ABCI_VALIDATORS_PATH: &str = "/cosmos.staking.v1beta1.Query/Validators"; pub(crate) const MIN_TX_SATOSHIS: i64 = 1; @@ -3324,6 +3326,7 @@ pub mod tendermint_coin_tests { use common::{block_on, wait_until_ms, DEX_FEE_ADDR_RAW_PUBKEY}; use cosmrs::proto::cosmos::tx::v1beta1::{GetTxRequest, GetTxResponse, GetTxsEventResponse}; use crypto::privkey::key_pair_from_seed; + use tendermint_rpc::Paging; use std::mem::discriminant; pub const IRIS_TESTNET_HTLC_PAIR1_SEED: &str = "iris test seed"; @@ -4225,4 +4228,47 @@ pub mod tendermint_coin_tests { assert!(parse_expected_sequence_number("").is_err()); assert!(parse_expected_sequence_number("check_tx log: account sequence mismatch, expected").is_err()); } + + #[test] + fn validators_debug() { + let nodes = vec![RpcNode::for_test(IRIS_TESTNET_RPC_URL)]; + let protocol_conf = get_iris_protocol(); + + let conf = TendermintConf { + avg_blocktime: AVG_BLOCKTIME, + derivation_path: None, + }; + + let ctx = mm2_core::mm_ctx::MmCtxBuilder::default().into_mm_arc(); + let key_pair = key_pair_from_seed(IRIS_TESTNET_HTLC_PAIR1_SEED).unwrap(); + let tendermint_pair = TendermintKeyPair::new(key_pair.private().secret, *key_pair.public()); + let activation_policy = + TendermintActivationPolicy::with_private_key_policy(TendermintPrivKeyPolicy::Iguana(tendermint_pair)); + + let coin = common::block_on(TendermintCoin::init( + &ctx, + "IRIS".to_string(), + conf, + protocol_conf, + nodes, + false, + activation_policy, + false, + )) + .unwrap(); + + let rpc_client = block_on(coin.rpc_client()).unwrap(); + let request = AbciRequest::new( + Some(ABCI_VALIDATORS_PATH.to_string()), + QueryValidatorsRequest { status: "".into(), pagination: None }.encode_to_vec(), + ABCI_REQUEST_HEIGHT, + ABCI_REQUEST_PROVE, + ); + let validators = block_on(rpc_client.perform(request)).unwrap(); + let validators = QueryValidatorsResponse::decode(validators.response.value.as_slice()).unwrap(); + + dbg!(validators.validators); + + assert!(false); + } } From a6ebe1c07f5c05be947b9c049ff5cd0fe315a796 Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Tue, 31 Dec 2024 15:54:32 +0300 Subject: [PATCH 02/16] save dev state Signed-off-by: onur-ozkan --- mm2src/coins/rpc_command/tendermint/mod.rs | 2 +- .../coins/rpc_command/tendermint/staking.rs | 16 +++--- mm2src/coins/tendermint/tendermint_coin.rs | 54 +++++++++++++++++-- 3 files changed, 60 insertions(+), 12 deletions(-) diff --git a/mm2src/coins/rpc_command/tendermint/mod.rs b/mm2src/coins/rpc_command/tendermint/mod.rs index d180c60f9f..9a3d714bd3 100644 --- a/mm2src/coins/rpc_command/tendermint/mod.rs +++ b/mm2src/coins/rpc_command/tendermint/mod.rs @@ -1,6 +1,6 @@ mod ibc_chains; -mod staking; mod ibc_transfer_channels; +pub mod staking; pub use ibc_chains::*; pub use ibc_transfer_channels::*; diff --git a/mm2src/coins/rpc_command/tendermint/staking.rs b/mm2src/coins/rpc_command/tendermint/staking.rs index 96f1dfb893..19174cd761 100644 --- a/mm2src/coins/rpc_command/tendermint/staking.rs +++ b/mm2src/coins/rpc_command/tendermint/staking.rs @@ -1,4 +1,4 @@ -use common::HttpStatusCode; +use common::{HttpStatusCode, PagingOptions}; use cosmrs::proto::tendermint::types::Validator; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::MmError; @@ -7,9 +7,10 @@ use crate::tendermint; pub type ValidatorsRPCResult = Result>; -#[derive(Clone, Deserialize)] +#[derive(Default, Deserialize)] pub enum ValidatorStatus { All, + #[default] Active, Jailed, } @@ -24,10 +25,12 @@ impl ToString for ValidatorStatus { } } -#[derive(Clone, Deserialize)] + +#[derive(Deserialize)] pub struct ValidatorsRPC { - pub(crate) page: u16, - pub(crate) limit: u8, + #[serde(flatten)] + paging: PagingOptions, + #[serde(default)] pub(crate) filter_by_status: ValidatorStatus, } @@ -55,6 +58,7 @@ impl HttpStatusCode for ValidatorsRPCError { } #[inline(always)] -pub async fn validators_list_rpc(_ctx: MmArc, _req: serde_json::Value) -> ValidatorsRPCResult { +pub async fn validators_list_rpc(ctx: MmArc, req: ValidatorsRPC) -> ValidatorsRPCResult { + // todo!() } diff --git a/mm2src/coins/tendermint/tendermint_coin.rs b/mm2src/coins/tendermint/tendermint_coin.rs index 7ccff2bbc5..6e65cc6664 100644 --- a/mm2src/coins/tendermint/tendermint_coin.rs +++ b/mm2src/coins/tendermint/tendermint_coin.rs @@ -6,6 +6,7 @@ use super::ibc::IBC_GAS_LIMIT_DEFAULT; use super::{rpc::*, TENDERMINT_COIN_PROTOCOL_TYPE}; use crate::coin_errors::{MyAddressError, ValidatePaymentError, ValidatePaymentResult}; use crate::hd_wallet::{HDPathAccountToAddressId, WithdrawFrom}; +use crate::rpc_command::tendermint::staking::ValidatorStatus; use crate::rpc_command::tendermint::{IBCChainRegistriesResponse, IBCChainRegistriesResult, IBCChainsRequestError, IBCTransferChannel, IBCTransferChannelTag, IBCTransferChannelsRequestError, IBCTransferChannelsResponse, IBCTransferChannelsResult, CHAIN_REGISTRY_BRANCH, @@ -35,18 +36,20 @@ use bitcrypto::{dhash160, sha256}; use common::executor::{abortable_queue::AbortableQueue, AbortableSystem}; use common::executor::{AbortedError, Timer}; use common::log::{debug, warn}; -use common::{get_utc_timestamp, now_sec, Future01CompatExt, DEX_FEE_ADDR_PUBKEY}; +use common::{get_utc_timestamp, now_sec, Future01CompatExt, PagingOptions, DEX_FEE_ADDR_PUBKEY}; use cosmrs::bank::MsgSend; use cosmrs::crypto::secp256k1::SigningKey; -use cosmrs::proto::cosmos::staking::v1beta1::{QueryValidatorsRequest, QueryValidatorsResponse}; use cosmrs::proto::cosmos::auth::v1beta1::{BaseAccount, QueryAccountRequest, QueryAccountResponse}; use cosmrs::proto::cosmos::bank::v1beta1::{MsgSend as MsgSendProto, QueryBalanceRequest, QueryBalanceResponse}; +use cosmrs::proto::cosmos::base::query::v1beta1::PageRequest; use cosmrs::proto::cosmos::base::tendermint::v1beta1::{GetBlockByHeightRequest, GetBlockByHeightResponse, GetLatestBlockRequest, GetLatestBlockResponse}; use cosmrs::proto::cosmos::base::v1beta1::Coin as CoinProto; +use cosmrs::proto::cosmos::staking::v1beta1::{QueryValidatorsRequest, QueryValidatorsResponse}; use cosmrs::proto::cosmos::tx::v1beta1::{GetTxRequest, GetTxResponse, GetTxsEventRequest, GetTxsEventResponse, SimulateRequest, SimulateResponse, Tx, TxBody, TxRaw}; use cosmrs::proto::prost::{DecodeError, Message}; +use cosmrs::proto::tendermint::types::Validator; use cosmrs::tendermint::block::Height; use cosmrs::tendermint::chain::Id as ChainId; use cosmrs::tendermint::PublicKey; @@ -74,7 +77,7 @@ use regex::Regex; use rpc::v1::types::Bytes as BytesJson; use serde_json::{self as json, Value as Json}; use std::collections::HashMap; -use std::convert::TryFrom; +use std::convert::{TryFrom, TryInto}; use std::io; use std::num::NonZeroU32; use std::ops::Deref; @@ -2082,6 +2085,43 @@ impl TendermintCoin { None } + + async fn validators_list( + &self, + paging: PagingOptions, + filter_status: ValidatorStatus, + ) -> MmResult, TendermintCoinRpcError> { + let request = QueryValidatorsRequest { + status: filter_status.to_string(), + pagination: Some(PageRequest { + key: vec![], + offset: ((paging.page_number.get() - 1usize) * paging.limit) + .try_into() + .expect("usize to u64 convertion should never fail"), + limit: paging + .limit + .try_into() + .expect("usize to u64 convertion should never fail"), + count_total: false, + reverse: false, + }), + }; + + let response = self + .rpc_client() + .await? + .abci_query( + Some(ABCI_VALIDATORS_PATH.to_string()), + request.encode_to_vec(), + ABCI_REQUEST_HEIGHT, + ABCI_REQUEST_PROVE, + ) + .await?; + + let response = QueryValidatorsResponse::decode(response.value.as_slice())?; + + return Ok(vec![]) + } } fn clients_from_urls(ctx: &MmArc, nodes: Vec) -> MmResult, TendermintInitErrorKind> { @@ -3326,8 +3366,8 @@ pub mod tendermint_coin_tests { use common::{block_on, wait_until_ms, DEX_FEE_ADDR_RAW_PUBKEY}; use cosmrs::proto::cosmos::tx::v1beta1::{GetTxRequest, GetTxResponse, GetTxsEventResponse}; use crypto::privkey::key_pair_from_seed; - use tendermint_rpc::Paging; use std::mem::discriminant; + use tendermint_rpc::Paging; pub const IRIS_TESTNET_HTLC_PAIR1_SEED: &str = "iris test seed"; // pub const IRIS_TESTNET_HTLC_PAIR1_PUB_KEY: &[u8] = &[ @@ -4260,7 +4300,11 @@ pub mod tendermint_coin_tests { let rpc_client = block_on(coin.rpc_client()).unwrap(); let request = AbciRequest::new( Some(ABCI_VALIDATORS_PATH.to_string()), - QueryValidatorsRequest { status: "".into(), pagination: None }.encode_to_vec(), + QueryValidatorsRequest { + status: "".into(), + pagination: None, + } + .encode_to_vec(), ABCI_REQUEST_HEIGHT, ABCI_REQUEST_PROVE, ); From 9198049042c8a1e4364e2b1e6ab8be0a8a98a469 Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Wed, 1 Jan 2025 13:59:05 +0300 Subject: [PATCH 03/16] make proto types serializable for RPC endpoint Signed-off-by: onur-ozkan --- .../coins/rpc_command/tendermint/staking.rs | 90 +++++++++++++------ mm2src/coins/tendermint/tendermint_coin.rs | 87 +++++++++++++----- 2 files changed, 128 insertions(+), 49 deletions(-) diff --git a/mm2src/coins/rpc_command/tendermint/staking.rs b/mm2src/coins/rpc_command/tendermint/staking.rs index 19174cd761..a29f7ca256 100644 --- a/mm2src/coins/rpc_command/tendermint/staking.rs +++ b/mm2src/coins/rpc_command/tendermint/staking.rs @@ -1,11 +1,10 @@ -use common::{HttpStatusCode, PagingOptions}; -use cosmrs::proto::tendermint::types::Validator; +use common::PagingOptions; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::MmError; -use crate::tendermint; +use crate::{lp_coinfind_or_err, tendermint::TendermintCoinRpcError, MmCoinEnum}; -pub type ValidatorsRPCResult = Result>; +pub type ValidatorsRPCResult = Result>; #[derive(Default, Deserialize)] pub enum ValidatorStatus { @@ -25,9 +24,9 @@ impl ToString for ValidatorStatus { } } - #[derive(Deserialize)] pub struct ValidatorsRPC { + coin: String, #[serde(flatten)] paging: PagingOptions, #[serde(default)] @@ -36,29 +35,68 @@ pub struct ValidatorsRPC { #[derive(Clone, Serialize)] pub struct ValidatorsRPCResponse { - pub(crate) chain_registry_list: Vec, -} - -#[derive(Clone, Debug, Display, Serialize, SerializeErrorType, PartialEq)] -#[serde(tag = "error_type", content = "error_data")] -pub enum ValidatorsRPCError { - #[display(fmt = "Transport error: {}", _0)] - Transport(String), - #[display(fmt = "Internal error: {}", _0)] - InternalError(String), -} - -impl HttpStatusCode for ValidatorsRPCError { - fn status_code(&self) -> common::StatusCode { - match self { - ValidatorsRPCError::Transport(_) => common::StatusCode::SERVICE_UNAVAILABLE, - ValidatorsRPCError::InternalError(_) => common::StatusCode::INTERNAL_SERVER_ERROR, - } - } + pub(crate) validators: Vec, } #[inline(always)] pub async fn validators_list_rpc(ctx: MmArc, req: ValidatorsRPC) -> ValidatorsRPCResult { - // - todo!() + let validators = match lp_coinfind_or_err(&ctx, &req.coin).await { + Ok(MmCoinEnum::Tendermint(coin)) => coin.validators_list(req.filter_by_status, req.paging).await?, + Ok(MmCoinEnum::TendermintToken(token)) => { + token + .platform_coin + .validators_list(req.filter_by_status, req.paging) + .await? + }, + Ok(_) => todo!(), + Err(_) => todo!(), + }; + + let validators_json = validators + .into_iter() + .map(|v| { + let serializable_description = v.description.map(|d| { + json!({ + "moniker": d.moniker, + "identity": d.identity, + "website": d.website, + "security_contact": d.security_contact, + "details": d.details, + }) + }); + + let serializable_commission = v.commission.map(|c| { + let serializable_commission_rates = c.commission_rates.map(|cr| { + json!({ + "rate": cr.rate, + "max_rate": cr.max_rate, + "max_change_rate": cr.max_change_rate + }) + }); + + json!({ + "commission_rates": serializable_commission_rates, + "update_time": c.update_time + }) + }); + + json!({ + "operator_address": v.operator_address, + "consensus_pubkey": v.consensus_pubkey, + "jailed": v.jailed, + "status": v.status, + "tokens": v.tokens, + "delegator_shares": v.delegator_shares, + "description": serializable_description, + "unbonding_height": v.unbonding_height, + "unbonding_time": v.unbonding_time, + "commission": serializable_commission, + "min_self_delegation": v.min_self_delegation, + }) + }) + .collect(); + + Ok(ValidatorsRPCResponse { + validators: validators_json, + }) } diff --git a/mm2src/coins/tendermint/tendermint_coin.rs b/mm2src/coins/tendermint/tendermint_coin.rs index 6e65cc6664..1ea217be13 100644 --- a/mm2src/coins/tendermint/tendermint_coin.rs +++ b/mm2src/coins/tendermint/tendermint_coin.rs @@ -45,11 +45,12 @@ use cosmrs::proto::cosmos::base::query::v1beta1::PageRequest; use cosmrs::proto::cosmos::base::tendermint::v1beta1::{GetBlockByHeightRequest, GetBlockByHeightResponse, GetLatestBlockRequest, GetLatestBlockResponse}; use cosmrs::proto::cosmos::base::v1beta1::Coin as CoinProto; -use cosmrs::proto::cosmos::staking::v1beta1::{QueryValidatorsRequest, QueryValidatorsResponse}; +use cosmrs::proto::cosmos::staking::v1beta1::{QueryValidatorsRequest, + QueryValidatorsResponse as QueryValidatorsResponseProto}; use cosmrs::proto::cosmos::tx::v1beta1::{GetTxRequest, GetTxResponse, GetTxsEventRequest, GetTxsEventResponse, SimulateRequest, SimulateResponse, Tx, TxBody, TxRaw}; use cosmrs::proto::prost::{DecodeError, Message}; -use cosmrs::proto::tendermint::types::Validator; +use cosmrs::staking::{QueryValidatorsResponse, Validator}; use cosmrs::tendermint::block::Height; use cosmrs::tendermint::chain::Id as ChainId; use cosmrs::tendermint::PublicKey; @@ -77,7 +78,7 @@ use regex::Regex; use rpc::v1::types::Bytes as BytesJson; use serde_json::{self as json, Value as Json}; use std::collections::HashMap; -use std::convert::{TryFrom, TryInto}; +use std::convert::TryFrom; use std::io; use std::num::NonZeroU32; use std::ops::Deref; @@ -440,6 +441,14 @@ pub enum TendermintCoinRpcError { UnexpectedAccountType { prefix: String, }, + #[display(fmt = "Coin '{ticker}' could not be found in coins configuration.")] + CoinNotFound { + ticker: String, + }, + #[display(fmt = "'{ticker}' is not a Cosmos coin.")] + UnexpectedCoinType { + ticker: String, + }, } impl From for TendermintCoinRpcError { @@ -460,11 +469,18 @@ impl From for BalanceError { TendermintCoinRpcError::InvalidResponse(e) => BalanceError::InvalidResponse(e), TendermintCoinRpcError::Prost(e) => BalanceError::InvalidResponse(e), TendermintCoinRpcError::PerformError(e) => BalanceError::Transport(e), - TendermintCoinRpcError::RpcClientError(e) => BalanceError::Transport(e), - TendermintCoinRpcError::InternalError(e) => BalanceError::Internal(e), + TendermintCoinRpcError::RpcClientError(e) | TendermintCoinRpcError::InternalError(e) => { + BalanceError::Internal(e) + }, TendermintCoinRpcError::UnexpectedAccountType { prefix } => { BalanceError::Internal(format!("Account type '{prefix}' is not supported for HTLCs")) }, + TendermintCoinRpcError::CoinNotFound { ticker } => { + BalanceError::Internal(format!("Coin '{ticker}' could not be found in coins configuration.")) + }, + TendermintCoinRpcError::UnexpectedCoinType { ticker } => { + BalanceError::Internal(format!("'{ticker}' is not a Cosmos coin.")) + }, } } } @@ -475,11 +491,18 @@ impl From for ValidatePaymentError { TendermintCoinRpcError::InvalidResponse(e) => ValidatePaymentError::InvalidRpcResponse(e), TendermintCoinRpcError::Prost(e) => ValidatePaymentError::InvalidRpcResponse(e), TendermintCoinRpcError::PerformError(e) => ValidatePaymentError::Transport(e), - TendermintCoinRpcError::RpcClientError(e) => ValidatePaymentError::Transport(e), - TendermintCoinRpcError::InternalError(e) => ValidatePaymentError::InternalError(e), + TendermintCoinRpcError::RpcClientError(e) | TendermintCoinRpcError::InternalError(e) => { + ValidatePaymentError::InternalError(e) + }, TendermintCoinRpcError::UnexpectedAccountType { prefix } => { ValidatePaymentError::InvalidParameter(format!("Account type '{prefix}' is not supported for HTLCs")) }, + TendermintCoinRpcError::CoinNotFound { ticker } => ValidatePaymentError::InvalidParameter(format!( + "Coin '{ticker}' could not be found in coins configuration." + )), + TendermintCoinRpcError::UnexpectedCoinType { ticker } => { + ValidatePaymentError::InvalidParameter(format!("'{ticker}' is not a Cosmos coin.")) + }, } } } @@ -2086,41 +2109,38 @@ impl TendermintCoin { None } - async fn validators_list( + pub(crate) async fn validators_list( &self, - paging: PagingOptions, filter_status: ValidatorStatus, + paging: PagingOptions, ) -> MmResult, TendermintCoinRpcError> { let request = QueryValidatorsRequest { status: filter_status.to_string(), pagination: Some(PageRequest { key: vec![], - offset: ((paging.page_number.get() - 1usize) * paging.limit) - .try_into() - .expect("usize to u64 convertion should never fail"), - limit: paging - .limit - .try_into() - .expect("usize to u64 convertion should never fail"), + offset: ((paging.page_number.get() - 1usize) * paging.limit) as u64, + limit: paging.limit as u64, count_total: false, reverse: false, }), }; - let response = self + let raw_response = self .rpc_client() .await? .abci_query( - Some(ABCI_VALIDATORS_PATH.to_string()), + Some(ABCI_VALIDATORS_PATH.to_owned()), request.encode_to_vec(), ABCI_REQUEST_HEIGHT, ABCI_REQUEST_PROVE, ) .await?; - let response = QueryValidatorsResponse::decode(response.value.as_slice())?; + let decoded_proto = QueryValidatorsResponseProto::decode(raw_response.value.as_slice())?; + let typed_response = QueryValidatorsResponse::try_from(decoded_proto) + .map_err(|e| TendermintCoinRpcError::InternalError(e.to_string()))?; - return Ok(vec![]) + return Ok(typed_response.validators); } } @@ -3367,7 +3387,6 @@ pub mod tendermint_coin_tests { use cosmrs::proto::cosmos::tx::v1beta1::{GetTxRequest, GetTxResponse, GetTxsEventResponse}; use crypto::privkey::key_pair_from_seed; use std::mem::discriminant; - use tendermint_rpc::Paging; pub const IRIS_TESTNET_HTLC_PAIR1_SEED: &str = "iris test seed"; // pub const IRIS_TESTNET_HTLC_PAIR1_PUB_KEY: &[u8] = &[ @@ -4309,9 +4328,31 @@ pub mod tendermint_coin_tests { ABCI_REQUEST_PROVE, ); let validators = block_on(rpc_client.perform(request)).unwrap(); - let validators = QueryValidatorsResponse::decode(validators.response.value.as_slice()).unwrap(); + let validators = QueryValidatorsResponseProto::decode(validators.response.value.as_slice()).unwrap(); + + let validators = cosmrs::staking::QueryValidatorsResponse::try_from(validators).unwrap(); + + let validators_json: Vec = validators + .validators + .into_iter() + .map(|v| { + json!({ + "operator_address": v.operator_address, + "consensus_pubkey": v.consensus_pubkey, + "jailed": v.jailed, + "status": v.status, + "tokens": v.tokens, + "delegator_shares": v.delegator_shares, + // "description": v.description, + "unbonding_height": v.unbonding_height, + "unbonding_time": v.unbonding_time, + // "commission": v.commission, + "min_self_delegation": v.min_self_delegation, + }) + }) + .collect(); - dbg!(validators.validators); + dbg!(validators_json); assert!(false); } From b48c1ba1bfbe9754c715b354eceb02c8a74686b0 Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Wed, 1 Jan 2025 13:59:42 +0300 Subject: [PATCH 04/16] remove dummy test Signed-off-by: onur-ozkan --- mm2src/coins/tendermint/tendermint_coin.rs | 69 ---------------------- 1 file changed, 69 deletions(-) diff --git a/mm2src/coins/tendermint/tendermint_coin.rs b/mm2src/coins/tendermint/tendermint_coin.rs index 1ea217be13..293adecba9 100644 --- a/mm2src/coins/tendermint/tendermint_coin.rs +++ b/mm2src/coins/tendermint/tendermint_coin.rs @@ -4287,73 +4287,4 @@ pub mod tendermint_coin_tests { assert!(parse_expected_sequence_number("").is_err()); assert!(parse_expected_sequence_number("check_tx log: account sequence mismatch, expected").is_err()); } - - #[test] - fn validators_debug() { - let nodes = vec![RpcNode::for_test(IRIS_TESTNET_RPC_URL)]; - let protocol_conf = get_iris_protocol(); - - let conf = TendermintConf { - avg_blocktime: AVG_BLOCKTIME, - derivation_path: None, - }; - - let ctx = mm2_core::mm_ctx::MmCtxBuilder::default().into_mm_arc(); - let key_pair = key_pair_from_seed(IRIS_TESTNET_HTLC_PAIR1_SEED).unwrap(); - let tendermint_pair = TendermintKeyPair::new(key_pair.private().secret, *key_pair.public()); - let activation_policy = - TendermintActivationPolicy::with_private_key_policy(TendermintPrivKeyPolicy::Iguana(tendermint_pair)); - - let coin = common::block_on(TendermintCoin::init( - &ctx, - "IRIS".to_string(), - conf, - protocol_conf, - nodes, - false, - activation_policy, - false, - )) - .unwrap(); - - let rpc_client = block_on(coin.rpc_client()).unwrap(); - let request = AbciRequest::new( - Some(ABCI_VALIDATORS_PATH.to_string()), - QueryValidatorsRequest { - status: "".into(), - pagination: None, - } - .encode_to_vec(), - ABCI_REQUEST_HEIGHT, - ABCI_REQUEST_PROVE, - ); - let validators = block_on(rpc_client.perform(request)).unwrap(); - let validators = QueryValidatorsResponseProto::decode(validators.response.value.as_slice()).unwrap(); - - let validators = cosmrs::staking::QueryValidatorsResponse::try_from(validators).unwrap(); - - let validators_json: Vec = validators - .validators - .into_iter() - .map(|v| { - json!({ - "operator_address": v.operator_address, - "consensus_pubkey": v.consensus_pubkey, - "jailed": v.jailed, - "status": v.status, - "tokens": v.tokens, - "delegator_shares": v.delegator_shares, - // "description": v.description, - "unbonding_height": v.unbonding_height, - "unbonding_time": v.unbonding_time, - // "commission": v.commission, - "min_self_delegation": v.min_self_delegation, - }) - }) - .collect(); - - dbg!(validators_json); - - assert!(false); - } } From 2470594686594e3881b86f951ec44fb72edb970c Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Wed, 1 Jan 2025 14:24:50 +0300 Subject: [PATCH 05/16] add RPC error type Signed-off-by: onur-ozkan --- .../coins/rpc_command/tendermint/staking.rs | 46 +++++++++++++++++-- mm2src/coins/tendermint/tendermint_coin.rs | 20 -------- .../mm2_main/src/rpc/dispatcher/dispatcher.rs | 2 + 3 files changed, 45 insertions(+), 23 deletions(-) diff --git a/mm2src/coins/rpc_command/tendermint/staking.rs b/mm2src/coins/rpc_command/tendermint/staking.rs index a29f7ca256..8e83aa6d50 100644 --- a/mm2src/coins/rpc_command/tendermint/staking.rs +++ b/mm2src/coins/rpc_command/tendermint/staking.rs @@ -1,10 +1,10 @@ -use common::PagingOptions; +use common::{HttpStatusCode, PagingOptions, StatusCode}; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::MmError; use crate::{lp_coinfind_or_err, tendermint::TendermintCoinRpcError, MmCoinEnum}; -pub type ValidatorsRPCResult = Result>; +pub type ValidatorsRPCResult = Result>; #[derive(Default, Deserialize)] pub enum ValidatorStatus { @@ -38,8 +38,48 @@ pub struct ValidatorsRPCResponse { pub(crate) validators: Vec, } +#[derive(Clone, Debug, Display, Serialize, SerializeErrorType, PartialEq)] +#[serde(tag = "error_type", content = "error_data")] +pub enum ValidatorsRPCError { + #[display(fmt = "Coin '{ticker}' could not be found in coins configuration.")] + CoinNotFound { ticker: String }, + #[display(fmt = "'{ticker}' is not a Cosmos coin.")] + UnexpectedCoinType { ticker: String }, + #[display(fmt = "Transport error: {}", _0)] + Transport(String), + #[display(fmt = "Internal error: {}", _0)] + InternalError(String), +} + +impl HttpStatusCode for ValidatorsRPCError { + fn status_code(&self) -> common::StatusCode { + match self { + ValidatorsRPCError::Transport(_) => StatusCode::SERVICE_UNAVAILABLE, + ValidatorsRPCError::InternalError(_) => StatusCode::INTERNAL_SERVER_ERROR, + ValidatorsRPCError::UnexpectedCoinType { .. } | ValidatorsRPCError::CoinNotFound { .. } => { + StatusCode::BAD_REQUEST + }, + } + } +} + +impl From for ValidatorsRPCError { + fn from(e: TendermintCoinRpcError) -> Self { + match e { + TendermintCoinRpcError::InvalidResponse(e) + | TendermintCoinRpcError::PerformError(e) + | TendermintCoinRpcError::RpcClientError(e) => ValidatorsRPCError::Transport(e), + TendermintCoinRpcError::Prost(e) | TendermintCoinRpcError::InternalError(e) => ValidatorsRPCError::InternalError(e), + TendermintCoinRpcError::UnexpectedAccountType { .. } => ValidatorsRPCError::InternalError( + "RPC client got an unexpected error 'TendermintCoinRpcError::UnexpectedAccountType', this isn't normal." + .into(), + ), + } + } +} + #[inline(always)] -pub async fn validators_list_rpc(ctx: MmArc, req: ValidatorsRPC) -> ValidatorsRPCResult { +pub async fn validators_rpc(ctx: MmArc, req: ValidatorsRPC) -> ValidatorsRPCResult { let validators = match lp_coinfind_or_err(&ctx, &req.coin).await { Ok(MmCoinEnum::Tendermint(coin)) => coin.validators_list(req.filter_by_status, req.paging).await?, Ok(MmCoinEnum::TendermintToken(token)) => { diff --git a/mm2src/coins/tendermint/tendermint_coin.rs b/mm2src/coins/tendermint/tendermint_coin.rs index 293adecba9..5a1d9e22d7 100644 --- a/mm2src/coins/tendermint/tendermint_coin.rs +++ b/mm2src/coins/tendermint/tendermint_coin.rs @@ -441,14 +441,6 @@ pub enum TendermintCoinRpcError { UnexpectedAccountType { prefix: String, }, - #[display(fmt = "Coin '{ticker}' could not be found in coins configuration.")] - CoinNotFound { - ticker: String, - }, - #[display(fmt = "'{ticker}' is not a Cosmos coin.")] - UnexpectedCoinType { - ticker: String, - }, } impl From for TendermintCoinRpcError { @@ -475,12 +467,6 @@ impl From for BalanceError { TendermintCoinRpcError::UnexpectedAccountType { prefix } => { BalanceError::Internal(format!("Account type '{prefix}' is not supported for HTLCs")) }, - TendermintCoinRpcError::CoinNotFound { ticker } => { - BalanceError::Internal(format!("Coin '{ticker}' could not be found in coins configuration.")) - }, - TendermintCoinRpcError::UnexpectedCoinType { ticker } => { - BalanceError::Internal(format!("'{ticker}' is not a Cosmos coin.")) - }, } } } @@ -497,12 +483,6 @@ impl From for ValidatePaymentError { TendermintCoinRpcError::UnexpectedAccountType { prefix } => { ValidatePaymentError::InvalidParameter(format!("Account type '{prefix}' is not supported for HTLCs")) }, - TendermintCoinRpcError::CoinNotFound { ticker } => ValidatePaymentError::InvalidParameter(format!( - "Coin '{ticker}' could not be found in coins configuration." - )), - TendermintCoinRpcError::UnexpectedCoinType { ticker } => { - ValidatePaymentError::InvalidParameter(format!("'{ticker}' is not a Cosmos coin.")) - }, } } } diff --git a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs index 937db9631b..fd9babd2c5 100644 --- a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs +++ b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs @@ -23,6 +23,7 @@ use crate::rpc::lp_commands::trezor::trezor_connection_status; use crate::rpc::rate_limiter::{process_rate_limit, RateLimitContext}; use coins::eth::EthCoin; use coins::my_tx_history_v2::my_tx_history_v2_rpc; +use coins::rpc_command::tendermint::staking::validators_rpc; use coins::rpc_command::tendermint::{ibc_chains, ibc_transfer_channels}; use coins::rpc_command::{account_balance::account_balance, get_current_mtp::get_current_mtp_rpc, @@ -212,6 +213,7 @@ async fn dispatcher_v2(request: MmRpcRequest, ctx: MmArc) -> DispatcherResult handle_mmrpc(ctx, request, start_version_stat_collection).await, "stop_simple_market_maker_bot" => handle_mmrpc(ctx, request, stop_simple_market_maker_bot).await, "stop_version_stat_collection" => handle_mmrpc(ctx, request, stop_version_stat_collection).await, + "tendermint_validators" => handle_mmrpc(ctx, request, validators_rpc).await, "trade_preimage" => handle_mmrpc(ctx, request, trade_preimage_rpc).await, "trezor_connection_status" => handle_mmrpc(ctx, request, trezor_connection_status).await, "update_nft" => handle_mmrpc(ctx, request, update_nft).await, From c4df52a5a95c985f3553e484e065c972dad525b6 Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Wed, 1 Jan 2025 14:26:01 +0300 Subject: [PATCH 06/16] add TODO Signed-off-by: onur-ozkan --- mm2src/coins/tendermint/tendermint_coin.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mm2src/coins/tendermint/tendermint_coin.rs b/mm2src/coins/tendermint/tendermint_coin.rs index 5a1d9e22d7..e13cc7dbe1 100644 --- a/mm2src/coins/tendermint/tendermint_coin.rs +++ b/mm2src/coins/tendermint/tendermint_coin.rs @@ -429,6 +429,8 @@ pub enum TendermintInitErrorKind { CantUseWatchersWithPubkeyPolicy, } +/// TODO: Rename this into `ClientRpcError` because this is very +/// confusing atm. #[derive(Display, Debug, Serialize, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] pub enum TendermintCoinRpcError { From 95d008be85777df1039d941344991c72f198b61f Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Thu, 2 Jan 2025 12:01:38 +0300 Subject: [PATCH 07/16] fix status filtering Signed-off-by: onur-ozkan --- mm2src/coins/rpc_command/tendermint/staking.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/mm2src/coins/rpc_command/tendermint/staking.rs b/mm2src/coins/rpc_command/tendermint/staking.rs index 8e83aa6d50..4acdcd55b6 100644 --- a/mm2src/coins/rpc_command/tendermint/staking.rs +++ b/mm2src/coins/rpc_command/tendermint/staking.rs @@ -10,22 +10,23 @@ pub type ValidatorsRPCResult = Result String { match self { - ValidatorStatus::All => "".into(), - ValidatorStatus::Active => "Bonded".into(), - ValidatorStatus::Jailed => "Unbonded".into(), + ValidatorStatus::All => String::default(), + ValidatorStatus::Bonded => "BOND_STATUS_BONDED".into(), + ValidatorStatus::Unbonded => "BOND_STATUS_UNBONDED".into(), } } } #[derive(Deserialize)] pub struct ValidatorsRPC { + #[serde(rename = "ticker")] coin: String, #[serde(flatten)] paging: PagingOptions, From 61b165442a2b56469b04db8dda9534148a5cf34c Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Thu, 2 Jan 2025 12:06:02 +0300 Subject: [PATCH 08/16] fix clippy warn Signed-off-by: onur-ozkan --- mm2src/coins/tendermint/tendermint_coin.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm2src/coins/tendermint/tendermint_coin.rs b/mm2src/coins/tendermint/tendermint_coin.rs index e13cc7dbe1..6f53ce5851 100644 --- a/mm2src/coins/tendermint/tendermint_coin.rs +++ b/mm2src/coins/tendermint/tendermint_coin.rs @@ -2122,7 +2122,7 @@ impl TendermintCoin { let typed_response = QueryValidatorsResponse::try_from(decoded_proto) .map_err(|e| TendermintCoinRpcError::InternalError(e.to_string()))?; - return Ok(typed_response.validators); + Ok(typed_response.validators) } } From 8d59f4d2a6e9ba4ba27d7a08dd5525117ebc0f47 Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Thu, 2 Jan 2025 12:33:47 +0300 Subject: [PATCH 09/16] resolve `todo!()`s Signed-off-by: onur-ozkan --- mm2src/coins/rpc_command/tendermint/staking.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/mm2src/coins/rpc_command/tendermint/staking.rs b/mm2src/coins/rpc_command/tendermint/staking.rs index 4acdcd55b6..6c4a6141df 100644 --- a/mm2src/coins/rpc_command/tendermint/staking.rs +++ b/mm2src/coins/rpc_command/tendermint/staking.rs @@ -57,9 +57,8 @@ impl HttpStatusCode for ValidatorsRPCError { match self { ValidatorsRPCError::Transport(_) => StatusCode::SERVICE_UNAVAILABLE, ValidatorsRPCError::InternalError(_) => StatusCode::INTERNAL_SERVER_ERROR, - ValidatorsRPCError::UnexpectedCoinType { .. } | ValidatorsRPCError::CoinNotFound { .. } => { - StatusCode::BAD_REQUEST - }, + ValidatorsRPCError::CoinNotFound { .. } => StatusCode::NOT_FOUND, + ValidatorsRPCError::UnexpectedCoinType { .. } => StatusCode::BAD_REQUEST, } } } @@ -89,8 +88,8 @@ pub async fn validators_rpc(ctx: MmArc, req: ValidatorsRPC) -> ValidatorsRPCResu .validators_list(req.filter_by_status, req.paging) .await? }, - Ok(_) => todo!(), - Err(_) => todo!(), + Ok(_) => return MmError::err(ValidatorsRPCError::UnexpectedCoinType { ticker: req.coin }), + Err(_) => return MmError::err(ValidatorsRPCError::UnexpectedCoinType { ticker: req.coin }), }; let validators_json = validators From 05eb07e052df60ad7ecd409ac5b40c7b8a3a1bab Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Thu, 2 Jan 2025 12:35:33 +0300 Subject: [PATCH 10/16] remove inline attribute Signed-off-by: onur-ozkan --- mm2src/coins/rpc_command/tendermint/staking.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/mm2src/coins/rpc_command/tendermint/staking.rs b/mm2src/coins/rpc_command/tendermint/staking.rs index 6c4a6141df..1bbbbd6ff5 100644 --- a/mm2src/coins/rpc_command/tendermint/staking.rs +++ b/mm2src/coins/rpc_command/tendermint/staking.rs @@ -78,7 +78,6 @@ impl From for ValidatorsRPCError { } } -#[inline(always)] pub async fn validators_rpc(ctx: MmArc, req: ValidatorsRPC) -> ValidatorsRPCResult { let validators = match lp_coinfind_or_err(&ctx, &req.coin).await { Ok(MmCoinEnum::Tendermint(coin)) => coin.validators_list(req.filter_by_status, req.paging).await?, From 838bee4027c89de58a3a7b4f1377d729762885d2 Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Thu, 2 Jan 2025 13:11:34 +0300 Subject: [PATCH 11/16] improve `validators_rpc` Signed-off-by: onur-ozkan --- .../coins/rpc_command/tendermint/staking.rs | 92 ++++++++++--------- 1 file changed, 47 insertions(+), 45 deletions(-) diff --git a/mm2src/coins/rpc_command/tendermint/staking.rs b/mm2src/coins/rpc_command/tendermint/staking.rs index 1bbbbd6ff5..f6e4461137 100644 --- a/mm2src/coins/rpc_command/tendermint/staking.rs +++ b/mm2src/coins/rpc_command/tendermint/staking.rs @@ -1,4 +1,5 @@ use common::{HttpStatusCode, PagingOptions, StatusCode}; +use cosmrs::staking::{Commission, Description, Validator}; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::MmError; @@ -79,6 +80,51 @@ impl From for ValidatorsRPCError { } pub async fn validators_rpc(ctx: MmArc, req: ValidatorsRPC) -> ValidatorsRPCResult { + fn maybe_jsonize_description(description: Option) -> Option { + description.map(|d| { + json!({ + "moniker": d.moniker, + "identity": d.identity, + "website": d.website, + "security_contact": d.security_contact, + "details": d.details, + }) + }) + } + + fn maybe_jsonize_commission(commission: Option) -> Option { + commission.map(|c| { + let rates = c.commission_rates.map(|cr| { + json!({ + "rate": cr.rate, + "max_rate": cr.max_rate, + "max_change_rate": cr.max_change_rate + }) + }); + + json!({ + "commission_rates": rates, + "update_time": c.update_time + }) + }) + } + + fn jsonize_validator(v: Validator) -> serde_json::Value { + json!({ + "operator_address": v.operator_address, + "consensus_pubkey": v.consensus_pubkey, + "jailed": v.jailed, + "status": v.status, + "tokens": v.tokens, + "delegator_shares": v.delegator_shares, + "description": maybe_jsonize_description(v.description), + "unbonding_height": v.unbonding_height, + "unbonding_time": v.unbonding_time, + "commission": maybe_jsonize_commission(v.commission), + "min_self_delegation": v.min_self_delegation, + }) + } + let validators = match lp_coinfind_or_err(&ctx, &req.coin).await { Ok(MmCoinEnum::Tendermint(coin)) => coin.validators_list(req.filter_by_status, req.paging).await?, Ok(MmCoinEnum::TendermintToken(token)) => { @@ -91,51 +137,7 @@ pub async fn validators_rpc(ctx: MmArc, req: ValidatorsRPC) -> ValidatorsRPCResu Err(_) => return MmError::err(ValidatorsRPCError::UnexpectedCoinType { ticker: req.coin }), }; - let validators_json = validators - .into_iter() - .map(|v| { - let serializable_description = v.description.map(|d| { - json!({ - "moniker": d.moniker, - "identity": d.identity, - "website": d.website, - "security_contact": d.security_contact, - "details": d.details, - }) - }); - - let serializable_commission = v.commission.map(|c| { - let serializable_commission_rates = c.commission_rates.map(|cr| { - json!({ - "rate": cr.rate, - "max_rate": cr.max_rate, - "max_change_rate": cr.max_change_rate - }) - }); - - json!({ - "commission_rates": serializable_commission_rates, - "update_time": c.update_time - }) - }); - - json!({ - "operator_address": v.operator_address, - "consensus_pubkey": v.consensus_pubkey, - "jailed": v.jailed, - "status": v.status, - "tokens": v.tokens, - "delegator_shares": v.delegator_shares, - "description": serializable_description, - "unbonding_height": v.unbonding_height, - "unbonding_time": v.unbonding_time, - "commission": serializable_commission, - "min_self_delegation": v.min_self_delegation, - }) - }) - .collect(); - Ok(ValidatorsRPCResponse { - validators: validators_json, + validators: validators.into_iter().map(jsonize_validator).collect(), }) } From ee20b9af70f15c453a5f105aeae99cf177d624cc Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Thu, 2 Jan 2025 13:40:46 +0300 Subject: [PATCH 12/16] add coverage for tendermint_validators RPC Signed-off-by: onur-ozkan --- .../tests/docker_tests/tendermint_tests.rs | 28 ++++++++- mm2src/mm2_test_helpers/src/for_tests.rs | 57 +++++++++++++------ 2 files changed, 67 insertions(+), 18 deletions(-) diff --git a/mm2src/mm2_main/tests/docker_tests/tendermint_tests.rs b/mm2src/mm2_main/tests/docker_tests/tendermint_tests.rs index 9fe3858736..c602c93662 100644 --- a/mm2src/mm2_main/tests/docker_tests/tendermint_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/tendermint_tests.rs @@ -5,7 +5,7 @@ use mm2_test_helpers::for_tests::{atom_testnet_conf, disable_coin, disable_coin_ enable_tendermint_token, enable_tendermint_without_balance, get_tendermint_my_tx_history, ibc_withdraw, iris_ibc_nucleus_testnet_conf, my_balance, nucleus_testnet_conf, orderbook, orderbook_v2, send_raw_transaction, - set_price, withdraw_v1, MarketMakerIt, Mm2TestConf}; + set_price, tendermint_validators, withdraw_v1, MarketMakerIt, Mm2TestConf}; use mm2_test_helpers::structs::{Bip44Chain, HDAccountAddressId, OrderbookAddress, OrderbookV2Response, RpcV2Response, TendermintActivationResult, TransactionDetails}; use serde_json::json; @@ -651,6 +651,32 @@ fn test_passive_coin_and_force_disable() { block_on(disable_coin_err(&mm, token, false)); } +#[test] +fn test_tendermint_validators_rpc() { + let coins = json!([nucleus_testnet_conf()]); + let platform_coin = coins[0]["coin"].as_str().unwrap(); + + let conf = Mm2TestConf::seednode(TENDERMINT_TEST_SEED, &coins); + let mm = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); + + let activation_res = block_on(enable_tendermint( + &mm, + platform_coin, + &[], + NUCLEUS_TESTNET_RPC_URLS, + false, + )); + assert!(&activation_res.get("result").unwrap().get("address").is_some()); + + let validators_raw_response = block_on(tendermint_validators(&mm, platform_coin, "All", 10, 1)); + + assert_eq!( + validators_raw_response["result"]["validators"][0]["operator_address"], + "nucvaloper15d4sf4z6y0vk9dnum8yzkvr9c3wq4q897vefpu" + ); + assert_eq!(validators_raw_response["result"]["validators"][0]["jailed"], false); +} + mod swap { use super::*; diff --git a/mm2src/mm2_test_helpers/src/for_tests.rs b/mm2src/mm2_test_helpers/src/for_tests.rs index 7592384696..5deae3d4b3 100644 --- a/mm2src/mm2_test_helpers/src/for_tests.rs +++ b/mm2src/mm2_test_helpers/src/for_tests.rs @@ -3091,6 +3091,33 @@ pub async fn enable_tendermint_token(mm: &MarketMakerIt, coin: &str) -> Json { json::from_str(&request.1).unwrap() } +pub async fn tendermint_validators( + mm: &MarketMakerIt, + coin: &str, + filter_by_status: &str, + limit: usize, + page_number: usize, +) -> Json { + let rpc_endpoint = "tendermint_validators"; + let request = json!({ + "userpass": mm.userpass, + "method": rpc_endpoint, + "mmrpc": "2.0", + "params": { + "ticker": coin, + "filter_by_status": filter_by_status, + "limit": limit, + "page_number": page_number + } + }); + log!("{rpc_endpoint} request {}", json::to_string(&request).unwrap()); + + let request = mm.rpc(&request).await.unwrap(); + assert_eq!(request.0, StatusCode::OK, "{rpc_endpoint} failed: {}", request.1); + log!("{rpc_endpoint} response {}", request.1); + json::from_str(&request.1).unwrap() +} + pub async fn init_utxo_electrum( mm: &MarketMakerIt, coin: &str, @@ -3271,18 +3298,19 @@ async fn init_erc20_token( protocol: Option, path_to_address: Option, ) -> Result<(StatusCode, Json), Json> { - let (status, response, _) = mm.rpc(&json!({ - "userpass": mm.userpass, - "method": "task::enable_erc20::init", - "mmrpc": "2.0", - "params": { - "ticker": ticker, - "protocol": protocol, - "activation_params": { - "path_to_address": path_to_address.unwrap_or_default(), + let (status, response, _) = mm + .rpc(&json!({ + "userpass": mm.userpass, + "method": "task::enable_erc20::init", + "mmrpc": "2.0", + "params": { + "ticker": ticker, + "protocol": protocol, + "activation_params": { + "path_to_address": path_to_address.unwrap_or_default(), + } } - } - })) + })) .await .unwrap(); @@ -3352,12 +3380,7 @@ pub async fn get_token_info(mm: &MarketMakerIt, protocol: Json) -> TokenInfoResp })) .await .unwrap(); - assert_eq!( - response.0, - StatusCode::OK, - "'get_token_info' failed: {}", - response.1 - ); + assert_eq!(response.0, StatusCode::OK, "'get_token_info' failed: {}", response.1); let response_json: Json = json::from_str(&response.1).unwrap(); json::from_value(response_json["result"].clone()).unwrap() } From 4c63f6efbef97cf5a88a9b58ffb8d49041255de3 Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Thu, 2 Jan 2025 18:58:21 +0300 Subject: [PATCH 13/16] apply nit changes Signed-off-by: onur-ozkan --- mm2src/coins/rpc_command/tendermint/staking.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/mm2src/coins/rpc_command/tendermint/staking.rs b/mm2src/coins/rpc_command/tendermint/staking.rs index f6e4461137..e64140313c 100644 --- a/mm2src/coins/rpc_command/tendermint/staking.rs +++ b/mm2src/coins/rpc_command/tendermint/staking.rs @@ -5,10 +5,8 @@ use mm2_err_handle::prelude::MmError; use crate::{lp_coinfind_or_err, tendermint::TendermintCoinRpcError, MmCoinEnum}; -pub type ValidatorsRPCResult = Result>; - #[derive(Default, Deserialize)] -pub enum ValidatorStatus { +pub(crate) enum ValidatorStatus { All, #[default] Bonded, @@ -32,12 +30,12 @@ pub struct ValidatorsRPC { #[serde(flatten)] paging: PagingOptions, #[serde(default)] - pub(crate) filter_by_status: ValidatorStatus, + filter_by_status: ValidatorStatus, } #[derive(Clone, Serialize)] pub struct ValidatorsRPCResponse { - pub(crate) validators: Vec, + validators: Vec, } #[derive(Clone, Debug, Display, Serialize, SerializeErrorType, PartialEq)] @@ -79,7 +77,10 @@ impl From for ValidatorsRPCError { } } -pub async fn validators_rpc(ctx: MmArc, req: ValidatorsRPC) -> ValidatorsRPCResult { +pub async fn validators_rpc( + ctx: MmArc, + req: ValidatorsRPC, +) -> Result> { fn maybe_jsonize_description(description: Option) -> Option { description.map(|d| { json!({ From 43dbf48d4ae09d1774b2df5dc195a236920b3e64 Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Mon, 6 Jan 2025 09:22:46 +0300 Subject: [PATCH 14/16] document `ValidatorStatus` Signed-off-by: onur-ozkan --- mm2src/coins/rpc_command/tendermint/staking.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mm2src/coins/rpc_command/tendermint/staking.rs b/mm2src/coins/rpc_command/tendermint/staking.rs index e64140313c..b9c2a618f1 100644 --- a/mm2src/coins/rpc_command/tendermint/staking.rs +++ b/mm2src/coins/rpc_command/tendermint/staking.rs @@ -5,11 +5,16 @@ use mm2_err_handle::prelude::MmError; use crate::{lp_coinfind_or_err, tendermint::TendermintCoinRpcError, MmCoinEnum}; +/// Represents current status of the validator. #[derive(Default, Deserialize)] pub(crate) enum ValidatorStatus { All, + /// Validator is in the active set and participates in consensus. #[default] Bonded, + /// Validator is not in the active set and does not participate in consensus. + /// Accordingly, they do not receive rewards and cannot be slashed. + /// It is possible to delegate tokens to a validator in this state. Unbonded, } From db5cff64dce750f9e2e097869ee32e4350e669a6 Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Mon, 6 Jan 2025 15:26:37 +0300 Subject: [PATCH 15/16] use proper error variant on coin filtering Signed-off-by: onur-ozkan --- mm2src/coins/rpc_command/tendermint/staking.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm2src/coins/rpc_command/tendermint/staking.rs b/mm2src/coins/rpc_command/tendermint/staking.rs index b9c2a618f1..98f973605e 100644 --- a/mm2src/coins/rpc_command/tendermint/staking.rs +++ b/mm2src/coins/rpc_command/tendermint/staking.rs @@ -140,7 +140,7 @@ pub async fn validators_rpc( .await? }, Ok(_) => return MmError::err(ValidatorsRPCError::UnexpectedCoinType { ticker: req.coin }), - Err(_) => return MmError::err(ValidatorsRPCError::UnexpectedCoinType { ticker: req.coin }), + Err(_) => return MmError::err(ValidatorsRPCError::CoinNotFound { ticker: req.coin }), }; Ok(ValidatorsRPCResponse { From 96306cc665ae62d03f36f64358a67c228ae38d60 Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Tue, 7 Jan 2025 09:46:00 +0300 Subject: [PATCH 16/16] apply nits Signed-off-by: onur-ozkan --- mm2src/coins/rpc_command/tendermint/staking.rs | 3 ++- mm2src/coins/tendermint/tendermint_coin.rs | 12 ++++++------ mm2src/mm2_test_helpers/src/for_tests.rs | 8 ++++---- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/mm2src/coins/rpc_command/tendermint/staking.rs b/mm2src/coins/rpc_command/tendermint/staking.rs index 98f973605e..c6ac3dca4e 100644 --- a/mm2src/coins/rpc_command/tendermint/staking.rs +++ b/mm2src/coins/rpc_command/tendermint/staking.rs @@ -14,13 +14,14 @@ pub(crate) enum ValidatorStatus { Bonded, /// Validator is not in the active set and does not participate in consensus. /// Accordingly, they do not receive rewards and cannot be slashed. - /// It is possible to delegate tokens to a validator in this state. + /// It is still possible to delegate tokens to a validator in this state. Unbonded, } impl ToString for ValidatorStatus { fn to_string(&self) -> String { match self { + // An empty string doesn't filter any validators and we get an unfiltered result. ValidatorStatus::All => String::default(), ValidatorStatus::Bonded => "BOND_STATUS_BONDED".into(), ValidatorStatus::Unbonded => "BOND_STATUS_UNBONDED".into(), diff --git a/mm2src/coins/tendermint/tendermint_coin.rs b/mm2src/coins/tendermint/tendermint_coin.rs index 6f53ce5851..9573e6de4b 100644 --- a/mm2src/coins/tendermint/tendermint_coin.rs +++ b/mm2src/coins/tendermint/tendermint_coin.rs @@ -462,10 +462,10 @@ impl From for BalanceError { match err { TendermintCoinRpcError::InvalidResponse(e) => BalanceError::InvalidResponse(e), TendermintCoinRpcError::Prost(e) => BalanceError::InvalidResponse(e), - TendermintCoinRpcError::PerformError(e) => BalanceError::Transport(e), - TendermintCoinRpcError::RpcClientError(e) | TendermintCoinRpcError::InternalError(e) => { - BalanceError::Internal(e) + TendermintCoinRpcError::PerformError(e) | TendermintCoinRpcError::RpcClientError(e) => { + BalanceError::Transport(e) }, + TendermintCoinRpcError::InternalError(e) => BalanceError::Internal(e), TendermintCoinRpcError::UnexpectedAccountType { prefix } => { BalanceError::Internal(format!("Account type '{prefix}' is not supported for HTLCs")) }, @@ -478,10 +478,10 @@ impl From for ValidatePaymentError { match err { TendermintCoinRpcError::InvalidResponse(e) => ValidatePaymentError::InvalidRpcResponse(e), TendermintCoinRpcError::Prost(e) => ValidatePaymentError::InvalidRpcResponse(e), - TendermintCoinRpcError::PerformError(e) => ValidatePaymentError::Transport(e), - TendermintCoinRpcError::RpcClientError(e) | TendermintCoinRpcError::InternalError(e) => { - ValidatePaymentError::InternalError(e) + TendermintCoinRpcError::PerformError(e) | TendermintCoinRpcError::RpcClientError(e) => { + ValidatePaymentError::Transport(e) }, + TendermintCoinRpcError::InternalError(e) => ValidatePaymentError::InternalError(e), TendermintCoinRpcError::UnexpectedAccountType { prefix } => { ValidatePaymentError::InvalidParameter(format!("Account type '{prefix}' is not supported for HTLCs")) }, diff --git a/mm2src/mm2_test_helpers/src/for_tests.rs b/mm2src/mm2_test_helpers/src/for_tests.rs index 5deae3d4b3..b367c4653c 100644 --- a/mm2src/mm2_test_helpers/src/for_tests.rs +++ b/mm2src/mm2_test_helpers/src/for_tests.rs @@ -3112,10 +3112,10 @@ pub async fn tendermint_validators( }); log!("{rpc_endpoint} request {}", json::to_string(&request).unwrap()); - let request = mm.rpc(&request).await.unwrap(); - assert_eq!(request.0, StatusCode::OK, "{rpc_endpoint} failed: {}", request.1); - log!("{rpc_endpoint} response {}", request.1); - json::from_str(&request.1).unwrap() + let response = mm.rpc(&request).await.unwrap(); + assert_eq!(response.0, StatusCode::OK, "{rpc_endpoint} failed: {}", response.1); + log!("{rpc_endpoint} response {}", response.1); + json::from_str(&response.1).unwrap() } pub async fn init_utxo_electrum(