diff --git a/Cargo.lock b/Cargo.lock index 17a35767ab..d8108c00fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -881,7 +881,6 @@ dependencies = [ "mm2_db", "mm2_err_handle", "mm2_event_stream", - "mm2_git", "mm2_io", "mm2_metamask", "mm2_metrics", diff --git a/mm2src/coins/Cargo.toml b/mm2src/coins/Cargo.toml index 13aa9c2b72..33c8457dce 100644 --- a/mm2src/coins/Cargo.toml +++ b/mm2src/coins/Cargo.toml @@ -66,7 +66,6 @@ nom = "6.1.2" mm2_core = { path = "../mm2_core" } mm2_err_handle = { path = "../mm2_err_handle" } mm2_event_stream = { path = "../mm2_event_stream" } -mm2_git = { path = "../mm2_git" } mm2_io = { path = "../mm2_io" } mm2_metrics = { path = "../mm2_metrics" } mm2_net = { path = "../mm2_net" } diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 0bf7f34315..05872037a5 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -73,6 +73,7 @@ use mm2_rpc::data::legacy::{EnabledCoin, GetEnabledResponse, Mm2RpcResult}; use mocktopus::macros::*; use parking_lot::Mutex as PaMutex; use rpc::v1::types::{Bytes as BytesJson, H256 as H256Json}; +use rpc_command::tendermint::ibc::ChannelId; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde_json::{self as json, Value as Json}; use std::array::TryFromSliceError; @@ -2220,7 +2221,7 @@ pub struct WithdrawRequest { fee: Option, memo: Option, /// Tendermint specific field used for manually providing the IBC channel IDs. - ibc_source_channel: Option, + ibc_source_channel: Option, /// Currently, this flag is used by ETH/ERC20 coins activated with MetaMask **only**. #[cfg(target_arch = "wasm32")] #[serde(default)] @@ -3177,15 +3178,22 @@ pub enum WithdrawError { }, #[display(fmt = "Signing error {}", _0)] SigningError(String), - #[display(fmt = "Eth transaction type not supported")] + #[display(fmt = "Transaction type not supported")] TxTypeNotSupported, - #[display(fmt = "'chain_registry_name' was not found in coins configuration for '{}'", _0)] - RegistryNameIsMissing(String), #[display( - fmt = "IBC channel could not found for '{}' address. Consider providing it manually with 'ibc_source_channel' in the request.", - _0 + fmt = "IBC channel could not be found in coins file for '{}' address. Provide it manually by including `ibc_source_channel` in the request.", + target_address )] - IBCChannelCouldNotFound(String), + IBCChannelCouldNotFound { + target_address: String, + }, + #[display( + fmt = "IBC channel '{}' is not healthy. Provide a healthy one manually by including `ibc_source_channel` in the request.", + channel_id + )] + IBCChannelNotHealthy { + channel_id: ChannelId, + }, } impl HttpStatusCode for WithdrawError { @@ -3214,8 +3222,8 @@ impl HttpStatusCode for WithdrawError { | WithdrawError::NoChainIdSet { .. } | WithdrawError::TxTypeNotSupported | WithdrawError::SigningError(_) - | WithdrawError::RegistryNameIsMissing(_) - | WithdrawError::IBCChannelCouldNotFound(_) + | WithdrawError::IBCChannelCouldNotFound { .. } + | WithdrawError::IBCChannelNotHealthy { .. } | WithdrawError::MyAddressNotNftOwner { .. } => StatusCode::BAD_REQUEST, WithdrawError::HwError(_) => StatusCode::GONE, #[cfg(target_arch = "wasm32")] diff --git a/mm2src/coins/rpc_command/mod.rs b/mm2src/coins/rpc_command/mod.rs index c401853b2d..a47a9bf25a 100644 --- a/mm2src/coins/rpc_command/mod.rs +++ b/mm2src/coins/rpc_command/mod.rs @@ -7,5 +7,6 @@ pub mod init_account_balance; pub mod init_create_account; pub mod init_scan_for_new_addresses; pub mod init_withdraw; -#[cfg(not(target_arch = "wasm32"))] pub mod lightning; pub mod tendermint; + +#[cfg(not(target_arch = "wasm32"))] pub mod lightning; diff --git a/mm2src/coins/rpc_command/tendermint/ibc.rs b/mm2src/coins/rpc_command/tendermint/ibc.rs new file mode 100644 index 0000000000..48df82c44a --- /dev/null +++ b/mm2src/coins/rpc_command/tendermint/ibc.rs @@ -0,0 +1,12 @@ +use std::fmt; + +#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, Hash)] +pub struct ChannelId(u16); + +impl fmt::Display for ChannelId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "channel-{}", self.0) } +} + +impl ChannelId { + pub fn new(id: u16) -> Self { Self(id) } +} diff --git a/mm2src/coins/rpc_command/tendermint/ibc_chains.rs b/mm2src/coins/rpc_command/tendermint/ibc_chains.rs deleted file mode 100644 index 67ed93e9fa..0000000000 --- a/mm2src/coins/rpc_command/tendermint/ibc_chains.rs +++ /dev/null @@ -1,35 +0,0 @@ -use common::HttpStatusCode; -use mm2_core::mm_ctx::MmArc; -use mm2_err_handle::prelude::MmError; - -use crate::tendermint; - -pub type IBCChainRegistriesResult = Result>; - -#[derive(Clone, Serialize)] -pub struct IBCChainRegistriesResponse { - pub(crate) chain_registry_list: Vec, -} - -#[derive(Clone, Debug, Display, Serialize, SerializeErrorType, PartialEq)] -#[serde(tag = "error_type", content = "error_data")] -pub enum IBCChainsRequestError { - #[display(fmt = "Transport error: {}", _0)] - Transport(String), - #[display(fmt = "Internal error: {}", _0)] - InternalError(String), -} - -impl HttpStatusCode for IBCChainsRequestError { - fn status_code(&self) -> common::StatusCode { - match self { - IBCChainsRequestError::Transport(_) => common::StatusCode::SERVICE_UNAVAILABLE, - IBCChainsRequestError::InternalError(_) => common::StatusCode::INTERNAL_SERVER_ERROR, - } - } -} - -#[inline(always)] -pub async fn ibc_chains(_ctx: MmArc, _req: serde_json::Value) -> IBCChainRegistriesResult { - tendermint::get_ibc_chain_list().await -} diff --git a/mm2src/coins/rpc_command/tendermint/ibc_transfer_channels.rs b/mm2src/coins/rpc_command/tendermint/ibc_transfer_channels.rs deleted file mode 100644 index 4edcd0cd55..0000000000 --- a/mm2src/coins/rpc_command/tendermint/ibc_transfer_channels.rs +++ /dev/null @@ -1,105 +0,0 @@ -use common::HttpStatusCode; -use mm2_core::mm_ctx::MmArc; -use mm2_err_handle::prelude::MmError; - -use crate::{coin_conf, tendermint::get_ibc_transfer_channels}; - -pub type IBCTransferChannelsResult = Result>; - -#[derive(Clone, Deserialize)] -pub struct IBCTransferChannelsRequest { - pub(crate) source_coin: String, - pub(crate) destination_coin: String, -} - -#[derive(Clone, Serialize)] -pub struct IBCTransferChannelsResponse { - pub(crate) ibc_transfer_channels: Vec, -} - -#[derive(Clone, Serialize, Deserialize)] -pub(crate) struct IBCTransferChannel { - pub(crate) channel_id: String, - pub(crate) ordering: String, - pub(crate) version: String, - pub(crate) tags: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub(crate) struct IBCTransferChannelTag { - pub(crate) status: String, - pub(crate) preferred: bool, - pub(crate) dex: Option, -} - -#[derive(Clone, Debug, Display, Serialize, SerializeErrorType, PartialEq)] -#[serde(tag = "error_type", content = "error_data")] -pub enum IBCTransferChannelsRequestError { - #[display(fmt = "No such coin {}", _0)] - NoSuchCoin(String), - #[display( - fmt = "Only tendermint based coins are allowed for `ibc_transfer_channels` operation. Current coin: {}", - _0 - )] - UnsupportedCoin(String), - #[display( - fmt = "'chain_registry_name' was not found in coins configuration for '{}' prefix. Either update the coins configuration or use 'ibc_source_channel' in the request.", - _0 - )] - RegistryNameIsMissing(String), - #[display(fmt = "Could not find '{}' registry source.", _0)] - RegistrySourceCouldNotFound(String), - #[display(fmt = "Transport error: {}", _0)] - Transport(String), - #[display(fmt = "Could not found channel for '{}'.", _0)] - CouldNotFindChannel(String), - #[display(fmt = "Internal error: {}", _0)] - InternalError(String), -} - -impl HttpStatusCode for IBCTransferChannelsRequestError { - fn status_code(&self) -> common::StatusCode { - match self { - IBCTransferChannelsRequestError::UnsupportedCoin(_) | IBCTransferChannelsRequestError::NoSuchCoin(_) => { - common::StatusCode::BAD_REQUEST - }, - IBCTransferChannelsRequestError::CouldNotFindChannel(_) - | IBCTransferChannelsRequestError::RegistryNameIsMissing(_) - | IBCTransferChannelsRequestError::RegistrySourceCouldNotFound(_) => common::StatusCode::NOT_FOUND, - IBCTransferChannelsRequestError::Transport(_) => common::StatusCode::SERVICE_UNAVAILABLE, - IBCTransferChannelsRequestError::InternalError(_) => common::StatusCode::INTERNAL_SERVER_ERROR, - } - } -} - -pub async fn ibc_transfer_channels(ctx: MmArc, req: IBCTransferChannelsRequest) -> IBCTransferChannelsResult { - let source_coin_conf = coin_conf(&ctx, &req.source_coin); - let source_registry_name = source_coin_conf - .get("protocol") - .unwrap_or(&serde_json::Value::Null) - .get("protocol_data") - .unwrap_or(&serde_json::Value::Null) - .get("chain_registry_name") - .map(|t| t.as_str().unwrap_or_default().to_owned()); - - let Some(source_registry_name) = source_registry_name else { - return MmError::err(IBCTransferChannelsRequestError::RegistryNameIsMissing(req.source_coin)); - }; - - let destination_coin_conf = coin_conf(&ctx, &req.destination_coin); - let destination_registry_name = destination_coin_conf - .get("protocol") - .unwrap_or(&serde_json::Value::Null) - .get("protocol_data") - .unwrap_or(&serde_json::Value::Null) - .get("chain_registry_name") - .map(|t| t.as_str().unwrap_or_default().to_owned()); - - let Some(destination_registry_name) = destination_registry_name else { - return MmError::err(IBCTransferChannelsRequestError::RegistryNameIsMissing( - req.destination_coin, - )); - }; - - get_ibc_transfer_channels(source_registry_name, destination_registry_name).await -} diff --git a/mm2src/coins/rpc_command/tendermint/mod.rs b/mm2src/coins/rpc_command/tendermint/mod.rs index 9a3d714bd3..d0f2c82685 100644 --- a/mm2src/coins/rpc_command/tendermint/mod.rs +++ b/mm2src/coins/rpc_command/tendermint/mod.rs @@ -1,13 +1,2 @@ -mod ibc_chains; -mod ibc_transfer_channels; +pub mod ibc; pub mod staking; - -pub use ibc_chains::*; -pub use ibc_transfer_channels::*; - -// Global constants for interacting with https://github.com/KomodoPlatform/chain-registry repository -// using `mm2_git` crate. -pub(crate) const CHAIN_REGISTRY_REPO_OWNER: &str = "KomodoPlatform"; -pub(crate) const CHAIN_REGISTRY_REPO_NAME: &str = "chain-registry"; -pub(crate) const CHAIN_REGISTRY_BRANCH: &str = "nucl"; -pub(crate) const CHAIN_REGISTRY_IBC_DIR_NAME: &str = "_IBC"; diff --git a/mm2src/coins/rpc_command/tendermint/staking.rs b/mm2src/coins/rpc_command/tendermint/staking.rs index 190477b7bd..8a83e0e6cc 100644 --- a/mm2src/coins/rpc_command/tendermint/staking.rs +++ b/mm2src/coins/rpc_command/tendermint/staking.rs @@ -47,7 +47,8 @@ impl From for StakingInfoError { match e { TendermintCoinRpcError::InvalidResponse(e) | TendermintCoinRpcError::PerformError(e) - | TendermintCoinRpcError::RpcClientError(e) => StakingInfoError::Transport(e), + | TendermintCoinRpcError::RpcClientError(e) + | TendermintCoinRpcError::NotFound(e) => StakingInfoError::Transport(e), TendermintCoinRpcError::Prost(e) | TendermintCoinRpcError::InternalError(e) => StakingInfoError::Internal(e), TendermintCoinRpcError::UnexpectedAccountType { .. } => StakingInfoError::Internal( "RPC client got an unexpected error 'TendermintCoinRpcError::UnexpectedAccountType', this isn't normal." diff --git a/mm2src/coins/tendermint/tendermint_coin.rs b/mm2src/coins/tendermint/tendermint_coin.rs index 4de40f9b74..d529af2298 100644 --- a/mm2src/coins/tendermint/tendermint_coin.rs +++ b/mm2src/coins/tendermint/tendermint_coin.rs @@ -3,17 +3,13 @@ use super::htlc::{ClaimHtlcMsg, ClaimHtlcProto, CreateHtlcMsg, CreateHtlcProto, QueryHtlcResponse, TendermintHtlc, HTLC_STATE_COMPLETED, HTLC_STATE_OPEN, HTLC_STATE_REFUNDED}; use super::ibc::transfer_v1::MsgTransfer; use super::ibc::IBC_GAS_LIMIT_DEFAULT; -use super::{rpc::*, TENDERMINT_COIN_PROTOCOL_TYPE}; +use super::rpc::*; use crate::coin_errors::{MyAddressError, ValidatePaymentError, ValidatePaymentResult}; use crate::hd_wallet::{HDPathAccountToAddressId, WithdrawFrom}; +use crate::rpc_command::tendermint::ibc::ChannelId; use crate::rpc_command::tendermint::staking::{ClaimRewardsPayload, Delegation, DelegationPayload, DelegationsQueryResponse, Undelegation, UndelegationEntry, UndelegationsQueryResponse, ValidatorStatus}; -use crate::rpc_command::tendermint::{IBCChainRegistriesResponse, IBCChainRegistriesResult, IBCChainsRequestError, - IBCTransferChannel, IBCTransferChannelTag, IBCTransferChannelsRequestError, - IBCTransferChannelsResponse, IBCTransferChannelsResult, CHAIN_REGISTRY_BRANCH, - CHAIN_REGISTRY_IBC_DIR_NAME, CHAIN_REGISTRY_REPO_NAME, CHAIN_REGISTRY_REPO_OWNER}; -use crate::tendermint::ibc::IBC_OUT_SOURCE_PORT; use crate::utxo::sat_from_big_decimal; use crate::utxo::utxo_common::big_decimal_from_sat; use crate::{big_decimal_from_sat_unsigned, BalanceError, BalanceFut, BigDecimal, CheckIfMyPaymentSentArgs, @@ -54,6 +50,8 @@ use cosmrs::proto::cosmos::staking::v1beta1::{QueryDelegationRequest, QueryDeleg QueryValidatorsResponse as QueryValidatorsResponseProto}; use cosmrs::proto::cosmos::tx::v1beta1::{GetTxRequest, GetTxResponse, SimulateRequest, SimulateResponse, Tx, TxBody, TxRaw}; +use cosmrs::proto::ibc; +use cosmrs::proto::ibc::core::channel::v1::{QueryChannelRequest, QueryChannelResponse}; use cosmrs::proto::prost::{DecodeError, Message}; use cosmrs::staking::{MsgDelegate, MsgUndelegate, QueryValidatorsResponse, Validator}; use cosmrs::tendermint::block::Height; @@ -73,7 +71,6 @@ use itertools::Itertools; use keys::{KeyPair, Public}; use mm2_core::mm_ctx::{MmArc, MmWeak}; use mm2_err_handle::prelude::*; -use mm2_git::{FileMetadata, GitController, GithubClient, RepositoryOperations, GITHUB_API_URI}; use mm2_number::bigdecimal::ParseBigDecimalError; use mm2_number::MmNumber; use mm2_p2p::p2p_ctx::P2PContext; @@ -106,6 +103,7 @@ const ABCI_DELEGATION_PATH: &str = "/cosmos.staking.v1beta1.Query/Delegation"; const ABCI_DELEGATOR_DELEGATIONS_PATH: &str = "/cosmos.staking.v1beta1.Query/DelegatorDelegations"; const ABCI_DELEGATOR_UNDELEGATIONS_PATH: &str = "/cosmos.staking.v1beta1.Query/DelegatorUnbondingDelegations"; const ABCI_DELEGATION_REWARDS_PATH: &str = "/cosmos.distribution.v1beta1.Query/DelegationRewards"; +const ABCI_IBC_CHANNEL_QUERY_PATH: &str = "/ibc.core.channel.v1.Query/Channel"; pub(crate) const MIN_TX_SATOSHIS: i64 = 1; @@ -197,7 +195,8 @@ pub struct TendermintProtocolInfo { pub account_prefix: String, chain_id: String, gas_price: Option, - chain_registry_name: Option, + #[serde(default)] + ibc_channels: HashMap, } #[derive(Clone)] @@ -389,9 +388,11 @@ pub struct TendermintCoinImpl { pub(super) abortable_system: AbortableQueue, pub(crate) history_sync_state: Mutex, client: TendermintRpcClient, - pub(crate) chain_registry_name: Option, - pub ctx: MmWeak, + pub(crate) ctx: MmWeak, pub(crate) is_keplr_from_ledger: bool, + /// Key represents the account prefix of the target chain and + /// the value is the channel ID used for sending transactions. + ibc_channels: HashMap, } #[derive(Clone)] @@ -455,6 +456,7 @@ pub enum TendermintCoinRpcError { UnexpectedAccountType { prefix: String, }, + NotFound(String), } impl From for TendermintCoinRpcError { @@ -478,9 +480,9 @@ impl From for BalanceError { match err { TendermintCoinRpcError::InvalidResponse(e) => BalanceError::InvalidResponse(e), TendermintCoinRpcError::Prost(e) => BalanceError::InvalidResponse(e), - TendermintCoinRpcError::PerformError(e) | TendermintCoinRpcError::RpcClientError(e) => { - BalanceError::Transport(e) - }, + TendermintCoinRpcError::PerformError(e) + | TendermintCoinRpcError::RpcClientError(e) + | TendermintCoinRpcError::NotFound(e) => BalanceError::Transport(e), TendermintCoinRpcError::InternalError(e) => BalanceError::Internal(e), TendermintCoinRpcError::UnexpectedAccountType { prefix } => { BalanceError::Internal(format!("Account type '{prefix}' is not supported for HTLCs")) @@ -494,9 +496,9 @@ impl From for ValidatePaymentError { match err { TendermintCoinRpcError::InvalidResponse(e) => ValidatePaymentError::InvalidRpcResponse(e), TendermintCoinRpcError::Prost(e) => ValidatePaymentError::InvalidRpcResponse(e), - TendermintCoinRpcError::PerformError(e) | TendermintCoinRpcError::RpcClientError(e) => { - ValidatePaymentError::Transport(e) - }, + TendermintCoinRpcError::PerformError(e) + | TendermintCoinRpcError::RpcClientError(e) + | TendermintCoinRpcError::NotFound(e) => ValidatePaymentError::Transport(e), TendermintCoinRpcError::InternalError(e) => ValidatePaymentError::InternalError(e), TendermintCoinRpcError::UnexpectedAccountType { prefix } => { ValidatePaymentError::InvalidParameter(format!("Account type '{prefix}' is not supported for HTLCs")) @@ -727,37 +729,70 @@ impl TendermintCoin { abortable_system, history_sync_state: Mutex::new(history_sync_state), client: TendermintRpcClient(AsyncMutex::new(client_impl)), - chain_registry_name: protocol_info.chain_registry_name, + ibc_channels: protocol_info.ibc_channels, ctx: ctx.weak(), is_keplr_from_ledger, }))) } - /// Extracts corresponding IBC channel ID for `AccountId` from https://github.com/KomodoPlatform/chain-registry/tree/nucl. - pub(crate) async fn detect_channel_id_for_ibc_transfer( + /// Finds the IBC channel by querying the given channel ID and port ID + /// and returns its information. + async fn query_ibc_channel( &self, - to_address: &AccountId, - ) -> Result> { - let ctx = MmArc::from_weak(&self.ctx).ok_or_else(|| WithdrawError::InternalError("No context".to_owned()))?; + channel_id: ChannelId, + port_id: &str, + ) -> MmResult { + let payload = QueryChannelRequest { + channel_id: channel_id.to_string(), + port_id: port_id.to_string(), + } + .encode_to_vec(); - let source_registry_name = self - .chain_registry_name - .clone() - .ok_or_else(|| WithdrawError::RegistryNameIsMissing(to_address.prefix().to_owned()))?; + let request = AbciRequest::new( + Some(ABCI_IBC_CHANNEL_QUERY_PATH.to_string()), + payload, + ABCI_REQUEST_HEIGHT, + ABCI_REQUEST_PROVE, + ); - let destination_registry_name = chain_registry_name_from_account_prefix(&ctx, to_address.prefix()) - .ok_or_else(|| WithdrawError::RegistryNameIsMissing(to_address.prefix().to_owned()))?; + let response = self.rpc_client().await?.perform(request).await?; + let response = QueryChannelResponse::decode(response.response.value.as_slice())?; - let channels = get_ibc_transfer_channels(source_registry_name, destination_registry_name) - .await - .map_err(|_| WithdrawError::IBCChannelCouldNotFound(to_address.to_string()))?; + response.channel.ok_or_else(|| { + MmError::new(TendermintCoinRpcError::NotFound(format!( + "No result for channel id: {channel_id}, port: {port_id}." + ))) + }) + } + + /// Returns a **healthy** IBC channel ID for the given target address. + pub(crate) async fn get_healthy_ibc_channel_for_address( + &self, + target_address: &AccountId, + ) -> Result> { + // ref: https://github.com/cosmos/ibc-go/blob/7f34724b982581435441e0bb70598c3e3a77f061/proto/ibc/core/channel/v1/channel.proto#L51-L68 + const STATE_OPEN: i32 = 3; + + let channel_id = + *self + .ibc_channels + .get(target_address.prefix()) + .ok_or_else(|| WithdrawError::IBCChannelCouldNotFound { + target_address: target_address.to_string(), + })?; + + let channel = self.query_ibc_channel(channel_id, "transfer").await?; - Ok(channels - .ibc_transfer_channels - .last() - .ok_or_else(|| WithdrawError::InternalError("channel list can not be empty".to_owned()))? - .channel_id - .clone()) + // TODO: Extend the validation logic to also include: + // + // - Checking the time of the last update on the channel + // - Verifying the total amount transferred since the channel was created + // - Check the channel creation time + if channel.state != STATE_OPEN { + return MmError::err(WithdrawError::IBCChannelNotHealthy { channel_id }); + } + + Ok(channel_id) } #[inline(always)] @@ -2940,46 +2975,6 @@ fn clients_from_urls(ctx: &MmArc, nodes: Vec) -> MmResult IBCChainRegistriesResult { - fn map_metadata_to_chain_registry_name(metadata: &FileMetadata) -> Result> { - let split_filename_by_dash: Vec<&str> = metadata.name.split('-').collect(); - let chain_registry_name = split_filename_by_dash - .first() - .or_mm_err(|| { - IBCChainsRequestError::InternalError(format!( - "Could not read chain registry name from '{}'", - metadata.name - )) - })? - .to_string(); - - Ok(chain_registry_name) - } - - let git_controller: GitController = GitController::new(GITHUB_API_URI); - - let metadata_list = git_controller - .client - .get_file_metadata_list( - CHAIN_REGISTRY_REPO_OWNER, - CHAIN_REGISTRY_REPO_NAME, - CHAIN_REGISTRY_BRANCH, - CHAIN_REGISTRY_IBC_DIR_NAME, - ) - .await - .map_err(|e| IBCChainsRequestError::Transport(format!("{:?}", e)))?; - - let chain_list: Result, MmError> = - metadata_list.iter().map(map_metadata_to_chain_registry_name).collect(); - - let mut distinct_chain_list = chain_list?; - distinct_chain_list.dedup(); - - Ok(IBCChainRegistriesResponse { - chain_registry_list: distinct_chain_list, - }) -} - #[async_trait] #[allow(unused_variables)] impl MmCoin for TendermintCoin { @@ -3037,7 +3032,7 @@ impl MmCoin for TendermintCoin { let channel_id = if is_ibc_transfer { match &req.ibc_source_channel { Some(_) => req.ibc_source_channel, - None => Some(coin.detect_channel_id_for_ibc_transfer(&to_address).await?), + None => Some(coin.get_healthy_ibc_channel_for_address(&to_address).await?), } } else { None @@ -3048,7 +3043,7 @@ impl MmCoin for TendermintCoin { to_address.clone(), &coin.denom, amount_denom, - channel_id.clone(), + channel_id, ) .await?; @@ -3086,6 +3081,8 @@ impl MmCoin for TendermintCoin { // calculates a higher fee than us, the withdrawal might fail), we use three times // the actual fee. fee_amount_u64 * 3 + } else if is_ibc_transfer { + fee_amount_u64 * 3 / 2 } else { fee_amount_u64 }; @@ -3354,7 +3351,7 @@ impl MarketCoinOps for TendermintCoin { self.send_raw_tx_bytes(&tx_bytes) } - /// Consider using `seq_safe_raw_tx_bytes` instead. + /// Consider using `seq_safe_send_raw_tx_bytes` instead. /// This is considered as unsafe due to sequence mismatches. fn send_raw_tx_bytes(&self, tx: &[u8]) -> Box + Send> { // as sanity check @@ -3838,54 +3835,15 @@ pub fn tendermint_priv_key_policy( } } -pub(crate) fn chain_registry_name_from_account_prefix(ctx: &MmArc, prefix: &str) -> Option { - let Some(coins) = ctx.conf["coins"].as_array() else { - return None; - }; - - for coin in coins { - let protocol = coin - .get("protocol") - .unwrap_or(&serde_json::Value::Null) - .get("type") - .unwrap_or(&serde_json::Value::Null) - .as_str(); - - if protocol != Some(TENDERMINT_COIN_PROTOCOL_TYPE) { - continue; - } - - let coin_account_prefix = coin - .get("protocol") - .unwrap_or(&serde_json::Value::Null) - .get("protocol_data") - .unwrap_or(&serde_json::Value::Null) - .get("account_prefix") - .map(|t| t.as_str().unwrap_or_default()); - - if coin_account_prefix == Some(prefix) { - return coin - .get("protocol") - .unwrap_or(&serde_json::Value::Null) - .get("protocol_data") - .unwrap_or(&serde_json::Value::Null) - .get("chain_registry_name") - .map(|t| t.as_str().unwrap_or_default().to_owned()); - } - } - - None -} - pub(crate) async fn create_withdraw_msg_as_any( sender: AccountId, receiver: AccountId, denom: &Denom, amount: u64, - ibc_source_channel: Option, + ibc_source_channel: Option, ) -> Result> { if let Some(channel_id) = ibc_source_channel { - MsgTransfer::new_with_default_timeout(channel_id, sender, receiver, Coin { + MsgTransfer::new_with_default_timeout(channel_id.to_string(), sender, receiver, Coin { denom: denom.clone(), amount: amount.into(), }) @@ -3904,86 +3862,6 @@ pub(crate) async fn create_withdraw_msg_as_any( .map_to_mm(|e| WithdrawError::InternalError(e.to_string())) } -pub async fn get_ibc_transfer_channels( - source_registry_name: String, - destination_registry_name: String, -) -> IBCTransferChannelsResult { - #[derive(Deserialize)] - struct ChainRegistry { - channels: Vec, - } - - #[derive(Deserialize)] - struct ChannelInfo { - channel_id: String, - port_id: String, - } - - #[derive(Deserialize)] - struct IbcChannel { - #[allow(dead_code)] - chain_1: ChannelInfo, - chain_2: ChannelInfo, - ordering: String, - version: String, - tags: Option, - } - - let source_filename = format!("{}-{}.json", source_registry_name, destination_registry_name); - let git_controller: GitController = GitController::new(GITHUB_API_URI); - - let metadata_list = git_controller - .client - .get_file_metadata_list( - CHAIN_REGISTRY_REPO_OWNER, - CHAIN_REGISTRY_REPO_NAME, - CHAIN_REGISTRY_BRANCH, - CHAIN_REGISTRY_IBC_DIR_NAME, - ) - .await - .map_err(|e| IBCTransferChannelsRequestError::Transport(format!("{:?}", e)))?; - - let source_channel_file = metadata_list - .iter() - .find(|metadata| metadata.name == source_filename) - .or_mm_err(|| IBCTransferChannelsRequestError::RegistrySourceCouldNotFound(source_filename))?; - - let mut registry_object = git_controller - .client - .deserialize_json_source::(source_channel_file.to_owned()) - .await - .map_err(|e| IBCTransferChannelsRequestError::Transport(format!("{:?}", e)))?; - - registry_object - .channels - .retain(|ch| ch.chain_2.port_id == *IBC_OUT_SOURCE_PORT); - - let result: Vec = registry_object - .channels - .iter() - .map(|ch| IBCTransferChannel { - channel_id: ch.chain_2.channel_id.clone(), - ordering: ch.ordering.clone(), - version: ch.version.clone(), - tags: ch.tags.clone().map(|t| IBCTransferChannelTag { - status: t.status, - preferred: t.preferred, - dex: t.dex, - }), - }) - .collect(); - - if result.is_empty() { - return MmError::err(IBCTransferChannelsRequestError::CouldNotFindChannel( - destination_registry_name, - )); - } - - Ok(IBCTransferChannelsResponse { - ibc_transfer_channels: result, - }) -} - fn extract_big_decimal_from_dec_coin(dec_coin: &DecCoin, decimals: u32) -> Result { let raw = BigDecimal::from_str(&dec_coin.amount)?; // `DecCoin` represents decimal numbers as integer-like strings where the last 18 digits are the decimal part. @@ -4060,18 +3938,21 @@ pub mod tendermint_coin_tests { account_prefix: String::from("iaa"), chain_id: String::from("nyancat-9"), gas_price: None, - chain_registry_name: None, + ibc_channels: HashMap::new(), } } fn get_iris_protocol() -> TendermintProtocolInfo { + let mut ibc_channels = HashMap::new(); + ibc_channels.insert("cosmos".into(), ChannelId::new(0)); + TendermintProtocolInfo { decimals: 6, denom: String::from("unyan"), account_prefix: String::from("iaa"), chain_id: String::from("nyancat-9"), gas_price: None, - chain_registry_name: None, + ibc_channels, } } @@ -4082,7 +3963,7 @@ pub mod tendermint_coin_tests { account_prefix: String::from("nuc"), chain_id: String::from("nucleus-testnet"), gas_price: None, - chain_registry_name: None, + ibc_channels: HashMap::new(), } } @@ -5113,4 +4994,43 @@ pub mod tendermint_coin_tests { assert_eq!(expected_list, actual_list); } + + #[test] + fn test_get_ibc_channel_for_target_address() { + let nodes = vec![RpcNode::for_test(IRIS_TESTNET_RPC_URL)]; + let protocol_conf = get_iris_protocol(); + let ctx = mm2_core::mm_ctx::MmCtxBuilder::default().into_mm_arc(); + let conf = TendermintConf { + avg_blocktime: AVG_BLOCKTIME, + derivation_path: None, + }; + + 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 = block_on(TendermintCoin::init( + &ctx, + "IRIS-TEST".to_string(), + conf, + protocol_conf, + nodes, + false, + activation_policy, + false, + )) + .unwrap(); + + let expected_channel = ChannelId::new(0); + let expected_channel_str = "channel-0"; + + let addr = AccountId::from_str("cosmos1aghdjgt5gzntzqgdxdzhjfry90upmtfsy2wuwp").unwrap(); + + let actual_channel = block_on(coin.get_healthy_ibc_channel_for_address(&addr)).unwrap(); + let actual_channel_str = actual_channel.to_string(); + + assert_eq!(expected_channel, actual_channel); + assert_eq!(expected_channel_str, actual_channel_str); + } } diff --git a/mm2src/coins/tendermint/tendermint_token.rs b/mm2src/coins/tendermint/tendermint_token.rs index eaee2abeb2..f03e18654a 100644 --- a/mm2src/coins/tendermint/tendermint_token.rs +++ b/mm2src/coins/tendermint/tendermint_token.rs @@ -424,7 +424,7 @@ impl MmCoin for TendermintToken { let channel_id = if is_ibc_transfer { match &req.ibc_source_channel { Some(_) => req.ibc_source_channel, - None => Some(platform.detect_channel_id_for_ibc_transfer(&to_address).await?), + None => Some(platform.get_healthy_ibc_channel_for_address(&to_address).await?), } } else { None @@ -435,7 +435,7 @@ impl MmCoin for TendermintToken { to_address.clone(), &token.denom, amount_denom, - channel_id.clone(), + channel_id, ) .await?; diff --git a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs index f3286e5ab5..bbbeca9050 100644 --- a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs +++ b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs @@ -25,7 +25,6 @@ use crate::rpc::rate_limiter::{process_rate_limit, RateLimitContext}; use coins::eth::fee_estimation::rpc::get_eth_estimated_fee_per_gas; use coins::eth::EthCoin; use coins::my_tx_history_v2::my_tx_history_v2_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, get_enabled_coins::get_enabled_coins, @@ -244,8 +243,6 @@ async fn dispatcher_v2(request: MmRpcRequest, ctx: MmArc) -> DispatcherResult handle_mmrpc(ctx, request, update_version_stat_collection).await, "verify_message" => handle_mmrpc(ctx, request, verify_message).await, "withdraw" => handle_mmrpc(ctx, request, withdraw).await, - "ibc_chains" => handle_mmrpc(ctx, request, ibc_chains).await, - "ibc_transfer_channels" => handle_mmrpc(ctx, request, ibc_transfer_channels).await, "peer_connection_healthcheck" => handle_mmrpc(ctx, request, peer_connection_healthcheck_rpc).await, "withdraw_nft" => handle_mmrpc(ctx, request, withdraw_nft).await, "get_eth_estimated_fee_per_gas" => handle_mmrpc(ctx, request, get_eth_estimated_fee_per_gas).await, diff --git a/mm2src/mm2_main/tests/docker_tests/tendermint_tests.rs b/mm2src/mm2_main/tests/docker_tests/tendermint_tests.rs index 45e3b4a03e..f29a7ef34b 100644 --- a/mm2src/mm2_main/tests/docker_tests/tendermint_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/tendermint_tests.rs @@ -329,7 +329,7 @@ fn test_custom_gas_limit_on_tendermint_withdraw() { fn test_tendermint_ibc_withdraw() { let _lock = SEQUENCE_LOCK.lock().unwrap(); // visit `{swagger_address}/ibc/core/channel/v1/channels?pagination.limit=10000` to see the full list of ibc channels - const IBC_SOURCE_CHANNEL: &str = "channel-3"; + const IBC_SOURCE_CHANNEL: u16 = 3; const IBC_TARGET_ADDRESS: &str = "cosmos1r5v5srda7xfth3hn2s26txvrcrntldjumt8mhl"; const MY_ADDRESS: &str = "nuc150evuj4j7k9kgu38e453jdv9m3u0ft2n4fgzfr"; @@ -376,7 +376,7 @@ fn test_tendermint_ibc_withdraw() { fn test_tendermint_ibc_withdraw_hd() { let _lock = SEQUENCE_LOCK.lock().unwrap(); // visit `{swagger_address}/ibc/core/channel/v1/channels?pagination.limit=10000` to see the full list of ibc channels - const IBC_SOURCE_CHANNEL: &str = "channel-3"; + const IBC_SOURCE_CHANNEL: u16 = 3; const IBC_TARGET_ADDRESS: &str = "nuc150evuj4j7k9kgu38e453jdv9m3u0ft2n4fgzfr"; const MY_ADDRESS: &str = "cosmos134h9tv7866jcuw708w5w76lcfx7s3x2ysyalxy"; diff --git a/mm2src/mm2_test_helpers/src/for_tests.rs b/mm2src/mm2_test_helpers/src/for_tests.rs index 621e76a597..b6e67525dc 100644 --- a/mm2src/mm2_test_helpers/src/for_tests.rs +++ b/mm2src/mm2_test_helpers/src/for_tests.rs @@ -2729,7 +2729,7 @@ pub async fn withdraw_v1( pub async fn ibc_withdraw( mm: &MarketMakerIt, - source_channel: &str, + source_channel: u16, coin: &str, to: &str, amount: &str,