diff --git a/Cargo.lock b/Cargo.lock index e6897dd508..2dafc1ce04 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1627,12 +1627,14 @@ dependencies = [ name = "ibc-test-framework" version = "0.14.1" dependencies = [ + "async-trait", "color-eyre", "crossbeam-channel 0.5.4", "env_logger", "eyre", "flex-error", "hex", + "http", "ibc", "ibc-proto", "ibc-relayer", diff --git a/relayer/src/chain/cosmos.rs b/relayer/src/chain/cosmos.rs index 9f40e24851..4a7c1c5855 100644 --- a/relayer/src/chain/cosmos.rs +++ b/relayer/src/chain/cosmos.rs @@ -72,6 +72,7 @@ use crate::chain::cosmos::query::status::query_status; use crate::chain::cosmos::query::tx::query_txs; use crate::chain::cosmos::query::{abci_query, fetch_version_specs, packet_query}; use crate::chain::cosmos::types::account::Account; +use crate::chain::cosmos::types::config::TxConfig; use crate::chain::cosmos::types::gas::{default_gas_from_config, max_gas_from_config}; use crate::chain::tx::TrackedMsgs; use crate::chain::{ChainEndpoint, HealthCheck}; @@ -103,6 +104,7 @@ pub const GENESIS_MAX_BYTES_MAX_FRACTION: f64 = 0.9; pub struct CosmosSdkChain { config: ChainConfig, + tx_config: TxConfig, rpc_client: HttpClient, grpc_addr: Uri, rt: Arc, @@ -402,10 +404,9 @@ impl CosmosSdkChain { get_or_fetch_account(&self.grpc_addr, &key_entry.account, &mut self.account).await?; send_batched_messages_and_wait_commit( - &self.config, - &self.rpc_client, - &self.config.rpc_addr, - &self.grpc_addr, + &self.tx_config, + self.config.max_msg_num, + self.config.max_tx_size, &key_entry, account, &self.config.memo_prefix, @@ -431,9 +432,9 @@ impl CosmosSdkChain { get_or_fetch_account(&self.grpc_addr, &key_entry.account, &mut self.account).await?; send_batched_messages_and_wait_check_tx( - &self.config, - &self.rpc_client, - &self.grpc_addr, + &self.tx_config, + self.config.max_msg_num, + self.config.max_tx_size, &key_entry, account, &self.config.memo_prefix, @@ -461,6 +462,8 @@ impl ChainEndpoint for CosmosSdkChain { let grpc_addr = Uri::from_str(&config.grpc_addr.to_string()) .map_err(|e| Error::invalid_uri(config.grpc_addr.to_string(), e))?; + let tx_config = TxConfig::try_from(&config)?; + // Retrieve the version specification of this chain let chain = Self { @@ -470,6 +473,7 @@ impl ChainEndpoint for CosmosSdkChain { rt, keybase, account: None, + tx_config, }; Ok(chain) diff --git a/relayer/src/chain/cosmos/batch.rs b/relayer/src/chain/cosmos/batch.rs index 1e2cf2ae68..867a1989b3 100644 --- a/relayer/src/chain/cosmos/batch.rs +++ b/relayer/src/chain/cosmos/batch.rs @@ -2,23 +2,20 @@ use ibc::events::IbcEvent; use ibc_proto::google::protobuf::Any; use prost::Message; use tendermint_rpc::endpoint::broadcast::tx_sync::Response; -use tendermint_rpc::{HttpClient, Url}; -use tonic::codegen::http::Uri; use crate::chain::cosmos::retry::send_tx_with_account_sequence_retry; use crate::chain::cosmos::types::account::Account; +use crate::chain::cosmos::types::config::TxConfig; use crate::chain::cosmos::types::tx::TxSyncResult; use crate::chain::cosmos::wait::wait_for_block_commits; -use crate::config::types::Memo; -use crate::config::ChainConfig; +use crate::config::types::{MaxMsgNum, MaxTxSize, Memo}; use crate::error::Error; use crate::keyring::KeyEntry; pub async fn send_batched_messages_and_wait_commit( - config: &ChainConfig, - rpc_client: &HttpClient, - rpc_address: &Url, - grpc_address: &Uri, + config: &TxConfig, + max_msg_num: MaxMsgNum, + max_tx_size: MaxTxSize, key_entry: &KeyEntry, account: &mut Account, tx_memo: &Memo, @@ -30,8 +27,8 @@ pub async fn send_batched_messages_and_wait_commit( let mut tx_sync_results = send_messages_as_batches( config, - rpc_client, - grpc_address, + max_msg_num, + max_tx_size, key_entry, account, tx_memo, @@ -40,9 +37,9 @@ pub async fn send_batched_messages_and_wait_commit( .await?; wait_for_block_commits( - &config.id, - rpc_client, - rpc_address, + &config.chain_id, + &config.rpc_client, + &config.rpc_address, &config.rpc_timeout, &mut tx_sync_results, ) @@ -57,9 +54,9 @@ pub async fn send_batched_messages_and_wait_commit( } pub async fn send_batched_messages_and_wait_check_tx( - config: &ChainConfig, - rpc_client: &HttpClient, - grpc_address: &Uri, + config: &TxConfig, + max_msg_num: MaxMsgNum, + max_tx_size: MaxTxSize, key_entry: &KeyEntry, account: &mut Account, tx_memo: &Memo, @@ -69,22 +66,14 @@ pub async fn send_batched_messages_and_wait_check_tx( return Ok(Vec::new()); } - let batches = batch_messages(config, messages)?; + let batches = batch_messages(max_msg_num, max_tx_size, messages)?; let mut responses = Vec::new(); for batch in batches { - let response = send_tx_with_account_sequence_retry( - config, - rpc_client, - grpc_address, - key_entry, - account, - tx_memo, - batch, - 0, - ) - .await?; + let response = + send_tx_with_account_sequence_retry(config, key_entry, account, tx_memo, batch, 0) + .await?; responses.push(response); } @@ -93,9 +82,9 @@ pub async fn send_batched_messages_and_wait_check_tx( } async fn send_messages_as_batches( - config: &ChainConfig, - rpc_client: &HttpClient, - grpc_address: &Uri, + config: &TxConfig, + max_msg_num: MaxMsgNum, + max_tx_size: MaxTxSize, key_entry: &KeyEntry, account: &mut Account, tx_memo: &Memo, @@ -105,24 +94,16 @@ async fn send_messages_as_batches( return Ok(Vec::new()); } - let batches = batch_messages(config, messages)?; + let batches = batch_messages(max_msg_num, max_tx_size, messages)?; let mut tx_sync_results = Vec::new(); for batch in batches { let events_per_tx = vec![IbcEvent::default(); batch.len()]; - let response = send_tx_with_account_sequence_retry( - config, - rpc_client, - grpc_address, - key_entry, - account, - tx_memo, - batch, - 0, - ) - .await?; + let response = + send_tx_with_account_sequence_retry(config, key_entry, account, tx_memo, batch, 0) + .await?; let tx_sync_result = TxSyncResult { response, @@ -135,9 +116,13 @@ async fn send_messages_as_batches( Ok(tx_sync_results) } -fn batch_messages(config: &ChainConfig, messages: Vec) -> Result>, Error> { - let max_message_count = config.max_msg_num.to_usize(); - let max_tx_size = config.max_tx_size.into(); +fn batch_messages( + max_msg_num: MaxMsgNum, + max_tx_size: MaxTxSize, + messages: Vec, +) -> Result>, Error> { + let max_message_count = max_msg_num.to_usize(); + let max_tx_size = max_tx_size.into(); let mut batches = vec![]; diff --git a/relayer/src/chain/cosmos/encode.rs b/relayer/src/chain/cosmos/encode.rs index fcd20f28a0..416bc049d0 100644 --- a/relayer/src/chain/cosmos/encode.rs +++ b/relayer/src/chain/cosmos/encode.rs @@ -7,15 +7,15 @@ use ibc_proto::google::protobuf::Any; use tendermint::account::Id as AccountId; use crate::chain::cosmos::types::account::{Account, AccountNumber, AccountSequence}; +use crate::chain::cosmos::types::config::TxConfig; use crate::chain::cosmos::types::tx::SignedTx; use crate::config::types::Memo; use crate::config::AddressType; -use crate::config::ChainConfig; use crate::error::Error; use crate::keyring::{sign_message, KeyEntry}; pub fn sign_and_encode_tx( - config: &ChainConfig, + config: &TxConfig, key_entry: &KeyEntry, account: &Account, tx_memo: &Memo, @@ -34,7 +34,7 @@ pub fn sign_and_encode_tx( } pub fn sign_tx( - config: &ChainConfig, + config: &TxConfig, key_entry: &KeyEntry, account: &Account, tx_memo: &Memo, @@ -50,7 +50,7 @@ pub fn sign_tx( let (auth_info, auth_info_bytes) = auth_info_and_bytes(signer, fee.clone())?; let signed_doc = encode_sign_doc( - &config.id, + &config.chain_id, key_entry, &config.address_type, account.number, diff --git a/relayer/src/chain/cosmos/estimate.rs b/relayer/src/chain/cosmos/estimate.rs index 55ecbd9146..5b6379cc14 100644 --- a/relayer/src/chain/cosmos/estimate.rs +++ b/relayer/src/chain/cosmos/estimate.rs @@ -8,21 +8,20 @@ use crate::chain::cosmos::encode::sign_tx; use crate::chain::cosmos::gas::{gas_amount_to_fees, PrettyFee}; use crate::chain::cosmos::simulate::send_tx_simulate; use crate::chain::cosmos::types::account::Account; +use crate::chain::cosmos::types::config::TxConfig; use crate::chain::cosmos::types::gas::GasConfig; use crate::config::types::Memo; -use crate::config::ChainConfig; use crate::error::Error; use crate::keyring::KeyEntry; pub async fn estimate_tx_fees( - config: &ChainConfig, - grpc_address: &Uri, + config: &TxConfig, key_entry: &KeyEntry, account: &Account, tx_memo: &Memo, messages: Vec, ) -> Result { - let gas_config = GasConfig::from_chain_config(config); + let gas_config = &config.gas_config; debug!( "max fee, for use in tx simulation: {}", @@ -44,7 +43,8 @@ pub async fn estimate_tx_fees( signatures: signed_tx.signatures, }; - let estimated_fee = estimate_fee_with_tx(&gas_config, grpc_address, &config.id, tx).await?; + let estimated_fee = + estimate_fee_with_tx(gas_config, &config.grpc_address, &config.chain_id, tx).await?; Ok(estimated_fee) } diff --git a/relayer/src/chain/cosmos/query/account.rs b/relayer/src/chain/cosmos/query/account.rs index b7d9f43b9d..15cdd8f467 100644 --- a/relayer/src/chain/cosmos/query/account.rs +++ b/relayer/src/chain/cosmos/query/account.rs @@ -49,7 +49,10 @@ pub async fn refresh_account<'a>( } /// Uses the GRPC client to retrieve the account sequence -async fn query_account(grpc_address: &Uri, account_address: &str) -> Result { +pub async fn query_account( + grpc_address: &Uri, + account_address: &str, +) -> Result { let mut client = QueryClient::connect(grpc_address.clone()) .await .map_err(Error::grpc_transport)?; diff --git a/relayer/src/chain/cosmos/retry.rs b/relayer/src/chain/cosmos/retry.rs index 9960455969..2b901243b9 100644 --- a/relayer/src/chain/cosmos/retry.rs +++ b/relayer/src/chain/cosmos/retry.rs @@ -5,15 +5,13 @@ use ibc_proto::google::protobuf::Any; use std::thread; use tendermint::abci::Code; use tendermint_rpc::endpoint::broadcast::tx_sync::Response; -use tendermint_rpc::HttpClient; -use tonic::codegen::http::Uri; use tracing::{debug, error, span, warn, Level}; use crate::chain::cosmos::query::account::refresh_account; use crate::chain::cosmos::tx::estimate_fee_and_send_tx; use crate::chain::cosmos::types::account::Account; +use crate::chain::cosmos::types::config::TxConfig; use crate::config::types::Memo; -use crate::config::ChainConfig; use crate::error::Error; use crate::keyring::KeyEntry; use crate::sdk_error::sdk_error_from_tx_sync_error_code; @@ -47,9 +45,7 @@ const INCORRECT_ACCOUNT_SEQUENCE_ERR: u32 = 32; /// nonetheless at the worker `step` level). Upon case #2, we retry /// submitting the same transaction. pub async fn send_tx_with_account_sequence_retry( - config: &ChainConfig, - rpc_client: &HttpClient, - grpc_address: &Uri, + config: &TxConfig, key_entry: &KeyEntry, account: &mut Account, tx_memo: &Memo, @@ -58,12 +54,10 @@ pub async fn send_tx_with_account_sequence_retry( ) -> Result { crate::time!("send_tx_with_account_sequence_retry"); let _span = - span!(Level::ERROR, "send_tx_with_account_sequence_retry", id = %config.id).entered(); + span!(Level::ERROR, "send_tx_with_account_sequence_retry", id = %config.chain_id).entered(); do_send_tx_with_account_sequence_retry( config, - rpc_client, - grpc_address, key_entry, account, tx_memo, @@ -77,9 +71,7 @@ pub async fn send_tx_with_account_sequence_retry( // do not currently support recursive async functions behind the // `async fn` syntactic sugar. fn do_send_tx_with_account_sequence_retry<'a>( - config: &'a ChainConfig, - rpc_client: &'a HttpClient, - grpc_address: &'a Uri, + config: &'a TxConfig, key_entry: &'a KeyEntry, account: &'a mut Account, tx_memo: &'a Memo, @@ -93,16 +85,8 @@ fn do_send_tx_with_account_sequence_retry<'a>( account.sequence, ); - let tx_result = estimate_fee_and_send_tx( - config, - rpc_client, - grpc_address, - key_entry, - account, - tx_memo, - messages.clone(), - ) - .await; + let tx_result = + estimate_fee_and_send_tx(config, key_entry, account, tx_memo, messages.clone()).await; match tx_result { // Gas estimation failed with acct. s.n. mismatch at estimate gas step. @@ -113,7 +97,7 @@ fn do_send_tx_with_account_sequence_retry<'a>( // retry at the worker-level will handle retrying. Err(e) if mismatching_account_sequence_number(&e) => { warn!("failed at estimate_gas step mismatching account sequence: dropping the tx & refreshing account sequence number"); - refresh_account(grpc_address, &key_entry.account, account).await?; + refresh_account(&config.grpc_address, &key_entry.account, account).await?; // Note: propagating error here can lead to bug & dropped packets: // https://github.com/informalsystems/ibc-rs/issues/1153 // But periodic packet clearing will catch any dropped packets. @@ -130,13 +114,11 @@ fn do_send_tx_with_account_sequence_retry<'a>( let backoff = retry_counter * BACKOFF_MULTIPLIER_ACCOUNT_SEQUENCE_RETRY; thread::sleep(Duration::from_millis(backoff)); - refresh_account(grpc_address, &key_entry.account, account).await?; + refresh_account(&config.grpc_address, &key_entry.account, account).await?; // Now retry. do_send_tx_with_account_sequence_retry( config, - rpc_client, - grpc_address, key_entry, account, tx_memo, diff --git a/relayer/src/chain/cosmos/tx.rs b/relayer/src/chain/cosmos/tx.rs index 0ac651d270..e6a0519e1f 100644 --- a/relayer/src/chain/cosmos/tx.rs +++ b/relayer/src/chain/cosmos/tx.rs @@ -2,44 +2,29 @@ use ibc_proto::cosmos::tx::v1beta1::Fee; use ibc_proto::google::protobuf::Any; use tendermint_rpc::endpoint::broadcast::tx_sync::Response; use tendermint_rpc::{Client, HttpClient, Url}; -use tonic::codegen::http::Uri; use crate::chain::cosmos::encode::sign_and_encode_tx; use crate::chain::cosmos::estimate::estimate_tx_fees; use crate::chain::cosmos::types::account::Account; +use crate::chain::cosmos::types::config::TxConfig; use crate::config::types::Memo; -use crate::config::ChainConfig; use crate::error::Error; use crate::keyring::KeyEntry; pub async fn estimate_fee_and_send_tx( - config: &ChainConfig, - rpc_client: &HttpClient, - grpc_address: &Uri, + config: &TxConfig, key_entry: &KeyEntry, account: &Account, tx_memo: &Memo, messages: Vec, ) -> Result { - let fee = estimate_tx_fees( - config, - grpc_address, - key_entry, - account, - tx_memo, - messages.clone(), - ) - .await?; + let fee = estimate_tx_fees(config, key_entry, account, tx_memo, messages.clone()).await?; - send_tx_with_fee( - config, rpc_client, key_entry, account, tx_memo, messages, &fee, - ) - .await + send_tx_with_fee(config, key_entry, account, tx_memo, messages, &fee).await } async fn send_tx_with_fee( - config: &ChainConfig, - rpc_client: &HttpClient, + config: &TxConfig, key_entry: &KeyEntry, account: &Account, tx_memo: &Memo, @@ -48,7 +33,7 @@ async fn send_tx_with_fee( ) -> Result { let tx_bytes = sign_and_encode_tx(config, key_entry, account, tx_memo, messages, fee)?; - let response = broadcast_tx_sync(rpc_client, &config.rpc_addr, tx_bytes).await?; + let response = broadcast_tx_sync(&config.rpc_client, &config.rpc_address, tx_bytes).await?; Ok(response) } diff --git a/relayer/src/chain/cosmos/types/config.rs b/relayer/src/chain/cosmos/types/config.rs new file mode 100644 index 0000000000..fa2f924d2d --- /dev/null +++ b/relayer/src/chain/cosmos/types/config.rs @@ -0,0 +1,44 @@ +use core::str::FromStr; +use core::time::Duration; +use http::Uri; +use ibc::core::ics24_host::identifier::ChainId; +use tendermint_rpc::{HttpClient, Url}; + +use crate::chain::cosmos::types::gas::GasConfig; +use crate::config::{AddressType, ChainConfig}; +use crate::error::Error; + +#[derive(Debug, Clone)] +pub struct TxConfig { + pub chain_id: ChainId, + pub gas_config: GasConfig, + pub rpc_client: HttpClient, + pub rpc_address: Url, + pub grpc_address: Uri, + pub rpc_timeout: Duration, + pub address_type: AddressType, +} + +impl<'a> TryFrom<&'a ChainConfig> for TxConfig { + type Error = Error; + + fn try_from(config: &'a ChainConfig) -> Result { + let rpc_client = HttpClient::new(config.rpc_addr.clone()) + .map_err(|e| Error::rpc(config.rpc_addr.clone(), e))?; + + let grpc_address = Uri::from_str(&config.grpc_addr.to_string()) + .map_err(|e| Error::invalid_uri(config.grpc_addr.to_string(), e))?; + + let gas_config = GasConfig::from(config); + + Ok(Self { + chain_id: config.id.clone(), + gas_config, + rpc_client, + rpc_address: config.rpc_addr.clone(), + grpc_address, + rpc_timeout: config.rpc_timeout, + address_type: config.address_type.clone(), + }) + } +} diff --git a/relayer/src/chain/cosmos/types/gas.rs b/relayer/src/chain/cosmos/types/gas.rs index 55625dab3c..79e1001772 100644 --- a/relayer/src/chain/cosmos/types/gas.rs +++ b/relayer/src/chain/cosmos/types/gas.rs @@ -11,6 +11,7 @@ const DEFAULT_GAS_PRICE_ADJUSTMENT: f64 = 0.1; const DEFAULT_FEE_GRANTER: &str = ""; +#[derive(Debug, Clone)] pub struct GasConfig { pub default_gas: u64, pub max_gas: u64, @@ -20,9 +21,9 @@ pub struct GasConfig { pub fee_granter: String, } -impl GasConfig { - pub fn from_chain_config(config: &ChainConfig) -> GasConfig { - GasConfig { +impl<'a> From<&'a ChainConfig> for GasConfig { + fn from(config: &'a ChainConfig) -> Self { + Self { default_gas: default_gas_from_config(config), max_gas: max_gas_from_config(config), gas_adjustment: gas_adjustment_from_config(config), diff --git a/relayer/src/chain/cosmos/types/mod.rs b/relayer/src/chain/cosmos/types/mod.rs index 4d91a59966..b8fc7adc9d 100644 --- a/relayer/src/chain/cosmos/types/mod.rs +++ b/relayer/src/chain/cosmos/types/mod.rs @@ -1,3 +1,4 @@ pub mod account; +pub mod config; pub mod gas; pub mod tx; diff --git a/relayer/src/transfer.rs b/relayer/src/transfer.rs index 111f862ac9..e44b04cda9 100644 --- a/relayer/src/transfer.rs +++ b/relayer/src/transfer.rs @@ -6,9 +6,11 @@ use flex_error::{define_error, DetailOnly}; use ibc::applications::ics20_fungible_token_transfer::msgs::transfer::MsgTransfer; use ibc::core::ics24_host::identifier::{ChainId, ChannelId, PortId}; use ibc::events::IbcEvent; +use ibc::signer::Signer; use ibc::timestamp::{Timestamp, TimestampOverflowError}; use ibc::tx_msg::Msg; use ibc::Height; +use ibc_proto::google::protobuf::Any; use uint::FromStrRadixErr; use crate::chain::handle::ChainHandle; @@ -75,6 +77,12 @@ impl FromStr for Amount { } } +impl From for Amount { + fn from(amount: u64) -> Self { + Self(amount.into()) + } +} + #[derive(Copy, Clone)] pub struct TransferTimeout { pub timeout_height: Height, @@ -129,6 +137,32 @@ pub struct TransferOptions { pub number_msgs: usize, } +pub fn build_transfer_message( + packet_src_port_id: PortId, + packet_src_channel_id: ChannelId, + amount: Amount, + denom: String, + sender: Signer, + receiver: Signer, + timeout_height: Height, + timeout_timestamp: Timestamp, +) -> Any { + let msg = MsgTransfer { + source_port: packet_src_port_id, + source_channel: packet_src_channel_id, + token: Some(ibc_proto::cosmos::base::v1beta1::Coin { + denom, + amount: amount.to_string(), + }), + sender, + receiver, + timeout_height, + timeout_timestamp, + }; + + msg.to_any() +} + pub fn build_and_send_transfer_messages( packet_src_chain: &SrcChain, // the chain whose account is debited packet_dst_chain: &DstChain, // the chain whose account eventually gets credited @@ -141,14 +175,14 @@ pub fn build_and_send_transfer_messages Result<(), Error> { @@ -58,3 +57,42 @@ impl BinaryChannelTest for SimulationTest { suspend() } } + +/** + Perform the same operation as `hermes tx raw ft-transfer`. + + The function call skips the checks done in the CLI, as we already + have the necessary information given to us by the test framework. + + Note that we cannot change the sender's wallet in this case, + as the current `send_tx` implementation in + [`CosmosSdkChain`](ibc_relayer::chain::cosmos::CosmosSdkChain) + always use the signer wallet configured in the + [`ChainConfig`](ibc_relayer::config::ChainConfig). +*/ +fn tx_raw_ft_transfer( + src_handle: &SrcChain, + dst_handle: &DstChain, + channel: &ConnectedChannel, + recipient: &MonoTagged, + denom: &MonoTagged, + amount: u64, + timeout_height_offset: u64, + timeout_duration: Duration, + number_messages: usize, +) -> Result, Error> { + let transfer_options = TransferOptions { + packet_src_port_id: channel.port_a.value().clone(), + packet_src_channel_id: *channel.channel_id_a.value(), + amount: Amount(amount.into()), + denom: denom.value().to_string(), + receiver: Some(recipient.value().0.clone()), + timeout_height_offset, + timeout_duration, + number_msgs: number_messages, + }; + + let events = build_and_send_transfer_messages(src_handle, dst_handle, &transfer_options)?; + + Ok(events) +} diff --git a/tools/integration-test/src/tests/memo.rs b/tools/integration-test/src/tests/memo.rs index 4328c7ee2e..b77f501e50 100644 --- a/tools/integration-test/src/tests/memo.rs +++ b/tools/integration-test/src/tests/memo.rs @@ -41,13 +41,13 @@ impl BinaryChannelTest for MemoTest { let a_to_b_amount = random_u64_range(1000, 5000); - chains.node_a.chain_driver().transfer_token( + chains.node_a.chain_driver().ibc_transfer_token( &channel.port_a.as_ref(), &channel.channel_id_a.as_ref(), - &chains.node_a.wallets().user1().address(), + &chains.node_a.wallets().user1(), &chains.node_b.wallets().user1().address(), - a_to_b_amount, &denom_a, + a_to_b_amount, )?; let denom_b = derive_ibc_denom( diff --git a/tools/integration-test/src/tests/ordered_channel.rs b/tools/integration-test/src/tests/ordered_channel.rs index 4533d9f58f..ea0467dd33 100644 --- a/tools/integration-test/src/tests/ordered_channel.rs +++ b/tools/integration-test/src/tests/ordered_channel.rs @@ -51,13 +51,13 @@ impl BinaryChannelTest for OrderedChannelTest { amount1 ); - chains.node_a.chain_driver().transfer_token( + chains.node_a.chain_driver().ibc_transfer_token( &channel.port_a.as_ref(), &channel.channel_id_a.as_ref(), - &wallet_a.address(), + &wallet_a.as_ref(), &wallet_b.address(), - amount1, &denom_a, + amount1, )?; sleep(Duration::from_secs(1)); @@ -72,13 +72,13 @@ impl BinaryChannelTest for OrderedChannelTest { amount2 ); - chains.node_a.chain_driver().transfer_token( + chains.node_a.chain_driver().ibc_transfer_token( &channel.port_a.as_ref(), &channel.channel_id_a.as_ref(), - &wallet_a.address(), + &wallet_a.as_ref(), &wallet_b.address(), - amount2, &denom_a, + amount2, )?; sleep(Duration::from_secs(1)); diff --git a/tools/integration-test/src/tests/query_packet.rs b/tools/integration-test/src/tests/query_packet.rs index f4dd72abdd..4f91e3d3fe 100644 --- a/tools/integration-test/src/tests/query_packet.rs +++ b/tools/integration-test/src/tests/query_packet.rs @@ -46,13 +46,13 @@ impl BinaryChannelTest for QueryPacketPendingTest { amount1 ); - chains.node_a.chain_driver().transfer_token( + chains.node_a.chain_driver().ibc_transfer_token( &channel.port_a.as_ref(), &channel.channel_id_a.as_ref(), - &wallet_a.address(), + &wallet_a.as_ref(), &wallet_b.address(), - amount1, &denom_a, + amount1, )?; sleep(Duration::from_secs(2)); @@ -102,13 +102,13 @@ impl BinaryChannelTest for QueryPacketPendingTest { let denom_b = chains.node_b.denom(); let amount2 = random_u64_range(1000, 5000); - chains.node_b.chain_driver().transfer_token( + chains.node_b.chain_driver().ibc_transfer_token( &channel.port_b.as_ref(), &channel.channel_id_b.as_ref(), - &wallet_b.address(), + &wallet_b.as_ref(), &wallet_a.address(), - amount2, &denom_b, + amount2, )?; info!( diff --git a/tools/integration-test/src/tests/supervisor.rs b/tools/integration-test/src/tests/supervisor.rs index c13db53080..4e739edc58 100644 --- a/tools/integration-test/src/tests/supervisor.rs +++ b/tools/integration-test/src/tests/supervisor.rs @@ -95,14 +95,14 @@ impl BinaryChainTest for SupervisorTest { // wallet to mess up the account sequence number on both sides. chains.node_a.chain_driver().local_transfer_token( - &chains.node_a.wallets().relayer().address(), + &chains.node_a.wallets().relayer(), &chains.node_a.wallets().user2().address(), 1000, &denom_a, )?; chains.node_b.chain_driver().local_transfer_token( - &chains.node_b.wallets().relayer().address(), + &chains.node_b.wallets().relayer(), &chains.node_b.wallets().user2().address(), 1000, &chains.node_b.denom(), @@ -116,13 +116,13 @@ impl BinaryChainTest for SupervisorTest { denom_a ); - chains.node_a.chain_driver().transfer_token( + chains.node_a.chain_driver().ibc_transfer_token( &port_a.as_ref(), &channel_id_a.as_ref(), - &wallet_a.address(), + &wallet_a.as_ref(), &wallet_b.address(), - transfer_amount, &denom_a, + transfer_amount, )?; // During the test, you should see error logs showing "account sequence mismatch". diff --git a/tools/integration-test/src/tests/ternary_transfer.rs b/tools/integration-test/src/tests/ternary_transfer.rs index e34a9a9eb5..4c23a9eb4b 100644 --- a/tools/integration-test/src/tests/ternary_transfer.rs +++ b/tools/integration-test/src/tests/ternary_transfer.rs @@ -57,13 +57,13 @@ impl NaryChannelTest<3> for TernaryIbcTransferTest { denom_a ); - node_a.chain_driver().transfer_token( + node_a.chain_driver().ibc_transfer_token( &channel_a_to_b.port_a.as_ref(), &channel_a_to_b.channel_id_a.as_ref(), - &wallet_a1.address(), + &wallet_a1.as_ref(), &wallet_b1.address(), - a_to_b_amount, &denom_a, + a_to_b_amount, )?; let denom_a_to_b = derive_ibc_denom( @@ -107,13 +107,13 @@ impl NaryChannelTest<3> for TernaryIbcTransferTest { let b_to_c_amount = 2500; - node_b.chain_driver().transfer_token( + node_b.chain_driver().ibc_transfer_token( &channel_b_to_c.port_a.as_ref(), &channel_b_to_c.channel_id_a.as_ref(), - &wallet_b1.address(), + &wallet_b1.as_ref(), &wallet_c1.address(), - b_to_c_amount, &denom_a_to_b.as_ref(), + b_to_c_amount, )?; // Chain C will receive ibc/port-c/channel-c/port-b/channel-b/denom @@ -145,13 +145,13 @@ impl NaryChannelTest<3> for TernaryIbcTransferTest { let c_to_a_amount = 800; - node_c.chain_driver().transfer_token( + node_c.chain_driver().ibc_transfer_token( &channel_c_to_a.port_a.as_ref(), &channel_c_to_a.channel_id_a.as_ref(), - &wallet_c1.address(), + &wallet_c1.as_ref(), &wallet_a1.address(), - c_to_a_amount, &denom_a_to_c.as_ref(), + c_to_a_amount, )?; // Chain A will receive ibc/port-a/channel-a/port-c/channel-c/port-b/channel-b/denom @@ -175,13 +175,13 @@ impl NaryChannelTest<3> for TernaryIbcTransferTest { let c_to_b_amount = 500; - node_c.chain_driver().transfer_token( + node_c.chain_driver().ibc_transfer_token( &channel_b_to_c.port_b.as_ref(), &channel_b_to_c.channel_id_b.as_ref(), - &wallet_c1.address(), + &wallet_c1.as_ref(), &wallet_b2.address(), - c_to_b_amount, &denom_a_to_c.as_ref(), + c_to_b_amount, )?; // Chain B will receive ibc/port-b/channel-b/denom diff --git a/tools/integration-test/src/tests/transfer.rs b/tools/integration-test/src/tests/transfer.rs index 53e5d0c844..a5a76750c8 100644 --- a/tools/integration-test/src/tests/transfer.rs +++ b/tools/integration-test/src/tests/transfer.rs @@ -70,13 +70,13 @@ impl BinaryChannelTest for IbcTransferTest { denom_a ); - chains.node_a.chain_driver().transfer_token( + chains.node_a.chain_driver().ibc_transfer_token( &channel.port_a.as_ref(), &channel.channel_id_a.as_ref(), - &wallet_a.address(), + &wallet_a.as_ref(), &wallet_b.address(), - a_to_b_amount, &denom_a, + a_to_b_amount, )?; let denom_b = derive_ibc_denom( @@ -123,13 +123,13 @@ impl BinaryChannelTest for IbcTransferTest { denom_b ); - chains.node_b.chain_driver().transfer_token( + chains.node_b.chain_driver().ibc_transfer_token( &channel.port_b.as_ref(), &channel.channel_id_b.as_ref(), - &wallet_b.address(), + &wallet_b.as_ref(), &wallet_c.address(), - b_to_a_amount, &denom_b.as_ref(), + b_to_a_amount, )?; chains.node_b.chain_driver().assert_eventual_wallet_amount( diff --git a/tools/test-framework/Cargo.toml b/tools/test-framework/Cargo.toml index 1baec236bc..a1729b95f3 100644 --- a/tools/test-framework/Cargo.toml +++ b/tools/test-framework/Cargo.toml @@ -21,6 +21,8 @@ ibc-proto = { path = "../../proto" } tendermint = { version = "=0.23.7" } tendermint-rpc = { version = "=0.23.7", features = ["http-client", "websocket-client"] } +async-trait = "0.1.53" +http = "0.2.6" tokio = { version = "1.0", features = ["full"] } tracing = "0.1.34" tracing-subscriber = "0.3.11" diff --git a/tools/test-framework/src/bootstrap/init.rs b/tools/test-framework/src/bootstrap/init.rs index 0dfc120c62..ca5719b324 100644 --- a/tools/test-framework/src/bootstrap/init.rs +++ b/tools/test-framework/src/bootstrap/init.rs @@ -41,6 +41,8 @@ pub fn init_test() -> Result { let base_chain_store_dir = env::var("CHAIN_STORE_DIR").unwrap_or_else(|_| "data".to_string()); + let account_prefix = env::var("ACCOUNT_PREFIX").unwrap_or_else(|_| "cosmos".to_string()); + let chain_store_dir = format!("{}/test-{}", base_chain_store_dir, random_u32()); fs::create_dir_all(&chain_store_dir)?; @@ -55,6 +57,7 @@ pub fn init_test() -> Result { Ok(TestConfig { chain_command_path, chain_store_dir, + account_prefix, hang_on_fail, bootstrap_with_random_ids: true, }) diff --git a/tools/test-framework/src/bootstrap/single.rs b/tools/test-framework/src/bootstrap/single.rs index e85595b9df..f64e1a0f9d 100644 --- a/tools/test-framework/src/bootstrap/single.rs +++ b/tools/test-framework/src/bootstrap/single.rs @@ -97,6 +97,13 @@ pub fn bootstrap_single_node( Ok(()) })?; + chain_driver.update_chain_config("app.toml", |config| { + config::set_grpc_port(config, chain_driver.grpc_port)?; + config::disable_grpc_web(config)?; + + Ok(()) + })?; + let process = chain_driver.start()?; chain_driver.assert_eventual_wallet_amount(&relayer.address, initial_amount, &denom)?; diff --git a/tools/test-framework/src/chain/builder.rs b/tools/test-framework/src/chain/builder.rs index 91af6dcafc..ebbeacc42a 100644 --- a/tools/test-framework/src/chain/builder.rs +++ b/tools/test-framework/src/chain/builder.rs @@ -2,7 +2,9 @@ Builder construct that spawn new chains with some common parameters. */ +use alloc::sync::Arc; use ibc::core::ics24_host::identifier::ChainId; +use tokio::runtime::Runtime; use crate::chain::driver::ChainDriver; use crate::error::Error; @@ -31,26 +33,39 @@ pub struct ChainBuilder { The filesystem path to store the data files used by the chain. */ pub base_store_dir: String, + + pub account_prefix: String, + + pub runtime: Arc, } impl ChainBuilder { /** Create a new `ChainBuilder`. */ - pub fn new(command_path: &str, base_store_dir: &str) -> Self { + pub fn new( + command_path: &str, + base_store_dir: &str, + account_prefix: &str, + runtime: Arc, + ) -> Self { Self { command_path: command_path.to_string(), base_store_dir: base_store_dir.to_string(), + account_prefix: account_prefix.to_string(), + runtime, } } /** Create a `ChainBuilder` based on the provided [`TestConfig`]. */ - pub fn new_with_config(config: &TestConfig) -> Self { + pub fn new_with_config(config: &TestConfig, runtime: Arc) -> Self { Self::new( &config.chain_command_path, &format!("{}", config.chain_store_dir.display()), + &config.account_prefix, + runtime, ) } @@ -86,10 +101,12 @@ impl ChainBuilder { self.command_path.clone(), chain_id, home_path, + self.account_prefix.clone(), rpc_port, grpc_port, grpc_web_port, p2p_port, + self.runtime.clone(), )?; Ok(driver) diff --git a/tools/test-framework/src/chain/config.rs b/tools/test-framework/src/chain/config.rs index e8f70fc008..bec1529485 100644 --- a/tools/test-framework/src/chain/config.rs +++ b/tools/test-framework/src/chain/config.rs @@ -27,8 +27,8 @@ pub fn set_rpc_port(config: &mut Value, port: u16) -> Result<(), Error> { pub fn set_grpc_port(config: &mut Value, port: u16) -> Result<(), Error> { config - .get_mut("grpc-web") - .ok_or_else(|| eyre!("expect grpc-web section"))? + .get_mut("grpc") + .ok_or_else(|| eyre!("expect grpc section"))? .as_table_mut() .ok_or_else(|| eyre!("expect object"))? .insert("address".to_string(), format!("0.0.0.0:{}", port).into()); @@ -36,6 +36,17 @@ pub fn set_grpc_port(config: &mut Value, port: u16) -> Result<(), Error> { Ok(()) } +pub fn disable_grpc_web(config: &mut Value) -> Result<(), Error> { + if let Some(field) = config.get_mut("grpc-web") { + field + .as_table_mut() + .ok_or_else(|| eyre!("expect object"))? + .insert("enable".to_string(), false.into()); + } + + Ok(()) +} + /// Set the `p2p` field in the full node config. pub fn set_p2p_port(config: &mut Value, port: u16) -> Result<(), Error> { config diff --git a/tools/test-framework/src/chain/driver.rs b/tools/test-framework/src/chain/driver.rs index c1b925984e..4734967e70 100644 --- a/tools/test-framework/src/chain/driver.rs +++ b/tools/test-framework/src/chain/driver.rs @@ -5,23 +5,26 @@ use core::str::FromStr; use core::time::Duration; +use alloc::sync::Arc; use eyre::eyre; -use semver::Version; use serde_json as json; use std::fs; use std::path::PathBuf; use std::process::{Command, Stdio}; use std::str; +use tokio::runtime::Runtime; use toml; use tracing::debug; use ibc::core::ics24_host::identifier::ChainId; +use ibc_proto::google::protobuf::Any; +use ibc_relayer::chain::cosmos::types::config::TxConfig; use ibc_relayer::keyring::{HDPath, KeyEntry, KeyFile}; use crate::chain::exec::{simple_exec, ExecOutput}; -use crate::chain::version::get_chain_command_version; use crate::error::{handle_generic_error, Error}; use crate::ibc::denom::Denom; +use crate::relayer::tx::{new_tx_config_for_test, simple_send_tx}; use crate::types::env::{EnvWriter, ExportEnv}; use crate::types::process::ChildProcess; use crate::types::wallet::{Wallet, WalletAddress, WalletId}; @@ -30,7 +33,6 @@ use crate::util::retry::assert_eventually_succeed; pub mod interchain; pub mod query_txs; -pub mod tagged; pub mod transfer; /** @@ -70,8 +72,6 @@ pub struct ChainDriver { */ pub command_path: String, - pub command_version: Option, - /** The ID of the chain. */ @@ -82,6 +82,8 @@ pub struct ChainDriver { */ pub home_path: String, + pub account_prefix: String, + /** The port used for RPC. */ @@ -98,6 +100,10 @@ pub struct ChainDriver { The port used for P2P. (Currently unused other than for setup) */ pub p2p_port: u16, + + pub tx_config: TxConfig, + + pub runtime: Arc, } impl ExportEnv for ChainDriver { @@ -115,24 +121,30 @@ impl ChainDriver { command_path: String, chain_id: ChainId, home_path: String, + account_prefix: String, rpc_port: u16, grpc_port: u16, grpc_web_port: u16, p2p_port: u16, + runtime: Arc, ) -> Result { - // Assume we're on Gaia 6 if we can't get a version - // (eg. with `icad`, which returns an empty string). - let command_version = get_chain_command_version(&command_path)?; + let tx_config = new_tx_config_for_test( + chain_id.clone(), + format!("http://localhost:{}", rpc_port), + format!("http://localhost:{}", grpc_port), + )?; Ok(Self { command_path, - command_version, chain_id, home_path, + account_prefix, rpc_port, grpc_port, grpc_web_port, p2p_port, + tx_config, + runtime, }) } @@ -371,7 +383,9 @@ impl ChainDriver { file: &str, cont: impl FnOnce(&mut toml::Value) -> Result<(), Error>, ) -> Result<(), Error> { - let config1 = self.read_file(&format!("config/{}", file))?; + let config_path = format!("config/{}", file); + + let config1 = self.read_file(&config_path)?; let mut config2 = toml::from_str(&config1).map_err(handle_generic_error)?; @@ -379,18 +393,11 @@ impl ChainDriver { let config3 = toml::to_string_pretty(&config2).map_err(handle_generic_error)?; - self.write_file("config/config.toml", &config3)?; + self.write_file(&config_path, &config3)?; Ok(()) } - pub fn is_v6_or_later(&self) -> bool { - match &self.command_version { - Some(version) => version.major >= 6, - None => true, - } - } - /** Start a full node by running in the background `gaiad start`. @@ -410,20 +417,7 @@ impl ChainDriver { &self.rpc_listen_address(), ]; - // Gaia v6 requires the GRPC web port to be unique, - // but the argument is not available in earlier version - let extra_args = [ - "--grpc-web.address", - &format!("localhost:{}", self.grpc_web_port), - ]; - - let args: Vec<&str> = if self.is_v6_or_later() { - let mut list = base_args.to_vec(); - list.extend_from_slice(&extra_args); - list - } else { - base_args.to_vec() - }; + let args: Vec<&str> = base_args.to_vec(); let mut child = Command::new(&self.command_path) .args(&args) @@ -480,6 +474,11 @@ impl ChainDriver { Ok(amount) } + pub fn send_tx(&self, wallet: &Wallet, messages: Vec) -> Result<(), Error> { + self.runtime + .block_on(simple_send_tx(&self.tx_config, &wallet.key, messages)) + } + /** Assert that a wallet should eventually have the expected amount in the given denomination. diff --git a/tools/test-framework/src/chain/driver/transfer.rs b/tools/test-framework/src/chain/driver/transfer.rs index 063213daa0..0f6f5fa6c5 100644 --- a/tools/test-framework/src/chain/driver/transfer.rs +++ b/tools/test-framework/src/chain/driver/transfer.rs @@ -2,57 +2,15 @@ Methods for performing IBC token transfer on a chain. */ -use ibc::core::ics24_host::identifier::{ChannelId, PortId}; - use crate::error::Error; use crate::ibc::denom::Denom; -use crate::types::wallet::WalletAddress; +use crate::types::wallet::{Wallet, WalletAddress}; use super::ChainDriver; -/** - Submits an IBC token transfer transaction. - - We use gaiad instead of the internal raw tx transfer to transfer tokens, - as the current chain implementation cannot dynamically switch the sender, - and instead always use the configured relayer wallet for sending tokens. -*/ -pub fn transfer_token( - driver: &ChainDriver, - port_id: &PortId, - channel_id: &ChannelId, - sender: &WalletAddress, - recipient: &WalletAddress, - amount: u64, - denom: &Denom, -) -> Result<(), Error> { - driver.exec(&[ - "--node", - &driver.rpc_listen_address(), - "tx", - "ibc-transfer", - "transfer", - port_id.as_str(), - &channel_id.to_string(), - &recipient.0, - &format!("{}{}", amount, denom), - "--from", - &sender.0, - "--chain-id", - driver.chain_id.as_str(), - "--home", - &driver.home_path, - "--keyring-backend", - "test", - "--yes", - ])?; - - Ok(()) -} - pub fn local_transfer_token( driver: &ChainDriver, - sender: &WalletAddress, + sender: &Wallet, recipient: &WalletAddress, amount: u64, denom: &Denom, @@ -63,7 +21,7 @@ pub fn local_transfer_token( "tx", "bank", "send", - &sender.0, + &sender.address.0, &recipient.0, &format!("{}{}", amount, denom), "--chain-id", diff --git a/tools/test-framework/src/chain/mod.rs b/tools/test-framework/src/chain/mod.rs index d6017f534f..7180f61a41 100644 --- a/tools/test-framework/src/chain/mod.rs +++ b/tools/test-framework/src/chain/mod.rs @@ -18,4 +18,5 @@ pub mod builder; pub mod config; pub mod driver; pub mod exec; +pub mod tagged; pub mod version; diff --git a/tools/test-framework/src/chain/driver/tagged.rs b/tools/test-framework/src/chain/tagged.rs similarity index 74% rename from tools/test-framework/src/chain/driver/tagged.rs rename to tools/test-framework/src/chain/tagged.rs index 04ce2b9676..da85694af5 100644 --- a/tools/test-framework/src/chain/driver/tagged.rs +++ b/tools/test-framework/src/chain/tagged.rs @@ -2,20 +2,24 @@ Methods for tagged version of the chain driver. */ +use ibc_proto::google::protobuf::Any; +use ibc_relayer::chain::cosmos::types::config::TxConfig; use serde::Serialize; use serde_json as json; +use crate::chain::driver::interchain::{ + interchain_submit, query_interchain_account, register_interchain_account, +}; +use crate::chain::driver::query_txs::query_recipient_transactions; +use crate::chain::driver::transfer::local_transfer_token; +use crate::chain::driver::ChainDriver; use crate::error::Error; use crate::ibc::denom::Denom; use crate::prelude::TaggedConnectionIdRef; -use crate::types::id::{TaggedChannelIdRef, TaggedPortIdRef}; +use crate::relayer::transfer::ibc_token_transfer; +use crate::types::id::{TaggedChainIdRef, TaggedChannelIdRef, TaggedPortIdRef}; use crate::types::tagged::*; -use crate::types::wallet::WalletAddress; - -use super::interchain::{interchain_submit, query_interchain_account, register_interchain_account}; -use super::query_txs::query_recipient_transactions; -use super::transfer::{local_transfer_token, transfer_token}; -use super::ChainDriver; +use crate::types::wallet::{Wallet, WalletAddress}; /** A [`ChainDriver`] may be tagged with a `Chain` tag in the form @@ -28,6 +32,13 @@ use super::ChainDriver; methods are used with the values associated to the correct chain. */ pub trait TaggedChainDriverExt { + fn chain_id(&self) -> TaggedChainIdRef; + + fn tx_config(&self) -> MonoTagged; + + fn send_tx(&self, wallet: &MonoTagged, messages: Vec) + -> Result<(), Error>; + /** Tagged version of [`ChainDriver::query_balance`]. @@ -54,8 +65,8 @@ pub trait TaggedChainDriverExt { ) -> Result<(), Error>; /** - Tagged version of [`transfer_token`]. Submits an IBC token transfer - transaction to `Chain` to any other `Counterparty` chain. + Submits an IBC token transfer transaction to `Chain` to any other + `Counterparty` chain. The following parameters are accepted: @@ -65,27 +76,27 @@ pub trait TaggedChainDriverExt { - A `ChannelId` on `Chain` that corresponds to a channel connected to `Counterparty`. - - The wallet address of the sender on `Chain`. + - The [`Wallet`] of the sender on `Chain`. - - The wallet address of the recipient on `Counterparty`. - - - The transfer amount. + - The [`WalletAddress`] address of the recipient on `Counterparty`. - The denomination of the amount on `Chain`. + + - The transfer amount. */ - fn transfer_token( + fn ibc_transfer_token( &self, port_id: &TaggedPortIdRef, channel_id: &TaggedChannelIdRef, - sender: &MonoTagged, + sender: &MonoTagged, recipient: &MonoTagged, - amount: u64, denom: &MonoTagged, + amount: u64, ) -> Result<(), Error>; fn local_transfer_token( &self, - sender: &MonoTagged, + sender: &MonoTagged, recipient: &MonoTagged, amount: u64, denom: &MonoTagged, @@ -122,7 +133,23 @@ pub trait TaggedChainDriverExt { ) -> Result<(), Error>; } -impl<'a, Chain> TaggedChainDriverExt for MonoTagged { +impl<'a, Chain: Send> TaggedChainDriverExt for MonoTagged { + fn chain_id(&self) -> TaggedChainIdRef { + self.map_ref(|val| &val.chain_id) + } + + fn tx_config(&self) -> MonoTagged { + self.map_ref(|val| &val.tx_config) + } + + fn send_tx( + &self, + wallet: &MonoTagged, + messages: Vec, + ) -> Result<(), Error> { + self.value().send_tx(wallet.value(), messages) + } + fn query_balance( &self, wallet_id: &MonoTagged, @@ -141,29 +168,29 @@ impl<'a, Chain> TaggedChainDriverExt for MonoTagged( + fn ibc_transfer_token( &self, port_id: &TaggedPortIdRef, channel_id: &TaggedChannelIdRef, - sender: &MonoTagged, + sender: &MonoTagged, recipient: &MonoTagged, - amount: u64, denom: &MonoTagged, + amount: u64, ) -> Result<(), Error> { - transfer_token( - self.value(), - port_id.value(), - channel_id.value(), - sender.value(), - recipient.value(), + self.value().runtime.block_on(ibc_token_transfer( + &self.tx_config(), + port_id, + channel_id, + sender, + recipient, + denom, amount, - denom.value(), - ) + )) } fn local_transfer_token( &self, - sender: &MonoTagged, + sender: &MonoTagged, recipient: &MonoTagged, amount: u64, denom: &MonoTagged, diff --git a/tools/test-framework/src/chain/version.rs b/tools/test-framework/src/chain/version.rs index 2119c4ed14..db27cd0948 100644 --- a/tools/test-framework/src/chain/version.rs +++ b/tools/test-framework/src/chain/version.rs @@ -14,7 +14,7 @@ pub fn get_chain_command_version(command: &str) -> Result, Error output.stdout }; - let version_str = match raw_version_str.strip_prefix('v') { + let version_str = match raw_version_str.trim().strip_prefix('v') { Some(str) => str.trim(), None => raw_version_str.trim(), }; diff --git a/tools/test-framework/src/framework/base.rs b/tools/test-framework/src/framework/base.rs index 28eb30db87..0ba59ea9fb 100644 --- a/tools/test-framework/src/framework/base.rs +++ b/tools/test-framework/src/framework/base.rs @@ -3,6 +3,8 @@ initializing the logger and loading the test configuration. */ +use alloc::sync::Arc; +use tokio::runtime::Runtime; use tracing::info; use crate::bootstrap::init::init_test; @@ -90,11 +92,14 @@ where { fn run(&self) -> Result<(), Error> { let mut config = init_test()?; + + let runtime = Arc::new(Runtime::new()?); + self.test.get_overrides().modify_test_config(&mut config); info!("starting test with test config: {:?}", config); - let builder = ChainBuilder::new_with_config(&config); + let builder = ChainBuilder::new_with_config(&config, runtime); self.test.run(&config, &builder)?; diff --git a/tools/test-framework/src/prelude.rs b/tools/test-framework/src/prelude.rs index 0ac8385340..624ed806a4 100644 --- a/tools/test-framework/src/prelude.rs +++ b/tools/test-framework/src/prelude.rs @@ -14,7 +14,8 @@ pub use ibc_relayer::supervisor::SupervisorHandle; pub use std::thread::sleep; pub use tracing::{debug, error, info, warn}; -pub use crate::chain::driver::{tagged::TaggedChainDriverExt, ChainDriver}; +pub use crate::chain::driver::ChainDriver; +pub use crate::chain::tagged::TaggedChainDriverExt; pub use crate::error::{handle_generic_error, Error}; pub use crate::framework::base::HasOverrides; pub use crate::framework::binary::chain::{ @@ -44,6 +45,7 @@ pub use crate::framework::nary::connection::{ pub use crate::framework::nary::node::{run_nary_node_test, NaryNodeTest, RunNaryNodeTest}; pub use crate::framework::overrides::TestOverrides; pub use crate::framework::supervisor::RunWithSupervisor; +pub use crate::ibc::denom::Denom; pub use crate::relayer::channel::TaggedChannelEndExt; pub use crate::relayer::connection::{TaggedConnectionEndExt, TaggedConnectionExt}; pub use crate::relayer::driver::RelayerDriver; diff --git a/tools/test-framework/src/relayer/mod.rs b/tools/test-framework/src/relayer/mod.rs index 312150afdd..44a848d41e 100644 --- a/tools/test-framework/src/relayer/mod.rs +++ b/tools/test-framework/src/relayer/mod.rs @@ -25,3 +25,4 @@ pub mod driver; pub mod foreign_client; pub mod refresh; pub mod transfer; +pub mod tx; diff --git a/tools/test-framework/src/relayer/transfer.rs b/tools/test-framework/src/relayer/transfer.rs index 81643bd4da..d4ac86aff0 100644 --- a/tools/test-framework/src/relayer/transfer.rs +++ b/tools/test-framework/src/relayer/transfer.rs @@ -3,64 +3,75 @@ `hermes tx raw ft-transfer`. */ +use core::ops::Add; use core::time::Duration; -use ibc::events::IbcEvent; -use ibc_relayer::chain::handle::ChainHandle; -use ibc_relayer::transfer::{build_and_send_transfer_messages, Amount, TransferOptions}; +use ibc::signer::Signer; +use ibc::timestamp::Timestamp; +use ibc::Height; +use ibc_proto::google::protobuf::Any; +use ibc_relayer::chain::cosmos::types::config::TxConfig; +use ibc_relayer::transfer::build_transfer_message as raw_build_transfer_message; -use crate::error::Error; +use crate::error::{handle_generic_error, Error}; use crate::ibc::denom::Denom; -use crate::types::binary::channel::ConnectedChannel; +use crate::relayer::tx::simple_send_tx; +use crate::types::id::{TaggedChannelIdRef, TaggedPortIdRef}; use crate::types::tagged::*; -use crate::types::wallet::WalletAddress; +use crate::types::wallet::{Wallet, WalletAddress}; -/** - Perform the same operation as `hermes tx raw ft-transfer`. +pub fn build_transfer_message( + port_id: &TaggedPortIdRef<'_, SrcChain, DstChain>, + channel_id: &TaggedChannelIdRef<'_, SrcChain, DstChain>, + sender: &MonoTagged, + recipient: &MonoTagged, + denom: &MonoTagged, + amount: u64, +) -> Result { + let timeout_timestamp = Timestamp::now() + .add(Duration::from_secs(60)) + .map_err(handle_generic_error)?; - The function call skips the checks done in the CLI, as we already - have the necessary information given to us by the test framework. + Ok(raw_build_transfer_message( + (*port_id.value()).clone(), + **channel_id.value(), + amount.into(), + denom.value().to_string(), + Signer::new(sender.value().address.0.clone()), + Signer::new(recipient.value().0.clone()), + Height::zero(), + timeout_timestamp, + )) +} + +/** + Perform a simplified version of IBC token transfer for testing purpose. - Note that we cannot change the sender's wallet in this case, - as the current `send_tx` implementation in - [`CosmosSdkChain`](ibc_relayer::chain::cosmos::CosmosSdkChain) - always use the signer wallet configured in the - [`ChainConfig`](ibc_relayer::config::ChainConfig). + It makes use of the local time to construct a 60 seconds IBC timeout + for testing. During test, all chains should have the same local clock. + We are also not really interested in setting a timeout for most tests, + so we just put an approximate 1 minute timeout as the timeout + field is compulsary, and we want to avoid IBC timeout on CI. - Currently the only way you can transfer using a different wallet - is to create a brand new [`ChainHandle`] with the new wallet - specified in the [`ChainConfig`](ibc_relayer::config::ChainConfig). + The other reason we do not allow precise timeout to be specified is + because it requires accessing the counterparty chain to query for + the parameters. This will complicate the API which is unnecessary + in most cases. - Alternatively, it is recommended that for simple case of IBC token - transfer, test authors should instead use the - [`transfer_token`](crate::chain::driver::transfer::transfer_token) - function provided by [`ChainDriver`](crate::chain::driver::ChainDriver). - That uses the `gaiad tx ibc-transfer` command to do the IBC transfer, - which is much simpler as compared to the current way the relayer code - is organized. + If tests require explicit timeout, they should explicitly construct the + transfer message and pass it to send_tx. */ -pub fn tx_raw_ft_transfer( - src_handle: &SrcChain, - dst_handle: &DstChain, - channel: &ConnectedChannel, +pub async fn ibc_token_transfer( + tx_config: &MonoTagged, + port_id: &TaggedPortIdRef<'_, SrcChain, DstChain>, + channel_id: &TaggedChannelIdRef<'_, SrcChain, DstChain>, + sender: &MonoTagged, recipient: &MonoTagged, denom: &MonoTagged, amount: u64, - timeout_height_offset: u64, - timeout_duration: Duration, - number_messages: usize, -) -> Result, Error> { - let transfer_options = TransferOptions { - packet_src_port_id: channel.port_a.value().clone(), - packet_src_channel_id: *channel.channel_id_a.value(), - amount: Amount(amount.into()), - denom: denom.value().to_string(), - receiver: Some(recipient.value().0.clone()), - timeout_height_offset, - timeout_duration, - number_msgs: number_messages, - }; +) -> Result<(), Error> { + let message = build_transfer_message(port_id, channel_id, sender, recipient, denom, amount)?; - let events = build_and_send_transfer_messages(src_handle, dst_handle, &transfer_options)?; + simple_send_tx(tx_config.value(), &sender.value().key, vec![message]).await?; - Ok(events) + Ok(()) } diff --git a/tools/test-framework/src/relayer/tx.rs b/tools/test-framework/src/relayer/tx.rs new file mode 100644 index 0000000000..18be12730d --- /dev/null +++ b/tools/test-framework/src/relayer/tx.rs @@ -0,0 +1,129 @@ +use core::str::FromStr; +use core::time::Duration; +use eyre::eyre; +use http::uri::Uri; +use ibc::core::ics24_host::identifier::ChainId; +use ibc::events::IbcEvent; +use ibc_proto::cosmos::tx::v1beta1::Fee; +use ibc_proto::google::protobuf::Any; +use ibc_relayer::chain::cosmos::gas::calculate_fee; +use ibc_relayer::chain::cosmos::query::account::query_account; +use ibc_relayer::chain::cosmos::tx::estimate_fee_and_send_tx; +use ibc_relayer::chain::cosmos::types::config::TxConfig; +use ibc_relayer::chain::cosmos::types::gas::GasConfig; +use ibc_relayer::chain::cosmos::types::tx::TxSyncResult; +use ibc_relayer::chain::cosmos::wait::wait_for_block_commits; +use ibc_relayer::config::GasPrice; +use ibc_relayer::keyring::KeyEntry; +use tendermint_rpc::{HttpClient, Url}; + +use crate::error::{handle_generic_error, Error}; + +pub fn gas_config_for_test() -> GasConfig { + let max_gas = 3000000; + let gas_adjustment = 0.1; + let gas_price = GasPrice::new(0.001, "stake".to_string()); + + let default_gas = max_gas; + let fee_granter = "".to_string(); + + let max_fee = Fee { + amount: vec![calculate_fee(max_gas, &gas_price)], + gas_limit: max_gas, + payer: "".to_string(), + granter: fee_granter.clone(), + }; + + GasConfig { + default_gas, + max_gas, + gas_adjustment, + gas_price, + max_fee, + fee_granter, + } +} + +pub fn new_tx_config_for_test( + chain_id: ChainId, + raw_rpc_address: String, + raw_grpc_address: String, +) -> Result { + let rpc_address = Url::from_str(&raw_rpc_address).map_err(handle_generic_error)?; + + let rpc_client = HttpClient::new(rpc_address.clone()).map_err(handle_generic_error)?; + + let grpc_address = Uri::from_str(&raw_grpc_address).map_err(handle_generic_error)?; + + let gas_config = gas_config_for_test(); + + let rpc_timeout = Duration::from_secs(30); + + let address_type = Default::default(); + + Ok(TxConfig { + chain_id, + gas_config, + rpc_client, + rpc_address, + grpc_address, + rpc_timeout, + address_type, + }) +} + +/** + A simplified version of send_tx that does not depend on `ChainHandle`. + + This allows different wallet ([`KeyEntry`]) to be used for submitting + transactions. The simple behavior as follows: + + - Query the account information on the fly. This may introduce more + overhead in production, but does not matter in testing. + - Do not split the provided messages into smaller batches. + - Wait for TX sync result, and error if any result contains + error event. +*/ +pub async fn simple_send_tx( + config: &TxConfig, + key_entry: &KeyEntry, + messages: Vec, +) -> Result<(), Error> { + let account = query_account(&config.grpc_address, &key_entry.account) + .await? + .into(); + + let message_count = messages.len(); + + let response = + estimate_fee_and_send_tx(config, key_entry, &account, &Default::default(), messages) + .await?; + + let events_per_tx = vec![IbcEvent::default(); message_count]; + + let tx_sync_result = TxSyncResult { + response, + events: events_per_tx, + }; + + let mut tx_sync_results = vec![tx_sync_result]; + + wait_for_block_commits( + &config.chain_id, + &config.rpc_client, + &config.rpc_address, + &config.rpc_timeout, + &mut tx_sync_results, + ) + .await?; + + for result in tx_sync_results.iter() { + for event in result.events.iter() { + if let IbcEvent::ChainError(e) = event { + return Err(Error::generic(eyre!("send_tx result in error: {}", e))); + } + } + } + + Ok(()) +} diff --git a/tools/test-framework/src/types/config.rs b/tools/test-framework/src/types/config.rs index 7a555d81ce..bc971f999c 100644 --- a/tools/test-framework/src/types/config.rs +++ b/tools/test-framework/src/types/config.rs @@ -28,6 +28,8 @@ pub struct TestConfig { */ pub chain_command_path: String, + pub account_prefix: String, + /** The directory path for storing the chain and relayer files. Defaults to `"data"`. This can be overridden with the `$CHAIN_STORE_DIR` diff --git a/tools/test-framework/src/types/single/node.rs b/tools/test-framework/src/types/single/node.rs index 9d86c2728f..55817d0415 100644 --- a/tools/test-framework/src/types/single/node.rs +++ b/tools/test-framework/src/types/single/node.rs @@ -19,6 +19,10 @@ use crate::types::process::ChildProcess; use crate::types::tagged::*; use crate::types::wallet::TestWallets; +pub type TaggedFullNode = MonoTagged; + +pub type TaggedFullNodeRef<'a, Chain> = MonoTagged; + /** Represents a full node running as a child process managed by the test. */ @@ -120,7 +124,7 @@ impl FullNode { websocket_addr: Url::from_str(&self.chain_driver.websocket_address())?, grpc_addr: Url::from_str(&self.chain_driver.grpc_address())?, rpc_timeout: Duration::from_secs(10), - account_prefix: "cosmos".to_string(), + account_prefix: self.chain_driver.account_prefix.clone(), key_name: self.wallets.relayer.id.0.clone(), // By default we use in-memory key store to avoid polluting diff --git a/tools/test-framework/src/types/tagged/dual.rs b/tools/test-framework/src/types/tagged/dual.rs index f1ab3f583f..851e2a9428 100644 --- a/tools/test-framework/src/types/tagged/dual.rs +++ b/tools/test-framework/src/types/tagged/dual.rs @@ -391,6 +391,9 @@ impl AsRef for Tagged { impl Copy for Tagged {} +unsafe impl Send for Tagged {} +unsafe impl Sync for Tagged {} + impl Clone for Tagged { fn clone(&self) -> Self { Self::new(self.0.clone()) diff --git a/tools/test-framework/src/types/tagged/mono.rs b/tools/test-framework/src/types/tagged/mono.rs index c3631604d2..ef4a228b93 100644 --- a/tools/test-framework/src/types/tagged/mono.rs +++ b/tools/test-framework/src/types/tagged/mono.rs @@ -362,6 +362,9 @@ impl IntoIterator for Tagged { impl Copy for Tagged {} +unsafe impl Send for Tagged {} +unsafe impl Sync for Tagged {} + impl Clone for Tagged { fn clone(&self) -> Self { Self::new(self.0.clone()) diff --git a/tools/test-framework/src/util/retry.rs b/tools/test-framework/src/util/retry.rs index 30b208d133..2d88feb2b7 100644 --- a/tools/test-framework/src/util/retry.rs +++ b/tools/test-framework/src/util/retry.rs @@ -4,7 +4,7 @@ use core::time::Duration; use std::thread::sleep; -use tracing::{debug, info}; +use tracing::{info, trace}; use crate::error::Error; @@ -28,7 +28,7 @@ pub fn assert_eventually_succeed( return Ok(res); } Err(e) => { - debug!("retrying task {} that failed with error: {}", task_name, e); + trace!("retrying task {} that failed with error: {}", task_name, e); sleep(interval) } }