Skip to content

Commit

Permalink
Merge branch 'dev' into fix-utxo-tx-fee-for-change
Browse files Browse the repository at this point in the history
* dev:
  feat(tendermint): validators RPC (#2310)
  chore(CI): validate Cargo lock file (#2309)
  test(P2P): add test for peer time sync validation (#2304)
  • Loading branch information
dimxy committed Jan 8, 2025
2 parents cea1b2f + 1908a2e commit 455403c
Show file tree
Hide file tree
Showing 12 changed files with 380 additions and 24 deletions.
16 changes: 16 additions & 0 deletions .github/workflows/validate-cargo-lock.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: Validate Cargo.lock
on: [push]

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
validate-cargo-lock:
name: Checking Cargo.lock file
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Validate Cargo.lock
run: cargo update -w --locked
1 change: 1 addition & 0 deletions mm2src/coins/rpc_command/tendermint/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod ibc_chains;
mod ibc_transfer_channels;
pub mod staking;

pub use ibc_chains::*;
pub use ibc_transfer_channels::*;
Expand Down
150 changes: 150 additions & 0 deletions mm2src/coins/rpc_command/tendermint/staking.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
use common::{HttpStatusCode, PagingOptions, StatusCode};
use cosmrs::staking::{Commission, Description, Validator};
use mm2_core::mm_ctx::MmArc;
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 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(),
}
}
}

#[derive(Deserialize)]
pub struct ValidatorsRPC {
#[serde(rename = "ticker")]
coin: String,
#[serde(flatten)]
paging: PagingOptions,
#[serde(default)]
filter_by_status: ValidatorStatus,
}

#[derive(Clone, Serialize)]
pub struct ValidatorsRPCResponse {
validators: Vec<serde_json::Value>,
}

#[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::CoinNotFound { .. } => StatusCode::NOT_FOUND,
ValidatorsRPCError::UnexpectedCoinType { .. } => StatusCode::BAD_REQUEST,
}
}
}

impl From<TendermintCoinRpcError> 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(),
),
}
}
}

pub async fn validators_rpc(
ctx: MmArc,
req: ValidatorsRPC,
) -> Result<ValidatorsRPCResponse, MmError<ValidatorsRPCError>> {
fn maybe_jsonize_description(description: Option<Description>) -> Option<serde_json::Value> {
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<Commission>) -> Option<serde_json::Value> {
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)) => {
token
.platform_coin
.validators_list(req.filter_by_status, req.paging)
.await?
},
Ok(_) => return MmError::err(ValidatorsRPCError::UnexpectedCoinType { ticker: req.coin }),
Err(_) => return MmError::err(ValidatorsRPCError::CoinNotFound { ticker: req.coin }),
};

Ok(ValidatorsRPCResponse {
validators: validators.into_iter().map(jsonize_validator).collect(),
})
}
54 changes: 49 additions & 5 deletions mm2src/coins/tendermint/tendermint_coin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -35,17 +36,21 @@ 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::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 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::staking::{QueryValidatorsResponse, Validator};
use cosmrs::tendermint::block::Height;
use cosmrs::tendermint::chain::Id as ChainId;
use cosmrs::tendermint::PublicKey;
Expand Down Expand Up @@ -89,6 +94,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;

Expand Down Expand Up @@ -423,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 {
Expand Down Expand Up @@ -454,8 +462,9 @@ impl From<TendermintCoinRpcError> 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) => BalanceError::Transport(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"))
Expand All @@ -469,8 +478,9 @@ impl From<TendermintCoinRpcError> 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) => ValidatePaymentError::Transport(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"))
Expand Down Expand Up @@ -2080,6 +2090,40 @@ impl TendermintCoin {

None
}

pub(crate) async fn validators_list(
&self,
filter_status: ValidatorStatus,
paging: PagingOptions,
) -> MmResult<Vec<Validator>, TendermintCoinRpcError> {
let request = QueryValidatorsRequest {
status: filter_status.to_string(),
pagination: Some(PageRequest {
key: vec![],
offset: ((paging.page_number.get() - 1usize) * paging.limit) as u64,
limit: paging.limit as u64,
count_total: false,
reverse: false,
}),
};

let raw_response = self
.rpc_client()
.await?
.abci_query(
Some(ABCI_VALIDATORS_PATH.to_owned()),
request.encode_to_vec(),
ABCI_REQUEST_HEIGHT,
ABCI_REQUEST_PROVE,
)
.await?;

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()))?;

Ok(typed_response.validators)
}
}

fn clients_from_urls(ctx: &MmArc, nodes: Vec<RpcNode>) -> MmResult<Vec<HttpClient>, TendermintInitErrorKind> {
Expand Down
1 change: 1 addition & 0 deletions mm2src/common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ path = "common.rs"
doctest = false

[features]
for-tests = []
track-ctx-pointer = ["shared_ref_counter/enable", "shared_ref_counter/log"]

[dependencies]
Expand Down
12 changes: 11 additions & 1 deletion mm2src/common/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1080,7 +1080,17 @@ impl<Id> Default for PagingOptionsEnum<Id> {
}

#[inline(always)]
pub fn get_utc_timestamp() -> i64 { Utc::now().timestamp() }
pub fn get_utc_timestamp() -> i64 {
// get_utc_timestamp for tests allowing to add some bias to 'now'
#[cfg(feature = "for-tests")]
return Utc::now().timestamp()
+ std::env::var("TEST_TIMESTAMP_OFFSET")
.map(|s| s.as_str().parse::<i64>().unwrap_or_default())
.unwrap_or_default();

#[cfg(not(feature = "for-tests"))]
return Utc::now().timestamp();
}

#[inline(always)]
pub fn get_utc_timestamp_nanos() -> i64 { Utc::now().timestamp_nanos() }
Expand Down
1 change: 1 addition & 0 deletions mm2src/mm2_main/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ winapi = "0.3"
[dev-dependencies]
coins = { path = "../coins", features = ["for-tests"] }
coins_activation = { path = "../coins_activation", features = ["for-tests"] }
common = { path = "../common", features = ["for-tests"] }
mm2_test_helpers = { path = "../mm2_test_helpers" }
trading_api = { path = "../trading_api", features = ["mocktopus"] }
mocktopus = "0.8.0"
Expand Down
2 changes: 2 additions & 0 deletions mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -212,6 +213,7 @@ async fn dispatcher_v2(request: MmRpcRequest, ctx: MmArc) -> DispatcherResult<Re
"start_version_stat_collection" => 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,
Expand Down
Loading

0 comments on commit 455403c

Please sign in to comment.