From 162fea4f03b793b2ab761753b758a357339bfa23 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Fri, 11 Mar 2022 16:36:13 +0000 Subject: [PATCH 01/30] Refactoring send_tx --- relayer/src/chain/cosmos.rs | 2 + relayer/src/chain/cosmos/tx.rs | 532 +++++++++++++++++++ relayer/src/chain/cosmos/types/gas_config.rs | 58 ++ relayer/src/chain/cosmos/types/mod.rs | 5 + relayer/src/chain/cosmos/types/signed_tx.rs | 9 + relayer/src/keyring.rs | 27 + relayer/src/lib.rs | 1 + 7 files changed, 634 insertions(+) create mode 100644 relayer/src/chain/cosmos/tx.rs create mode 100644 relayer/src/chain/cosmos/types/gas_config.rs create mode 100644 relayer/src/chain/cosmos/types/mod.rs create mode 100644 relayer/src/chain/cosmos/types/signed_tx.rs diff --git a/relayer/src/chain/cosmos.rs b/relayer/src/chain/cosmos.rs index cc0aedb08c..a8477ba572 100644 --- a/relayer/src/chain/cosmos.rs +++ b/relayer/src/chain/cosmos.rs @@ -100,6 +100,8 @@ use ibc::core::ics24_host::path::{ use super::{tx::TrackedMsgs, ChainEndpoint, HealthCheck}; mod compatibility; +pub mod tx; +pub mod types; pub mod version; /// Default gas limit when submitting a transaction. diff --git a/relayer/src/chain/cosmos/tx.rs b/relayer/src/chain/cosmos/tx.rs new file mode 100644 index 0000000000..6fca2bc27a --- /dev/null +++ b/relayer/src/chain/cosmos/tx.rs @@ -0,0 +1,532 @@ +use core::cmp::min; +use ibc::core::ics24_host::identifier::ChainId; +use ibc_proto::cosmos::tx::v1beta1::mode_info::{Single, Sum}; +use ibc_proto::cosmos::tx::v1beta1::{ + Fee, ModeInfo, SignDoc, SignerInfo, SimulateRequest, SimulateResponse, Tx, TxRaw, +}; +use prost_types::Any; +use tendermint_rpc::endpoint::broadcast::tx_sync::Response; +use tendermint_rpc::{HttpClient, Url}; +use tonic::codegen::http::Uri; +use tracing::{debug, error}; + +use crate::chain::cosmos::types::{GasConfig, SignedTx}; +use crate::chain::cosmos::{ + auth_info_and_bytes, broadcast_tx_sync, calculate_fee, mul_ceil, tx_body_and_bytes, +}; +use crate::config::types::Memo; +use crate::config::AddressType; +use crate::config::ChainConfig; +use crate::error::Error; +use crate::keyring::{sign_message, KeyEntry}; +use crate::sdk_error::sdk_error_from_tx_sync_error_code; + +pub fn encode_key_bytes(key: &KeyEntry) -> Result, Error> { + let mut pk_buf = Vec::new(); + + prost::Message::encode(&key.public_key.public_key.to_bytes(), &mut pk_buf) + .map_err(|e| Error::protobuf_encode("PublicKey".into(), e))?; + + Ok(pk_buf) +} + +pub fn encode_sign_doc( + chain_id: &ChainId, + key: &KeyEntry, + address_type: &AddressType, + body_bytes: Vec, + auth_info_bytes: Vec, + account_number: u64, +) -> Result, Error> { + let sign_doc = SignDoc { + body_bytes, + auth_info_bytes, + chain_id: chain_id.to_string(), + account_number, + }; + + // A protobuf serialization of a SignDoc + let mut signdoc_buf = Vec::new(); + prost::Message::encode(&sign_doc, &mut signdoc_buf).unwrap(); + + let signed = sign_message(key, signdoc_buf, address_type).map_err(Error::key_base)?; + + Ok(signed) +} + +pub fn encode_signer_info( + key_bytes: Vec, + address_type: &AddressType, + sequence: u64, +) -> Result { + let pk_type = match address_type { + AddressType::Cosmos => "/cosmos.crypto.secp256k1.PubKey".to_string(), + AddressType::Ethermint { pk_type } => pk_type.clone(), + }; + // Create a MsgSend proto Any message + let pk_any = Any { + type_url: pk_type, + value: key_bytes, + }; + + let single = Single { mode: 1 }; + let sum_single = Some(Sum::Single(single)); + let mode = Some(ModeInfo { sum: sum_single }); + let signer_info = SignerInfo { + public_key: Some(pk_any), + mode_info: mode, + sequence, + }; + Ok(signer_info) +} + +pub fn batch_messages( + messages: Vec, + max_message_count: usize, + max_tx_size: usize, +) -> Result>, Error> { + let mut batches = vec![]; + + let mut current_count = 0; + let mut current_size = 0; + let mut current_batch = vec![]; + + for message in messages.into_iter() { + current_count += 1; + current_size += message_size(&message)?; + current_batch.push(message); + + if current_count >= max_message_count || current_size >= max_tx_size { + let insert_batch = current_batch.drain(..).collect(); + batches.push(insert_batch); + current_count = 0; + current_size = 0; + } + } + + if !current_batch.is_empty() { + batches.push(current_batch); + } + + Ok(batches) +} + +pub fn message_size(message: &Any) -> Result { + let mut buf = Vec::new(); + + prost::Message::encode(message, &mut buf) + .map_err(|e| Error::protobuf_encode("Message".into(), e))?; + + Ok(buf.len()) +} + +pub async fn estimate_gas( + chain_id: &ChainId, + tx: Tx, + grpc_address: &Uri, + default_gas: u64, + max_gas: u64, +) -> Result { + let response = send_tx_simulate(tx, grpc_address).await; + + let estimated_gas = match response { + Ok(response) => { + let m_gas_info = response.gas_info; + + debug!( + "[{}] send_tx: tx simulation successful, simulated gas: {:?}", + chain_id, m_gas_info, + ); + + match m_gas_info { + Some(gas) => gas.gas_used, + None => default_gas, + } + } + Err(e) => { + error!( + "[{}] send_tx: failed to estimate gas, falling back on default gas, error: {}", + chain_id, + e.detail() + ); + + default_gas + } + }; + + if estimated_gas > max_gas { + debug!( + estimated = ?estimated_gas, + max = ?max_gas, + "[{}] send_tx: estimated gas is higher than max gas", + chain_id, + ); + + Err(Error::tx_simulate_gas_estimate_exceeded( + chain_id.clone(), + estimated_gas, + max_gas, + )) + } else { + Ok(estimated_gas) + } +} + +pub async fn send_messages_as_batches( + config: &ChainConfig, + rpc_client: &HttpClient, + rpc_address: &Url, + grpc_address: &Uri, + messages: Vec, + account_sequence: &mut u64, + account_number: u64, + key_entry: &KeyEntry, + tx_memo: &Memo, +) -> Result, Error> { + let max_message_count = config.max_msg_num.0; + let max_tx_size = config.max_tx_size.into(); + + if messages.is_empty() { + return Ok(Vec::new()); + } + + let batches = batch_messages(messages, max_message_count, max_tx_size)?; + + let mut responses = Vec::new(); + + for batch in batches { + let response = estimate_fee_and_send_tx( + config, + rpc_client, + rpc_address, + grpc_address, + batch, + account_sequence, + account_number, + key_entry, + tx_memo, + ) + .await?; + + responses.push(response); + } + + Ok(responses) +} + +pub async fn estimate_fee_and_send_tx( + config: &ChainConfig, + rpc_client: &HttpClient, + rpc_address: &Url, + grpc_address: &Uri, + messages: Vec, + account_sequence: &mut u64, + account_number: u64, + key_entry: &KeyEntry, + tx_memo: &Memo, +) -> Result { + let fee = estimate_tx_fees( + config, + grpc_address, + *account_sequence, + account_number, + messages.clone(), + key_entry, + tx_memo, + ) + .await?; + + send_tx_and_update_account_sequence( + config, + rpc_client, + rpc_address, + &fee, + account_sequence, + account_number, + messages, + key_entry, + tx_memo, + ) + .await +} + +pub async fn send_tx_and_update_account_sequence( + config: &ChainConfig, + rpc_client: &HttpClient, + rpc_address: &Url, + fee: &Fee, + account_sequence: &mut u64, + account_number: u64, + messages: Vec, + key_entry: &KeyEntry, + tx_memo: &Memo, +) -> Result { + let response = raw_send_tx( + config, + rpc_client, + rpc_address, + fee, + *account_sequence, + account_number, + messages, + key_entry, + tx_memo, + ) + .await?; + + match response.code { + tendermint::abci::Code::Ok => { + // A success means the account s.n. was increased + *account_sequence += 1; + debug!("[{}] send_tx: broadcast_tx_sync: {:?}", config.id, response); + } + tendermint::abci::Code::Err(code) => { + // Avoid increasing the account s.n. if CheckTx failed + // Log the error + error!( + "[{}] send_tx: broadcast_tx_sync: {:?}: diagnostic: {:?}", + config.id, + response, + sdk_error_from_tx_sync_error_code(code) + ); + } + } + + Ok(response) +} + +pub async fn raw_send_tx( + config: &ChainConfig, + rpc_client: &HttpClient, + rpc_address: &Url, + fee: &Fee, + account_sequence: u64, + account_number: u64, + messages: Vec, + key_entry: &KeyEntry, + tx_memo: &Memo, +) -> Result { + let tx_bytes = sign_and_encode_tx( + config, + messages, + account_sequence, + key_entry, + fee, + tx_memo, + account_number, + )?; + + let response = broadcast_tx_sync(rpc_client, rpc_address, tx_bytes).await?; + + Ok(response) +} + +pub async fn estimate_tx_fees( + config: &ChainConfig, + grpc_address: &Uri, + account_sequence: u64, + account_number: u64, + messages: Vec, + key_entry: &KeyEntry, + tx_memo: &Memo, +) -> Result { + let gas_config = GasConfig::from_chain_config(config); + + let signed_tx = encode_tx_to_raw( + config, + messages, + account_sequence, + key_entry, + &gas_config.max_fee, + tx_memo, + account_number, + )?; + + let tx = Tx { + body: Some(signed_tx.body), + auth_info: Some(signed_tx.auth_info), + signatures: signed_tx.signatures, + }; + + let estimated_fee = estimate_gas_with_raw_tx(&gas_config, &config.id, grpc_address, tx).await?; + + Ok(estimated_fee) +} + +pub fn sign_and_encode_tx( + config: &ChainConfig, + messages: Vec, + account_sequence: u64, + key_entry: &KeyEntry, + fee: &Fee, + tx_memo: &Memo, + account_number: u64, +) -> Result, Error> { + let signed_tx = encode_tx_to_raw( + config, + messages, + account_sequence, + key_entry, + fee, + tx_memo, + account_number, + )?; + + let tx_raw = TxRaw { + body_bytes: signed_tx.body_bytes, + auth_info_bytes: signed_tx.auth_info_bytes, + signatures: signed_tx.signatures, + }; + + encode_tx_raw(tx_raw) +} + +pub fn encode_tx_raw(tx_raw: TxRaw) -> Result, Error> { + let mut tx_bytes = Vec::new(); + prost::Message::encode(&tx_raw, &mut tx_bytes) + .map_err(|e| Error::protobuf_encode("Transaction".to_string(), e))?; + + Ok(tx_bytes) +} + +pub fn encode_tx_to_raw( + config: &ChainConfig, + messages: Vec, + account_sequence: u64, + key_entry: &KeyEntry, + fee: &Fee, + tx_memo: &Memo, + account_number: u64, +) -> Result { + let key_bytes = encode_key_bytes(key_entry)?; + + let signer = encode_signer_info(key_bytes, &config.address_type, account_sequence)?; + + let (body, body_bytes) = tx_body_and_bytes(messages, tx_memo)?; + + let (auth_info, auth_info_bytes) = auth_info_and_bytes(signer, fee.clone())?; + + let signed_doc = encode_sign_doc( + &config.id, + key_entry, + &config.address_type, + body_bytes.clone(), + auth_info_bytes.clone(), + account_number, + )?; + + Ok(SignedTx { + body, + body_bytes, + auth_info, + auth_info_bytes, + signatures: vec![signed_doc], + }) +} + +pub async fn send_tx_simulate(tx: Tx, grpc_address: &Uri) -> Result { + use ibc_proto::cosmos::tx::v1beta1::service_client::ServiceClient; + + // The `tx` field of `SimulateRequest` was deprecated in Cosmos SDK 0.43 in favor of `tx_bytes`. + let mut tx_bytes = vec![]; + prost::Message::encode(&tx, &mut tx_bytes).unwrap(); // FIXME: Handle error here + + #[allow(deprecated)] + let req = SimulateRequest { + tx: Some(tx), // needed for simulation to go through with Cosmos SDK < 0.43 + tx_bytes, // needed for simulation to go through with Cosmos SDk >= 0.43 + }; + + let mut client = ServiceClient::connect(grpc_address.clone()) + .await + .map_err(Error::grpc_transport)?; + + let request = tonic::Request::new(req); + let response = client + .simulate(request) + .await + .map_err(Error::grpc_status)? + .into_inner(); + + Ok(response) +} + +pub async fn estimate_gas_with_raw_tx( + gas_config: &GasConfig, + chain_id: &ChainId, + grpc_address: &Uri, + tx: Tx, +) -> Result { + let response = send_tx_simulate(tx, grpc_address).await; + + let estimated_gas = match response { + Ok(response) => { + let m_gas_info = response.gas_info; + + debug!( + "[{}] send_tx: tx simulation successful, simulated gas: {:?}", + chain_id, m_gas_info, + ); + + match m_gas_info { + Some(gas) => gas.gas_used, + None => gas_config.default_gas, + } + } + Err(e) => { + error!( + "[{}] send_tx: failed to estimate gas, falling back on default gas, error: {}", + chain_id, + e.detail() + ); + + gas_config.default_gas + } + }; + + if estimated_gas > gas_config.max_gas { + debug!( + estimated = ?estimated_gas, + max = ?gas_config.max_gas, + "[{}] send_tx: estimated gas is higher than max gas", + chain_id, + ); + + Err(Error::tx_simulate_gas_estimate_exceeded( + chain_id.clone(), + estimated_gas, + gas_config.max_gas, + )) + } else { + Ok(gas_amount_to_fees(gas_config, estimated_gas)) + } +} + +pub fn gas_amount_to_fees(config: &GasConfig, gas_amount: u64) -> Fee { + let gas_limit = adjust_gas_with_simulated_fees(config, gas_amount); + + let amount = calculate_fee(gas_limit, &config.gas_price); + + Fee { + amount: vec![amount], + gas_limit, + payer: "".to_string(), + granter: "".to_string(), + } +} + +/// Adjusts the fee based on the configured `gas_adjustment` to prevent out of gas errors. +/// The actual gas cost, when a transaction is executed, may be slightly higher than the +/// one returned by the simulation. +pub fn adjust_gas_with_simulated_fees(config: &GasConfig, gas_amount: u64) -> u64 { + let gas_adjustment = config.gas_adjustment; + let max_gas = config.max_gas; + + let (_, digits) = mul_ceil(gas_amount, gas_adjustment).to_u64_digits(); + assert!(digits.len() == 1); + + let adjustment = digits[0]; + let gas = gas_amount.checked_add(adjustment).unwrap_or(u64::MAX); + + min(gas, max_gas) +} diff --git a/relayer/src/chain/cosmos/types/gas_config.rs b/relayer/src/chain/cosmos/types/gas_config.rs new file mode 100644 index 0000000000..0e179621d6 --- /dev/null +++ b/relayer/src/chain/cosmos/types/gas_config.rs @@ -0,0 +1,58 @@ +use ibc_proto::cosmos::tx::v1beta1::Fee; + +use crate::chain::cosmos::calculate_fee; +use crate::config::{ChainConfig, GasPrice}; + +/// Default gas limit when submitting a transaction. +const DEFAULT_MAX_GAS: u64 = 400_000; + +/// Fraction of the estimated gas to add to the estimated gas amount when submitting a transaction. +const DEFAULT_GAS_PRICE_ADJUSTMENT: f64 = 0.1; + +pub struct GasConfig { + pub default_gas: u64, + pub max_gas: u64, + pub gas_adjustment: f64, + pub gas_price: GasPrice, + pub max_fee: Fee, +} + +impl GasConfig { + pub fn from_chain_config(config: &ChainConfig) -> GasConfig { + GasConfig { + default_gas: default_gas_from_config(config), + max_gas: max_gas_from_config(config), + gas_adjustment: gas_adjustment_from_config(config), + gas_price: config.gas_price.clone(), + max_fee: max_fee_from_config(config), + } + } +} + +pub fn default_gas_from_config(config: &ChainConfig) -> u64 { + config + .default_gas + .unwrap_or_else(|| max_gas_from_config(config)) +} + +pub fn max_gas_from_config(config: &ChainConfig) -> u64 { + config.max_gas.unwrap_or(DEFAULT_MAX_GAS) +} + +pub fn gas_adjustment_from_config(config: &ChainConfig) -> f64 { + config + .gas_adjustment + .unwrap_or(DEFAULT_GAS_PRICE_ADJUSTMENT) +} + +pub fn max_fee_from_config(config: &ChainConfig) -> Fee { + let gas_limit = max_gas_from_config(config); + let amount = calculate_fee(gas_limit, &config.gas_price); + + Fee { + amount: vec![amount], + gas_limit, + payer: "".to_string(), + granter: "".to_string(), + } +} diff --git a/relayer/src/chain/cosmos/types/mod.rs b/relayer/src/chain/cosmos/types/mod.rs new file mode 100644 index 0000000000..f1dfddc9c0 --- /dev/null +++ b/relayer/src/chain/cosmos/types/mod.rs @@ -0,0 +1,5 @@ +pub mod gas_config; +pub mod signed_tx; + +pub use gas_config::GasConfig; +pub use signed_tx::SignedTx; diff --git a/relayer/src/chain/cosmos/types/signed_tx.rs b/relayer/src/chain/cosmos/types/signed_tx.rs new file mode 100644 index 0000000000..bef10ceee5 --- /dev/null +++ b/relayer/src/chain/cosmos/types/signed_tx.rs @@ -0,0 +1,9 @@ +use ibc_proto::cosmos::tx::v1beta1::{AuthInfo, TxBody}; + +pub struct SignedTx { + pub body: TxBody, + pub body_bytes: Vec, + pub auth_info: AuthInfo, + pub auth_info_bytes: Vec, + pub signatures: Vec>, +} diff --git a/relayer/src/keyring.rs b/relayer/src/keyring.rs index 389c616528..741402dc42 100644 --- a/relayer/src/keyring.rs +++ b/relayer/src/keyring.rs @@ -389,6 +389,33 @@ impl KeyRing { } } +/// Sign a message +pub fn sign_message( + key: &KeyEntry, + msg: Vec, + address_type: &AddressType, +) -> Result, Error> { + let private_key_bytes = key.private_key.private_key.to_bytes(); + match address_type { + AddressType::Ethermint { ref pk_type } if pk_type.ends_with(".ethsecp256k1.PubKey") => { + let hash = keccak256_hash(msg.as_slice()); + let s = Secp256k1::signing_only(); + // SAFETY: hash is 32 bytes, as expected in `Message::from_slice` -- see `keccak256_hash`, hence `unwrap` + let sign_msg = Message::from_slice(hash.as_slice()).unwrap(); + let key = SecretKey::from_slice(private_key_bytes.as_slice()) + .map_err(Error::invalid_key_raw)?; + let (_, sig_bytes) = s.sign_recoverable(&sign_msg, &key).serialize_compact(); + Ok(sig_bytes.to_vec()) + } + AddressType::Cosmos | AddressType::Ethermint { .. } => { + let signing_key = + SigningKey::from_bytes(private_key_bytes.as_slice()).map_err(Error::invalid_key)?; + let signature: Signature = signing_key.sign(&msg); + Ok(signature.as_ref().to_vec()) + } + } +} + /// Decode an extended private key from a mnemonic fn private_key_from_mnemonic( mnemonic_words: &str, diff --git a/relayer/src/lib.rs b/relayer/src/lib.rs index 47552d930c..7d99879458 100644 --- a/relayer/src/lib.rs +++ b/relayer/src/lib.rs @@ -7,6 +7,7 @@ unused_qualifications, rust_2018_idioms )] +#![allow(clippy::too_many_arguments)] // TODO: disable unwraps: // https://github.com/informalsystems/ibc-rs/issues/987 // #![cfg_attr(not(test), deny(clippy::unwrap_used))] From d73a8b838abca9c476856e93d603ba774687be5e Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Mon, 14 Mar 2022 13:15:32 +0100 Subject: [PATCH 02/30] WIP refactoring --- relayer/src/chain/cosmos.rs | 1 + relayer/src/chain/cosmos/encode.rs | 114 ++++++++++++++++++++++++++++ relayer/src/chain/cosmos/tx.rs | 116 ++--------------------------- 3 files changed, 120 insertions(+), 111 deletions(-) create mode 100644 relayer/src/chain/cosmos/encode.rs diff --git a/relayer/src/chain/cosmos.rs b/relayer/src/chain/cosmos.rs index a8477ba572..7e8f210e4e 100644 --- a/relayer/src/chain/cosmos.rs +++ b/relayer/src/chain/cosmos.rs @@ -100,6 +100,7 @@ use ibc::core::ics24_host::path::{ use super::{tx::TrackedMsgs, ChainEndpoint, HealthCheck}; mod compatibility; +pub mod encode; pub mod tx; pub mod types; pub mod version; diff --git a/relayer/src/chain/cosmos/encode.rs b/relayer/src/chain/cosmos/encode.rs new file mode 100644 index 0000000000..646dadefd6 --- /dev/null +++ b/relayer/src/chain/cosmos/encode.rs @@ -0,0 +1,114 @@ +use ibc::core::ics24_host::identifier::ChainId; +use ibc_proto::cosmos::tx::v1beta1::mode_info::{Single, Sum}; +use ibc_proto::cosmos::tx::v1beta1::{Fee, ModeInfo, SignDoc, SignerInfo, TxRaw}; +use prost_types::Any; + +use crate::chain::cosmos::types::SignedTx; +use crate::chain::cosmos::{auth_info_and_bytes, tx_body_and_bytes}; +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 encode_key_bytes(key: &KeyEntry) -> Result, Error> { + let mut pk_buf = Vec::new(); + + prost::Message::encode(&key.public_key.public_key.to_bytes(), &mut pk_buf) + .map_err(|e| Error::protobuf_encode("PublicKey".into(), e))?; + + Ok(pk_buf) +} + +pub fn encode_sign_doc( + chain_id: &ChainId, + key: &KeyEntry, + address_type: &AddressType, + body_bytes: Vec, + auth_info_bytes: Vec, + account_number: u64, +) -> Result, Error> { + let sign_doc = SignDoc { + body_bytes, + auth_info_bytes, + chain_id: chain_id.to_string(), + account_number, + }; + + // A protobuf serialization of a SignDoc + let mut signdoc_buf = Vec::new(); + prost::Message::encode(&sign_doc, &mut signdoc_buf).unwrap(); + + let signed = sign_message(key, signdoc_buf, address_type).map_err(Error::key_base)?; + + Ok(signed) +} + +pub fn encode_signer_info( + key_bytes: Vec, + address_type: &AddressType, + sequence: u64, +) -> Result { + let pk_type = match address_type { + AddressType::Cosmos => "/cosmos.crypto.secp256k1.PubKey".to_string(), + AddressType::Ethermint { pk_type } => pk_type.clone(), + }; + // Create a MsgSend proto Any message + let pk_any = Any { + type_url: pk_type, + value: key_bytes, + }; + + let single = Single { mode: 1 }; + let sum_single = Some(Sum::Single(single)); + let mode = Some(ModeInfo { sum: sum_single }); + let signer_info = SignerInfo { + public_key: Some(pk_any), + mode_info: mode, + sequence, + }; + Ok(signer_info) +} + +pub fn encode_tx_raw(tx_raw: TxRaw) -> Result, Error> { + let mut tx_bytes = Vec::new(); + prost::Message::encode(&tx_raw, &mut tx_bytes) + .map_err(|e| Error::protobuf_encode("Transaction".to_string(), e))?; + + Ok(tx_bytes) +} + +pub fn encode_tx_to_raw( + config: &ChainConfig, + messages: Vec, + account_sequence: u64, + key_entry: &KeyEntry, + fee: &Fee, + tx_memo: &Memo, + account_number: u64, +) -> Result { + let key_bytes = encode_key_bytes(key_entry)?; + + let signer = encode_signer_info(key_bytes, &config.address_type, account_sequence)?; + + let (body, body_bytes) = tx_body_and_bytes(messages, tx_memo)?; + + let (auth_info, auth_info_bytes) = auth_info_and_bytes(signer, fee.clone())?; + + let signed_doc = encode_sign_doc( + &config.id, + key_entry, + &config.address_type, + body_bytes.clone(), + auth_info_bytes.clone(), + account_number, + )?; + + Ok(SignedTx { + body, + body_bytes, + auth_info, + auth_info_bytes, + signatures: vec![signed_doc], + }) +} diff --git a/relayer/src/chain/cosmos/tx.rs b/relayer/src/chain/cosmos/tx.rs index 6fca2bc27a..5627ed1eac 100644 --- a/relayer/src/chain/cosmos/tx.rs +++ b/relayer/src/chain/cosmos/tx.rs @@ -1,84 +1,21 @@ use core::cmp::min; use ibc::core::ics24_host::identifier::ChainId; -use ibc_proto::cosmos::tx::v1beta1::mode_info::{Single, Sum}; -use ibc_proto::cosmos::tx::v1beta1::{ - Fee, ModeInfo, SignDoc, SignerInfo, SimulateRequest, SimulateResponse, Tx, TxRaw, -}; +use ibc_proto::cosmos::tx::v1beta1::{Fee, SimulateRequest, SimulateResponse, Tx, TxRaw}; use prost_types::Any; use tendermint_rpc::endpoint::broadcast::tx_sync::Response; use tendermint_rpc::{HttpClient, Url}; use tonic::codegen::http::Uri; use tracing::{debug, error}; -use crate::chain::cosmos::types::{GasConfig, SignedTx}; -use crate::chain::cosmos::{ - auth_info_and_bytes, broadcast_tx_sync, calculate_fee, mul_ceil, tx_body_and_bytes, -}; +use crate::chain::cosmos::types::GasConfig; +use crate::chain::cosmos::{broadcast_tx_sync, calculate_fee, mul_ceil}; use crate::config::types::Memo; -use crate::config::AddressType; use crate::config::ChainConfig; use crate::error::Error; -use crate::keyring::{sign_message, KeyEntry}; +use crate::keyring::KeyEntry; use crate::sdk_error::sdk_error_from_tx_sync_error_code; -pub fn encode_key_bytes(key: &KeyEntry) -> Result, Error> { - let mut pk_buf = Vec::new(); - - prost::Message::encode(&key.public_key.public_key.to_bytes(), &mut pk_buf) - .map_err(|e| Error::protobuf_encode("PublicKey".into(), e))?; - - Ok(pk_buf) -} - -pub fn encode_sign_doc( - chain_id: &ChainId, - key: &KeyEntry, - address_type: &AddressType, - body_bytes: Vec, - auth_info_bytes: Vec, - account_number: u64, -) -> Result, Error> { - let sign_doc = SignDoc { - body_bytes, - auth_info_bytes, - chain_id: chain_id.to_string(), - account_number, - }; - - // A protobuf serialization of a SignDoc - let mut signdoc_buf = Vec::new(); - prost::Message::encode(&sign_doc, &mut signdoc_buf).unwrap(); - - let signed = sign_message(key, signdoc_buf, address_type).map_err(Error::key_base)?; - - Ok(signed) -} - -pub fn encode_signer_info( - key_bytes: Vec, - address_type: &AddressType, - sequence: u64, -) -> Result { - let pk_type = match address_type { - AddressType::Cosmos => "/cosmos.crypto.secp256k1.PubKey".to_string(), - AddressType::Ethermint { pk_type } => pk_type.clone(), - }; - // Create a MsgSend proto Any message - let pk_any = Any { - type_url: pk_type, - value: key_bytes, - }; - - let single = Single { mode: 1 }; - let sum_single = Some(Sum::Single(single)); - let mode = Some(ModeInfo { sum: sum_single }); - let signer_info = SignerInfo { - public_key: Some(pk_any), - mode_info: mode, - sequence, - }; - Ok(signer_info) -} +use super::encode::{encode_tx_raw, encode_tx_to_raw}; pub fn batch_messages( messages: Vec, @@ -381,49 +318,6 @@ pub fn sign_and_encode_tx( encode_tx_raw(tx_raw) } -pub fn encode_tx_raw(tx_raw: TxRaw) -> Result, Error> { - let mut tx_bytes = Vec::new(); - prost::Message::encode(&tx_raw, &mut tx_bytes) - .map_err(|e| Error::protobuf_encode("Transaction".to_string(), e))?; - - Ok(tx_bytes) -} - -pub fn encode_tx_to_raw( - config: &ChainConfig, - messages: Vec, - account_sequence: u64, - key_entry: &KeyEntry, - fee: &Fee, - tx_memo: &Memo, - account_number: u64, -) -> Result { - let key_bytes = encode_key_bytes(key_entry)?; - - let signer = encode_signer_info(key_bytes, &config.address_type, account_sequence)?; - - let (body, body_bytes) = tx_body_and_bytes(messages, tx_memo)?; - - let (auth_info, auth_info_bytes) = auth_info_and_bytes(signer, fee.clone())?; - - let signed_doc = encode_sign_doc( - &config.id, - key_entry, - &config.address_type, - body_bytes.clone(), - auth_info_bytes.clone(), - account_number, - )?; - - Ok(SignedTx { - body, - body_bytes, - auth_info, - auth_info_bytes, - signatures: vec![signed_doc], - }) -} - pub async fn send_tx_simulate(tx: Tx, grpc_address: &Uri) -> Result { use ibc_proto::cosmos::tx::v1beta1::service_client::ServiceClient; From 517432f9f6ba41558829ff42cf150aacf524043f Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Fri, 18 Mar 2022 12:55:55 +0100 Subject: [PATCH 03/30] More code reorganization --- relayer/src/chain/cosmos.rs | 37 ++-- relayer/src/chain/cosmos/batch.rs | 43 +++++ relayer/src/chain/cosmos/encode.rs | 28 +++ relayer/src/chain/cosmos/estimate.rs | 149 +++++++++++++++ relayer/src/chain/cosmos/gas.rs | 55 ++++++ relayer/src/chain/cosmos/simulate.rs | 31 +++ relayer/src/chain/cosmos/tx.rs | 271 +-------------------------- 7 files changed, 322 insertions(+), 292 deletions(-) create mode 100644 relayer/src/chain/cosmos/batch.rs create mode 100644 relayer/src/chain/cosmos/estimate.rs create mode 100644 relayer/src/chain/cosmos/gas.rs create mode 100644 relayer/src/chain/cosmos/simulate.rs diff --git a/relayer/src/chain/cosmos.rs b/relayer/src/chain/cosmos.rs index 7e8f210e4e..d28032228d 100644 --- a/relayer/src/chain/cosmos.rs +++ b/relayer/src/chain/cosmos.rs @@ -7,7 +7,6 @@ use core::{ time::Duration, }; use num_bigint::BigInt; -use num_rational::BigRational; use std::{fmt, thread, time::Instant}; use bech32::{ToBase32, Variant}; @@ -53,6 +52,10 @@ use ibc::core::ics04_channel::packet::{Packet, PacketMsgType, Sequence}; use ibc::core::ics23_commitment::commitment::CommitmentPrefix; use ibc::core::ics23_commitment::merkle::convert_tm_to_ics_merkle_proof; use ibc::core::ics24_host::identifier::{ChainId, ChannelId, ClientId, ConnectionId, PortId}; +use ibc::core::ics24_host::path::{ + AcksPath, ChannelEndsPath, ClientConsensusStatePath, ClientStatePath, CommitmentsPath, + ConnectionsPath, ReceiptsPath, SeqRecvsPath, +}; use ibc::core::ics24_host::{ClientUpgradePath, Path, IBC_QUERY_PATH, SDK_UPGRADE_QUERY_PATH}; use ibc::events::{from_tx_response_event, IbcEvent}; use ibc::query::QueryBlockRequest; @@ -81,6 +84,9 @@ use ibc_proto::ibc::core::connection::v1::{ QueryClientConnectionsRequest, QueryConnectionsRequest, }; +use crate::chain::cosmos::gas::{calculate_fee, mul_ceil}; +use crate::chain::tx::TrackedMsgs; +use crate::chain::{ChainEndpoint, HealthCheck}; use crate::chain::{QueryResponse, StatusResponse}; use crate::config::types::Memo; use crate::config::{AddressType, ChainConfig, GasPrice}; @@ -92,15 +98,12 @@ use crate::light_client::{LightClient, Verified}; use crate::sdk_error::sdk_error_from_tx_sync_error_code; use crate::util::retry::{retry_with_index, RetryResult}; -use ibc::core::ics24_host::path::{ - AcksPath, ChannelEndsPath, ClientConsensusStatePath, ClientStatePath, CommitmentsPath, - ConnectionsPath, ReceiptsPath, SeqRecvsPath, -}; - -use super::{tx::TrackedMsgs, ChainEndpoint, HealthCheck}; - +pub mod batch; mod compatibility; pub mod encode; +pub mod estimate; +pub mod gas; +pub mod simulate; pub mod tx; pub mod types; pub mod version; @@ -2567,24 +2570,6 @@ impl fmt::Display for PrettyFee<'_> { } } -fn calculate_fee(adjusted_gas_amount: u64, gas_price: &GasPrice) -> Coin { - let fee_amount = mul_ceil(adjusted_gas_amount, gas_price.price); - - Coin { - denom: gas_price.denom.to_string(), - amount: fee_amount.to_string(), - } -} - -/// Multiply `a` with `f` and round the result up to the nearest integer. -fn mul_ceil(a: u64, f: f64) -> BigInt { - assert!(f.is_finite()); - - let a = BigInt::from(a); - let f = BigRational::from_float(f).expect("f is finite"); - (f * a).ceil().to_integer() -} - /// Compute the `max_clock_drift` for a (new) client state /// as a function of the configuration of the source chain /// and the destination chain configuration. diff --git a/relayer/src/chain/cosmos/batch.rs b/relayer/src/chain/cosmos/batch.rs new file mode 100644 index 0000000000..04a1356e72 --- /dev/null +++ b/relayer/src/chain/cosmos/batch.rs @@ -0,0 +1,43 @@ +use prost_types::Any; + +use crate::error::Error; + +pub fn batch_messages( + messages: Vec, + max_message_count: usize, + max_tx_size: usize, +) -> Result>, Error> { + let mut batches = vec![]; + + let mut current_count = 0; + let mut current_size = 0; + let mut current_batch = vec![]; + + for message in messages.into_iter() { + current_count += 1; + current_size += message_size(&message)?; + current_batch.push(message); + + if current_count >= max_message_count || current_size >= max_tx_size { + let insert_batch = current_batch.drain(..).collect(); + batches.push(insert_batch); + current_count = 0; + current_size = 0; + } + } + + if !current_batch.is_empty() { + batches.push(current_batch); + } + + Ok(batches) +} + +pub fn message_size(message: &Any) -> Result { + let mut buf = Vec::new(); + + prost::Message::encode(message, &mut buf) + .map_err(|e| Error::protobuf_encode("Message".into(), e))?; + + Ok(buf.len()) +} diff --git a/relayer/src/chain/cosmos/encode.rs b/relayer/src/chain/cosmos/encode.rs index 646dadefd6..34ca122ff1 100644 --- a/relayer/src/chain/cosmos/encode.rs +++ b/relayer/src/chain/cosmos/encode.rs @@ -11,6 +11,34 @@ use crate::config::ChainConfig; use crate::error::Error; use crate::keyring::{sign_message, KeyEntry}; +pub fn sign_and_encode_tx( + config: &ChainConfig, + messages: Vec, + account_sequence: u64, + key_entry: &KeyEntry, + fee: &Fee, + tx_memo: &Memo, + account_number: u64, +) -> Result, Error> { + let signed_tx = encode_tx_to_raw( + config, + messages, + account_sequence, + key_entry, + fee, + tx_memo, + account_number, + )?; + + let tx_raw = TxRaw { + body_bytes: signed_tx.body_bytes, + auth_info_bytes: signed_tx.auth_info_bytes, + signatures: signed_tx.signatures, + }; + + encode_tx_raw(tx_raw) +} + pub fn encode_key_bytes(key: &KeyEntry) -> Result, Error> { let mut pk_buf = Vec::new(); diff --git a/relayer/src/chain/cosmos/estimate.rs b/relayer/src/chain/cosmos/estimate.rs new file mode 100644 index 0000000000..200396f3f2 --- /dev/null +++ b/relayer/src/chain/cosmos/estimate.rs @@ -0,0 +1,149 @@ +use ibc::core::ics24_host::identifier::ChainId; +use ibc_proto::cosmos::tx::v1beta1::{Fee, Tx}; +use prost_types::Any; +use tonic::codegen::http::Uri; +use tracing::{debug, error}; + +use crate::chain::cosmos::encode::encode_tx_to_raw; +use crate::chain::cosmos::gas::gas_amount_to_fees; +use crate::chain::cosmos::simulate::send_tx_simulate; +use crate::chain::cosmos::types::GasConfig; +use crate::config::types::Memo; +use crate::config::ChainConfig; +use crate::error::Error; +use crate::keyring::KeyEntry; + +pub async fn estimate_gas( + chain_id: &ChainId, + tx: Tx, + grpc_address: &Uri, + default_gas: u64, + max_gas: u64, +) -> Result { + let response = send_tx_simulate(tx, grpc_address).await; + + let estimated_gas = match response { + Ok(response) => { + let m_gas_info = response.gas_info; + + debug!( + "[{}] send_tx: tx simulation successful, simulated gas: {:?}", + chain_id, m_gas_info, + ); + + match m_gas_info { + Some(gas) => gas.gas_used, + None => default_gas, + } + } + Err(e) => { + error!( + "[{}] send_tx: failed to estimate gas, falling back on default gas, error: {}", + chain_id, + e.detail() + ); + + default_gas + } + }; + + if estimated_gas > max_gas { + debug!( + estimated = ?estimated_gas, + max = ?max_gas, + "[{}] send_tx: estimated gas is higher than max gas", + chain_id, + ); + + Err(Error::tx_simulate_gas_estimate_exceeded( + chain_id.clone(), + estimated_gas, + max_gas, + )) + } else { + Ok(estimated_gas) + } +} + +pub async fn estimate_tx_fees( + config: &ChainConfig, + grpc_address: &Uri, + account_sequence: u64, + account_number: u64, + messages: Vec, + key_entry: &KeyEntry, + tx_memo: &Memo, +) -> Result { + let gas_config = GasConfig::from_chain_config(config); + + let signed_tx = encode_tx_to_raw( + config, + messages, + account_sequence, + key_entry, + &gas_config.max_fee, + tx_memo, + account_number, + )?; + + let tx = Tx { + body: Some(signed_tx.body), + auth_info: Some(signed_tx.auth_info), + signatures: signed_tx.signatures, + }; + + let estimated_fee = estimate_gas_with_raw_tx(&gas_config, &config.id, grpc_address, tx).await?; + + Ok(estimated_fee) +} + +pub async fn estimate_gas_with_raw_tx( + gas_config: &GasConfig, + chain_id: &ChainId, + grpc_address: &Uri, + tx: Tx, +) -> Result { + let response = send_tx_simulate(tx, grpc_address).await; + + let estimated_gas = match response { + Ok(response) => { + let m_gas_info = response.gas_info; + + debug!( + "[{}] send_tx: tx simulation successful, simulated gas: {:?}", + chain_id, m_gas_info, + ); + + match m_gas_info { + Some(gas) => gas.gas_used, + None => gas_config.default_gas, + } + } + Err(e) => { + error!( + "[{}] send_tx: failed to estimate gas, falling back on default gas, error: {}", + chain_id, + e.detail() + ); + + gas_config.default_gas + } + }; + + if estimated_gas > gas_config.max_gas { + debug!( + estimated = ?estimated_gas, + max = ?gas_config.max_gas, + "[{}] send_tx: estimated gas is higher than max gas", + chain_id, + ); + + Err(Error::tx_simulate_gas_estimate_exceeded( + chain_id.clone(), + estimated_gas, + gas_config.max_gas, + )) + } else { + Ok(gas_amount_to_fees(gas_config, estimated_gas)) + } +} diff --git a/relayer/src/chain/cosmos/gas.rs b/relayer/src/chain/cosmos/gas.rs new file mode 100644 index 0000000000..1ff2d0d773 --- /dev/null +++ b/relayer/src/chain/cosmos/gas.rs @@ -0,0 +1,55 @@ +use core::cmp::min; +use ibc_proto::cosmos::base::v1beta1::Coin; +use ibc_proto::cosmos::tx::v1beta1::Fee; +use num_bigint::BigInt; +use num_rational::BigRational; + +use crate::chain::cosmos::types::GasConfig; +use crate::config::GasPrice; + +pub fn gas_amount_to_fees(config: &GasConfig, gas_amount: u64) -> Fee { + let gas_limit = adjust_gas_with_simulated_fees(config, gas_amount); + + let amount = calculate_fee(gas_limit, &config.gas_price); + + Fee { + amount: vec![amount], + gas_limit, + payer: "".to_string(), + granter: "".to_string(), + } +} + +/// Adjusts the fee based on the configured `gas_adjustment` to prevent out of gas errors. +/// The actual gas cost, when a transaction is executed, may be slightly higher than the +/// one returned by the simulation. +pub fn adjust_gas_with_simulated_fees(config: &GasConfig, gas_amount: u64) -> u64 { + let gas_adjustment = config.gas_adjustment; + let max_gas = config.max_gas; + + let (_, digits) = mul_ceil(gas_amount, gas_adjustment).to_u64_digits(); + assert!(digits.len() == 1); + + let adjustment = digits[0]; + let gas = gas_amount.checked_add(adjustment).unwrap_or(u64::MAX); + + min(gas, max_gas) +} + +pub fn calculate_fee(adjusted_gas_amount: u64, gas_price: &GasPrice) -> Coin { + let fee_amount = mul_ceil(adjusted_gas_amount, gas_price.price); + + Coin { + denom: gas_price.denom.to_string(), + amount: fee_amount.to_string(), + } +} + +/// Multiply `a` with `f` and round the result up to the nearest integer. +pub fn mul_ceil(a: u64, f: f64) -> BigInt { + assert!(f.is_finite()); + + let a = BigInt::from(a); + let f = BigRational::from_float(f).expect("f is finite"); + (f * a).ceil().to_integer() +} diff --git a/relayer/src/chain/cosmos/simulate.rs b/relayer/src/chain/cosmos/simulate.rs new file mode 100644 index 0000000000..f00452e938 --- /dev/null +++ b/relayer/src/chain/cosmos/simulate.rs @@ -0,0 +1,31 @@ +use ibc_proto::cosmos::tx::v1beta1::{SimulateRequest, SimulateResponse, Tx}; +use tonic::codegen::http::Uri; + +use crate::error::Error; + +pub async fn send_tx_simulate(tx: Tx, grpc_address: &Uri) -> Result { + use ibc_proto::cosmos::tx::v1beta1::service_client::ServiceClient; + + // The `tx` field of `SimulateRequest` was deprecated in Cosmos SDK 0.43 in favor of `tx_bytes`. + let mut tx_bytes = vec![]; + prost::Message::encode(&tx, &mut tx_bytes).unwrap(); // FIXME: Handle error here + + #[allow(deprecated)] + let req = SimulateRequest { + tx: Some(tx), // needed for simulation to go through with Cosmos SDK < 0.43 + tx_bytes, // needed for simulation to go through with Cosmos SDk >= 0.43 + }; + + let mut client = ServiceClient::connect(grpc_address.clone()) + .await + .map_err(Error::grpc_transport)?; + + let request = tonic::Request::new(req); + let response = client + .simulate(request) + .await + .map_err(Error::grpc_status)? + .into_inner(); + + Ok(response) +} diff --git a/relayer/src/chain/cosmos/tx.rs b/relayer/src/chain/cosmos/tx.rs index 5627ed1eac..eb1a13e1b9 100644 --- a/relayer/src/chain/cosmos/tx.rs +++ b/relayer/src/chain/cosmos/tx.rs @@ -1,114 +1,20 @@ -use core::cmp::min; -use ibc::core::ics24_host::identifier::ChainId; -use ibc_proto::cosmos::tx::v1beta1::{Fee, SimulateRequest, SimulateResponse, Tx, TxRaw}; +use ibc_proto::cosmos::tx::v1beta1::Fee; use prost_types::Any; use tendermint_rpc::endpoint::broadcast::tx_sync::Response; use tendermint_rpc::{HttpClient, Url}; use tonic::codegen::http::Uri; use tracing::{debug, error}; -use crate::chain::cosmos::types::GasConfig; -use crate::chain::cosmos::{broadcast_tx_sync, calculate_fee, mul_ceil}; +use crate::chain::cosmos::batch::batch_messages; +use crate::chain::cosmos::broadcast_tx_sync; +use crate::chain::cosmos::encode::sign_and_encode_tx; +use crate::chain::cosmos::estimate::estimate_tx_fees; 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; -use super::encode::{encode_tx_raw, encode_tx_to_raw}; - -pub fn batch_messages( - messages: Vec, - max_message_count: usize, - max_tx_size: usize, -) -> Result>, Error> { - let mut batches = vec![]; - - let mut current_count = 0; - let mut current_size = 0; - let mut current_batch = vec![]; - - for message in messages.into_iter() { - current_count += 1; - current_size += message_size(&message)?; - current_batch.push(message); - - if current_count >= max_message_count || current_size >= max_tx_size { - let insert_batch = current_batch.drain(..).collect(); - batches.push(insert_batch); - current_count = 0; - current_size = 0; - } - } - - if !current_batch.is_empty() { - batches.push(current_batch); - } - - Ok(batches) -} - -pub fn message_size(message: &Any) -> Result { - let mut buf = Vec::new(); - - prost::Message::encode(message, &mut buf) - .map_err(|e| Error::protobuf_encode("Message".into(), e))?; - - Ok(buf.len()) -} - -pub async fn estimate_gas( - chain_id: &ChainId, - tx: Tx, - grpc_address: &Uri, - default_gas: u64, - max_gas: u64, -) -> Result { - let response = send_tx_simulate(tx, grpc_address).await; - - let estimated_gas = match response { - Ok(response) => { - let m_gas_info = response.gas_info; - - debug!( - "[{}] send_tx: tx simulation successful, simulated gas: {:?}", - chain_id, m_gas_info, - ); - - match m_gas_info { - Some(gas) => gas.gas_used, - None => default_gas, - } - } - Err(e) => { - error!( - "[{}] send_tx: failed to estimate gas, falling back on default gas, error: {}", - chain_id, - e.detail() - ); - - default_gas - } - }; - - if estimated_gas > max_gas { - debug!( - estimated = ?estimated_gas, - max = ?max_gas, - "[{}] send_tx: estimated gas is higher than max gas", - chain_id, - ); - - Err(Error::tx_simulate_gas_estimate_exceeded( - chain_id.clone(), - estimated_gas, - max_gas, - )) - } else { - Ok(estimated_gas) - } -} - pub async fn send_messages_as_batches( config: &ChainConfig, rpc_client: &HttpClient, @@ -257,170 +163,3 @@ pub async fn raw_send_tx( Ok(response) } - -pub async fn estimate_tx_fees( - config: &ChainConfig, - grpc_address: &Uri, - account_sequence: u64, - account_number: u64, - messages: Vec, - key_entry: &KeyEntry, - tx_memo: &Memo, -) -> Result { - let gas_config = GasConfig::from_chain_config(config); - - let signed_tx = encode_tx_to_raw( - config, - messages, - account_sequence, - key_entry, - &gas_config.max_fee, - tx_memo, - account_number, - )?; - - let tx = Tx { - body: Some(signed_tx.body), - auth_info: Some(signed_tx.auth_info), - signatures: signed_tx.signatures, - }; - - let estimated_fee = estimate_gas_with_raw_tx(&gas_config, &config.id, grpc_address, tx).await?; - - Ok(estimated_fee) -} - -pub fn sign_and_encode_tx( - config: &ChainConfig, - messages: Vec, - account_sequence: u64, - key_entry: &KeyEntry, - fee: &Fee, - tx_memo: &Memo, - account_number: u64, -) -> Result, Error> { - let signed_tx = encode_tx_to_raw( - config, - messages, - account_sequence, - key_entry, - fee, - tx_memo, - account_number, - )?; - - let tx_raw = TxRaw { - body_bytes: signed_tx.body_bytes, - auth_info_bytes: signed_tx.auth_info_bytes, - signatures: signed_tx.signatures, - }; - - encode_tx_raw(tx_raw) -} - -pub async fn send_tx_simulate(tx: Tx, grpc_address: &Uri) -> Result { - use ibc_proto::cosmos::tx::v1beta1::service_client::ServiceClient; - - // The `tx` field of `SimulateRequest` was deprecated in Cosmos SDK 0.43 in favor of `tx_bytes`. - let mut tx_bytes = vec![]; - prost::Message::encode(&tx, &mut tx_bytes).unwrap(); // FIXME: Handle error here - - #[allow(deprecated)] - let req = SimulateRequest { - tx: Some(tx), // needed for simulation to go through with Cosmos SDK < 0.43 - tx_bytes, // needed for simulation to go through with Cosmos SDk >= 0.43 - }; - - let mut client = ServiceClient::connect(grpc_address.clone()) - .await - .map_err(Error::grpc_transport)?; - - let request = tonic::Request::new(req); - let response = client - .simulate(request) - .await - .map_err(Error::grpc_status)? - .into_inner(); - - Ok(response) -} - -pub async fn estimate_gas_with_raw_tx( - gas_config: &GasConfig, - chain_id: &ChainId, - grpc_address: &Uri, - tx: Tx, -) -> Result { - let response = send_tx_simulate(tx, grpc_address).await; - - let estimated_gas = match response { - Ok(response) => { - let m_gas_info = response.gas_info; - - debug!( - "[{}] send_tx: tx simulation successful, simulated gas: {:?}", - chain_id, m_gas_info, - ); - - match m_gas_info { - Some(gas) => gas.gas_used, - None => gas_config.default_gas, - } - } - Err(e) => { - error!( - "[{}] send_tx: failed to estimate gas, falling back on default gas, error: {}", - chain_id, - e.detail() - ); - - gas_config.default_gas - } - }; - - if estimated_gas > gas_config.max_gas { - debug!( - estimated = ?estimated_gas, - max = ?gas_config.max_gas, - "[{}] send_tx: estimated gas is higher than max gas", - chain_id, - ); - - Err(Error::tx_simulate_gas_estimate_exceeded( - chain_id.clone(), - estimated_gas, - gas_config.max_gas, - )) - } else { - Ok(gas_amount_to_fees(gas_config, estimated_gas)) - } -} - -pub fn gas_amount_to_fees(config: &GasConfig, gas_amount: u64) -> Fee { - let gas_limit = adjust_gas_with_simulated_fees(config, gas_amount); - - let amount = calculate_fee(gas_limit, &config.gas_price); - - Fee { - amount: vec![amount], - gas_limit, - payer: "".to_string(), - granter: "".to_string(), - } -} - -/// Adjusts the fee based on the configured `gas_adjustment` to prevent out of gas errors. -/// The actual gas cost, when a transaction is executed, may be slightly higher than the -/// one returned by the simulation. -pub fn adjust_gas_with_simulated_fees(config: &GasConfig, gas_amount: u64) -> u64 { - let gas_adjustment = config.gas_adjustment; - let max_gas = config.max_gas; - - let (_, digits) = mul_ceil(gas_amount, gas_adjustment).to_u64_digits(); - assert!(digits.len() == 1); - - let adjustment = digits[0]; - let gas = gas_amount.checked_add(adjustment).unwrap_or(u64::MAX); - - min(gas, max_gas) -} From 8d4d371b190187102e9a031704447a3effe91ad1 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Fri, 18 Mar 2022 17:12:24 +0100 Subject: [PATCH 04/30] Split out functions from main cosmos.rs --- relayer/src/chain/cosmos.rs | 264 +++-------------------------- relayer/src/chain/cosmos/encode.rs | 53 +++++- relayer/src/chain/cosmos/query.rs | 182 ++++++++++++++++++++ relayer/src/chain/cosmos/tx.rs | 17 +- 4 files changed, 274 insertions(+), 242 deletions(-) create mode 100644 relayer/src/chain/cosmos/query.rs diff --git a/relayer/src/chain/cosmos.rs b/relayer/src/chain/cosmos.rs index d28032228d..8606770106 100644 --- a/relayer/src/chain/cosmos.rs +++ b/relayer/src/chain/cosmos.rs @@ -9,12 +9,9 @@ use core::{ use num_bigint::BigInt; use std::{fmt, thread, time::Instant}; -use bech32::{ToBase32, Variant}; use bitcoin::hashes::hex::ToHex; use itertools::Itertools; -use prost::Message; use prost_types::Any; -use tendermint::account::Id as AccountId; use tendermint::block::Height; use tendermint::consensus::Params as ConsensusParams; use tendermint::{ @@ -24,8 +21,6 @@ use tendermint::{ use tendermint_light_client_verifier::types::LightBlock as TMLightBlock; use tendermint_proto::Protobuf; use tendermint_rpc::endpoint::tx::Response as ResultTx; -use tendermint_rpc::query::Query; -use tendermint_rpc::Url; use tendermint_rpc::{ endpoint::broadcast::tx_sync::Response, endpoint::status, Client, HttpClient, Order, }; @@ -50,7 +45,6 @@ use ibc::core::ics04_channel::channel::{ use ibc::core::ics04_channel::events as ChannelEvents; use ibc::core::ics04_channel::packet::{Packet, PacketMsgType, Sequence}; use ibc::core::ics23_commitment::commitment::CommitmentPrefix; -use ibc::core::ics23_commitment::merkle::convert_tm_to_ics_merkle_proof; use ibc::core::ics24_host::identifier::{ChainId, ChannelId, ClientId, ConnectionId, PortId}; use ibc::core::ics24_host::path::{ AcksPath, ChannelEndsPath, ClientConsensusStatePath, ClientStatePath, CommitmentsPath, @@ -62,15 +56,12 @@ use ibc::query::QueryBlockRequest; use ibc::query::{QueryTxHash, QueryTxRequest}; use ibc::signer::Signer; use ibc::Height as ICSHeight; -use ibc_proto::cosmos::auth::v1beta1::{BaseAccount, EthAccount, QueryAccountRequest}; -use ibc_proto::cosmos::base::tendermint::v1beta1::service_client::ServiceClient; -use ibc_proto::cosmos::base::tendermint::v1beta1::GetNodeInfoRequest; +use ibc_proto::cosmos::auth::v1beta1::BaseAccount; use ibc_proto::cosmos::base::v1beta1::Coin; use ibc_proto::cosmos::staking::v1beta1::Params as StakingParams; use ibc_proto::cosmos::tx::v1beta1::mode_info::{Single, Sum}; use ibc_proto::cosmos::tx::v1beta1::{ - AuthInfo, Fee, ModeInfo, SignDoc, SignerInfo, SimulateRequest, SimulateResponse, Tx, TxBody, - TxRaw, + Fee, ModeInfo, SignDoc, SignerInfo, SimulateRequest, SimulateResponse, Tx, TxRaw, }; use ibc_proto::ibc::core::channel::v1::{ PacketState, QueryChannelClientStateRequest, QueryChannelsRequest, @@ -84,7 +75,12 @@ use ibc_proto::ibc::core::connection::v1::{ QueryClientConnectionsRequest, QueryConnectionsRequest, }; +use crate::chain::cosmos::encode::{auth_info_and_bytes, encode_to_bech32, tx_body_and_bytes}; use crate::chain::cosmos::gas::{calculate_fee, mul_ceil}; +use crate::chain::cosmos::query::{ + abci_query, fetch_version_specs, header_query, packet_query, query_account, tx_hash_query, +}; +use crate::chain::cosmos::tx::broadcast_tx_sync; use crate::chain::tx::TrackedMsgs; use crate::chain::{ChainEndpoint, HealthCheck}; use crate::chain::{QueryResponse, StatusResponse}; @@ -99,10 +95,11 @@ use crate::sdk_error::sdk_error_from_tx_sync_error_code; use crate::util::retry::{retry_with_index, RetryResult}; pub mod batch; -mod compatibility; +pub mod compatibility; pub mod encode; pub mod estimate; pub mod gas; +pub mod query; pub mod simulate; pub mod tx; pub mod types; @@ -634,7 +631,14 @@ impl CosmosSdkChain { return Err(Error::private_store()); } - let response = self.block_on(abci_query(self, path, data.to_string(), height, prove))?; + let response = self.block_on(abci_query( + &self.rpc_client, + &self.config.rpc_addr, + path, + data.to_string(), + height, + prove, + ))?; // TODO - Verify response proof, if requested. if prove {} @@ -659,7 +663,8 @@ impl CosmosSdkChain { let path = TendermintABCIPath::from_str(SDK_UPGRADE_QUERY_PATH) .expect("Turning SDK upgrade query path constant into a Tendermint ABCI path"); let response: QueryResponse = self.block_on(abci_query( - self, + &self.rpc_client, + &self.config.rpc_addr, path, Path::Upgrade(data).to_string(), prev_height, @@ -719,8 +724,14 @@ impl CosmosSdkChain { Ok((key, key_bytes)) } + fn query_account(&self) -> Result { + crate::telemetry!(query, self.id(), "query_account"); + + self.block_on(query_account(self.grpc_addr.clone(), self.key()?.account)) + } + fn refresh_account(&mut self) -> Result<(), Error> { - let account = self.block_on(query_account(self, self.key()?.account))?; + let account = self.query_account()?; info!( sequence = %account.sequence, number = %account.account_number, @@ -2130,47 +2141,6 @@ impl ChainEndpoint for CosmosSdkChain { } } -fn packet_query(request: &QueryPacketEventDataRequest, seq: Sequence) -> Query { - tendermint_rpc::query::Query::eq( - format!("{}.packet_src_channel", request.event_id.as_str()), - request.source_channel_id.to_string(), - ) - .and_eq( - format!("{}.packet_src_port", request.event_id.as_str()), - request.source_port_id.to_string(), - ) - .and_eq( - format!("{}.packet_dst_channel", request.event_id.as_str()), - request.destination_channel_id.to_string(), - ) - .and_eq( - format!("{}.packet_dst_port", request.event_id.as_str()), - request.destination_port_id.to_string(), - ) - .and_eq( - format!("{}.packet_sequence", request.event_id.as_str()), - seq.to_string(), - ) -} - -fn header_query(request: &QueryClientEventRequest) -> Query { - tendermint_rpc::query::Query::eq( - format!("{}.client_id", request.event_id.as_str()), - request.client_id.to_string(), - ) - .and_eq( - format!("{}.consensus_height", request.event_id.as_str()), - format!( - "{}-{}", - request.consensus_height.revision_number, request.consensus_height.revision_height - ), - ) -} - -fn tx_hash_query(request: &QueryTxHash) -> Query { - tendermint_rpc::query::Query::eq("tx.hash", request.0.to_string()) -} - // Extract the packet events from the query_txs RPC response. For any given // packet query, there is at most one Tx matching such query. Moreover, a Tx may // contain several events, but a single one must match the packet query. @@ -2288,111 +2258,6 @@ fn filter_matching_event( } } -/// Perform a generic `abci_query`, and return the corresponding deserialized response data. -async fn abci_query( - chain: &CosmosSdkChain, - path: TendermintABCIPath, - data: String, - height: Height, - prove: bool, -) -> Result { - let height = if height.value() == 0 { - None - } else { - Some(height) - }; - - // Use the Tendermint-rs RPC client to do the query. - let response = chain - .rpc_client - .abci_query(Some(path), data.into_bytes(), height, prove) - .await - .map_err(|e| Error::rpc(chain.config.rpc_addr.clone(), e))?; - - if !response.code.is_ok() { - // Fail with response log. - return Err(Error::abci_query(response)); - } - - if prove && response.proof.is_none() { - // Fail due to empty proof - return Err(Error::empty_response_proof()); - } - - let proof = response - .proof - .map(|p| convert_tm_to_ics_merkle_proof(&p)) - .transpose() - .map_err(Error::ics23)?; - - let response = QueryResponse { - value: response.value, - height: response.height, - proof, - }; - - Ok(response) -} - -/// Perform a `broadcast_tx_sync`, and return the corresponding deserialized response data. -pub async fn broadcast_tx_sync( - rpc_client: &HttpClient, - rpc_address: &Url, - data: Vec, -) -> Result { - let response = rpc_client - .broadcast_tx_sync(data.into()) - .await - .map_err(|e| Error::rpc(rpc_address.clone(), e))?; - - Ok(response) -} - -/// Uses the GRPC client to retrieve the account sequence -async fn query_account(chain: &CosmosSdkChain, address: String) -> Result { - crate::telemetry!(query, chain.id(), "query_account"); - - let mut client = ibc_proto::cosmos::auth::v1beta1::query_client::QueryClient::connect( - chain.grpc_addr.clone(), - ) - .await - .map_err(Error::grpc_transport)?; - - let request = tonic::Request::new(QueryAccountRequest { - address: address.clone(), - }); - - let response = client.account(request).await; - - // Querying for an account might fail, i.e. if the account doesn't actually exist - let resp_account = match response.map_err(Error::grpc_status)?.into_inner().account { - Some(account) => account, - None => return Err(Error::empty_query_account(address)), - }; - - if resp_account.type_url == "/cosmos.auth.v1beta1.BaseAccount" { - Ok(BaseAccount::decode(resp_account.value.as_slice()) - .map_err(|e| Error::protobuf_decode("BaseAccount".to_string(), e))?) - } else if resp_account.type_url.ends_with(".EthAccount") { - Ok(EthAccount::decode(resp_account.value.as_slice()) - .map_err(|e| Error::protobuf_decode("EthAccount".to_string(), e))? - .base_account - .ok_or_else(Error::empty_base_account)?) - } else { - Err(Error::unknown_account_type(resp_account.type_url)) - } -} - -fn encode_to_bech32(address: &str, account_prefix: &str) -> Result { - let account = AccountId::from_str(address) - .map_err(|e| Error::invalid_key_address(address.to_string(), e))?; - - let encoded = bech32::encode(account_prefix, account.to_base32(), Variant::Bech32) - .map_err(Error::bech32_encoding)?; - - Ok(encoded) -} - /// Returns the suffix counter for a CosmosSDK client id. /// Returns `None` if the client identifier is malformed /// and the suffix could not be parsed. @@ -2411,39 +2276,6 @@ pub struct TxSyncResult { events: Vec, } -pub fn auth_info_and_bytes( - signer_info: SignerInfo, - fee: Fee, -) -> Result<(AuthInfo, Vec), Error> { - let auth_info = AuthInfo { - signer_infos: vec![signer_info], - fee: Some(fee), - }; - - // A protobuf serialization of a AuthInfo - let mut auth_buf = Vec::new(); - prost::Message::encode(&auth_info, &mut auth_buf) - .map_err(|e| Error::protobuf_encode(String::from("AuthInfo"), e))?; - Ok((auth_info, auth_buf)) -} - -pub fn tx_body_and_bytes(proto_msgs: Vec, memo: &Memo) -> Result<(TxBody, Vec), Error> { - // Create TxBody - let body = TxBody { - messages: proto_msgs.to_vec(), - memo: memo.to_string(), - timeout_height: 0_u64, - extension_options: Vec::::new(), - non_critical_extension_options: Vec::::new(), - }; - - // A protobuf serialization of a TxBody - let mut body_buf = Vec::new(); - prost::Message::encode(&body, &mut body_buf) - .map_err(|e| Error::protobuf_encode(String::from("TxBody"), e))?; - Ok((body, body_buf)) -} - fn do_health_check(chain: &CosmosSdkChain) -> Result<(), Error> { let chain_id = chain.id(); let grpc_address = chain.grpc_addr.to_string(); @@ -2487,50 +2319,6 @@ fn do_health_check(chain: &CosmosSdkChain) -> Result<(), Error> { Ok(()) } -/// Queries the chain to obtain the version information. -async fn fetch_version_specs( - chain_id: &ChainId, - grpc_address: &Uri, -) -> Result { - let grpc_addr_string = grpc_address.to_string(); - - // Construct a gRPC client - let mut client = ServiceClient::connect(grpc_address.clone()) - .await - .map_err(|e| { - Error::fetch_version_grpc_transport( - chain_id.clone(), - grpc_addr_string.clone(), - "tendermint::ServiceClient".to_string(), - e, - ) - })?; - - let request = tonic::Request::new(GetNodeInfoRequest {}); - - let response = client.get_node_info(request).await.map_err(|e| { - Error::fetch_version_grpc_status( - chain_id.clone(), - grpc_addr_string.clone(), - "tendermint::ServiceClient".to_string(), - e, - ) - })?; - - let version = response.into_inner().application_version.ok_or_else(|| { - Error::fetch_version_invalid_version_response( - chain_id.clone(), - grpc_addr_string.clone(), - "tendermint::GetNodeInfoRequest".to_string(), - ) - })?; - - // Parse the raw version info into a domain-type `version::Specs` - version - .try_into() - .map_err(|e| Error::fetch_version_parsing(chain_id.clone(), grpc_addr_string.clone(), e)) -} - /// Determine whether the given error yielded by `tx_simulate` /// can be recovered from by submitting the tx anyway. fn can_recover_from_simulation_failure(e: &Error) -> bool { diff --git a/relayer/src/chain/cosmos/encode.rs b/relayer/src/chain/cosmos/encode.rs index 34ca122ff1..e7abc57f7b 100644 --- a/relayer/src/chain/cosmos/encode.rs +++ b/relayer/src/chain/cosmos/encode.rs @@ -1,10 +1,12 @@ +use bech32::{ToBase32, Variant}; +use core::str::FromStr; use ibc::core::ics24_host::identifier::ChainId; use ibc_proto::cosmos::tx::v1beta1::mode_info::{Single, Sum}; -use ibc_proto::cosmos::tx::v1beta1::{Fee, ModeInfo, SignDoc, SignerInfo, TxRaw}; +use ibc_proto::cosmos::tx::v1beta1::{AuthInfo, Fee, ModeInfo, SignDoc, SignerInfo, TxBody, TxRaw}; use prost_types::Any; +use tendermint::account::Id as AccountId; use crate::chain::cosmos::types::SignedTx; -use crate::chain::cosmos::{auth_info_and_bytes, tx_body_and_bytes}; use crate::config::types::Memo; use crate::config::AddressType; use crate::config::ChainConfig; @@ -140,3 +142,50 @@ pub fn encode_tx_to_raw( signatures: vec![signed_doc], }) } + +pub fn encode_to_bech32(address: &str, account_prefix: &str) -> Result { + let account = AccountId::from_str(address) + .map_err(|e| Error::invalid_key_address(address.to_string(), e))?; + + let encoded = bech32::encode(account_prefix, account.to_base32(), Variant::Bech32) + .map_err(Error::bech32_encoding)?; + + Ok(encoded) +} + +pub fn auth_info_and_bytes( + signer_info: SignerInfo, + fee: Fee, +) -> Result<(AuthInfo, Vec), Error> { + let auth_info = AuthInfo { + signer_infos: vec![signer_info], + fee: Some(fee), + }; + + // A protobuf serialization of a AuthInfo + let mut auth_buf = Vec::new(); + + prost::Message::encode(&auth_info, &mut auth_buf) + .map_err(|e| Error::protobuf_encode(String::from("AuthInfo"), e))?; + + Ok((auth_info, auth_buf)) +} + +pub fn tx_body_and_bytes(proto_msgs: Vec, memo: &Memo) -> Result<(TxBody, Vec), Error> { + // Create TxBody + let body = TxBody { + messages: proto_msgs.to_vec(), + memo: memo.to_string(), + timeout_height: 0_u64, + extension_options: Vec::::new(), + non_critical_extension_options: Vec::::new(), + }; + + // A protobuf serialization of a TxBody + let mut body_buf = Vec::new(); + + prost::Message::encode(&body, &mut body_buf) + .map_err(|e| Error::protobuf_encode(String::from("TxBody"), e))?; + + Ok((body, body_buf)) +} diff --git a/relayer/src/chain/cosmos/query.rs b/relayer/src/chain/cosmos/query.rs new file mode 100644 index 0000000000..8378fb11aa --- /dev/null +++ b/relayer/src/chain/cosmos/query.rs @@ -0,0 +1,182 @@ +use http::uri::Uri; +use ibc::core::ics02_client::client_consensus::QueryClientEventRequest; +use ibc::core::ics04_channel::channel::QueryPacketEventDataRequest; +use ibc::core::ics04_channel::packet::Sequence; +use ibc::core::ics23_commitment::merkle::convert_tm_to_ics_merkle_proof; +use ibc::core::ics24_host::identifier::ChainId; +use ibc::query::QueryTxHash; +use ibc_proto::cosmos::auth::v1beta1::query_client::QueryClient; +use ibc_proto::cosmos::auth::v1beta1::{BaseAccount, EthAccount, QueryAccountRequest}; +use ibc_proto::cosmos::base::tendermint::v1beta1::service_client::ServiceClient; +use ibc_proto::cosmos::base::tendermint::v1beta1::GetNodeInfoRequest; +use prost::Message; +use tendermint::abci::Path as TendermintABCIPath; +use tendermint::block::Height; +use tendermint_rpc::query::Query; +use tendermint_rpc::{Client, HttpClient, Url}; + +use crate::chain::cosmos::version::Specs; +use crate::chain::QueryResponse; +use crate::error::Error; + +/// Uses the GRPC client to retrieve the account sequence +pub async fn query_account( + grpc_address: Uri, + account_address: String, +) -> Result { + let mut client = QueryClient::connect(grpc_address) + .await + .map_err(Error::grpc_transport)?; + + let request = tonic::Request::new(QueryAccountRequest { + address: account_address.clone(), + }); + + let response = client.account(request).await; + + // Querying for an account might fail, i.e. if the account doesn't actually exist + let resp_account = match response.map_err(Error::grpc_status)?.into_inner().account { + Some(account) => account, + None => return Err(Error::empty_query_account(account_address)), + }; + + if resp_account.type_url == "/cosmos.auth.v1beta1.BaseAccount" { + Ok(BaseAccount::decode(resp_account.value.as_slice()) + .map_err(|e| Error::protobuf_decode("BaseAccount".to_string(), e))?) + } else if resp_account.type_url.ends_with(".EthAccount") { + Ok(EthAccount::decode(resp_account.value.as_slice()) + .map_err(|e| Error::protobuf_decode("EthAccount".to_string(), e))? + .base_account + .ok_or_else(Error::empty_base_account)?) + } else { + Err(Error::unknown_account_type(resp_account.type_url)) + } +} + +pub fn packet_query(request: &QueryPacketEventDataRequest, seq: Sequence) -> Query { + tendermint_rpc::query::Query::eq( + format!("{}.packet_src_channel", request.event_id.as_str()), + request.source_channel_id.to_string(), + ) + .and_eq( + format!("{}.packet_src_port", request.event_id.as_str()), + request.source_port_id.to_string(), + ) + .and_eq( + format!("{}.packet_dst_channel", request.event_id.as_str()), + request.destination_channel_id.to_string(), + ) + .and_eq( + format!("{}.packet_dst_port", request.event_id.as_str()), + request.destination_port_id.to_string(), + ) + .and_eq( + format!("{}.packet_sequence", request.event_id.as_str()), + seq.to_string(), + ) +} + +pub fn header_query(request: &QueryClientEventRequest) -> Query { + tendermint_rpc::query::Query::eq( + format!("{}.client_id", request.event_id.as_str()), + request.client_id.to_string(), + ) + .and_eq( + format!("{}.consensus_height", request.event_id.as_str()), + format!( + "{}-{}", + request.consensus_height.revision_number, request.consensus_height.revision_height + ), + ) +} + +pub fn tx_hash_query(request: &QueryTxHash) -> Query { + tendermint_rpc::query::Query::eq("tx.hash", request.0.to_string()) +} + +/// Perform a generic `abci_query`, and return the corresponding deserialized response data. +pub async fn abci_query( + rpc_client: &HttpClient, + rpc_address: &Url, + path: TendermintABCIPath, + data: String, + height: Height, + prove: bool, +) -> Result { + let height = if height.value() == 0 { + None + } else { + Some(height) + }; + + // Use the Tendermint-rs RPC client to do the query. + let response = rpc_client + .abci_query(Some(path), data.into_bytes(), height, prove) + .await + .map_err(|e| Error::rpc(rpc_address.clone(), e))?; + + if !response.code.is_ok() { + // Fail with response log. + return Err(Error::abci_query(response)); + } + + if prove && response.proof.is_none() { + // Fail due to empty proof + return Err(Error::empty_response_proof()); + } + + let proof = response + .proof + .map(|p| convert_tm_to_ics_merkle_proof(&p)) + .transpose() + .map_err(Error::ics23)?; + + let response = QueryResponse { + value: response.value, + height: response.height, + proof, + }; + + Ok(response) +} + +/// Queries the chain to obtain the version information. +pub async fn fetch_version_specs(chain_id: &ChainId, grpc_address: &Uri) -> Result { + let grpc_addr_string = grpc_address.to_string(); + + // Construct a gRPC client + let mut client = ServiceClient::connect(grpc_address.clone()) + .await + .map_err(|e| { + Error::fetch_version_grpc_transport( + chain_id.clone(), + grpc_addr_string.clone(), + "tendermint::ServiceClient".to_string(), + e, + ) + })?; + + let request = tonic::Request::new(GetNodeInfoRequest {}); + + let response = client.get_node_info(request).await.map_err(|e| { + Error::fetch_version_grpc_status( + chain_id.clone(), + grpc_addr_string.clone(), + "tendermint::ServiceClient".to_string(), + e, + ) + })?; + + let version = response.into_inner().application_version.ok_or_else(|| { + Error::fetch_version_invalid_version_response( + chain_id.clone(), + grpc_addr_string.clone(), + "tendermint::GetNodeInfoRequest".to_string(), + ) + })?; + + // Parse the raw version info into a domain-type `version::Specs` + version + .try_into() + .map_err(|e| Error::fetch_version_parsing(chain_id.clone(), grpc_addr_string.clone(), e)) +} diff --git a/relayer/src/chain/cosmos/tx.rs b/relayer/src/chain/cosmos/tx.rs index eb1a13e1b9..ba9b274d53 100644 --- a/relayer/src/chain/cosmos/tx.rs +++ b/relayer/src/chain/cosmos/tx.rs @@ -1,12 +1,11 @@ use ibc_proto::cosmos::tx::v1beta1::Fee; use prost_types::Any; use tendermint_rpc::endpoint::broadcast::tx_sync::Response; -use tendermint_rpc::{HttpClient, Url}; +use tendermint_rpc::{Client, HttpClient, Url}; use tonic::codegen::http::Uri; use tracing::{debug, error}; use crate::chain::cosmos::batch::batch_messages; -use crate::chain::cosmos::broadcast_tx_sync; use crate::chain::cosmos::encode::sign_and_encode_tx; use crate::chain::cosmos::estimate::estimate_tx_fees; use crate::config::types::Memo; @@ -163,3 +162,17 @@ pub async fn raw_send_tx( Ok(response) } + +/// Perform a `broadcast_tx_sync`, and return the corresponding deserialized response data. +pub async fn broadcast_tx_sync( + rpc_client: &HttpClient, + rpc_address: &Url, + data: Vec, +) -> Result { + let response = rpc_client + .broadcast_tx_sync(data.into()) + .await + .map_err(|e| Error::rpc(rpc_address.clone(), e))?; + + Ok(response) +} From b7ff345602f4c1f620f3be55b0172eb6d74001da Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Fri, 18 Mar 2022 18:05:55 +0100 Subject: [PATCH 05/30] Use refactored code to send_tx --- relayer/src/chain/cosmos.rs | 355 ++---------------------------- relayer/src/chain/cosmos/retry.rs | 16 ++ relayer/src/chain/cosmos/tx.rs | 37 +--- 3 files changed, 43 insertions(+), 365 deletions(-) create mode 100644 relayer/src/chain/cosmos/retry.rs diff --git a/relayer/src/chain/cosmos.rs b/relayer/src/chain/cosmos.rs index 8606770106..9605a66266 100644 --- a/relayer/src/chain/cosmos.rs +++ b/relayer/src/chain/cosmos.rs @@ -1,13 +1,12 @@ use alloc::sync::Arc; use core::{ - cmp::min, convert::{TryFrom, TryInto}, future::Future, str::FromStr, time::Duration, }; use num_bigint::BigInt; -use std::{fmt, thread, time::Instant}; +use std::{thread, time::Instant}; use bitcoin::hashes::hex::ToHex; use itertools::Itertools; @@ -57,12 +56,7 @@ use ibc::query::{QueryTxHash, QueryTxRequest}; use ibc::signer::Signer; use ibc::Height as ICSHeight; use ibc_proto::cosmos::auth::v1beta1::BaseAccount; -use ibc_proto::cosmos::base::v1beta1::Coin; use ibc_proto::cosmos::staking::v1beta1::Params as StakingParams; -use ibc_proto::cosmos::tx::v1beta1::mode_info::{Single, Sum}; -use ibc_proto::cosmos::tx::v1beta1::{ - Fee, ModeInfo, SignDoc, SignerInfo, SimulateRequest, SimulateResponse, Tx, TxRaw, -}; use ibc_proto::ibc::core::channel::v1::{ PacketState, QueryChannelClientStateRequest, QueryChannelsRequest, QueryConnectionChannelsRequest, QueryNextSequenceReceiveRequest, @@ -75,17 +69,17 @@ use ibc_proto::ibc::core::connection::v1::{ QueryClientConnectionsRequest, QueryConnectionsRequest, }; -use crate::chain::cosmos::encode::{auth_info_and_bytes, encode_to_bech32, tx_body_and_bytes}; +use crate::chain::cosmos::encode::encode_to_bech32; use crate::chain::cosmos::gas::{calculate_fee, mul_ceil}; use crate::chain::cosmos::query::{ abci_query, fetch_version_specs, header_query, packet_query, query_account, tx_hash_query, }; -use crate::chain::cosmos::tx::broadcast_tx_sync; +use crate::chain::cosmos::tx::estimate_fee_and_send_tx; use crate::chain::tx::TrackedMsgs; use crate::chain::{ChainEndpoint, HealthCheck}; use crate::chain::{QueryResponse, StatusResponse}; use crate::config::types::Memo; -use crate::config::{AddressType, ChainConfig, GasPrice}; +use crate::config::ChainConfig; use crate::error::Error; use crate::event::monitor::{EventMonitor, EventReceiver, TxMonitorCmd}; use crate::keyring::{KeyEntry, KeyRing}; @@ -100,6 +94,7 @@ pub mod encode; pub mod estimate; pub mod gas; pub mod query; +pub mod retry; pub mod simulate; pub mod tx; pub mod types; @@ -108,11 +103,6 @@ pub mod version; /// Default gas limit when submitting a transaction. const DEFAULT_MAX_GAS: u64 = 400_000; -/// Fraction of the estimated gas to add to the estimated gas amount when submitting a transaction. -const DEFAULT_GAS_PRICE_ADJUSTMENT: f64 = 0.1; - -const DEFAULT_FEE_GRANTER: &str = ""; - /// Upper limit on the size of transactions submitted by Hermes, expressed as a /// fraction of the maximum block size defined in the Tendermint core consensus parameters. pub const GENESIS_MAX_BYTES_MAX_FRACTION: f64 = 0.9; @@ -121,25 +111,6 @@ pub const GENESIS_MAX_BYTES_MAX_FRACTION: f64 = 0.9; // https://github.com/cosmos/cosmos-sdk/blob/v0.44.0/types/errors/errors.go#L115-L117 pub const INCORRECT_ACCOUNT_SEQUENCE_ERR: u32 = 32; -mod retry_strategy { - use crate::util::retry::Fixed; - use core::time::Duration; - - // Maximum number of retries for send_tx in the case of - // an account sequence mismatch at broadcast step. - pub const MAX_ACCOUNT_SEQUENCE_RETRY: u32 = 1; - - // Backoff multiplier to apply while retrying in the case - // of account sequence mismatch. - pub const BACKOFF_MULTIPLIER_ACCOUNT_SEQUENCE_RETRY: u64 = 300; - - pub fn wait_for_block_commits(max_total_wait: Duration) -> impl Iterator { - let backoff_millis = 300; // The periodic backoff - let count: usize = (max_total_wait.as_millis() / backoff_millis as u128) as usize; - Fixed::from_millis(backoff_millis).take(count) - } -} - pub struct CosmosSdkChain { config: ChainConfig, rpc_client: HttpClient, @@ -328,66 +299,18 @@ impl CosmosSdkChain { account_seq, ); - let signer_info = self.signer(account_seq)?; - let max_fee = self.max_fee(); - - debug!("max fee, for use in tx simulation: {}", PrettyFee(&max_fee)); - - let (body, body_buf) = tx_body_and_bytes(proto_msgs, self.tx_memo())?; - - let (auth_info, auth_buf) = auth_info_and_bytes(signer_info.clone(), max_fee)?; - let signed_doc = self.signed_doc(body_buf.clone(), auth_buf, account_seq)?; - - let simulate_tx = Tx { - body: Some(body), - auth_info: Some(auth_info), - signatures: vec![signed_doc], - }; - - // This may result in an account sequence mismatch error - let estimated_gas = self.estimate_gas(simulate_tx)?; - - if estimated_gas > self.max_gas() { - debug!( - id = %self.id(), estimated = ?estimated_gas, max = ?self.max_gas(), - "send_tx: estimated gas is higher than max gas" - ); - - return Err(Error::tx_simulate_gas_estimate_exceeded( - self.id().clone(), - estimated_gas, - self.max_gas(), - )); - } - - let adjusted_fee = self.fee_with_gas(estimated_gas); - - debug!( - id = %self.id(), - "send_tx: using {} gas, fee {}", - estimated_gas, - PrettyFee(&adjusted_fee) - ); - - let (_auth_adjusted, auth_buf_adjusted) = auth_info_and_bytes(signer_info, adjusted_fee)?; let account_number = self.account_number()?; - let signed_doc = - self.signed_doc(body_buf.clone(), auth_buf_adjusted.clone(), account_number)?; - let tx_raw = TxRaw { - body_bytes: body_buf, - auth_info_bytes: auth_buf_adjusted, - signatures: vec![signed_doc], - }; - - let mut tx_bytes = Vec::new(); - prost::Message::encode(&tx_raw, &mut tx_bytes) - .map_err(|e| Error::protobuf_encode(String::from("Transaction"), e))?; - - self.block_on(broadcast_tx_sync( + self.block_on(estimate_fee_and_send_tx( + &self.config, &self.rpc_client, &self.config.rpc_addr, - tx_bytes, + &self.grpc_addr, + proto_msgs, + account_seq, + account_number, + &self.key()?, + self.tx_memo(), )) } @@ -432,13 +355,13 @@ impl CosmosSdkChain { // Gas estimation succeeded. Broadcasting failed with a retry-able error. Ok(response) if response.code == Code::Err(INCORRECT_ACCOUNT_SEQUENCE_ERR) => { - if retry_counter < retry_strategy::MAX_ACCOUNT_SEQUENCE_RETRY { + if retry_counter < retry::MAX_ACCOUNT_SEQUENCE_RETRY { let retry_counter = retry_counter + 1; warn!("failed at broadcast step with incorrect account sequence. retrying ({}/{})", - retry_counter, retry_strategy::MAX_ACCOUNT_SEQUENCE_RETRY); + retry_counter, retry::MAX_ACCOUNT_SEQUENCE_RETRY); // Backoff & re-fetch the account s.n. - let backoff = (retry_counter as u64) - * retry_strategy::BACKOFF_MULTIPLIER_ACCOUNT_SEQUENCE_RETRY; + let backoff = + (retry_counter as u64) * retry::BACKOFF_MULTIPLIER_ACCOUNT_SEQUENCE_RETRY; thread::sleep(Duration::from_millis(backoff)); self.refresh_account()?; @@ -492,60 +415,6 @@ impl CosmosSdkChain { self.send_tx_with_account_sequence_retry(proto_msgs, 0) } - /// Try to simulate the given tx in order to estimate how much gas will be needed to submit it. - /// - /// It is possible that a batch of messages are fragmented by the caller (`send_msgs`) such that - /// they do not individually verify. For example for the following batch: - /// [`MsgUpdateClient`, `MsgRecvPacket`, ..., `MsgRecvPacket`] - /// - /// If the batch is split in two TX-es, the second one will fail the simulation in `deliverTx` check. - /// In this case we use the `default_gas` param. - fn estimate_gas(&mut self, tx: Tx) -> Result { - let simulated_gas = self.send_tx_simulate(tx).map(|sr| sr.gas_info); - let _span = span!(Level::ERROR, "estimate_gas").entered(); - - match simulated_gas { - Ok(Some(gas_info)) => { - debug!( - "tx simulation successful, gas amount used: {:?}", - gas_info.gas_used - ); - - Ok(gas_info.gas_used) - } - - Ok(None) => { - warn!( - "tx simulation successful but no gas amount used was returned, falling back on default gas: {}", - self.default_gas() - ); - - Ok(self.default_gas()) - } - - // If there is a chance that the tx will be accepted once actually submitted, we fall - // back on the max gas and will attempt to send it anyway. - // See `can_recover_from_simulation_failure` for more info. - Err(e) if can_recover_from_simulation_failure(&e) => { - warn!( - "failed to simulate tx, falling back on default gas because the error is potentially recoverable: {}", - e.detail() - ); - - Ok(self.default_gas()) - } - - Err(e) => { - error!( - "failed to simulate tx. propagating error to caller: {}", - e.detail() - ); - // Propagate the error, the retrying mechanism at caller may catch & retry. - Err(e) - } - } - } - /// The default amount of gas the relayer is willing to pay for a transaction, /// when it cannot simulate the tx and therefore estimate the gas amount needed. fn default_gas(&self) -> u64 { @@ -557,51 +426,6 @@ impl CosmosSdkChain { self.config.max_gas.unwrap_or(DEFAULT_MAX_GAS) } - /// Get the fee granter address - fn fee_granter(&self) -> &str { - self.config - .fee_granter - .as_deref() - .unwrap_or(DEFAULT_FEE_GRANTER) - } - - /// The gas price - fn gas_price(&self) -> &GasPrice { - &self.config.gas_price - } - - /// The gas price adjustment - fn gas_adjustment(&self) -> f64 { - self.config - .gas_adjustment - .unwrap_or(DEFAULT_GAS_PRICE_ADJUSTMENT) - } - - /// Adjusts the fee based on the configured `gas_adjustment` to prevent out of gas errors. - /// The actual gas cost, when a transaction is executed, may be slightly higher than the - /// one returned by the simulation. - fn apply_adjustment_to_gas(&self, gas_amount: u64) -> u64 { - assert!(self.gas_adjustment() <= 1.0); - - let (_, digits) = mul_ceil(gas_amount, self.gas_adjustment()).to_u64_digits(); - assert!(digits.len() == 1); - - let adjustment = digits[0]; - let gas = gas_amount.checked_add(adjustment).unwrap_or(u64::MAX); - - min(gas, self.max_gas()) - } - - /// The maximum fee the relayer pays for a transaction - fn max_fee_in_coins(&self) -> Coin { - calculate_fee(self.max_gas(), self.gas_price()) - } - - /// The fee in coins based on gas amount - fn fee_from_gas_in_coins(&self, gas: u64) -> Coin { - calculate_fee(gas, self.gas_price()) - } - /// The maximum number of messages included in a transaction fn max_msg_num(&self) -> usize { self.config.max_msg_num.into() @@ -676,54 +500,12 @@ impl CosmosSdkChain { Ok((response.value, proof)) } - fn send_tx_simulate(&self, tx: Tx) -> Result { - crate::time!("send_tx_simulate"); - - use ibc_proto::cosmos::tx::v1beta1::service_client::ServiceClient; - - // The `tx` field of `SimulateRequest` was deprecated in Cosmos SDK 0.43 in favor of `tx_bytes`. - let mut tx_bytes = vec![]; - prost::Message::encode(&tx, &mut tx_bytes) - .map_err(|e| Error::protobuf_encode(String::from("Transaction"), e))?; - - #[allow(deprecated)] - let req = SimulateRequest { - tx: Some(tx), // needed for simulation to go through with Cosmos SDK < 0.43 - tx_bytes, // needed for simulation to go through with Cosmos SDk >= 0.43 - }; - - let mut client = self - .block_on(ServiceClient::connect(self.grpc_addr.clone())) - .map_err(Error::grpc_transport)?; - - let request = tonic::Request::new(req); - let response = self - .block_on(client.simulate(request)) - .map_err(Error::grpc_status)? - .into_inner(); - - Ok(response) - } - fn key(&self) -> Result { self.keybase() .get_key(&self.config.key_name) .map_err(Error::key_base) } - fn key_bytes(&self, key: &KeyEntry) -> Result, Error> { - let mut pk_buf = Vec::new(); - prost::Message::encode(&key.public_key.public_key.to_bytes(), &mut pk_buf) - .map_err(|e| Error::protobuf_encode(String::from("Key bytes"), e))?; - Ok(pk_buf) - } - - fn key_and_bytes(&self) -> Result<(KeyEntry, Vec), Error> { - let key = self.key()?; - let key_bytes = self.key_bytes(&key)?; - Ok((key, key_bytes)) - } - fn query_account(&self) -> Result { crate::telemetry!(query, self.id(), "query_account"); @@ -767,80 +549,6 @@ impl CosmosSdkChain { } } - fn signer(&self, sequence: u64) -> Result { - let (_key, pk_buf) = self.key_and_bytes()?; - let pk_type = match &self.config.address_type { - AddressType::Cosmos => "/cosmos.crypto.secp256k1.PubKey".to_string(), - AddressType::Ethermint { pk_type } => pk_type.clone(), - }; - // Create a MsgSend proto Any message - let pk_any = Any { - type_url: pk_type, - value: pk_buf, - }; - - let single = Single { mode: 1 }; - let sum_single = Some(Sum::Single(single)); - let mode = Some(ModeInfo { sum: sum_single }); - let signer_info = SignerInfo { - public_key: Some(pk_any), - mode_info: mode, - sequence, - }; - Ok(signer_info) - } - - fn max_fee(&self) -> Fee { - Fee { - amount: vec![self.max_fee_in_coins()], - gas_limit: self.max_gas(), - payer: "".to_string(), - granter: self.fee_granter().to_string(), - } - } - - fn fee_with_gas(&self, gas_limit: u64) -> Fee { - let adjusted_gas_limit = self.apply_adjustment_to_gas(gas_limit); - - Fee { - amount: vec![self.fee_from_gas_in_coins(adjusted_gas_limit)], - gas_limit: adjusted_gas_limit, - payer: "".to_string(), - granter: self.fee_granter().to_string(), - } - } - - fn signed_doc( - &self, - body_bytes: Vec, - auth_info_bytes: Vec, - account_number: u64, - ) -> Result, Error> { - let sign_doc = SignDoc { - body_bytes, - auth_info_bytes, - chain_id: self.config.clone().id.to_string(), - account_number, - }; - - // A protobuf serialization of a SignDoc - let mut signdoc_buf = Vec::new(); - prost::Message::encode(&sign_doc, &mut signdoc_buf) - .map_err(|e| Error::protobuf_encode(String::from("SignDoc"), e))?; - - // Sign doc - let signed = self - .keybase - .sign_msg( - &self.config.key_name, - signdoc_buf, - &self.config.address_type, - ) - .map_err(Error::key_base)?; - - Ok(signed) - } - /// Given a vector of `TxSyncResult` elements, /// each including a transaction response hash for one or more messages, periodically queries the chain /// with the transaction hashes to get the list of IbcEvents included in those transactions. @@ -864,7 +572,7 @@ impl CosmosSdkChain { let start = Instant::now(); let result = retry_with_index( - retry_strategy::wait_for_block_commits(self.config.rpc_timeout), + retry::wait_for_block_commits(self.config.rpc_timeout), |index| { if all_tx_results_found(&tx_sync_results) { trace!( @@ -2319,17 +2027,6 @@ fn do_health_check(chain: &CosmosSdkChain) -> Result<(), Error> { Ok(()) } -/// Determine whether the given error yielded by `tx_simulate` -/// can be recovered from by submitting the tx anyway. -fn can_recover_from_simulation_failure(e: &Error) -> bool { - use crate::error::ErrorDetail::*; - - match e.detail() { - GrpcStatus(detail) => detail.is_client_state_height_too_low(), - _ => false, - } -} - /// Determine whether the given error yielded by `tx_simulate` /// indicates that the current sequence number cached in Hermes /// may be out-of-sync with the full node's version of the s.n. @@ -2342,22 +2039,6 @@ fn mismatching_account_sequence_number(e: &Error) -> bool { } } -struct PrettyFee<'a>(&'a Fee); - -impl fmt::Display for PrettyFee<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let amount = match self.0.amount.get(0) { - Some(coin) => format!("{}{}", coin.amount, coin.denom), - None => "".to_string(), - }; - - f.debug_struct("Fee") - .field("amount", &amount) - .field("gas_limit", &self.0.gas_limit) - .finish() - } -} - /// Compute the `max_clock_drift` for a (new) client state /// as a function of the configuration of the source chain /// and the destination chain configuration. diff --git a/relayer/src/chain/cosmos/retry.rs b/relayer/src/chain/cosmos/retry.rs new file mode 100644 index 0000000000..a2e23f7000 --- /dev/null +++ b/relayer/src/chain/cosmos/retry.rs @@ -0,0 +1,16 @@ +use crate::util::retry::Fixed; +use core::time::Duration; + +// Maximum number of retries for send_tx in the case of +// an account sequence mismatch at broadcast step. +pub const MAX_ACCOUNT_SEQUENCE_RETRY: u32 = 1; + +// Backoff multiplier to apply while retrying in the case +// of account sequence mismatch. +pub const BACKOFF_MULTIPLIER_ACCOUNT_SEQUENCE_RETRY: u64 = 300; + +pub fn wait_for_block_commits(max_total_wait: Duration) -> impl Iterator { + let backoff_millis = 300; // The periodic backoff + let count: usize = (max_total_wait.as_millis() / backoff_millis as u128) as usize; + Fixed::from_millis(backoff_millis).take(count) +} diff --git a/relayer/src/chain/cosmos/tx.rs b/relayer/src/chain/cosmos/tx.rs index ba9b274d53..7e1784fe28 100644 --- a/relayer/src/chain/cosmos/tx.rs +++ b/relayer/src/chain/cosmos/tx.rs @@ -43,13 +43,15 @@ pub async fn send_messages_as_batches( rpc_address, grpc_address, batch, - account_sequence, + *account_sequence, account_number, key_entry, tx_memo, ) .await?; + maybe_update_account_sequence(config, account_sequence, &response); + responses.push(response); } @@ -62,7 +64,7 @@ pub async fn estimate_fee_and_send_tx( rpc_address: &Url, grpc_address: &Uri, messages: Vec, - account_sequence: &mut u64, + account_sequence: u64, account_number: u64, key_entry: &KeyEntry, tx_memo: &Memo, @@ -70,7 +72,7 @@ pub async fn estimate_fee_and_send_tx( let fee = estimate_tx_fees( config, grpc_address, - *account_sequence, + account_sequence, account_number, messages.clone(), key_entry, @@ -78,7 +80,7 @@ pub async fn estimate_fee_and_send_tx( ) .await?; - send_tx_and_update_account_sequence( + raw_send_tx( config, rpc_client, rpc_address, @@ -92,30 +94,11 @@ pub async fn estimate_fee_and_send_tx( .await } -pub async fn send_tx_and_update_account_sequence( +pub fn maybe_update_account_sequence( config: &ChainConfig, - rpc_client: &HttpClient, - rpc_address: &Url, - fee: &Fee, account_sequence: &mut u64, - account_number: u64, - messages: Vec, - key_entry: &KeyEntry, - tx_memo: &Memo, -) -> Result { - let response = raw_send_tx( - config, - rpc_client, - rpc_address, - fee, - *account_sequence, - account_number, - messages, - key_entry, - tx_memo, - ) - .await?; - + response: &Response, +) { match response.code { tendermint::abci::Code::Ok => { // A success means the account s.n. was increased @@ -133,8 +116,6 @@ pub async fn send_tx_and_update_account_sequence( ); } } - - Ok(response) } pub async fn raw_send_tx( From f24fd1af3ea85c72577cb62cc962124906784fac Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Fri, 1 Apr 2022 13:16:35 +0200 Subject: [PATCH 06/30] Walk through send_tx_with_account_sequence --- relayer/src/chain/cosmos/estimate.rs | 217 +++++++++++++------ relayer/src/chain/cosmos/gas.rs | 30 ++- relayer/src/chain/cosmos/simulate.rs | 6 +- relayer/src/chain/cosmos/types/gas_config.rs | 23 +- 4 files changed, 201 insertions(+), 75 deletions(-) diff --git a/relayer/src/chain/cosmos/estimate.rs b/relayer/src/chain/cosmos/estimate.rs index fbe4becfff..0c8d0386de 100644 --- a/relayer/src/chain/cosmos/estimate.rs +++ b/relayer/src/chain/cosmos/estimate.rs @@ -2,11 +2,11 @@ use ibc::core::ics24_host::identifier::ChainId; use ibc_proto::cosmos::tx::v1beta1::{Fee, Tx}; use ibc_proto::google::protobuf::Any; use tonic::codegen::http::Uri; -use tracing::{debug, error}; +use tracing::{debug, error, span, warn, Level}; use crate::chain::cosmos::account::{AccountNumber, AccountSequence}; use crate::chain::cosmos::encode::encode_tx_to_raw; -use crate::chain::cosmos::gas::gas_amount_to_fees; +use crate::chain::cosmos::gas::{gas_amount_to_fees, PrettyFee}; use crate::chain::cosmos::simulate::send_tx_simulate; use crate::chain::cosmos::types::GasConfig; use crate::config::types::Memo; @@ -14,58 +14,6 @@ use crate::config::ChainConfig; use crate::error::Error; use crate::keyring::KeyEntry; -pub async fn estimate_gas( - chain_id: &ChainId, - tx: Tx, - grpc_address: &Uri, - default_gas: u64, - max_gas: u64, -) -> Result { - let response = send_tx_simulate(tx, grpc_address).await; - - let estimated_gas = match response { - Ok(response) => { - let m_gas_info = response.gas_info; - - debug!( - "[{}] send_tx: tx simulation successful, simulated gas: {:?}", - chain_id, m_gas_info, - ); - - match m_gas_info { - Some(gas) => gas.gas_used, - None => default_gas, - } - } - Err(e) => { - error!( - "[{}] send_tx: failed to estimate gas, falling back on default gas, error: {}", - chain_id, - e.detail() - ); - - default_gas - } - }; - - if estimated_gas > max_gas { - debug!( - estimated = ?estimated_gas, - max = ?max_gas, - "[{}] send_tx: estimated gas is higher than max gas", - chain_id, - ); - - Err(Error::tx_simulate_gas_estimate_exceeded( - chain_id.clone(), - estimated_gas, - max_gas, - )) - } else { - Ok(estimated_gas) - } -} - pub async fn estimate_tx_fees( config: &ChainConfig, grpc_address: &Uri, @@ -77,6 +25,11 @@ pub async fn estimate_tx_fees( ) -> Result { let gas_config = GasConfig::from_chain_config(config); + debug!( + "max fee, for use in tx simulation: {}", + PrettyFee(&gas_config.max_fee) + ); + let signed_tx = encode_tx_to_raw( config, messages, @@ -93,17 +46,146 @@ pub async fn estimate_tx_fees( signatures: signed_tx.signatures, }; - let estimated_fee = estimate_gas_with_raw_tx(&gas_config, &config.id, grpc_address, tx).await?; + let estimated_fee = estimate_fee_with_raw_tx(&gas_config, &config.id, grpc_address, tx).await?; Ok(estimated_fee) } -pub async fn estimate_gas_with_raw_tx( +pub async fn estimate_fee_with_raw_tx( gas_config: &GasConfig, chain_id: &ChainId, grpc_address: &Uri, tx: Tx, ) -> Result { + let estimated_gas = estimate_gas_with_raw_tx(gas_config, grpc_address, tx).await?; + + if estimated_gas > gas_config.max_gas { + debug!( + id = %chain_id, estimated = ?estimated_gas, max = ?gas_config.max_gas, + "send_tx: estimated gas is higher than max gas" + ); + + return Err(Error::tx_simulate_gas_estimate_exceeded( + chain_id.clone(), + estimated_gas, + gas_config.max_gas, + )); + } + + let adjusted_fee = gas_amount_to_fees(gas_config, estimated_gas); + + debug!( + id = %chain_id, + "send_tx: using {} gas, fee {}", + estimated_gas, + PrettyFee(&adjusted_fee) + ); + + Ok(adjusted_fee) +} + +pub async fn estimate_gas_with_raw_tx( + gas_config: &GasConfig, + grpc_address: &Uri, + tx: Tx, +) -> Result { + let simulated_gas = send_tx_simulate(tx, grpc_address) + .await + .map(|sr| sr.gas_info); + + let _span = span!(Level::ERROR, "estimate_gas").entered(); + + match simulated_gas { + Ok(Some(gas_info)) => { + debug!( + "tx simulation successful, gas amount used: {:?}", + gas_info.gas_used + ); + + Ok(gas_info.gas_used) + } + + Ok(None) => { + warn!( + "tx simulation successful but no gas amount used was returned, falling back on default gas: {}", + gas_config.default_gas + ); + + Ok(gas_config.default_gas) + } + + // If there is a chance that the tx will be accepted once actually submitted, we fall + // back on the max gas and will attempt to send it anyway. + // See `can_recover_from_simulation_failure` for more info. + Err(e) if can_recover_from_simulation_failure(&e) => { + warn!( + "failed to simulate tx, falling back on default gas because the error is potentially recoverable: {}", + e.detail() + ); + + Ok(gas_config.default_gas) + } + + Err(e) => { + error!( + "failed to simulate tx. propagating error to caller: {}", + e.detail() + ); + // Propagate the error, the retrying mechanism at caller may catch & retry. + Err(e) + } + } + + // let estimated_gas = match response { + // Ok(response) => { + // let m_gas_info = response.gas_info; + + // debug!( + // "[{}] send_tx: tx simulation successful, simulated gas: {:?}", + // chain_id, m_gas_info, + // ); + + // match m_gas_info { + // Some(gas) => gas.gas_used, + // None => gas_config.default_gas, + // } + // } + // Err(e) => { + // error!( + // "[{}] send_tx: failed to estimate gas, falling back on default gas, error: {}", + // chain_id, + // e.detail() + // ); + + // gas_config.default_gas + // } + // }; + + // if estimated_gas > gas_config.max_gas { + // debug!( + // estimated = ?estimated_gas, + // max = ?gas_config.max_gas, + // "[{}] send_tx: estimated gas is higher than max gas", + // chain_id, + // ); + + // Err(Error::tx_simulate_gas_estimate_exceeded( + // chain_id.clone(), + // estimated_gas, + // gas_config.max_gas, + // )) + // } else { + // Ok(gas_amount_to_fees(gas_config, estimated_gas)) + // } +} + +pub async fn estimate_gas( + chain_id: &ChainId, + tx: Tx, + grpc_address: &Uri, + default_gas: u64, + max_gas: u64, +) -> Result { let response = send_tx_simulate(tx, grpc_address).await; let estimated_gas = match response { @@ -117,7 +199,7 @@ pub async fn estimate_gas_with_raw_tx( match m_gas_info { Some(gas) => gas.gas_used, - None => gas_config.default_gas, + None => default_gas, } } Err(e) => { @@ -127,14 +209,14 @@ pub async fn estimate_gas_with_raw_tx( e.detail() ); - gas_config.default_gas + default_gas } }; - if estimated_gas > gas_config.max_gas { + if estimated_gas > max_gas { debug!( estimated = ?estimated_gas, - max = ?gas_config.max_gas, + max = ?max_gas, "[{}] send_tx: estimated gas is higher than max gas", chain_id, ); @@ -142,9 +224,20 @@ pub async fn estimate_gas_with_raw_tx( Err(Error::tx_simulate_gas_estimate_exceeded( chain_id.clone(), estimated_gas, - gas_config.max_gas, + max_gas, )) } else { - Ok(gas_amount_to_fees(gas_config, estimated_gas)) + Ok(estimated_gas) + } +} + +/// Determine whether the given error yielded by `tx_simulate` +/// can be recovered from by submitting the tx anyway. +fn can_recover_from_simulation_failure(e: &Error) -> bool { + use crate::error::ErrorDetail::*; + + match e.detail() { + GrpcStatus(detail) => detail.is_client_state_height_too_low(), + _ => false, } } diff --git a/relayer/src/chain/cosmos/gas.rs b/relayer/src/chain/cosmos/gas.rs index 1ff2d0d773..08565c4b8f 100644 --- a/relayer/src/chain/cosmos/gas.rs +++ b/relayer/src/chain/cosmos/gas.rs @@ -1,4 +1,5 @@ use core::cmp::min; +use core::fmt; use ibc_proto::cosmos::base::v1beta1::Coin; use ibc_proto::cosmos::tx::v1beta1::Fee; use num_bigint::BigInt; @@ -7,16 +8,18 @@ use num_rational::BigRational; use crate::chain::cosmos::types::GasConfig; use crate::config::GasPrice; +pub struct PrettyFee<'a>(pub &'a Fee); + pub fn gas_amount_to_fees(config: &GasConfig, gas_amount: u64) -> Fee { - let gas_limit = adjust_gas_with_simulated_fees(config, gas_amount); + let adjusted_gas_limit = adjust_gas_with_simulated_fees(config, gas_amount); - let amount = calculate_fee(gas_limit, &config.gas_price); + let amount = calculate_fee(adjusted_gas_limit, &config.gas_price); Fee { amount: vec![amount], - gas_limit, + gas_limit: adjusted_gas_limit, payer: "".to_string(), - granter: "".to_string(), + granter: config.fee_granter.clone(), } } @@ -25,7 +28,8 @@ pub fn gas_amount_to_fees(config: &GasConfig, gas_amount: u64) -> Fee { /// one returned by the simulation. pub fn adjust_gas_with_simulated_fees(config: &GasConfig, gas_amount: u64) -> u64 { let gas_adjustment = config.gas_adjustment; - let max_gas = config.max_gas; + + assert!(gas_adjustment <= 1.0); let (_, digits) = mul_ceil(gas_amount, gas_adjustment).to_u64_digits(); assert!(digits.len() == 1); @@ -33,7 +37,7 @@ pub fn adjust_gas_with_simulated_fees(config: &GasConfig, gas_amount: u64) -> u6 let adjustment = digits[0]; let gas = gas_amount.checked_add(adjustment).unwrap_or(u64::MAX); - min(gas, max_gas) + min(gas, config.max_gas) } pub fn calculate_fee(adjusted_gas_amount: u64, gas_price: &GasPrice) -> Coin { @@ -53,3 +57,17 @@ pub fn mul_ceil(a: u64, f: f64) -> BigInt { let f = BigRational::from_float(f).expect("f is finite"); (f * a).ceil().to_integer() } + +impl fmt::Display for PrettyFee<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let amount = match self.0.amount.get(0) { + Some(coin) => format!("{}{}", coin.amount, coin.denom), + None => "".to_string(), + }; + + f.debug_struct("Fee") + .field("amount", &amount) + .field("gas_limit", &self.0.gas_limit) + .finish() + } +} diff --git a/relayer/src/chain/cosmos/simulate.rs b/relayer/src/chain/cosmos/simulate.rs index f00452e938..7694a58d59 100644 --- a/relayer/src/chain/cosmos/simulate.rs +++ b/relayer/src/chain/cosmos/simulate.rs @@ -1,14 +1,16 @@ +use ibc_proto::cosmos::tx::v1beta1::service_client::ServiceClient; use ibc_proto::cosmos::tx::v1beta1::{SimulateRequest, SimulateResponse, Tx}; use tonic::codegen::http::Uri; use crate::error::Error; pub async fn send_tx_simulate(tx: Tx, grpc_address: &Uri) -> Result { - use ibc_proto::cosmos::tx::v1beta1::service_client::ServiceClient; + crate::time!("send_tx_simulate"); // The `tx` field of `SimulateRequest` was deprecated in Cosmos SDK 0.43 in favor of `tx_bytes`. let mut tx_bytes = vec![]; - prost::Message::encode(&tx, &mut tx_bytes).unwrap(); // FIXME: Handle error here + prost::Message::encode(&tx, &mut tx_bytes) + .map_err(|e| Error::protobuf_encode(String::from("Transaction"), e))?; #[allow(deprecated)] let req = SimulateRequest { diff --git a/relayer/src/chain/cosmos/types/gas_config.rs b/relayer/src/chain/cosmos/types/gas_config.rs index 0e179621d6..7facffc244 100644 --- a/relayer/src/chain/cosmos/types/gas_config.rs +++ b/relayer/src/chain/cosmos/types/gas_config.rs @@ -9,12 +9,15 @@ const DEFAULT_MAX_GAS: u64 = 400_000; /// Fraction of the estimated gas to add to the estimated gas amount when submitting a transaction. const DEFAULT_GAS_PRICE_ADJUSTMENT: f64 = 0.1; +const DEFAULT_FEE_GRANTER: &str = ""; + pub struct GasConfig { pub default_gas: u64, pub max_gas: u64, pub gas_adjustment: f64, pub gas_price: GasPrice, pub max_fee: Fee, + pub fee_granter: String, } impl GasConfig { @@ -25,6 +28,7 @@ impl GasConfig { gas_adjustment: gas_adjustment_from_config(config), gas_price: config.gas_price.clone(), max_fee: max_fee_from_config(config), + fee_granter: fee_granter_from_config(config), } } } @@ -45,14 +49,23 @@ pub fn gas_adjustment_from_config(config: &ChainConfig) -> f64 { .unwrap_or(DEFAULT_GAS_PRICE_ADJUSTMENT) } +pub fn fee_granter_from_config(config: &ChainConfig) -> String { + config + .fee_granter + .as_deref() + .unwrap_or(DEFAULT_FEE_GRANTER) + .to_string() +} + pub fn max_fee_from_config(config: &ChainConfig) -> Fee { - let gas_limit = max_gas_from_config(config); - let amount = calculate_fee(gas_limit, &config.gas_price); + let max_gas = max_gas_from_config(config); + let max_fee_in_coins = calculate_fee(max_gas, &config.gas_price); + let fee_granter = fee_granter_from_config(config); Fee { - amount: vec![amount], - gas_limit, + amount: vec![max_fee_in_coins], + gas_limit: max_gas, payer: "".to_string(), - granter: "".to_string(), + granter: fee_granter, } } From bb989f8a4ec0291a07d474fbb98c622192a9350f Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Fri, 1 Apr 2022 13:35:57 +0200 Subject: [PATCH 07/30] Refactor code --- relayer/src/chain/cosmos/batch.rs | 81 +++++++++++++- relayer/src/chain/cosmos/encode.rs | 87 ++++++++------- relayer/src/chain/cosmos/estimate.rs | 106 ++----------------- relayer/src/chain/cosmos/gas.rs | 34 +++--- relayer/src/chain/cosmos/tx.rs | 71 ------------- relayer/src/chain/cosmos/types/gas_config.rs | 10 +- 6 files changed, 150 insertions(+), 239 deletions(-) diff --git a/relayer/src/chain/cosmos/batch.rs b/relayer/src/chain/cosmos/batch.rs index 8ca177b360..de673a4220 100644 --- a/relayer/src/chain/cosmos/batch.rs +++ b/relayer/src/chain/cosmos/batch.rs @@ -1,6 +1,61 @@ use ibc_proto::google::protobuf::Any; +use tendermint_rpc::endpoint::broadcast::tx_sync::Response; +use tendermint_rpc::{HttpClient, Url}; +use tonic::codegen::http::Uri; +use tracing::{debug, error}; +use crate::chain::cosmos::account::{AccountNumber, AccountSequence}; +use crate::chain::cosmos::tx::estimate_fee_and_send_tx; +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; + +// TODO: use this in send_messages_and_wait_commit +pub async fn send_messages_as_batches( + config: &ChainConfig, + rpc_client: &HttpClient, + rpc_address: &Url, + grpc_address: &Uri, + messages: Vec, + account_sequence: &mut AccountSequence, + account_number: AccountNumber, + key_entry: &KeyEntry, + tx_memo: &Memo, +) -> Result, Error> { + let max_message_count = config.max_msg_num.0; + let max_tx_size = config.max_tx_size.into(); + + if messages.is_empty() { + return Ok(Vec::new()); + } + + let batches = batch_messages(messages, max_message_count, max_tx_size)?; + + let mut responses = Vec::new(); + + for batch in batches { + let response = estimate_fee_and_send_tx( + config, + rpc_client, + rpc_address, + grpc_address, + batch, + *account_sequence, + account_number, + key_entry, + tx_memo, + ) + .await?; + + maybe_update_account_sequence(config, account_sequence, &response); + + responses.push(response); + } + + Ok(responses) +} pub fn batch_messages( messages: Vec, @@ -33,7 +88,7 @@ pub fn batch_messages( Ok(batches) } -pub fn message_size(message: &Any) -> Result { +fn message_size(message: &Any) -> Result { let mut buf = Vec::new(); prost::Message::encode(message, &mut buf) @@ -41,3 +96,27 @@ pub fn message_size(message: &Any) -> Result { Ok(buf.len()) } + +pub fn maybe_update_account_sequence( + config: &ChainConfig, + account_sequence: &mut AccountSequence, + response: &Response, +) { + match response.code { + tendermint::abci::Code::Ok => { + // A success means the account s.n. was increased + account_sequence.increment_mut(); + debug!("[{}] send_tx: broadcast_tx_sync: {:?}", config.id, response); + } + tendermint::abci::Code::Err(code) => { + // Avoid increasing the account s.n. if CheckTx failed + // Log the error + error!( + "[{}] send_tx: broadcast_tx_sync: {:?}: diagnostic: {:?}", + config.id, + response, + sdk_error_from_tx_sync_error_code(code) + ); + } + } +} diff --git a/relayer/src/chain/cosmos/encode.rs b/relayer/src/chain/cosmos/encode.rs index 0194af29cb..5a78718b45 100644 --- a/relayer/src/chain/cosmos/encode.rs +++ b/relayer/src/chain/cosmos/encode.rs @@ -23,7 +23,7 @@ pub fn sign_and_encode_tx( tx_memo: &Memo, account_number: AccountNumber, ) -> Result, Error> { - let signed_tx = encode_tx_to_raw( + let signed_tx = sign_tx( config, messages, account_sequence, @@ -42,7 +42,42 @@ pub fn sign_and_encode_tx( encode_tx_raw(tx_raw) } -pub fn encode_key_bytes(key: &KeyEntry) -> Result, Error> { +pub fn sign_tx( + config: &ChainConfig, + messages: Vec, + account_sequence: AccountSequence, + key_entry: &KeyEntry, + fee: &Fee, + tx_memo: &Memo, + account_number: AccountNumber, +) -> Result { + let key_bytes = encode_key_bytes(key_entry)?; + + let signer = encode_signer_info(key_bytes, &config.address_type, account_sequence)?; + + let (body, body_bytes) = tx_body_and_bytes(messages, tx_memo)?; + + let (auth_info, auth_info_bytes) = auth_info_and_bytes(signer, fee.clone())?; + + let signed_doc = encode_sign_doc( + &config.id, + key_entry, + &config.address_type, + body_bytes.clone(), + auth_info_bytes.clone(), + account_number, + )?; + + Ok(SignedTx { + body, + body_bytes, + auth_info, + auth_info_bytes, + signatures: vec![signed_doc], + }) +} + +fn encode_key_bytes(key: &KeyEntry) -> Result, Error> { let mut pk_buf = Vec::new(); prost::Message::encode(&key.public_key.public_key.to_bytes(), &mut pk_buf) @@ -51,7 +86,7 @@ pub fn encode_key_bytes(key: &KeyEntry) -> Result, Error> { Ok(pk_buf) } -pub fn encode_sign_doc( +fn encode_sign_doc( chain_id: &ChainId, key: &KeyEntry, address_type: &AddressType, @@ -75,7 +110,7 @@ pub fn encode_sign_doc( Ok(signed) } -pub fn encode_signer_info( +fn encode_signer_info( key_bytes: Vec, address_type: &AddressType, sequence: AccountSequence, @@ -101,7 +136,7 @@ pub fn encode_signer_info( Ok(signer_info) } -pub fn encode_tx_raw(tx_raw: TxRaw) -> Result, Error> { +fn encode_tx_raw(tx_raw: TxRaw) -> Result, Error> { let mut tx_bytes = Vec::new(); prost::Message::encode(&tx_raw, &mut tx_bytes) .map_err(|e| Error::protobuf_encode("Transaction".to_string(), e))?; @@ -109,41 +144,6 @@ pub fn encode_tx_raw(tx_raw: TxRaw) -> Result, Error> { Ok(tx_bytes) } -pub fn encode_tx_to_raw( - config: &ChainConfig, - messages: Vec, - account_sequence: AccountSequence, - key_entry: &KeyEntry, - fee: &Fee, - tx_memo: &Memo, - account_number: AccountNumber, -) -> Result { - let key_bytes = encode_key_bytes(key_entry)?; - - let signer = encode_signer_info(key_bytes, &config.address_type, account_sequence)?; - - let (body, body_bytes) = tx_body_and_bytes(messages, tx_memo)?; - - let (auth_info, auth_info_bytes) = auth_info_and_bytes(signer, fee.clone())?; - - let signed_doc = encode_sign_doc( - &config.id, - key_entry, - &config.address_type, - body_bytes.clone(), - auth_info_bytes.clone(), - account_number, - )?; - - Ok(SignedTx { - body, - body_bytes, - auth_info, - auth_info_bytes, - signatures: vec![signed_doc], - }) -} - pub fn encode_to_bech32(address: &str, account_prefix: &str) -> Result { let account = AccountId::from_str(address) .map_err(|e| Error::invalid_key_address(address.to_string(), e))?; @@ -154,10 +154,7 @@ pub fn encode_to_bech32(address: &str, account_prefix: &str) -> Result Result<(AuthInfo, Vec), Error> { +fn auth_info_and_bytes(signer_info: SignerInfo, fee: Fee) -> Result<(AuthInfo, Vec), Error> { let auth_info = AuthInfo { signer_infos: vec![signer_info], fee: Some(fee), @@ -172,7 +169,7 @@ pub fn auth_info_and_bytes( Ok((auth_info, auth_buf)) } -pub fn tx_body_and_bytes(proto_msgs: Vec, memo: &Memo) -> Result<(TxBody, Vec), Error> { +fn tx_body_and_bytes(proto_msgs: Vec, memo: &Memo) -> Result<(TxBody, Vec), Error> { // Create TxBody let body = TxBody { messages: proto_msgs.to_vec(), diff --git a/relayer/src/chain/cosmos/estimate.rs b/relayer/src/chain/cosmos/estimate.rs index 0c8d0386de..2132627afa 100644 --- a/relayer/src/chain/cosmos/estimate.rs +++ b/relayer/src/chain/cosmos/estimate.rs @@ -5,7 +5,7 @@ use tonic::codegen::http::Uri; use tracing::{debug, error, span, warn, Level}; use crate::chain::cosmos::account::{AccountNumber, AccountSequence}; -use crate::chain::cosmos::encode::encode_tx_to_raw; +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::GasConfig; @@ -30,7 +30,7 @@ pub async fn estimate_tx_fees( PrettyFee(&gas_config.max_fee) ); - let signed_tx = encode_tx_to_raw( + let signed_tx = sign_tx( config, messages, account_sequence, @@ -46,18 +46,18 @@ pub async fn estimate_tx_fees( signatures: signed_tx.signatures, }; - let estimated_fee = estimate_fee_with_raw_tx(&gas_config, &config.id, grpc_address, tx).await?; + let estimated_fee = estimate_fee_with_tx(&gas_config, &config.id, grpc_address, tx).await?; Ok(estimated_fee) } -pub async fn estimate_fee_with_raw_tx( +async fn estimate_fee_with_tx( gas_config: &GasConfig, chain_id: &ChainId, grpc_address: &Uri, tx: Tx, ) -> Result { - let estimated_gas = estimate_gas_with_raw_tx(gas_config, grpc_address, tx).await?; + let estimated_gas = estimate_gas_with_tx(gas_config, grpc_address, tx).await?; if estimated_gas > gas_config.max_gas { debug!( @@ -84,7 +84,7 @@ pub async fn estimate_fee_with_raw_tx( Ok(adjusted_fee) } -pub async fn estimate_gas_with_raw_tx( +async fn estimate_gas_with_tx( gas_config: &GasConfig, grpc_address: &Uri, tx: Tx, @@ -135,100 +135,6 @@ pub async fn estimate_gas_with_raw_tx( Err(e) } } - - // let estimated_gas = match response { - // Ok(response) => { - // let m_gas_info = response.gas_info; - - // debug!( - // "[{}] send_tx: tx simulation successful, simulated gas: {:?}", - // chain_id, m_gas_info, - // ); - - // match m_gas_info { - // Some(gas) => gas.gas_used, - // None => gas_config.default_gas, - // } - // } - // Err(e) => { - // error!( - // "[{}] send_tx: failed to estimate gas, falling back on default gas, error: {}", - // chain_id, - // e.detail() - // ); - - // gas_config.default_gas - // } - // }; - - // if estimated_gas > gas_config.max_gas { - // debug!( - // estimated = ?estimated_gas, - // max = ?gas_config.max_gas, - // "[{}] send_tx: estimated gas is higher than max gas", - // chain_id, - // ); - - // Err(Error::tx_simulate_gas_estimate_exceeded( - // chain_id.clone(), - // estimated_gas, - // gas_config.max_gas, - // )) - // } else { - // Ok(gas_amount_to_fees(gas_config, estimated_gas)) - // } -} - -pub async fn estimate_gas( - chain_id: &ChainId, - tx: Tx, - grpc_address: &Uri, - default_gas: u64, - max_gas: u64, -) -> Result { - let response = send_tx_simulate(tx, grpc_address).await; - - let estimated_gas = match response { - Ok(response) => { - let m_gas_info = response.gas_info; - - debug!( - "[{}] send_tx: tx simulation successful, simulated gas: {:?}", - chain_id, m_gas_info, - ); - - match m_gas_info { - Some(gas) => gas.gas_used, - None => default_gas, - } - } - Err(e) => { - error!( - "[{}] send_tx: failed to estimate gas, falling back on default gas, error: {}", - chain_id, - e.detail() - ); - - default_gas - } - }; - - if estimated_gas > max_gas { - debug!( - estimated = ?estimated_gas, - max = ?max_gas, - "[{}] send_tx: estimated gas is higher than max gas", - chain_id, - ); - - Err(Error::tx_simulate_gas_estimate_exceeded( - chain_id.clone(), - estimated_gas, - max_gas, - )) - } else { - Ok(estimated_gas) - } } /// Determine whether the given error yielded by `tx_simulate` diff --git a/relayer/src/chain/cosmos/gas.rs b/relayer/src/chain/cosmos/gas.rs index 08565c4b8f..8cbd96a113 100644 --- a/relayer/src/chain/cosmos/gas.rs +++ b/relayer/src/chain/cosmos/gas.rs @@ -23,23 +23,6 @@ pub fn gas_amount_to_fees(config: &GasConfig, gas_amount: u64) -> Fee { } } -/// Adjusts the fee based on the configured `gas_adjustment` to prevent out of gas errors. -/// The actual gas cost, when a transaction is executed, may be slightly higher than the -/// one returned by the simulation. -pub fn adjust_gas_with_simulated_fees(config: &GasConfig, gas_amount: u64) -> u64 { - let gas_adjustment = config.gas_adjustment; - - assert!(gas_adjustment <= 1.0); - - let (_, digits) = mul_ceil(gas_amount, gas_adjustment).to_u64_digits(); - assert!(digits.len() == 1); - - let adjustment = digits[0]; - let gas = gas_amount.checked_add(adjustment).unwrap_or(u64::MAX); - - min(gas, config.max_gas) -} - pub fn calculate_fee(adjusted_gas_amount: u64, gas_price: &GasPrice) -> Coin { let fee_amount = mul_ceil(adjusted_gas_amount, gas_price.price); @@ -58,6 +41,23 @@ pub fn mul_ceil(a: u64, f: f64) -> BigInt { (f * a).ceil().to_integer() } +/// Adjusts the fee based on the configured `gas_adjustment` to prevent out of gas errors. +/// The actual gas cost, when a transaction is executed, may be slightly higher than the +/// one returned by the simulation. +fn adjust_gas_with_simulated_fees(config: &GasConfig, gas_amount: u64) -> u64 { + let gas_adjustment = config.gas_adjustment; + + assert!(gas_adjustment <= 1.0); + + let (_, digits) = mul_ceil(gas_amount, gas_adjustment).to_u64_digits(); + assert!(digits.len() == 1); + + let adjustment = digits[0]; + let gas = gas_amount.checked_add(adjustment).unwrap_or(u64::MAX); + + min(gas, config.max_gas) +} + impl fmt::Display for PrettyFee<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let amount = match self.0.amount.get(0) { diff --git a/relayer/src/chain/cosmos/tx.rs b/relayer/src/chain/cosmos/tx.rs index 6bd722245c..8eb03e09e7 100644 --- a/relayer/src/chain/cosmos/tx.rs +++ b/relayer/src/chain/cosmos/tx.rs @@ -3,61 +3,14 @@ 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 tracing::{debug, error}; use crate::chain::cosmos::account::{AccountNumber, AccountSequence}; -use crate::chain::cosmos::batch::batch_messages; use crate::chain::cosmos::encode::sign_and_encode_tx; use crate::chain::cosmos::estimate::estimate_tx_fees; 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; - -pub async fn send_messages_as_batches( - config: &ChainConfig, - rpc_client: &HttpClient, - rpc_address: &Url, - grpc_address: &Uri, - messages: Vec, - account_sequence: &mut AccountSequence, - account_number: AccountNumber, - key_entry: &KeyEntry, - tx_memo: &Memo, -) -> Result, Error> { - let max_message_count = config.max_msg_num.0; - let max_tx_size = config.max_tx_size.into(); - - if messages.is_empty() { - return Ok(Vec::new()); - } - - let batches = batch_messages(messages, max_message_count, max_tx_size)?; - - let mut responses = Vec::new(); - - for batch in batches { - let response = estimate_fee_and_send_tx( - config, - rpc_client, - rpc_address, - grpc_address, - batch, - *account_sequence, - account_number, - key_entry, - tx_memo, - ) - .await?; - - maybe_update_account_sequence(config, account_sequence, &response); - - responses.push(response); - } - - Ok(responses) -} pub async fn estimate_fee_and_send_tx( config: &ChainConfig, @@ -95,30 +48,6 @@ pub async fn estimate_fee_and_send_tx( .await } -pub fn maybe_update_account_sequence( - config: &ChainConfig, - account_sequence: &mut AccountSequence, - response: &Response, -) { - match response.code { - tendermint::abci::Code::Ok => { - // A success means the account s.n. was increased - account_sequence.increment_mut(); - debug!("[{}] send_tx: broadcast_tx_sync: {:?}", config.id, response); - } - tendermint::abci::Code::Err(code) => { - // Avoid increasing the account s.n. if CheckTx failed - // Log the error - error!( - "[{}] send_tx: broadcast_tx_sync: {:?}: diagnostic: {:?}", - config.id, - response, - sdk_error_from_tx_sync_error_code(code) - ); - } - } -} - pub async fn raw_send_tx( config: &ChainConfig, rpc_client: &HttpClient, diff --git a/relayer/src/chain/cosmos/types/gas_config.rs b/relayer/src/chain/cosmos/types/gas_config.rs index 7facffc244..d8802e8579 100644 --- a/relayer/src/chain/cosmos/types/gas_config.rs +++ b/relayer/src/chain/cosmos/types/gas_config.rs @@ -33,23 +33,23 @@ impl GasConfig { } } -pub fn default_gas_from_config(config: &ChainConfig) -> u64 { +fn default_gas_from_config(config: &ChainConfig) -> u64 { config .default_gas .unwrap_or_else(|| max_gas_from_config(config)) } -pub fn max_gas_from_config(config: &ChainConfig) -> u64 { +fn max_gas_from_config(config: &ChainConfig) -> u64 { config.max_gas.unwrap_or(DEFAULT_MAX_GAS) } -pub fn gas_adjustment_from_config(config: &ChainConfig) -> f64 { +fn gas_adjustment_from_config(config: &ChainConfig) -> f64 { config .gas_adjustment .unwrap_or(DEFAULT_GAS_PRICE_ADJUSTMENT) } -pub fn fee_granter_from_config(config: &ChainConfig) -> String { +fn fee_granter_from_config(config: &ChainConfig) -> String { config .fee_granter .as_deref() @@ -57,7 +57,7 @@ pub fn fee_granter_from_config(config: &ChainConfig) -> String { .to_string() } -pub fn max_fee_from_config(config: &ChainConfig) -> Fee { +fn max_fee_from_config(config: &ChainConfig) -> Fee { let max_gas = max_gas_from_config(config); let max_fee_in_coins = calculate_fee(max_gas, &config.gas_price); let fee_granter = fee_granter_from_config(config); From a9eec753385324db1cdf47048e300526fae1817c Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Fri, 1 Apr 2022 15:14:04 +0200 Subject: [PATCH 08/30] Reorder function arguments --- relayer/src/chain/cosmos.rs | 6 ++--- relayer/src/chain/cosmos/batch.rs | 6 ++--- relayer/src/chain/cosmos/encode.rs | 30 +++++++++++----------- relayer/src/chain/cosmos/estimate.rs | 18 ++++++------- relayer/src/chain/cosmos/simulate.rs | 2 +- relayer/src/chain/cosmos/tx.rs | 38 ++++++++++++++-------------- 6 files changed, 50 insertions(+), 50 deletions(-) diff --git a/relayer/src/chain/cosmos.rs b/relayer/src/chain/cosmos.rs index df60553c59..33bdd7fabe 100644 --- a/relayer/src/chain/cosmos.rs +++ b/relayer/src/chain/cosmos.rs @@ -308,11 +308,11 @@ impl CosmosSdkChain { &self.rpc_client, &self.config.rpc_addr, &self.grpc_addr, - proto_msgs, - account_seq, - account_number, &self.key()?, self.tx_memo(), + account_number, + account_seq, + proto_msgs, )) } diff --git a/relayer/src/chain/cosmos/batch.rs b/relayer/src/chain/cosmos/batch.rs index de673a4220..20cb7fd5e1 100644 --- a/relayer/src/chain/cosmos/batch.rs +++ b/relayer/src/chain/cosmos/batch.rs @@ -41,11 +41,11 @@ pub async fn send_messages_as_batches( rpc_client, rpc_address, grpc_address, - batch, - *account_sequence, - account_number, key_entry, tx_memo, + account_number, + *account_sequence, + batch, ) .await?; diff --git a/relayer/src/chain/cosmos/encode.rs b/relayer/src/chain/cosmos/encode.rs index 5a78718b45..2352841f9a 100644 --- a/relayer/src/chain/cosmos/encode.rs +++ b/relayer/src/chain/cosmos/encode.rs @@ -16,21 +16,21 @@ use crate::keyring::{sign_message, KeyEntry}; pub fn sign_and_encode_tx( config: &ChainConfig, - messages: Vec, - account_sequence: AccountSequence, key_entry: &KeyEntry, - fee: &Fee, tx_memo: &Memo, account_number: AccountNumber, + account_sequence: AccountSequence, + messages: Vec, + fee: &Fee, ) -> Result, Error> { let signed_tx = sign_tx( config, - messages, - account_sequence, key_entry, - fee, tx_memo, account_number, + account_sequence, + messages, + fee, )?; let tx_raw = TxRaw { @@ -44,16 +44,16 @@ pub fn sign_and_encode_tx( pub fn sign_tx( config: &ChainConfig, - messages: Vec, - account_sequence: AccountSequence, key_entry: &KeyEntry, - fee: &Fee, tx_memo: &Memo, account_number: AccountNumber, + account_sequence: AccountSequence, + messages: Vec, + fee: &Fee, ) -> Result { let key_bytes = encode_key_bytes(key_entry)?; - let signer = encode_signer_info(key_bytes, &config.address_type, account_sequence)?; + let signer = encode_signer_info(&config.address_type, account_sequence, key_bytes)?; let (body, body_bytes) = tx_body_and_bytes(messages, tx_memo)?; @@ -63,9 +63,9 @@ pub fn sign_tx( &config.id, key_entry, &config.address_type, - body_bytes.clone(), - auth_info_bytes.clone(), account_number, + auth_info_bytes.clone(), + body_bytes.clone(), )?; Ok(SignedTx { @@ -90,9 +90,9 @@ fn encode_sign_doc( chain_id: &ChainId, key: &KeyEntry, address_type: &AddressType, - body_bytes: Vec, - auth_info_bytes: Vec, account_number: AccountNumber, + auth_info_bytes: Vec, + body_bytes: Vec, ) -> Result, Error> { let sign_doc = SignDoc { body_bytes, @@ -111,9 +111,9 @@ fn encode_sign_doc( } fn encode_signer_info( - key_bytes: Vec, address_type: &AddressType, sequence: AccountSequence, + key_bytes: Vec, ) -> Result { let pk_type = match address_type { AddressType::Cosmos => "/cosmos.crypto.secp256k1.PubKey".to_string(), diff --git a/relayer/src/chain/cosmos/estimate.rs b/relayer/src/chain/cosmos/estimate.rs index 2132627afa..64d646f712 100644 --- a/relayer/src/chain/cosmos/estimate.rs +++ b/relayer/src/chain/cosmos/estimate.rs @@ -17,11 +17,11 @@ use crate::keyring::KeyEntry; pub async fn estimate_tx_fees( config: &ChainConfig, grpc_address: &Uri, - account_sequence: AccountSequence, - account_number: AccountNumber, - messages: Vec, key_entry: &KeyEntry, tx_memo: &Memo, + account_number: AccountNumber, + account_sequence: AccountSequence, + messages: Vec, ) -> Result { let gas_config = GasConfig::from_chain_config(config); @@ -32,12 +32,12 @@ pub async fn estimate_tx_fees( let signed_tx = sign_tx( config, - messages, - account_sequence, key_entry, - &gas_config.max_fee, tx_memo, account_number, + account_sequence, + messages, + &gas_config.max_fee, )?; let tx = Tx { @@ -46,15 +46,15 @@ pub async fn estimate_tx_fees( signatures: signed_tx.signatures, }; - let estimated_fee = estimate_fee_with_tx(&gas_config, &config.id, grpc_address, tx).await?; + let estimated_fee = estimate_fee_with_tx(&gas_config, grpc_address, &config.id, tx).await?; Ok(estimated_fee) } async fn estimate_fee_with_tx( gas_config: &GasConfig, - chain_id: &ChainId, grpc_address: &Uri, + chain_id: &ChainId, tx: Tx, ) -> Result { let estimated_gas = estimate_gas_with_tx(gas_config, grpc_address, tx).await?; @@ -89,7 +89,7 @@ async fn estimate_gas_with_tx( grpc_address: &Uri, tx: Tx, ) -> Result { - let simulated_gas = send_tx_simulate(tx, grpc_address) + let simulated_gas = send_tx_simulate(grpc_address, tx) .await .map(|sr| sr.gas_info); diff --git a/relayer/src/chain/cosmos/simulate.rs b/relayer/src/chain/cosmos/simulate.rs index 7694a58d59..ce249a44db 100644 --- a/relayer/src/chain/cosmos/simulate.rs +++ b/relayer/src/chain/cosmos/simulate.rs @@ -4,7 +4,7 @@ use tonic::codegen::http::Uri; use crate::error::Error; -pub async fn send_tx_simulate(tx: Tx, grpc_address: &Uri) -> Result { +pub async fn send_tx_simulate(grpc_address: &Uri, tx: Tx) -> Result { crate::time!("send_tx_simulate"); // The `tx` field of `SimulateRequest` was deprecated in Cosmos SDK 0.43 in favor of `tx_bytes`. diff --git a/relayer/src/chain/cosmos/tx.rs b/relayer/src/chain/cosmos/tx.rs index 8eb03e09e7..fde9489ba9 100644 --- a/relayer/src/chain/cosmos/tx.rs +++ b/relayer/src/chain/cosmos/tx.rs @@ -17,56 +17,56 @@ pub async fn estimate_fee_and_send_tx( rpc_client: &HttpClient, rpc_address: &Url, grpc_address: &Uri, - messages: Vec, - account_sequence: AccountSequence, - account_number: AccountNumber, key_entry: &KeyEntry, tx_memo: &Memo, + account_number: AccountNumber, + account_sequence: AccountSequence, + messages: Vec, ) -> Result { let fee = estimate_tx_fees( config, grpc_address, - account_sequence, - account_number, - messages.clone(), key_entry, tx_memo, + account_number, + account_sequence, + messages.clone(), ) .await?; - raw_send_tx( + send_tx_with_fee( config, rpc_client, rpc_address, - &fee, - account_sequence, - account_number, - messages, key_entry, tx_memo, + account_number, + account_sequence, + messages, + &fee, ) .await } -pub async fn raw_send_tx( +pub async fn send_tx_with_fee( config: &ChainConfig, rpc_client: &HttpClient, rpc_address: &Url, - fee: &Fee, - account_sequence: AccountSequence, - account_number: AccountNumber, - messages: Vec, key_entry: &KeyEntry, tx_memo: &Memo, + account_number: AccountNumber, + account_sequence: AccountSequence, + messages: Vec, + fee: &Fee, ) -> Result { let tx_bytes = sign_and_encode_tx( config, - messages, - account_sequence, key_entry, - fee, tx_memo, account_number, + account_sequence, + messages, + fee, )?; let response = broadcast_tx_sync(rpc_client, rpc_address, tx_bytes).await?; From 0b3f89c404c3a4d34d2eaf20386578612cf0229a Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Fri, 1 Apr 2022 16:19:15 +0200 Subject: [PATCH 09/30] Refactor send_tx_with_account_sequence_retry into plain function --- relayer/src/chain/cosmos.rs | 182 +++---------------- relayer/src/chain/cosmos/batch.rs | 6 +- relayer/src/chain/cosmos/query.rs | 10 +- relayer/src/chain/cosmos/retry.rs | 174 +++++++++++++++++- relayer/src/chain/cosmos/types/gas_config.rs | 2 +- 5 files changed, 209 insertions(+), 165 deletions(-) diff --git a/relayer/src/chain/cosmos.rs b/relayer/src/chain/cosmos.rs index 33bdd7fabe..33f879ccd0 100644 --- a/relayer/src/chain/cosmos.rs +++ b/relayer/src/chain/cosmos.rs @@ -14,7 +14,7 @@ use itertools::Itertools; use tendermint::block::Height; use tendermint::consensus::Params as ConsensusParams; use tendermint::{ - abci::{Code, Event, Path as TendermintABCIPath}, + abci::{Event, Path as TendermintABCIPath}, node::info::TxIndexStatus, }; use tendermint_light_client_verifier::types::LightBlock as TMLightBlock; @@ -25,7 +25,7 @@ use tendermint_rpc::{ }; use tokio::runtime::Runtime as TokioRuntime; use tonic::codegen::http::Uri; -use tracing::{debug, error, info, span, trace, warn, Level}; +use tracing::{info, span, trace, warn, Level}; use ibc::clients::ics07_tendermint::client_state::{AllowUpdate, ClientState}; use ibc::clients::ics07_tendermint::consensus_state::ConsensusState as TMConsensusState; @@ -69,13 +69,14 @@ use ibc_proto::ibc::core::connection::v1::{ QueryClientConnectionsRequest, QueryConnectionsRequest, }; -use crate::chain::cosmos::account::{Account, AccountNumber, AccountSequence}; +use crate::chain::cosmos::account::Account; use crate::chain::cosmos::encode::encode_to_bech32; use crate::chain::cosmos::gas::{calculate_fee, mul_ceil}; use crate::chain::cosmos::query::{ abci_query, fetch_version_specs, header_query, packet_query, query_account, tx_hash_query, }; -use crate::chain::cosmos::tx::estimate_fee_and_send_tx; +use crate::chain::cosmos::retry::send_tx_with_account_sequence_retry; +use crate::chain::cosmos::types::gas_config::DEFAULT_MAX_GAS; use crate::chain::tx::TrackedMsgs; use crate::chain::{ChainEndpoint, HealthCheck}; use crate::chain::{QueryResponse, StatusResponse}; @@ -86,7 +87,6 @@ use crate::event::monitor::{EventMonitor, EventReceiver, TxMonitorCmd}; use crate::keyring::{KeyEntry, KeyRing}; use crate::light_client::tendermint::LightClient as TmLightClient; use crate::light_client::{LightClient, Verified}; -use crate::sdk_error::sdk_error_from_tx_sync_error_code; use crate::util::retry::{retry_with_index, RetryResult}; pub mod account; @@ -102,17 +102,10 @@ pub mod tx; pub mod types; pub mod version; -/// Default gas limit when submitting a transaction. -const DEFAULT_MAX_GAS: u64 = 400_000; - /// fraction of the maximum block size defined in the Tendermint core consensus parameters. pub const GENESIS_MAX_BYTES_MAX_FRACTION: f64 = 0.9; // https://github.com/cosmos/cosmos-sdk/blob/v0.44.0/types/errors/errors.go#L115-L117 -// The error "incorrect account sequence" is defined as the unique error code 32 in cosmos-sdk: -// https://github.com/cosmos/cosmos-sdk/blob/v0.44.0/types/errors/errors.go#L115-L117 -pub const INCORRECT_ACCOUNT_SEQUENCE_ERR: u32 = 32; - pub struct CosmosSdkChain { config: ChainConfig, rpc_client: HttpClient, @@ -290,126 +283,33 @@ impl CosmosSdkChain { self.rt.block_on(f) } - fn send_tx_with_account_sequence( + fn send_tx_with_account_sequence_retry( &mut self, proto_msgs: Vec, - account_seq: AccountSequence, + retry_counter: u64, ) -> Result { - debug!( - "sending {} messages using account sequence {}", - proto_msgs.len(), - account_seq, - ); - - let account_number = self.account_number()?; - - self.block_on(estimate_fee_and_send_tx( - &self.config, - &self.rpc_client, - &self.config.rpc_addr, - &self.grpc_addr, - &self.key()?, - self.tx_memo(), - account_number, - account_seq, + let runtime = self.rt.clone(); + let config = self.config.clone(); + let rpc_client = self.rpc_client.clone(); + let rpc_address = self.config.rpc_addr.clone(); + let grpc_address = self.grpc_addr.clone(); + let key_entry = self.key()?; + let memo = self.tx_memo().clone(); + let account = self.account()?; + + runtime.block_on(send_tx_with_account_sequence_retry( + &config, + &rpc_client, + &rpc_address, + &grpc_address, + &key_entry, + &memo, + account, proto_msgs, + retry_counter, )) } - /// Try to `send_tx` with retry on account sequence error. - /// An account sequence error can occur if the account sequence that - /// the relayer caches becomes outdated. This may happen if the relayer - /// wallet is used concurrently elsewhere, or when tx fees are mis-configured - /// leading to transactions hanging in the mempool. - /// - /// Account sequence mismatch error can occur at two separate steps: - /// 1. as Err variant, propagated from the `estimate_gas` step. - /// 2. as an Ok variant, with an Code::Err response, propagated from - /// the `broadcast_tx_sync` step. - /// - /// We treat both cases by re-fetching the account sequence number - /// from the full node. - /// Upon case #1, we do not retry submitting the same tx (retry happens - /// nonetheless at the worker `step` level). Upon case #2, we retry - /// submitting the same transaction. - fn send_tx_with_account_sequence_retry( - &mut self, - proto_msgs: Vec, - retry_counter: u32, - ) -> Result { - let account_sequence = self.account_sequence()?; - - match self.send_tx_with_account_sequence(proto_msgs.clone(), account_sequence) { - // Gas estimation failed with acct. s.n. mismatch at estimate gas step. - // This indicates that the full node did not yet push the previous tx out of its - // mempool. Possible explanations: fees too low, network congested, or full node - // congested. Whichever the case, it is more expedient in production to drop the tx - // and refresh the s.n., to allow proceeding to the other transactions. A separate - // 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"); - self.refresh_account()?; - // 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. - Err(e) - } - - // Gas estimation succeeded. Broadcasting failed with a retry-able error. - Ok(response) if response.code == Code::Err(INCORRECT_ACCOUNT_SEQUENCE_ERR) => { - if retry_counter < retry::MAX_ACCOUNT_SEQUENCE_RETRY { - let retry_counter = retry_counter + 1; - warn!("failed at broadcast step with incorrect account sequence. retrying ({}/{})", - retry_counter, retry::MAX_ACCOUNT_SEQUENCE_RETRY); - // Backoff & re-fetch the account s.n. - let backoff = - (retry_counter as u64) * retry::BACKOFF_MULTIPLIER_ACCOUNT_SEQUENCE_RETRY; - thread::sleep(Duration::from_millis(backoff)); - self.refresh_account()?; - - // Now retry. - self.send_tx_with_account_sequence_retry(proto_msgs, retry_counter + 1) - } else { - // If after the max retry we still get an account sequence mismatch error, - // we ignore the error and return the original response to downstream. - // We do not return an error here, because the current convention - // let the caller handle error responses separately. - error!("failed due to account sequence errors. the relayer wallet may be used elsewhere concurrently."); - Ok(response) - } - } - - // Catch-all arm for the Ok variant. - // This is the case when gas estimation succeeded. - Ok(response) => { - // Complete success. - match response.code { - tendermint::abci::Code::Ok => { - debug!("broadcast_tx_sync: {:?}", response); - - self.incr_account_sequence(); - Ok(response) - } - // Gas estimation succeeded, but broadcasting failed with unrecoverable error. - tendermint::abci::Code::Err(code) => { - // Avoid increasing the account s.n. if CheckTx failed - // Log the error - error!( - "broadcast_tx_sync: {:?}: diagnostic: {:?}", - response, - sdk_error_from_tx_sync_error_code(code) - ); - Ok(response) - } - } - } - - // Catch-all case for the Err variant. - // Gas estimation failure or other unrecoverable error, propagate. - Err(e) => Err(e), - } - } - fn send_tx(&mut self, proto_msgs: Vec) -> Result { crate::time!("send_tx"); let _span = span!(Level::ERROR, "send_tx", id = %self.id()).entered(); @@ -511,7 +411,7 @@ impl CosmosSdkChain { fn query_account(&self) -> Result { crate::telemetry!(query, self.id(), "query_account"); - self.block_on(query_account(self.grpc_addr.clone(), self.key()?.account)) + self.block_on(query_account(&self.grpc_addr, &self.key()?.account)) } fn refresh_account(&mut self) -> Result<(), Error> { @@ -527,31 +427,17 @@ impl CosmosSdkChain { Ok(()) } - fn account(&mut self) -> Result<&Account, Error> { + fn account(&mut self) -> Result<&mut Account, Error> { if self.account == None { self.refresh_account()?; } Ok(self .account - .as_ref() + .as_mut() .expect("account was supposedly just cached")) } - fn account_number(&mut self) -> Result { - Ok(self.account()?.number) - } - - fn account_sequence(&mut self) -> Result { - Ok(self.account()?.sequence) - } - - fn incr_account_sequence(&mut self) { - if let Some(account) = &mut self.account { - account.sequence = account.sequence.increment(); - } - } - /// Given a vector of `TxSyncResult` elements, /// each including a transaction response hash for one or more messages, periodically queries the chain /// with the transaction hashes to get the list of IbcEvents included in those transactions. @@ -2030,18 +1916,6 @@ fn do_health_check(chain: &CosmosSdkChain) -> Result<(), Error> { Ok(()) } -/// Determine whether the given error yielded by `tx_simulate` -/// indicates that the current sequence number cached in Hermes -/// may be out-of-sync with the full node's version of the s.n. -fn mismatching_account_sequence_number(e: &Error) -> bool { - use crate::error::ErrorDetail::*; - - match e.detail() { - GrpcStatus(detail) => detail.is_account_sequence_mismatch(), - _ => false, - } -} - /// Compute the `max_clock_drift` for a (new) client state /// as a function of the configuration of the source chain /// and the destination chain configuration. diff --git a/relayer/src/chain/cosmos/batch.rs b/relayer/src/chain/cosmos/batch.rs index 20cb7fd5e1..8aa5abfb0a 100644 --- a/relayer/src/chain/cosmos/batch.rs +++ b/relayer/src/chain/cosmos/batch.rs @@ -18,11 +18,11 @@ pub async fn send_messages_as_batches( rpc_client: &HttpClient, rpc_address: &Url, grpc_address: &Uri, - messages: Vec, - account_sequence: &mut AccountSequence, - account_number: AccountNumber, key_entry: &KeyEntry, tx_memo: &Memo, + account_number: AccountNumber, + account_sequence: &mut AccountSequence, + messages: Vec, ) -> Result, Error> { let max_message_count = config.max_msg_num.0; let max_tx_size = config.max_tx_size.into(); diff --git a/relayer/src/chain/cosmos/query.rs b/relayer/src/chain/cosmos/query.rs index 8378fb11aa..edbc52dff4 100644 --- a/relayer/src/chain/cosmos/query.rs +++ b/relayer/src/chain/cosmos/query.rs @@ -21,15 +21,15 @@ use crate::error::Error; /// Uses the GRPC client to retrieve the account sequence pub async fn query_account( - grpc_address: Uri, - account_address: String, + grpc_address: &Uri, + account_address: &str, ) -> Result { - let mut client = QueryClient::connect(grpc_address) + let mut client = QueryClient::connect(grpc_address.clone()) .await .map_err(Error::grpc_transport)?; let request = tonic::Request::new(QueryAccountRequest { - address: account_address.clone(), + address: account_address.to_string(), }); let response = client.account(request).await; @@ -37,7 +37,7 @@ pub async fn query_account( // Querying for an account might fail, i.e. if the account doesn't actually exist let resp_account = match response.map_err(Error::grpc_status)?.into_inner().account { Some(account) => account, - None => return Err(Error::empty_query_account(account_address)), + None => return Err(Error::empty_query_account(account_address.to_string())), }; if resp_account.type_url == "/cosmos.auth.v1beta1.BaseAccount" { diff --git a/relayer/src/chain/cosmos/retry.rs b/relayer/src/chain/cosmos/retry.rs index a2e23f7000..b39396626f 100644 --- a/relayer/src/chain/cosmos/retry.rs +++ b/relayer/src/chain/cosmos/retry.rs @@ -1,16 +1,186 @@ -use crate::util::retry::Fixed; +use core::future::Future; +use core::pin::Pin; use core::time::Duration; +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, Url}; +use tonic::codegen::http::Uri; +use tracing::{debug, error, warn}; + +use crate::chain::cosmos::account::Account; +use crate::chain::cosmos::query::query_account; +use crate::chain::cosmos::tx::estimate_fee_and_send_tx; +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; +use crate::util::retry::Fixed; // Maximum number of retries for send_tx in the case of // an account sequence mismatch at broadcast step. -pub const MAX_ACCOUNT_SEQUENCE_RETRY: u32 = 1; +pub const MAX_ACCOUNT_SEQUENCE_RETRY: u64 = 1; // Backoff multiplier to apply while retrying in the case // of account sequence mismatch. pub const BACKOFF_MULTIPLIER_ACCOUNT_SEQUENCE_RETRY: u64 = 300; +// The error "incorrect account sequence" is defined as the unique error code 32 in cosmos-sdk: +// https://github.com/cosmos/cosmos-sdk/blob/v0.44.0/types/errors/errors.go#L115-L117 +pub const INCORRECT_ACCOUNT_SEQUENCE_ERR: u32 = 32; + +/// Try to `send_tx` with retry on account sequence error. +/// An account sequence error can occur if the account sequence that +/// the relayer caches becomes outdated. This may happen if the relayer +/// wallet is used concurrently elsewhere, or when tx fees are mis-configured +/// leading to transactions hanging in the mempool. +/// +/// Account sequence mismatch error can occur at two separate steps: +/// 1. as Err variant, propagated from the `estimate_gas` step. +/// 2. as an Ok variant, with an Code::Err response, propagated from +/// the `broadcast_tx_sync` step. +/// +/// We treat both cases by re-fetching the account sequence number +/// from the full node. +/// Upon case #1, we do not retry submitting the same tx (retry happens +/// nonetheless at the worker `step` level). Upon case #2, we retry +/// submitting the same transaction. +pub fn send_tx_with_account_sequence_retry<'a>( + config: &'a ChainConfig, + rpc_client: &'a HttpClient, + rpc_address: &'a Url, + grpc_address: &'a Uri, + key_entry: &'a KeyEntry, + tx_memo: &'a Memo, + account: &'a mut Account, + messages: Vec, + retry_counter: u64, +) -> Pin> + 'a>> { + Box::pin(async move { + let tx_result = estimate_fee_and_send_tx( + config, + rpc_client, + rpc_address, + grpc_address, + key_entry, + tx_memo, + account.number, + account.sequence, + messages.clone(), + ) + .await; + + match tx_result { + // Gas estimation failed with acct. s.n. mismatch at estimate gas step. + // This indicates that the full node did not yet push the previous tx out of its + // mempool. Possible explanations: fees too low, network congested, or full node + // congested. Whichever the case, it is more expedient in production to drop the tx + // and refresh the s.n., to allow proceeding to the other transactions. A separate + // 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).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. + Err(e) + } + + // Gas estimation succeeded. Broadcasting failed with a retry-able error. + Ok(response) if response.code == Code::Err(INCORRECT_ACCOUNT_SEQUENCE_ERR) => { + if retry_counter < MAX_ACCOUNT_SEQUENCE_RETRY { + let retry_counter = retry_counter + 1; + warn!("failed at broadcast step with incorrect account sequence. retrying ({}/{})", + retry_counter, MAX_ACCOUNT_SEQUENCE_RETRY); + // Backoff & re-fetch the account s.n. + let backoff = retry_counter * BACKOFF_MULTIPLIER_ACCOUNT_SEQUENCE_RETRY; + + thread::sleep(Duration::from_millis(backoff)); + refresh_account(grpc_address, key_entry, account).await?; + + // Now retry. + send_tx_with_account_sequence_retry( + config, + rpc_client, + rpc_address, + grpc_address, + key_entry, + tx_memo, + account, + messages, + retry_counter + 1, + ) + .await + } else { + // If after the max retry we still get an account sequence mismatch error, + // we ignore the error and return the original response to downstream. + // We do not return an error here, because the current convention + // let the caller handle error responses separately. + error!("failed due to account sequence errors. the relayer wallet may be used elsewhere concurrently."); + Ok(response) + } + } + + // Catch-all arm for the Ok variant. + // This is the case when gas estimation succeeded. + Ok(response) => { + // Complete success. + match response.code { + Code::Ok => { + debug!("broadcast_tx_sync: {:?}", response); + + account.sequence.increment_mut(); + Ok(response) + } + // Gas estimation succeeded, but broadcasting failed with unrecoverable error. + Code::Err(code) => { + // Avoid increasing the account s.n. if CheckTx failed + // Log the error + error!( + "broadcast_tx_sync: {:?}: diagnostic: {:?}", + response, + sdk_error_from_tx_sync_error_code(code) + ); + Ok(response) + } + } + } + + // Catch-all case for the Err variant. + // Gas estimation failure or other unrecoverable error, propagate. + Err(e) => Err(e), + } + }) +} + +async fn refresh_account( + grpc_address: &Uri, + key_entry: &KeyEntry, + account: &mut Account, +) -> Result<(), Error> { + *account = query_account(grpc_address, &key_entry.account) + .await? + .into(); + + Ok(()) +} + pub fn wait_for_block_commits(max_total_wait: Duration) -> impl Iterator { let backoff_millis = 300; // The periodic backoff let count: usize = (max_total_wait.as_millis() / backoff_millis as u128) as usize; Fixed::from_millis(backoff_millis).take(count) } + +/// Determine whether the given error yielded by `tx_simulate` +/// indicates that the current sequence number cached in Hermes +/// may be out-of-sync with the full node's version of the s.n. +fn mismatching_account_sequence_number(e: &Error) -> bool { + use crate::error::ErrorDetail::*; + + match e.detail() { + GrpcStatus(detail) => detail.is_account_sequence_mismatch(), + _ => false, + } +} diff --git a/relayer/src/chain/cosmos/types/gas_config.rs b/relayer/src/chain/cosmos/types/gas_config.rs index d8802e8579..c8b2a5f0ac 100644 --- a/relayer/src/chain/cosmos/types/gas_config.rs +++ b/relayer/src/chain/cosmos/types/gas_config.rs @@ -4,7 +4,7 @@ use crate::chain::cosmos::calculate_fee; use crate::config::{ChainConfig, GasPrice}; /// Default gas limit when submitting a transaction. -const DEFAULT_MAX_GAS: u64 = 400_000; +pub const DEFAULT_MAX_GAS: u64 = 400_000; /// Fraction of the estimated gas to add to the estimated gas amount when submitting a transaction. const DEFAULT_GAS_PRICE_ADJUSTMENT: f64 = 0.1; From 4aac7c8dcbb02e682093061b672df546cd4f0bf6 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Mon, 4 Apr 2022 11:13:51 +0200 Subject: [PATCH 10/30] Refactor account query functions --- relayer/src/chain/cosmos.rs | 79 +++++---------- relayer/src/chain/cosmos/account.rs | 112 ---------------------- relayer/src/chain/cosmos/batch.rs | 41 ++------ relayer/src/chain/cosmos/encode.rs | 2 +- relayer/src/chain/cosmos/estimate.rs | 2 +- relayer/src/chain/cosmos/query.rs | 38 ++++++++ relayer/src/chain/cosmos/retry.rs | 49 ++++++---- relayer/src/chain/cosmos/tx.rs | 6 +- relayer/src/chain/cosmos/types/account.rs | 70 ++++++++++++++ relayer/src/chain/cosmos/types/mod.rs | 1 + 10 files changed, 173 insertions(+), 227 deletions(-) delete mode 100644 relayer/src/chain/cosmos/account.rs create mode 100644 relayer/src/chain/cosmos/types/account.rs diff --git a/relayer/src/chain/cosmos.rs b/relayer/src/chain/cosmos.rs index 33f879ccd0..5e67b31cef 100644 --- a/relayer/src/chain/cosmos.rs +++ b/relayer/src/chain/cosmos.rs @@ -55,7 +55,6 @@ use ibc::query::QueryBlockRequest; use ibc::query::{QueryTxHash, QueryTxRequest}; use ibc::signer::Signer; use ibc::Height as ICSHeight; -use ibc_proto::cosmos::auth::v1beta1::BaseAccount; use ibc_proto::cosmos::staking::v1beta1::Params as StakingParams; use ibc_proto::ibc::core::channel::v1::{ PacketState, QueryChannelClientStateRequest, QueryChannelsRequest, @@ -69,18 +68,18 @@ use ibc_proto::ibc::core::connection::v1::{ QueryClientConnectionsRequest, QueryConnectionsRequest, }; -use crate::chain::cosmos::account::Account; use crate::chain::cosmos::encode::encode_to_bech32; use crate::chain::cosmos::gas::{calculate_fee, mul_ceil}; use crate::chain::cosmos::query::{ - abci_query, fetch_version_specs, header_query, packet_query, query_account, tx_hash_query, + abci_query, fetch_version_specs, get_or_fetch_account, header_query, packet_query, + tx_hash_query, }; use crate::chain::cosmos::retry::send_tx_with_account_sequence_retry; +use crate::chain::cosmos::types::account::Account; use crate::chain::cosmos::types::gas_config::DEFAULT_MAX_GAS; use crate::chain::tx::TrackedMsgs; use crate::chain::{ChainEndpoint, HealthCheck}; use crate::chain::{QueryResponse, StatusResponse}; -use crate::config::types::Memo; use crate::config::ChainConfig; use crate::error::Error; use crate::event::monitor::{EventMonitor, EventReceiver, TxMonitorCmd}; @@ -89,7 +88,6 @@ use crate::light_client::tendermint::LightClient as TmLightClient; use crate::light_client::{LightClient, Verified}; use crate::util::retry::{retry_with_index, RetryResult}; -pub mod account; pub mod batch; pub mod compatibility; pub mod encode; @@ -283,38 +281,40 @@ impl CosmosSdkChain { self.rt.block_on(f) } - fn send_tx_with_account_sequence_retry( + async fn send_tx_with_account_sequence_retry( &mut self, proto_msgs: Vec, retry_counter: u64, ) -> Result { - let runtime = self.rt.clone(); - let config = self.config.clone(); - let rpc_client = self.rpc_client.clone(); - let rpc_address = self.config.rpc_addr.clone(); - let grpc_address = self.grpc_addr.clone(); let key_entry = self.key()?; - let memo = self.tx_memo().clone(); - let account = self.account()?; - - runtime.block_on(send_tx_with_account_sequence_retry( - &config, - &rpc_client, - &rpc_address, - &grpc_address, + + let account = + get_or_fetch_account(&self.grpc_addr, &key_entry.account, &mut self.account).await?; + + send_tx_with_account_sequence_retry( + &self.config, + &self.rpc_client, + &self.config.rpc_addr, + &self.grpc_addr, &key_entry, - &memo, + &self.config.memo_prefix, account, proto_msgs, retry_counter, - )) + ) + .await } fn send_tx(&mut self, proto_msgs: Vec) -> Result { crate::time!("send_tx"); let _span = span!(Level::ERROR, "send_tx", id = %self.id()).entered(); - self.send_tx_with_account_sequence_retry(proto_msgs, 0) + let runtime = self.rt.clone(); + + runtime.block_on(async { + self.send_tx_with_account_sequence_retry(proto_msgs, 0) + .await + }) } /// The default amount of gas the relayer is willing to pay for a transaction, @@ -408,36 +408,6 @@ impl CosmosSdkChain { .map_err(Error::key_base) } - fn query_account(&self) -> Result { - crate::telemetry!(query, self.id(), "query_account"); - - self.block_on(query_account(&self.grpc_addr, &self.key()?.account)) - } - - fn refresh_account(&mut self) -> Result<(), Error> { - let account = self.query_account()?; - info!( - sequence = %account.sequence, - number = %account.account_number, - "refresh: retrieved account", - ); - - self.account = Some(account.into()); - - Ok(()) - } - - fn account(&mut self) -> Result<&mut Account, Error> { - if self.account == None { - self.refresh_account()?; - } - - Ok(self - .account - .as_mut() - .expect("account was supposedly just cached")) - } - /// Given a vector of `TxSyncResult` elements, /// each including a transaction response hash for one or more messages, periodically queries the chain /// with the transaction hashes to get the list of IbcEvents included in those transactions. @@ -519,11 +489,6 @@ impl CosmosSdkChain { .unwrap_or(2 * unbonding_period / 3) } - /// Returns the preconfigured memo to be used for every submitted transaction - fn tx_memo(&self) -> &Memo { - &self.config.memo_prefix - } - /// Query the chain status via an RPC query. /// /// Returns an error if the node is still syncing and has not caught up, diff --git a/relayer/src/chain/cosmos/account.rs b/relayer/src/chain/cosmos/account.rs deleted file mode 100644 index a56b56a4fe..0000000000 --- a/relayer/src/chain/cosmos/account.rs +++ /dev/null @@ -1,112 +0,0 @@ -use core::fmt; - -use ibc_proto::cosmos::auth::v1beta1::BaseAccount; -use prost::Message; - -use crate::error::Error; - -use super::CosmosSdkChain; - -/// Wrapper for account number and sequence number. -/// -/// More fields may be added later. -#[derive(Clone, Debug, PartialEq)] -pub struct Account { - // pub address: String, - // pub pub_key: Option, - pub number: AccountNumber, - pub sequence: AccountSequence, -} - -impl From for Account { - fn from(value: BaseAccount) -> Self { - Self { - number: AccountNumber::new(value.account_number), - sequence: AccountSequence::new(value.sequence), - } - } -} - -/// Newtype for account numbers -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct AccountNumber(u64); - -impl AccountNumber { - pub fn new(number: u64) -> Self { - Self(number) - } - - pub fn to_u64(self) -> u64 { - self.0 - } -} - -impl fmt::Display for AccountNumber { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self) - } -} - -/// Newtype for account sequence numbers -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct AccountSequence(u64); - -impl AccountSequence { - pub fn new(sequence: u64) -> Self { - Self(sequence) - } - - pub fn to_u64(self) -> u64 { - self.0 - } - - pub fn increment(self) -> Self { - Self(self.0 + 1) - } - - pub fn increment_mut(&mut self) { - self.0 += 1 - } -} - -impl fmt::Display for AccountSequence { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self) - } -} - -/// Uses the GRPC client to retrieve the account sequence -pub async fn query_account(chain: &CosmosSdkChain, address: String) -> Result { - use ibc_proto::cosmos::auth::v1beta1::query_client::QueryClient; - use ibc_proto::cosmos::auth::v1beta1::{EthAccount, QueryAccountRequest}; - - crate::telemetry!(query, &chain.config.id, "query_account"); - - let mut client = QueryClient::connect(chain.grpc_addr.clone()) - .await - .map_err(Error::grpc_transport)?; - - let request = tonic::Request::new(QueryAccountRequest { - address: address.clone(), - }); - - let response = client.account(request).await; - - // Querying for an account might fail, i.e. if the account doesn't actually exist - let resp_account = match response.map_err(Error::grpc_status)?.into_inner().account { - Some(account) => account, - None => return Err(Error::empty_query_account(address)), - }; - - if resp_account.type_url == "/cosmos.auth.v1beta1.BaseAccount" { - Ok(BaseAccount::decode(resp_account.value.as_slice()) - .map_err(|e| Error::protobuf_decode("BaseAccount".to_string(), e))?) - } else if resp_account.type_url.ends_with(".EthAccount") { - Ok(EthAccount::decode(resp_account.value.as_slice()) - .map_err(|e| Error::protobuf_decode("EthAccount".to_string(), e))? - .base_account - .ok_or_else(Error::empty_base_account)?) - } else { - Err(Error::unknown_account_type(resp_account.type_url)) - } -} diff --git a/relayer/src/chain/cosmos/batch.rs b/relayer/src/chain/cosmos/batch.rs index 8aa5abfb0a..1ecce18053 100644 --- a/relayer/src/chain/cosmos/batch.rs +++ b/relayer/src/chain/cosmos/batch.rs @@ -2,15 +2,13 @@ use ibc_proto::google::protobuf::Any; use tendermint_rpc::endpoint::broadcast::tx_sync::Response; use tendermint_rpc::{HttpClient, Url}; use tonic::codegen::http::Uri; -use tracing::{debug, error}; -use crate::chain::cosmos::account::{AccountNumber, AccountSequence}; -use crate::chain::cosmos::tx::estimate_fee_and_send_tx; +use crate::chain::cosmos::retry::send_tx_with_account_sequence_retry; +use crate::chain::cosmos::types::account::Account; 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; // TODO: use this in send_messages_and_wait_commit pub async fn send_messages_as_batches( @@ -20,8 +18,7 @@ pub async fn send_messages_as_batches( grpc_address: &Uri, key_entry: &KeyEntry, tx_memo: &Memo, - account_number: AccountNumber, - account_sequence: &mut AccountSequence, + account: &mut Account, messages: Vec, ) -> Result, Error> { let max_message_count = config.max_msg_num.0; @@ -36,21 +33,19 @@ pub async fn send_messages_as_batches( let mut responses = Vec::new(); for batch in batches { - let response = estimate_fee_and_send_tx( + let response = send_tx_with_account_sequence_retry( config, rpc_client, rpc_address, grpc_address, key_entry, tx_memo, - account_number, - *account_sequence, + account, batch, + 0, ) .await?; - maybe_update_account_sequence(config, account_sequence, &response); - responses.push(response); } @@ -96,27 +91,3 @@ fn message_size(message: &Any) -> Result { Ok(buf.len()) } - -pub fn maybe_update_account_sequence( - config: &ChainConfig, - account_sequence: &mut AccountSequence, - response: &Response, -) { - match response.code { - tendermint::abci::Code::Ok => { - // A success means the account s.n. was increased - account_sequence.increment_mut(); - debug!("[{}] send_tx: broadcast_tx_sync: {:?}", config.id, response); - } - tendermint::abci::Code::Err(code) => { - // Avoid increasing the account s.n. if CheckTx failed - // Log the error - error!( - "[{}] send_tx: broadcast_tx_sync: {:?}: diagnostic: {:?}", - config.id, - response, - sdk_error_from_tx_sync_error_code(code) - ); - } - } -} diff --git a/relayer/src/chain/cosmos/encode.rs b/relayer/src/chain/cosmos/encode.rs index 2352841f9a..8ee61248ce 100644 --- a/relayer/src/chain/cosmos/encode.rs +++ b/relayer/src/chain/cosmos/encode.rs @@ -6,7 +6,7 @@ use ibc_proto::cosmos::tx::v1beta1::{AuthInfo, Fee, ModeInfo, SignDoc, SignerInf use ibc_proto::google::protobuf::Any; use tendermint::account::Id as AccountId; -use crate::chain::cosmos::account::{AccountNumber, AccountSequence}; +use crate::chain::cosmos::types::account::{AccountNumber, AccountSequence}; use crate::chain::cosmos::types::SignedTx; use crate::config::types::Memo; use crate::config::AddressType; diff --git a/relayer/src/chain/cosmos/estimate.rs b/relayer/src/chain/cosmos/estimate.rs index 64d646f712..03997cce80 100644 --- a/relayer/src/chain/cosmos/estimate.rs +++ b/relayer/src/chain/cosmos/estimate.rs @@ -4,10 +4,10 @@ use ibc_proto::google::protobuf::Any; use tonic::codegen::http::Uri; use tracing::{debug, error, span, warn, Level}; -use crate::chain::cosmos::account::{AccountNumber, AccountSequence}; 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::{AccountNumber, AccountSequence}; use crate::chain::cosmos::types::GasConfig; use crate::config::types::Memo; use crate::config::ChainConfig; diff --git a/relayer/src/chain/cosmos/query.rs b/relayer/src/chain/cosmos/query.rs index edbc52dff4..dfa5410317 100644 --- a/relayer/src/chain/cosmos/query.rs +++ b/relayer/src/chain/cosmos/query.rs @@ -14,11 +14,49 @@ use tendermint::abci::Path as TendermintABCIPath; use tendermint::block::Height; use tendermint_rpc::query::Query; use tendermint_rpc::{Client, HttpClient, Url}; +use tracing::info; +use crate::chain::cosmos::types::account::Account; use crate::chain::cosmos::version::Specs; use crate::chain::QueryResponse; use crate::error::Error; +pub async fn get_or_fetch_account<'a>( + grpc_address: &Uri, + account_address: &str, + m_account: &'a mut Option, +) -> Result<&'a mut Account, Error> { + match m_account { + Some(account) => Ok(account), + None => { + let account = query_account(grpc_address, account_address).await?; + *m_account = Some(account.into()); + + Ok(m_account + .as_mut() + .expect("account was supposedly just cached")) + } + } +} + +pub async fn refresh_account<'a>( + grpc_address: &Uri, + account_address: &str, + m_account: &'a mut Account, +) -> Result<(), Error> { + let account = query_account(grpc_address, account_address).await?; + + info!( + sequence = %account.sequence, + number = %account.account_number, + "refresh: retrieved account", + ); + + *m_account = account.into(); + + Ok(()) +} + /// Uses the GRPC client to retrieve the account sequence pub async fn query_account( grpc_address: &Uri, diff --git a/relayer/src/chain/cosmos/retry.rs b/relayer/src/chain/cosmos/retry.rs index b39396626f..c3d1d3d88f 100644 --- a/relayer/src/chain/cosmos/retry.rs +++ b/relayer/src/chain/cosmos/retry.rs @@ -9,9 +9,9 @@ use tendermint_rpc::{HttpClient, Url}; use tonic::codegen::http::Uri; use tracing::{debug, error, warn}; -use crate::chain::cosmos::account::Account; -use crate::chain::cosmos::query::query_account; +use crate::chain::cosmos::query::refresh_account; use crate::chain::cosmos::tx::estimate_fee_and_send_tx; +use crate::chain::cosmos::types::account::Account; use crate::config::types::Memo; use crate::config::ChainConfig; use crate::error::Error; @@ -47,7 +47,32 @@ pub const INCORRECT_ACCOUNT_SEQUENCE_ERR: u32 = 32; /// Upon case #1, we do not retry submitting the same tx (retry happens /// nonetheless at the worker `step` level). Upon case #2, we retry /// submitting the same transaction. -pub fn send_tx_with_account_sequence_retry<'a>( +pub async fn send_tx_with_account_sequence_retry( + config: &ChainConfig, + rpc_client: &HttpClient, + rpc_address: &Url, + grpc_address: &Uri, + key_entry: &KeyEntry, + tx_memo: &Memo, + account: &mut Account, + messages: Vec, + retry_counter: u64, +) -> Result { + do_send_tx_with_account_sequence_retry( + config, + rpc_client, + rpc_address, + grpc_address, + key_entry, + tx_memo, + account, + messages, + retry_counter, + ) + .await +} + +fn do_send_tx_with_account_sequence_retry<'a>( config: &'a ChainConfig, rpc_client: &'a HttpClient, rpc_address: &'a Url, @@ -81,7 +106,7 @@ pub fn 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).await?; + refresh_account(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. @@ -98,10 +123,10 @@ pub fn 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).await?; + refresh_account(grpc_address, &key_entry.account, account).await?; // Now retry. - send_tx_with_account_sequence_retry( + do_send_tx_with_account_sequence_retry( config, rpc_client, rpc_address, @@ -155,18 +180,6 @@ pub fn send_tx_with_account_sequence_retry<'a>( }) } -async fn refresh_account( - grpc_address: &Uri, - key_entry: &KeyEntry, - account: &mut Account, -) -> Result<(), Error> { - *account = query_account(grpc_address, &key_entry.account) - .await? - .into(); - - Ok(()) -} - pub fn wait_for_block_commits(max_total_wait: Duration) -> impl Iterator { let backoff_millis = 300; // The periodic backoff let count: usize = (max_total_wait.as_millis() / backoff_millis as u128) as usize; diff --git a/relayer/src/chain/cosmos/tx.rs b/relayer/src/chain/cosmos/tx.rs index fde9489ba9..477dea13e5 100644 --- a/relayer/src/chain/cosmos/tx.rs +++ b/relayer/src/chain/cosmos/tx.rs @@ -4,9 +4,9 @@ use tendermint_rpc::endpoint::broadcast::tx_sync::Response; use tendermint_rpc::{Client, HttpClient, Url}; use tonic::codegen::http::Uri; -use crate::chain::cosmos::account::{AccountNumber, AccountSequence}; use crate::chain::cosmos::encode::sign_and_encode_tx; use crate::chain::cosmos::estimate::estimate_tx_fees; +use crate::chain::cosmos::types::account::{AccountNumber, AccountSequence}; use crate::config::types::Memo; use crate::config::ChainConfig; use crate::error::Error; @@ -48,7 +48,7 @@ pub async fn estimate_fee_and_send_tx( .await } -pub async fn send_tx_with_fee( +async fn send_tx_with_fee( config: &ChainConfig, rpc_client: &HttpClient, rpc_address: &Url, @@ -75,7 +75,7 @@ pub async fn send_tx_with_fee( } /// Perform a `broadcast_tx_sync`, and return the corresponding deserialized response data. -pub async fn broadcast_tx_sync( +async fn broadcast_tx_sync( rpc_client: &HttpClient, rpc_address: &Url, data: Vec, diff --git a/relayer/src/chain/cosmos/types/account.rs b/relayer/src/chain/cosmos/types/account.rs new file mode 100644 index 0000000000..5350c7b373 --- /dev/null +++ b/relayer/src/chain/cosmos/types/account.rs @@ -0,0 +1,70 @@ +use core::fmt; +use ibc_proto::cosmos::auth::v1beta1::BaseAccount; + +/// Wrapper for account number and sequence number. +/// +/// More fields may be added later. +#[derive(Clone, Debug, PartialEq)] +pub struct Account { + // pub address: String, + // pub pub_key: Option, + pub number: AccountNumber, + pub sequence: AccountSequence, +} + +impl From for Account { + fn from(value: BaseAccount) -> Self { + Self { + number: AccountNumber::new(value.account_number), + sequence: AccountSequence::new(value.sequence), + } + } +} + +/// Newtype for account numbers +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct AccountNumber(u64); + +impl AccountNumber { + pub fn new(number: u64) -> Self { + Self(number) + } + + pub fn to_u64(self) -> u64 { + self.0 + } +} + +impl fmt::Display for AccountNumber { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +/// Newtype for account sequence numbers +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct AccountSequence(u64); + +impl AccountSequence { + pub fn new(sequence: u64) -> Self { + Self(sequence) + } + + pub fn to_u64(self) -> u64 { + self.0 + } + + pub fn increment(self) -> Self { + Self(self.0 + 1) + } + + pub fn increment_mut(&mut self) { + self.0 += 1 + } +} + +impl fmt::Display for AccountSequence { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} diff --git a/relayer/src/chain/cosmos/types/mod.rs b/relayer/src/chain/cosmos/types/mod.rs index f1dfddc9c0..9cadaec14a 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 gas_config; pub mod signed_tx; From 4d791fa9d5cced77ab84d15cc65cb34570424ac0 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Mon, 4 Apr 2022 12:02:34 +0200 Subject: [PATCH 11/30] Refactor query_tx --- relayer/src/chain/cosmos.rs | 262 +++++---------------------- relayer/src/chain/cosmos/query.rs | 2 + relayer/src/chain/cosmos/query/tx.rs | 250 +++++++++++++++++++++++++ relayer/src/chain/cosmos/retry.rs | 9 +- relayer/src/chain/cosmos/wait.rs | 8 + 5 files changed, 305 insertions(+), 226 deletions(-) create mode 100644 relayer/src/chain/cosmos/query/tx.rs create mode 100644 relayer/src/chain/cosmos/wait.rs diff --git a/relayer/src/chain/cosmos.rs b/relayer/src/chain/cosmos.rs index 5e67b31cef..3e740af7e4 100644 --- a/relayer/src/chain/cosmos.rs +++ b/relayer/src/chain/cosmos.rs @@ -19,7 +19,6 @@ use tendermint::{ }; use tendermint_light_client_verifier::types::LightBlock as TMLightBlock; use tendermint_proto::Protobuf; -use tendermint_rpc::endpoint::tx::Response as ResultTx; use tendermint_rpc::{ endpoint::broadcast::tx_sync::Response, endpoint::status, Client, HttpClient, Order, }; @@ -30,13 +29,10 @@ use tracing::{info, span, trace, warn, Level}; use ibc::clients::ics07_tendermint::client_state::{AllowUpdate, ClientState}; use ibc::clients::ics07_tendermint::consensus_state::ConsensusState as TMConsensusState; use ibc::clients::ics07_tendermint::header::Header as TmHeader; -use ibc::core::ics02_client::client_consensus::{ - AnyConsensusState, AnyConsensusStateWithHeight, QueryClientEventRequest, -}; +use ibc::core::ics02_client::client_consensus::{AnyConsensusState, AnyConsensusStateWithHeight}; use ibc::core::ics02_client::client_state::{AnyClientState, IdentifiedAnyClientState}; use ibc::core::ics02_client::client_type::ClientType; use ibc::core::ics02_client::error::Error as ClientError; -use ibc::core::ics02_client::events as ClientEvents; use ibc::core::ics03_connection::connection::{ConnectionEnd, IdentifiedConnectionEnd}; use ibc::core::ics04_channel::channel::{ ChannelEnd, IdentifiedChannelEnd, QueryPacketEventDataRequest, @@ -50,7 +46,7 @@ use ibc::core::ics24_host::path::{ ConnectionsPath, ReceiptsPath, SeqRecvsPath, }; use ibc::core::ics24_host::{ClientUpgradePath, Path, IBC_QUERY_PATH, SDK_UPGRADE_QUERY_PATH}; -use ibc::events::{from_tx_response_event, IbcEvent}; +use ibc::events::IbcEvent; use ibc::query::QueryBlockRequest; use ibc::query::{QueryTxHash, QueryTxRequest}; use ibc::signer::Signer; @@ -70,13 +66,14 @@ use ibc_proto::ibc::core::connection::v1::{ use crate::chain::cosmos::encode::encode_to_bech32; use crate::chain::cosmos::gas::{calculate_fee, mul_ceil}; +use crate::chain::cosmos::query::tx::query_txs; use crate::chain::cosmos::query::{ - abci_query, fetch_version_specs, get_or_fetch_account, header_query, packet_query, - tx_hash_query, + abci_query, fetch_version_specs, get_or_fetch_account, packet_query, }; use crate::chain::cosmos::retry::send_tx_with_account_sequence_retry; use crate::chain::cosmos::types::account::Account; use crate::chain::cosmos::types::gas_config::DEFAULT_MAX_GAS; +use crate::chain::cosmos::wait::wait_for_block_commits; use crate::chain::tx::TrackedMsgs; use crate::chain::{ChainEndpoint, HealthCheck}; use crate::chain::{QueryResponse, StatusResponse}; @@ -99,6 +96,7 @@ pub mod simulate; pub mod tx; pub mod types; pub mod version; +pub mod wait; /// fraction of the maximum block size defined in the Tendermint core consensus parameters. pub const GENESIS_MAX_BYTES_MAX_FRACTION: f64 = 0.9; @@ -430,50 +428,47 @@ impl CosmosSdkChain { thread::sleep(Duration::from_millis(200)); let start = Instant::now(); - let result = retry_with_index( - retry::wait_for_block_commits(self.config.rpc_timeout), - |index| { - if all_tx_results_found(&tx_sync_results) { - trace!( - id = %self.id(), - "wait_for_block_commits: retrieved {} tx results after {} tries ({}ms)", - tx_sync_results.len(), - index, - start.elapsed().as_millis() - ); + let result = retry_with_index(wait_for_block_commits(self.config.rpc_timeout), |index| { + if all_tx_results_found(&tx_sync_results) { + trace!( + id = %self.id(), + "wait_for_block_commits: retrieved {} tx results after {} tries ({}ms)", + tx_sync_results.len(), + index, + start.elapsed().as_millis() + ); - // All transactions confirmed - return RetryResult::Ok(()); - } + // All transactions confirmed + return RetryResult::Ok(()); + } - for TxSyncResult { response, events } in tx_sync_results.iter_mut() { - // If this transaction was not committed, determine whether it was because it failed - // or because it hasn't been committed yet. - if empty_event_present(events) { - // If the transaction failed, replace the events with an error, - // so that we don't attempt to resolve the transaction later on. - if response.code.value() != 0 { - *events = vec![IbcEvent::ChainError(format!( + for TxSyncResult { response, events } in tx_sync_results.iter_mut() { + // If this transaction was not committed, determine whether it was because it failed + // or because it hasn't been committed yet. + if empty_event_present(events) { + // If the transaction failed, replace the events with an error, + // so that we don't attempt to resolve the transaction later on. + if response.code.value() != 0 { + *events = vec![IbcEvent::ChainError(format!( "deliver_tx on chain {} for Tx hash {} reports error: code={:?}, log={:?}", self.id(), response.hash, response.code, response.log ))]; - // Otherwise, try to resolve transaction hash to the corresponding events. - } else if let Ok(events_per_tx) = - self.query_txs(QueryTxRequest::Transaction(QueryTxHash(response.hash))) - { - // If we get events back, progress was made, so we replace the events - // with the new ones. in both cases we will check in the next iteration - // whether or not the transaction was fully committed. - if !events_per_tx.is_empty() { - *events = events_per_tx; - } + // Otherwise, try to resolve transaction hash to the corresponding events. + } else if let Ok(events_per_tx) = + self.query_txs(QueryTxRequest::Transaction(QueryTxHash(response.hash))) + { + // If we get events back, progress was made, so we replace the events + // with the new ones. in both cases we will check in the next iteration + // whether or not the transaction was fully committed. + if !events_per_tx.is_empty() { + *events = events_per_tx; } } } - RetryResult::Retry(index) - }, - ); + } + RetryResult::Retry(index) + }); match result { // All transactions confirmed @@ -1364,99 +1359,12 @@ impl ChainEndpoint for CosmosSdkChain { crate::time!("query_txs"); crate::telemetry!(query, self.id(), "query_txs"); - match request { - QueryTxRequest::Packet(request) => { - crate::time!("query_txs: query packet events"); - - let mut result: Vec = vec![]; - - for seq in &request.sequences { - // query first (and only) Tx that includes the event specified in the query request - let response = self - .block_on(self.rpc_client.tx_search( - packet_query(&request, *seq), - false, - 1, - 1, // get only the first Tx matching the query - Order::Ascending, - )) - .map_err(|e| Error::rpc(self.config.rpc_addr.clone(), e))?; - - assert!( - response.txs.len() <= 1, - "packet_from_tx_search_response: unexpected number of txs" - ); - - if response.txs.is_empty() { - continue; - } - - if let Some(event) = packet_from_tx_search_response( - self.id(), - &request, - *seq, - response.txs[0].clone(), - ) { - result.push(event); - } - } - Ok(result) - } - - QueryTxRequest::Client(request) => { - crate::time!("query_txs: single client update event"); - - // query the first Tx that includes the event matching the client request - // Note: it is possible to have multiple Tx-es for same client and consensus height. - // In this case it must be true that the client updates were performed with tha - // same header as the first one, otherwise a subsequent transaction would have - // failed on chain. Therefore only one Tx is of interest and current API returns - // the first one. - let mut response = self - .block_on(self.rpc_client.tx_search( - header_query(&request), - false, - 1, - 1, // get only the first Tx matching the query - Order::Ascending, - )) - .map_err(|e| Error::rpc(self.config.rpc_addr.clone(), e))?; - - if response.txs.is_empty() { - return Ok(vec![]); - } - - // the response must include a single Tx as specified in the query. - assert!( - response.txs.len() <= 1, - "packet_from_tx_search_response: unexpected number of txs" - ); - - let tx = response.txs.remove(0); - let event = update_client_from_tx_search_response(self.id(), &request, tx); - - Ok(event.into_iter().collect()) - } - - QueryTxRequest::Transaction(tx) => { - let mut response = self - .block_on(self.rpc_client.tx_search( - tx_hash_query(&tx), - false, - 1, - 1, // get only the first Tx matching the query - Order::Ascending, - )) - .map_err(|e| Error::rpc(self.config.rpc_addr.clone(), e))?; - - if response.txs.is_empty() { - Ok(vec![]) - } else { - let tx = response.txs.remove(0); - Ok(all_ibc_events_from_tx_search_response(self.id(), tx)) - } - } - } + self.block_on(query_txs( + self.id(), + &self.rpc_client, + &self.config.rpc_addr, + request, + )) } fn query_blocks( @@ -1703,88 +1611,6 @@ impl ChainEndpoint for CosmosSdkChain { } } -// Extract the packet events from the query_txs RPC response. For any given -// packet query, there is at most one Tx matching such query. Moreover, a Tx may -// contain several events, but a single one must match the packet query. -// For example, if we're querying for the packet with sequence 3 and this packet -// was committed in some Tx along with the packet with sequence 4, the response -// will include both packets. For this reason, we iterate all packets in the Tx, -// searching for those that match (which must be a single one). -fn packet_from_tx_search_response( - chain_id: &ChainId, - request: &QueryPacketEventDataRequest, - seq: Sequence, - response: ResultTx, -) -> Option { - let height = ICSHeight::new(chain_id.version(), u64::from(response.height)); - if request.height != ICSHeight::zero() && height > request.height { - return None; - } - - response - .tx_result - .events - .into_iter() - .find_map(|ev| filter_matching_event(ev, request, seq)) -} - -// Extracts from the Tx the update client event for the requested client and height. -// Note: in the Tx, there may have been multiple events, some of them may be -// for update of other clients that are not relevant to the request. -// For example, if we're querying for a transaction that includes the update for client X at -// consensus height H, it is possible that the transaction also includes an update client -// for client Y at consensus height H'. This is the reason the code iterates all event fields in the -// returned Tx to retrieve the relevant ones. -// Returns `None` if no matching event was found. -fn update_client_from_tx_search_response( - chain_id: &ChainId, - request: &QueryClientEventRequest, - response: ResultTx, -) -> Option { - let height = ICSHeight::new(chain_id.version(), u64::from(response.height)); - if request.height != ICSHeight::zero() && height > request.height { - return None; - } - - response - .tx_result - .events - .into_iter() - .filter(|event| event.type_str == request.event_id.as_str()) - .flat_map(|event| ClientEvents::try_from_tx(&event)) - .flat_map(|event| match event { - IbcEvent::UpdateClient(mut update) => { - update.common.height = height; - Some(update) - } - _ => None, - }) - .find(|update| { - update.common.client_id == request.client_id - && update.common.consensus_height == request.consensus_height - }) - .map(IbcEvent::UpdateClient) -} - -fn all_ibc_events_from_tx_search_response(chain_id: &ChainId, response: ResultTx) -> Vec { - let height = ICSHeight::new(chain_id.version(), u64::from(response.height)); - let deliver_tx_result = response.tx_result; - if deliver_tx_result.code.is_err() { - return vec![IbcEvent::ChainError(format!( - "deliver_tx for {} reports error: code={:?}, log={:?}", - response.hash, deliver_tx_result.code, deliver_tx_result.log - ))]; - } - - let mut result = vec![]; - for event in deliver_tx_result.events { - if let Some(ibc_ev) = from_tx_response_event(height, &event) { - result.push(ibc_ev); - } - } - result -} - fn filter_matching_event( event: Event, request: &QueryPacketEventDataRequest, diff --git a/relayer/src/chain/cosmos/query.rs b/relayer/src/chain/cosmos/query.rs index dfa5410317..e86d820f1c 100644 --- a/relayer/src/chain/cosmos/query.rs +++ b/relayer/src/chain/cosmos/query.rs @@ -21,6 +21,8 @@ use crate::chain::cosmos::version::Specs; use crate::chain::QueryResponse; use crate::error::Error; +pub mod tx; + pub async fn get_or_fetch_account<'a>( grpc_address: &Uri, account_address: &str, diff --git a/relayer/src/chain/cosmos/query/tx.rs b/relayer/src/chain/cosmos/query/tx.rs new file mode 100644 index 0000000000..dab694a53c --- /dev/null +++ b/relayer/src/chain/cosmos/query/tx.rs @@ -0,0 +1,250 @@ +use ibc::core::ics02_client::client_consensus::QueryClientEventRequest; +use ibc::core::ics02_client::events as ClientEvents; +use ibc::core::ics04_channel::channel::QueryPacketEventDataRequest; +use ibc::core::ics04_channel::events as ChannelEvents; +use ibc::core::ics04_channel::packet::{Packet, Sequence}; +use ibc::core::ics24_host::identifier::ChainId; +use ibc::events::{from_tx_response_event, IbcEvent}; +use ibc::query::QueryTxRequest; +use ibc::Height as ICSHeight; +use tendermint::abci::Event; +use tendermint_rpc::endpoint::tx::Response as ResultTx; +use tendermint_rpc::{Client, HttpClient, Order, Url}; + +use crate::chain::cosmos::query::{header_query, packet_query, tx_hash_query}; +use crate::error::Error; + +/// This function queries transactions for events matching certain criteria. +/// 1. Client Update request - returns a vector with at most one update client event +/// 2. Packet event request - returns at most one packet event for each sequence specified +/// in the request. +/// Note - there is no way to format the packet query such that it asks for Tx-es with either +/// sequence (the query conditions can only be AND-ed). +/// There is a possibility to include "<=" and ">=" conditions but it doesn't work with +/// string attributes (sequence is emmitted as a string). +/// Therefore, for packets we perform one tx_search for each sequence. +/// Alternatively, a single query for all packets could be performed but it would return all +/// packets ever sent. +pub async fn query_txs( + chain_id: &ChainId, + rpc_client: &HttpClient, + rpc_address: &Url, + request: QueryTxRequest, +) -> Result, Error> { + crate::time!("query_txs"); + crate::telemetry!(query, chain_id, "query_txs"); + + match request { + QueryTxRequest::Packet(request) => { + crate::time!("query_txs: query packet events"); + + let mut result: Vec = vec![]; + + for seq in &request.sequences { + // query first (and only) Tx that includes the event specified in the query request + let response = rpc_client + .tx_search( + packet_query(&request, *seq), + false, + 1, + 1, // get only the first Tx matching the query + Order::Ascending, + ) + .await + .map_err(|e| Error::rpc(rpc_address.clone(), e))?; + + assert!( + response.txs.len() <= 1, + "packet_from_tx_search_response: unexpected number of txs" + ); + + if response.txs.is_empty() { + continue; + } + + if let Some(event) = packet_from_tx_search_response( + chain_id, + &request, + *seq, + response.txs[0].clone(), + ) { + result.push(event); + } + } + Ok(result) + } + + QueryTxRequest::Client(request) => { + crate::time!("query_txs: single client update event"); + + // query the first Tx that includes the event matching the client request + // Note: it is possible to have multiple Tx-es for same client and consensus height. + // In this case it must be true that the client updates were performed with tha + // same header as the first one, otherwise a subsequent transaction would have + // failed on chain. Therefore only one Tx is of interest and current API returns + // the first one. + let mut response = rpc_client + .tx_search( + header_query(&request), + false, + 1, + 1, // get only the first Tx matching the query + Order::Ascending, + ) + .await + .map_err(|e| Error::rpc(rpc_address.clone(), e))?; + + if response.txs.is_empty() { + return Ok(vec![]); + } + + // the response must include a single Tx as specified in the query. + assert!( + response.txs.len() <= 1, + "packet_from_tx_search_response: unexpected number of txs" + ); + + let tx = response.txs.remove(0); + let event = update_client_from_tx_search_response(chain_id, &request, tx); + + Ok(event.into_iter().collect()) + } + + QueryTxRequest::Transaction(tx) => { + let mut response = rpc_client + .tx_search( + tx_hash_query(&tx), + false, + 1, + 1, // get only the first Tx matching the query + Order::Ascending, + ) + .await + .map_err(|e| Error::rpc(rpc_address.clone(), e))?; + + if response.txs.is_empty() { + Ok(vec![]) + } else { + let tx = response.txs.remove(0); + Ok(all_ibc_events_from_tx_search_response(chain_id, tx)) + } + } + } +} + +// Extracts from the Tx the update client event for the requested client and height. +// Note: in the Tx, there may have been multiple events, some of them may be +// for update of other clients that are not relevant to the request. +// For example, if we're querying for a transaction that includes the update for client X at +// consensus height H, it is possible that the transaction also includes an update client +// for client Y at consensus height H'. This is the reason the code iterates all event fields in the +// returned Tx to retrieve the relevant ones. +// Returns `None` if no matching event was found. +fn update_client_from_tx_search_response( + chain_id: &ChainId, + request: &QueryClientEventRequest, + response: ResultTx, +) -> Option { + let height = ICSHeight::new(chain_id.version(), u64::from(response.height)); + if request.height != ICSHeight::zero() && height > request.height { + return None; + } + + response + .tx_result + .events + .into_iter() + .filter(|event| event.type_str == request.event_id.as_str()) + .flat_map(|event| ClientEvents::try_from_tx(&event)) + .flat_map(|event| match event { + IbcEvent::UpdateClient(mut update) => { + update.common.height = height; + Some(update) + } + _ => None, + }) + .find(|update| { + update.common.client_id == request.client_id + && update.common.consensus_height == request.consensus_height + }) + .map(IbcEvent::UpdateClient) +} + +// Extract the packet events from the query_txs RPC response. For any given +// packet query, there is at most one Tx matching such query. Moreover, a Tx may +// contain several events, but a single one must match the packet query. +// For example, if we're querying for the packet with sequence 3 and this packet +// was committed in some Tx along with the packet with sequence 4, the response +// will include both packets. For this reason, we iterate all packets in the Tx, +// searching for those that match (which must be a single one). +fn packet_from_tx_search_response( + chain_id: &ChainId, + request: &QueryPacketEventDataRequest, + seq: Sequence, + response: ResultTx, +) -> Option { + let height = ICSHeight::new(chain_id.version(), u64::from(response.height)); + if request.height != ICSHeight::zero() && height > request.height { + return None; + } + + response + .tx_result + .events + .into_iter() + .find_map(|ev| filter_matching_event(ev, request, seq)) +} + +fn filter_matching_event( + event: Event, + request: &QueryPacketEventDataRequest, + seq: Sequence, +) -> Option { + fn matches_packet( + request: &QueryPacketEventDataRequest, + seq: Sequence, + packet: &Packet, + ) -> bool { + packet.source_port == request.source_port_id + && packet.source_channel == request.source_channel_id + && packet.destination_port == request.destination_port_id + && packet.destination_channel == request.destination_channel_id + && packet.sequence == seq + } + + if event.type_str != request.event_id.as_str() { + return None; + } + + let ibc_event = ChannelEvents::try_from_tx(&event)?; + match ibc_event { + IbcEvent::SendPacket(ref send_ev) if matches_packet(request, seq, &send_ev.packet) => { + Some(ibc_event) + } + IbcEvent::WriteAcknowledgement(ref ack_ev) + if matches_packet(request, seq, &ack_ev.packet) => + { + Some(ibc_event) + } + _ => None, + } +} + +fn all_ibc_events_from_tx_search_response(chain_id: &ChainId, response: ResultTx) -> Vec { + let height = ICSHeight::new(chain_id.version(), u64::from(response.height)); + let deliver_tx_result = response.tx_result; + if deliver_tx_result.code.is_err() { + return vec![IbcEvent::ChainError(format!( + "deliver_tx for {} reports error: code={:?}, log={:?}", + response.hash, deliver_tx_result.code, deliver_tx_result.log + ))]; + } + + let mut result = vec![]; + for event in deliver_tx_result.events { + if let Some(ibc_ev) = from_tx_response_event(height, &event) { + result.push(ibc_ev); + } + } + result +} diff --git a/relayer/src/chain/cosmos/retry.rs b/relayer/src/chain/cosmos/retry.rs index c3d1d3d88f..450ddc419e 100644 --- a/relayer/src/chain/cosmos/retry.rs +++ b/relayer/src/chain/cosmos/retry.rs @@ -17,7 +17,6 @@ use crate::config::ChainConfig; use crate::error::Error; use crate::keyring::KeyEntry; use crate::sdk_error::sdk_error_from_tx_sync_error_code; -use crate::util::retry::Fixed; // Maximum number of retries for send_tx in the case of // an account sequence mismatch at broadcast step. @@ -180,14 +179,8 @@ fn do_send_tx_with_account_sequence_retry<'a>( }) } -pub fn wait_for_block_commits(max_total_wait: Duration) -> impl Iterator { - let backoff_millis = 300; // The periodic backoff - let count: usize = (max_total_wait.as_millis() / backoff_millis as u128) as usize; - Fixed::from_millis(backoff_millis).take(count) -} - /// Determine whether the given error yielded by `tx_simulate` -/// indicates that the current sequence number cached in Hermes +/// indicates hat the current sequence number cached in Hermes /// may be out-of-sync with the full node's version of the s.n. fn mismatching_account_sequence_number(e: &Error) -> bool { use crate::error::ErrorDetail::*; diff --git a/relayer/src/chain/cosmos/wait.rs b/relayer/src/chain/cosmos/wait.rs new file mode 100644 index 0000000000..9d29213af8 --- /dev/null +++ b/relayer/src/chain/cosmos/wait.rs @@ -0,0 +1,8 @@ +use crate::util::retry::Fixed; +use core::time::Duration; + +pub fn wait_for_block_commits(max_total_wait: Duration) -> impl Iterator { + let backoff_millis = 300; // The periodic backoff + let count: usize = (max_total_wait.as_millis() / backoff_millis as u128) as usize; + Fixed::from_millis(backoff_millis).take(count) +} From 65f9de65f8c4323d9bd82384927ba7ed4ad4da48 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Mon, 4 Apr 2022 21:49:02 +0200 Subject: [PATCH 12/30] Refactor wait_for_block_commits --- relayer/src/chain/cosmos.rs | 96 +++------------------ relayer/src/chain/cosmos/wait.rs | 138 +++++++++++++++++++++++++++++-- 2 files changed, 146 insertions(+), 88 deletions(-) diff --git a/relayer/src/chain/cosmos.rs b/relayer/src/chain/cosmos.rs index 3e740af7e4..3a9ebc7026 100644 --- a/relayer/src/chain/cosmos.rs +++ b/relayer/src/chain/cosmos.rs @@ -10,7 +10,6 @@ use std::{thread, time::Instant}; use bitcoin::hashes::hex::ToHex; use ibc_proto::google::protobuf::Any; -use itertools::Itertools; use tendermint::block::Height; use tendermint::consensus::Params as ConsensusParams; use tendermint::{ @@ -24,7 +23,7 @@ use tendermint_rpc::{ }; use tokio::runtime::Runtime as TokioRuntime; use tonic::codegen::http::Uri; -use tracing::{info, span, trace, warn, Level}; +use tracing::{span, warn, Level}; use ibc::clients::ics07_tendermint::client_state::{AllowUpdate, ClientState}; use ibc::clients::ics07_tendermint::consensus_state::ConsensusState as TMConsensusState; @@ -48,7 +47,7 @@ use ibc::core::ics24_host::path::{ use ibc::core::ics24_host::{ClientUpgradePath, Path, IBC_QUERY_PATH, SDK_UPGRADE_QUERY_PATH}; use ibc::events::IbcEvent; use ibc::query::QueryBlockRequest; -use ibc::query::{QueryTxHash, QueryTxRequest}; +use ibc::query::QueryTxRequest; use ibc::signer::Signer; use ibc::Height as ICSHeight; use ibc_proto::cosmos::staking::v1beta1::Params as StakingParams; @@ -73,7 +72,7 @@ use crate::chain::cosmos::query::{ use crate::chain::cosmos::retry::send_tx_with_account_sequence_retry; use crate::chain::cosmos::types::account::Account; use crate::chain::cosmos::types::gas_config::DEFAULT_MAX_GAS; -use crate::chain::cosmos::wait::wait_for_block_commits; +use crate::chain::cosmos::wait::{wait_for_block_commits, TxSyncResult}; use crate::chain::tx::TrackedMsgs; use crate::chain::{ChainEndpoint, HealthCheck}; use crate::chain::{QueryResponse, StatusResponse}; @@ -83,7 +82,6 @@ use crate::event::monitor::{EventMonitor, EventReceiver, TxMonitorCmd}; use crate::keyring::{KeyEntry, KeyRing}; use crate::light_client::tendermint::LightClient as TmLightClient; use crate::light_client::{LightClient, Verified}; -use crate::util::retry::{retry_with_index, RetryResult}; pub mod batch; pub mod compatibility; @@ -413,69 +411,18 @@ impl CosmosSdkChain { &self, mut tx_sync_results: Vec, ) -> Result, Error> { - let hashes = tx_sync_results - .iter() - .map(|res| res.response.hash.to_string()) - .join(", "); - - info!( - id = %self.id(), - "wait_for_block_commits: waiting for commit of tx hashes(s) {}", - hashes - ); + let runtime = self.rt.clone(); - // Wait a little bit initially - thread::sleep(Duration::from_millis(200)); - - let start = Instant::now(); - let result = retry_with_index(wait_for_block_commits(self.config.rpc_timeout), |index| { - if all_tx_results_found(&tx_sync_results) { - trace!( - id = %self.id(), - "wait_for_block_commits: retrieved {} tx results after {} tries ({}ms)", - tx_sync_results.len(), - index, - start.elapsed().as_millis() - ); - - // All transactions confirmed - return RetryResult::Ok(()); - } + runtime.block_on(wait_for_block_commits( + self.id(), + &self.rpc_client, + &self.config.rpc_addr, + &self.config.rpc_timeout, + &Instant::now(), + &mut tx_sync_results, + ))?; - for TxSyncResult { response, events } in tx_sync_results.iter_mut() { - // If this transaction was not committed, determine whether it was because it failed - // or because it hasn't been committed yet. - if empty_event_present(events) { - // If the transaction failed, replace the events with an error, - // so that we don't attempt to resolve the transaction later on. - if response.code.value() != 0 { - *events = vec![IbcEvent::ChainError(format!( - "deliver_tx on chain {} for Tx hash {} reports error: code={:?}, log={:?}", - self.id(), response.hash, response.code, response.log - ))]; - - // Otherwise, try to resolve transaction hash to the corresponding events. - } else if let Ok(events_per_tx) = - self.query_txs(QueryTxRequest::Transaction(QueryTxHash(response.hash))) - { - // If we get events back, progress was made, so we replace the events - // with the new ones. in both cases we will check in the next iteration - // whether or not the transaction was fully committed. - if !events_per_tx.is_empty() { - *events = events_per_tx; - } - } - } - } - RetryResult::Retry(index) - }); - - match result { - // All transactions confirmed - Ok(()) => Ok(tx_sync_results), - // Did not find confirmation - Err(_) => Err(Error::tx_no_confirmation()), - } + Ok(tx_sync_results) } fn trusting_period(&self, unbonding_period: Duration) -> Duration { @@ -517,16 +464,6 @@ impl CosmosSdkChain { } } -fn empty_event_present(events: &[IbcEvent]) -> bool { - events.iter().any(|ev| matches!(ev, IbcEvent::Empty(_))) -} - -fn all_tx_results_found(tx_sync_results: &[TxSyncResult]) -> bool { - tx_sync_results - .iter() - .all(|r| !empty_event_present(&r.events)) -} - impl ChainEndpoint for CosmosSdkChain { type LightBlock = TMLightBlock; type Header = TmHeader; @@ -1657,13 +1594,6 @@ fn client_id_suffix(client_id: &ClientId) -> Option { .and_then(|e| e.parse::().ok()) } -pub struct TxSyncResult { - // the broadcast_tx_sync response - response: Response, - // the events generated by a Tx once executed - events: Vec, -} - fn do_health_check(chain: &CosmosSdkChain) -> Result<(), Error> { let chain_id = chain.id(); let grpc_address = chain.grpc_addr.to_string(); diff --git a/relayer/src/chain/cosmos/wait.rs b/relayer/src/chain/cosmos/wait.rs index 9d29213af8..debb77bb12 100644 --- a/relayer/src/chain/cosmos/wait.rs +++ b/relayer/src/chain/cosmos/wait.rs @@ -1,8 +1,136 @@ -use crate::util::retry::Fixed; +use core::future::Future; +use core::pin::Pin; use core::time::Duration; +use ibc::core::ics24_host::identifier::ChainId; +use ibc::events::IbcEvent; +use ibc::query::{QueryTxHash, QueryTxRequest}; +use itertools::Itertools; +use std::thread; +use std::time::Instant; +use tendermint_rpc::endpoint::broadcast::tx_sync::Response; +use tendermint_rpc::{HttpClient, Url}; -pub fn wait_for_block_commits(max_total_wait: Duration) -> impl Iterator { - let backoff_millis = 300; // The periodic backoff - let count: usize = (max_total_wait.as_millis() / backoff_millis as u128) as usize; - Fixed::from_millis(backoff_millis).take(count) +use crate::chain::cosmos::query::tx::query_txs; +use crate::error::Error; +use tracing::{info, trace}; + +const WAIT_BACKOFF: Duration = Duration::from_millis(300); + +pub struct TxSyncResult { + // the broadcast_tx_sync response + pub response: Response, + // the events generated by a Tx once executed + pub events: Vec, +} + +/// Given a vector of `TxSyncResult` elements, +/// each including a transaction response hash for one or more messages, periodically queries the chain +/// with the transaction hashes to get the list of IbcEvents included in those transactions. +pub async fn wait_for_block_commits( + chain_id: &ChainId, + rpc_client: &HttpClient, + rpc_address: &Url, + rpc_timeout: &Duration, + start_time: &Instant, + tx_sync_results: &mut Vec, +) -> Result<(), Error> { + do_wait_for_block_commits( + chain_id, + rpc_client, + rpc_address, + rpc_timeout, + start_time, + tx_sync_results, + ) + .await +} + +pub fn do_wait_for_block_commits<'a>( + chain_id: &'a ChainId, + rpc_client: &'a HttpClient, + rpc_address: &'a Url, + rpc_timeout: &'a Duration, + start_time: &'a Instant, + tx_sync_results: &'a mut Vec, +) -> Pin> + 'a>> { + Box::pin(async move { + let hashes = tx_sync_results + .iter() + .map(|res| res.response.hash.to_string()) + .join(", "); + + info!( + id = %chain_id, + "wait_for_block_commits: waiting for commit of tx hashes(s) {}", + hashes + ); + + let elapsed = start_time.elapsed(); + + if all_tx_results_found(tx_sync_results) { + trace!( + id = %chain_id, + "wait_for_block_commits: retrieved {} tx results after {}ms", + tx_sync_results.len(), + elapsed.as_millis(), + ); + + Ok(()) + } else if &elapsed > rpc_timeout { + Err(Error::tx_no_confirmation()) + } else { + thread::sleep(WAIT_BACKOFF); + + for TxSyncResult { response, events } in tx_sync_results.iter_mut() { + // If this transaction was not committed, determine whether it was because it failed + // or because it hasn't been committed yet. + if empty_event_present(events) { + // If the transaction failed, replace the events with an error, + // so that we don't attempt to resolve the transaction later on. + if response.code.value() != 0 { + *events = vec![IbcEvent::ChainError(format!( + "deliver_tx on chain {} for Tx hash {} reports error: code={:?}, log={:?}", + chain_id, response.hash, response.code, response.log + ))]; + + // Otherwise, try to resolve transaction hash to the corresponding events. + } else if let Ok(events_per_tx) = query_txs( + chain_id, + rpc_client, + rpc_address, + QueryTxRequest::Transaction(QueryTxHash(response.hash)), + ) + .await + { + // If we get events back, progress was made, so we replace the events + // with the new ones. in both cases we will check in the next iteration + // whether or not the transaction was fully committed. + if !events_per_tx.is_empty() { + *events = events_per_tx; + } + } + } + } + + do_wait_for_block_commits( + chain_id, + rpc_client, + rpc_address, + rpc_timeout, + start_time, + tx_sync_results, + ) + .await + } + }) +} + +fn empty_event_present(events: &[IbcEvent]) -> bool { + events.iter().any(|ev| matches!(ev, IbcEvent::Empty(_))) +} + +fn all_tx_results_found(tx_sync_results: &[TxSyncResult]) -> bool { + tx_sync_results + .iter() + .all(|r| !empty_event_present(&r.events)) } From 80ab989a40cc25e48496bad284f4c9ab3c80ad8a Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Mon, 4 Apr 2022 22:01:58 +0200 Subject: [PATCH 13/30] Turn wait_for_block_commits into simple loop --- relayer/src/chain/cosmos/wait.rs | 128 ++++++++++++++----------------- 1 file changed, 56 insertions(+), 72 deletions(-) diff --git a/relayer/src/chain/cosmos/wait.rs b/relayer/src/chain/cosmos/wait.rs index debb77bb12..c1bcb08d52 100644 --- a/relayer/src/chain/cosmos/wait.rs +++ b/relayer/src/chain/cosmos/wait.rs @@ -1,5 +1,3 @@ -use core::future::Future; -use core::pin::Pin; use core::time::Duration; use ibc::core::ics24_host::identifier::ChainId; use ibc::events::IbcEvent; @@ -34,37 +32,18 @@ pub async fn wait_for_block_commits( start_time: &Instant, tx_sync_results: &mut Vec, ) -> Result<(), Error> { - do_wait_for_block_commits( - chain_id, - rpc_client, - rpc_address, - rpc_timeout, - start_time, - tx_sync_results, - ) - .await -} + let hashes = tx_sync_results + .iter() + .map(|res| res.response.hash.to_string()) + .join(", "); -pub fn do_wait_for_block_commits<'a>( - chain_id: &'a ChainId, - rpc_client: &'a HttpClient, - rpc_address: &'a Url, - rpc_timeout: &'a Duration, - start_time: &'a Instant, - tx_sync_results: &'a mut Vec, -) -> Pin> + 'a>> { - Box::pin(async move { - let hashes = tx_sync_results - .iter() - .map(|res| res.response.hash.to_string()) - .join(", "); - - info!( - id = %chain_id, - "wait_for_block_commits: waiting for commit of tx hashes(s) {}", - hashes - ); + info!( + id = %chain_id, + "wait_for_block_commits: waiting for commit of tx hashes(s) {}", + hashes + ); + loop { let elapsed = start_time.elapsed(); if all_tx_results_found(tx_sync_results) { @@ -75,54 +54,59 @@ pub fn do_wait_for_block_commits<'a>( elapsed.as_millis(), ); - Ok(()) + return Ok(()); } else if &elapsed > rpc_timeout { - Err(Error::tx_no_confirmation()) + return Err(Error::tx_no_confirmation()); } else { thread::sleep(WAIT_BACKOFF); - for TxSyncResult { response, events } in tx_sync_results.iter_mut() { - // If this transaction was not committed, determine whether it was because it failed - // or because it hasn't been committed yet. - if empty_event_present(events) { - // If the transaction failed, replace the events with an error, - // so that we don't attempt to resolve the transaction later on. - if response.code.value() != 0 { - *events = vec![IbcEvent::ChainError(format!( - "deliver_tx on chain {} for Tx hash {} reports error: code={:?}, log={:?}", - chain_id, response.hash, response.code, response.log - ))]; - - // Otherwise, try to resolve transaction hash to the corresponding events. - } else if let Ok(events_per_tx) = query_txs( - chain_id, - rpc_client, - rpc_address, - QueryTxRequest::Transaction(QueryTxHash(response.hash)), - ) - .await - { - // If we get events back, progress was made, so we replace the events - // with the new ones. in both cases we will check in the next iteration - // whether or not the transaction was fully committed. - if !events_per_tx.is_empty() { - *events = events_per_tx; - } - } - } + for tx_sync_result in tx_sync_results.iter_mut() { + // ignore error + let _ = + update_tx_sync_result(chain_id, rpc_client, rpc_address, tx_sync_result).await; } + } + } +} - do_wait_for_block_commits( - chain_id, - rpc_client, - rpc_address, - rpc_timeout, - start_time, - tx_sync_results, - ) - .await +async fn update_tx_sync_result( + chain_id: &ChainId, + rpc_client: &HttpClient, + rpc_address: &Url, + tx_sync_result: &mut TxSyncResult, +) -> Result<(), Error> { + let TxSyncResult { response, events } = tx_sync_result; + + // If this transaction was not committed, determine whether it was because it failed + // or because it hasn't been committed yet. + if empty_event_present(events) { + // If the transaction failed, replace the events with an error, + // so that we don't attempt to resolve the transaction later on. + if response.code.value() != 0 { + *events = vec![IbcEvent::ChainError(format!( + "deliver_tx on chain {} for Tx hash {} reports error: code={:?}, log={:?}", + chain_id, response.hash, response.code, response.log + ))]; + } + + // Otherwise, try to resolve transaction hash to the corresponding events. + let events_per_tx = query_txs( + chain_id, + rpc_client, + rpc_address, + QueryTxRequest::Transaction(QueryTxHash(response.hash)), + ) + .await?; + + // If we get events back, progress was made, so we replace the events + // with the new ones. in both cases we will check in the next iteration + // whether or not the transaction was fully committed. + if !events_per_tx.is_empty() { + *events = events_per_tx; } - }) + } + + Ok(()) } fn empty_event_present(events: &[IbcEvent]) -> bool { From a84057ec9af45ea642bebc701aa51410a315b447 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Tue, 5 Apr 2022 11:30:16 +0200 Subject: [PATCH 14/30] Refactor send_messages_and_wait_commit --- relayer/src/chain/cosmos.rs | 106 +++++++--------------- relayer/src/chain/cosmos/batch.rs | 71 +++++++++++++-- relayer/src/chain/cosmos/types/mod.rs | 1 + relayer/src/chain/cosmos/types/tx_sync.rs | 9 ++ relayer/src/chain/cosmos/wait.rs | 14 +-- relayer/src/chain/tx.rs | 4 +- 6 files changed, 110 insertions(+), 95 deletions(-) create mode 100644 relayer/src/chain/cosmos/types/tx_sync.rs diff --git a/relayer/src/chain/cosmos.rs b/relayer/src/chain/cosmos.rs index 669a56af80..d09dce1bc3 100644 --- a/relayer/src/chain/cosmos.rs +++ b/relayer/src/chain/cosmos.rs @@ -6,7 +6,7 @@ use core::{ time::Duration, }; use num_bigint::BigInt; -use std::{thread, time::Instant}; +use std::thread; use bitcoin::hashes::hex::ToHex; use ibc_proto::google::protobuf::Any; @@ -63,6 +63,7 @@ use ibc_proto::ibc::core::connection::v1::{ }; use crate::chain::client::ClientSettings; +use crate::chain::cosmos::batch::send_batched_messages_and_wait_commit; use crate::chain::cosmos::encode::encode_to_bech32; use crate::chain::cosmos::gas::{calculate_fee, mul_ceil}; use crate::chain::cosmos::query::tx::query_txs; @@ -72,7 +73,6 @@ use crate::chain::cosmos::query::{ use crate::chain::cosmos::retry::send_tx_with_account_sequence_retry; use crate::chain::cosmos::types::account::Account; use crate::chain::cosmos::types::gas_config::DEFAULT_MAX_GAS; -use crate::chain::cosmos::wait::{wait_for_block_commits, TxSyncResult}; use crate::chain::tx::TrackedMsgs; use crate::chain::{ChainEndpoint, HealthCheck}; use crate::chain::{QueryResponse, StatusResponse}; @@ -393,27 +393,6 @@ impl CosmosSdkChain { .map_err(Error::key_base) } - /// Given a vector of `TxSyncResult` elements, - /// each including a transaction response hash for one or more messages, periodically queries the chain - /// with the transaction hashes to get the list of IbcEvents included in those transactions. - pub fn wait_for_block_commits( - &self, - mut tx_sync_results: Vec, - ) -> Result, Error> { - let runtime = self.rt.clone(); - - runtime.block_on(wait_for_block_commits( - self.id(), - &self.rpc_client, - &self.config.rpc_addr, - &self.config.rpc_timeout, - &Instant::now(), - &mut tx_sync_results, - ))?; - - Ok(tx_sync_results) - } - fn trusting_period(&self, unbonding_period: Duration) -> Duration { self.config .trusting_period @@ -451,6 +430,35 @@ impl CosmosSdkChain { revision_height: u64::from(status.sync_info.latest_block_height), }) } + + async fn do_send_messages_and_wait_commit( + &mut self, + tracked_msgs: TrackedMsgs, + ) -> Result, Error> { + crate::time!("send_messages_and_wait_commit"); + + let _span = + span!(Level::DEBUG, "send_tx_commit", id = %tracked_msgs.tracking_id()).entered(); + + let proto_msgs = tracked_msgs.msgs; + + let key_entry = self.key()?; + + let account = + 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, + &key_entry, + &self.config.memo_prefix, + account, + proto_msgs, + ) + .await + } } impl ChainEndpoint for CosmosSdkChain { @@ -580,57 +588,9 @@ impl ChainEndpoint for CosmosSdkChain { &mut self, tracked_msgs: TrackedMsgs, ) -> Result, Error> { - crate::time!("send_messages_and_wait_commit"); - - let _span = - span!(Level::DEBUG, "send_tx_commit", id = %tracked_msgs.tracking_id()).entered(); - - let proto_msgs = tracked_msgs.messages(); - - if proto_msgs.is_empty() { - return Ok(vec![]); - } - let mut tx_sync_results = vec![]; - - let mut n = 0; - let mut size = 0; - let mut msg_batch = vec![]; - for msg in proto_msgs.iter() { - msg_batch.push(msg.clone()); - let mut buf = Vec::new(); - prost::Message::encode(msg, &mut buf) - .map_err(|e| Error::protobuf_encode(String::from("Message"), e))?; - n += 1; - size += buf.len(); - if n >= self.max_msg_num() || size >= self.max_tx_size() { - let events_per_tx = vec![IbcEvent::default(); msg_batch.len()]; - let tx_sync_result = self.send_tx(msg_batch)?; - tx_sync_results.push(TxSyncResult { - response: tx_sync_result, - events: events_per_tx, - }); - n = 0; - size = 0; - msg_batch = vec![]; - } - } - if !msg_batch.is_empty() { - let events_per_tx = vec![IbcEvent::default(); msg_batch.len()]; - let tx_sync_result = self.send_tx(msg_batch)?; - tx_sync_results.push(TxSyncResult { - response: tx_sync_result, - events: events_per_tx, - }); - } - - let tx_sync_results = self.wait_for_block_commits(tx_sync_results)?; - - let events = tx_sync_results - .into_iter() - .flat_map(|el| el.events) - .collect(); + let runtime = self.rt.clone(); - Ok(events) + runtime.block_on(self.do_send_messages_and_wait_commit(tracked_msgs)) } fn send_messages_and_wait_check_tx( diff --git a/relayer/src/chain/cosmos/batch.rs b/relayer/src/chain/cosmos/batch.rs index 1ecce18053..99b440d9b4 100644 --- a/relayer/src/chain/cosmos/batch.rs +++ b/relayer/src/chain/cosmos/batch.rs @@ -1,17 +1,18 @@ +use ibc::events::IbcEvent; use ibc_proto::google::protobuf::Any; -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::tx_sync::TxSyncResult; +use crate::chain::cosmos::wait::wait_for_block_commits; use crate::config::types::Memo; use crate::config::ChainConfig; use crate::error::Error; use crate::keyring::KeyEntry; -// TODO: use this in send_messages_and_wait_commit -pub async fn send_messages_as_batches( +pub async fn send_batched_messages_and_wait_commit( config: &ChainConfig, rpc_client: &HttpClient, rpc_address: &Url, @@ -20,19 +21,64 @@ pub async fn send_messages_as_batches( tx_memo: &Memo, account: &mut Account, messages: Vec, -) -> Result, Error> { - let max_message_count = config.max_msg_num.0; - let max_tx_size = config.max_tx_size.into(); +) -> Result, Error> { + if messages.is_empty() { + return Ok(Vec::new()); + } + let mut tx_sync_results = send_messages_as_batches( + config, + rpc_client, + rpc_address, + grpc_address, + key_entry, + tx_memo, + account, + messages, + ) + .await?; + + wait_for_block_commits( + &config.id, + rpc_client, + rpc_address, + &config.rpc_timeout, + &mut tx_sync_results, + ) + .await?; + + let events = tx_sync_results + .into_iter() + .flat_map(|el| el.events) + .collect(); + + Ok(events) +} + +pub async fn send_messages_as_batches( + config: &ChainConfig, + rpc_client: &HttpClient, + rpc_address: &Url, + grpc_address: &Uri, + key_entry: &KeyEntry, + tx_memo: &Memo, + account: &mut Account, + messages: Vec, +) -> Result, Error> { if messages.is_empty() { return Ok(Vec::new()); } + let max_message_count = config.max_msg_num.0; + let max_tx_size = config.max_tx_size.into(); + let batches = batch_messages(messages, max_message_count, max_tx_size)?; - let mut responses = Vec::new(); + 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, @@ -46,13 +92,18 @@ pub async fn send_messages_as_batches( ) .await?; - responses.push(response); + let tx_sync_result = TxSyncResult { + response, + events: events_per_tx, + }; + + tx_sync_results.push(tx_sync_result); } - Ok(responses) + Ok(tx_sync_results) } -pub fn batch_messages( +fn batch_messages( messages: Vec, max_message_count: usize, max_tx_size: usize, diff --git a/relayer/src/chain/cosmos/types/mod.rs b/relayer/src/chain/cosmos/types/mod.rs index 9cadaec14a..f40d9f6217 100644 --- a/relayer/src/chain/cosmos/types/mod.rs +++ b/relayer/src/chain/cosmos/types/mod.rs @@ -1,6 +1,7 @@ pub mod account; pub mod gas_config; pub mod signed_tx; +pub mod tx_sync; pub use gas_config::GasConfig; pub use signed_tx::SignedTx; diff --git a/relayer/src/chain/cosmos/types/tx_sync.rs b/relayer/src/chain/cosmos/types/tx_sync.rs new file mode 100644 index 0000000000..9855fcb9ff --- /dev/null +++ b/relayer/src/chain/cosmos/types/tx_sync.rs @@ -0,0 +1,9 @@ +use ibc::events::IbcEvent; +use tendermint_rpc::endpoint::broadcast::tx_sync::Response; + +pub struct TxSyncResult { + // the broadcast_tx_sync response + pub response: Response, + // the events generated by a Tx once executed + pub events: Vec, +} diff --git a/relayer/src/chain/cosmos/wait.rs b/relayer/src/chain/cosmos/wait.rs index c1bcb08d52..b01e12c0ac 100644 --- a/relayer/src/chain/cosmos/wait.rs +++ b/relayer/src/chain/cosmos/wait.rs @@ -5,22 +5,15 @@ use ibc::query::{QueryTxHash, QueryTxRequest}; use itertools::Itertools; use std::thread; use std::time::Instant; -use tendermint_rpc::endpoint::broadcast::tx_sync::Response; use tendermint_rpc::{HttpClient, Url}; +use tracing::{info, trace}; use crate::chain::cosmos::query::tx::query_txs; +use crate::chain::cosmos::types::tx_sync::TxSyncResult; use crate::error::Error; -use tracing::{info, trace}; const WAIT_BACKOFF: Duration = Duration::from_millis(300); -pub struct TxSyncResult { - // the broadcast_tx_sync response - pub response: Response, - // the events generated by a Tx once executed - pub events: Vec, -} - /// Given a vector of `TxSyncResult` elements, /// each including a transaction response hash for one or more messages, periodically queries the chain /// with the transaction hashes to get the list of IbcEvents included in those transactions. @@ -29,9 +22,10 @@ pub async fn wait_for_block_commits( rpc_client: &HttpClient, rpc_address: &Url, rpc_timeout: &Duration, - start_time: &Instant, tx_sync_results: &mut Vec, ) -> Result<(), Error> { + let start_time = Instant::now(); + let hashes = tx_sync_results .iter() .map(|res| res.response.hash.to_string()) diff --git a/relayer/src/chain/tx.rs b/relayer/src/chain/tx.rs index e234af229f..dc9d23f86e 100644 --- a/relayer/src/chain/tx.rs +++ b/relayer/src/chain/tx.rs @@ -9,8 +9,8 @@ use ibc_proto::google::protobuf::Any; /// by sharing the same `tracking_id`. #[derive(Debug, Clone)] pub struct TrackedMsgs { - msgs: Vec, - tracking_id: String, + pub msgs: Vec, + pub tracking_id: String, } impl TrackedMsgs { From 6f1596aa90146760640b2f76f0999cdf2f0cf6ad Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Tue, 5 Apr 2022 11:38:53 +0200 Subject: [PATCH 15/30] Refactor send_messages_and_wait_check_tx --- relayer/src/chain/cosmos.rs | 112 +++++++++--------------------- relayer/src/chain/cosmos/batch.rs | 53 +++++++++++--- 2 files changed, 78 insertions(+), 87 deletions(-) diff --git a/relayer/src/chain/cosmos.rs b/relayer/src/chain/cosmos.rs index d09dce1bc3..9e25a0a7e6 100644 --- a/relayer/src/chain/cosmos.rs +++ b/relayer/src/chain/cosmos.rs @@ -9,7 +9,6 @@ use num_bigint::BigInt; use std::thread; use bitcoin::hashes::hex::ToHex; -use ibc_proto::google::protobuf::Any; use tendermint::block::Height; use tendermint::{ abci::{Event, Path as TendermintABCIPath}, @@ -63,14 +62,15 @@ use ibc_proto::ibc::core::connection::v1::{ }; use crate::chain::client::ClientSettings; -use crate::chain::cosmos::batch::send_batched_messages_and_wait_commit; +use crate::chain::cosmos::batch::{ + send_batched_messages_and_wait_check_tx, send_batched_messages_and_wait_commit, +}; use crate::chain::cosmos::encode::encode_to_bech32; use crate::chain::cosmos::gas::{calculate_fee, mul_ceil}; use crate::chain::cosmos::query::tx::query_txs; use crate::chain::cosmos::query::{ abci_query, fetch_version_specs, get_or_fetch_account, packet_query, }; -use crate::chain::cosmos::retry::send_tx_with_account_sequence_retry; use crate::chain::cosmos::types::account::Account; use crate::chain::cosmos::types::gas_config::DEFAULT_MAX_GAS; use crate::chain::tx::TrackedMsgs; @@ -266,42 +266,6 @@ impl CosmosSdkChain { self.rt.block_on(f) } - async fn send_tx_with_account_sequence_retry( - &mut self, - proto_msgs: Vec, - retry_counter: u64, - ) -> Result { - let key_entry = self.key()?; - - let account = - get_or_fetch_account(&self.grpc_addr, &key_entry.account, &mut self.account).await?; - - send_tx_with_account_sequence_retry( - &self.config, - &self.rpc_client, - &self.config.rpc_addr, - &self.grpc_addr, - &key_entry, - &self.config.memo_prefix, - account, - proto_msgs, - retry_counter, - ) - .await - } - - fn send_tx(&mut self, proto_msgs: Vec) -> Result { - crate::time!("send_tx"); - let _span = span!(Level::ERROR, "send_tx", id = %self.id()).entered(); - - let runtime = self.rt.clone(); - - runtime.block_on(async { - self.send_tx_with_account_sequence_retry(proto_msgs, 0) - .await - }) - } - /// The default amount of gas the relayer is willing to pay for a transaction, /// when it cannot simulate the tx and therefore estimate the gas amount needed. fn default_gas(&self) -> u64 { @@ -313,11 +277,6 @@ impl CosmosSdkChain { self.config.max_gas.unwrap_or(DEFAULT_MAX_GAS) } - /// The maximum number of messages included in a transaction - fn max_msg_num(&self) -> usize { - self.config.max_msg_num.into() - } - /// The maximum size of any transaction sent by the relayer to this chain fn max_tx_size(&self) -> usize { self.config.max_tx_size.into() @@ -459,6 +418,35 @@ impl CosmosSdkChain { ) .await } + + async fn do_send_messages_and_wait_check_tx( + &mut self, + tracked_msgs: TrackedMsgs, + ) -> Result, Error> { + crate::time!("send_messages_and_wait_check_tx"); + + let span = span!(Level::DEBUG, "send_tx_check", id = %tracked_msgs.tracking_id()); + let _enter = span.enter(); + + let proto_msgs = tracked_msgs.msgs; + + let key_entry = self.key()?; + + let account = + 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.config.rpc_addr, + &self.grpc_addr, + &key_entry, + &self.config.memo_prefix, + account, + proto_msgs, + ) + .await + } } impl ChainEndpoint for CosmosSdkChain { @@ -597,41 +585,9 @@ impl ChainEndpoint for CosmosSdkChain { &mut self, tracked_msgs: TrackedMsgs, ) -> Result, Error> { - crate::time!("send_messages_and_wait_check_tx"); - - let span = span!(Level::DEBUG, "send_tx_check", id = %tracked_msgs.tracking_id()); - let _enter = span.enter(); - - let proto_msgs = tracked_msgs.messages(); - - if proto_msgs.is_empty() { - return Ok(vec![]); - } - let mut responses = vec![]; - - let mut n = 0; - let mut size = 0; - let mut msg_batch = vec![]; - for msg in proto_msgs.iter() { - msg_batch.push(msg.clone()); - let mut buf = Vec::new(); - prost::Message::encode(msg, &mut buf) - .map_err(|e| Error::protobuf_encode(String::from("Messages"), e))?; - n += 1; - size += buf.len(); - if n >= self.max_msg_num() || size >= self.max_tx_size() { - // Send the tx and enqueue the resulting response - responses.push(self.send_tx(msg_batch)?); - n = 0; - size = 0; - msg_batch = vec![]; - } - } - if !msg_batch.is_empty() { - responses.push(self.send_tx(msg_batch)?); - } + let runtime = self.rt.clone(); - Ok(responses) + runtime.block_on(self.do_send_messages_and_wait_check_tx(tracked_msgs)) } /// Get the account for the signer diff --git a/relayer/src/chain/cosmos/batch.rs b/relayer/src/chain/cosmos/batch.rs index 99b440d9b4..772d3c46ba 100644 --- a/relayer/src/chain/cosmos/batch.rs +++ b/relayer/src/chain/cosmos/batch.rs @@ -1,5 +1,6 @@ use ibc::events::IbcEvent; use ibc_proto::google::protobuf::Any; +use tendermint_rpc::endpoint::broadcast::tx_sync::Response; use tendermint_rpc::{HttpClient, Url}; use tonic::codegen::http::Uri; @@ -55,6 +56,44 @@ pub async fn send_batched_messages_and_wait_commit( Ok(events) } +pub async fn send_batched_messages_and_wait_check_tx( + config: &ChainConfig, + rpc_client: &HttpClient, + rpc_address: &Url, + grpc_address: &Uri, + key_entry: &KeyEntry, + tx_memo: &Memo, + account: &mut Account, + messages: Vec, +) -> Result, Error> { + if messages.is_empty() { + return Ok(Vec::new()); + } + + let batches = batch_messages(config, messages)?; + + let mut responses = Vec::new(); + + for batch in batches { + let response = send_tx_with_account_sequence_retry( + config, + rpc_client, + rpc_address, + grpc_address, + key_entry, + tx_memo, + account, + batch, + 0, + ) + .await?; + + responses.push(response); + } + + Ok(responses) +} + pub async fn send_messages_as_batches( config: &ChainConfig, rpc_client: &HttpClient, @@ -69,10 +108,7 @@ pub async fn send_messages_as_batches( return Ok(Vec::new()); } - let max_message_count = config.max_msg_num.0; - let max_tx_size = config.max_tx_size.into(); - - let batches = batch_messages(messages, max_message_count, max_tx_size)?; + let batches = batch_messages(config, messages)?; let mut tx_sync_results = Vec::new(); @@ -103,11 +139,10 @@ pub async fn send_messages_as_batches( Ok(tx_sync_results) } -fn batch_messages( - messages: Vec, - max_message_count: usize, - max_tx_size: usize, -) -> Result>, Error> { +fn batch_messages(config: &ChainConfig, messages: Vec) -> Result>, Error> { + let max_message_count = config.max_msg_num.0; + let max_tx_size = config.max_tx_size.into(); + let mut batches = vec![]; let mut current_count = 0; From a0ef1968a44b3388934d3fea3062654ce2a06af6 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Tue, 5 Apr 2022 11:53:40 +0200 Subject: [PATCH 16/30] Refactor sign_message --- Cargo.lock | 8 ++++---- relayer/src/keyring.rs | 20 +------------------- 2 files changed, 5 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d0edc7f814..94a0abc9d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3413,9 +3413,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.7" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "004cbc98f30fa233c61a38bc77e96a9106e65c88f2d3bef182ae952027e5753d" +checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd" dependencies = [ "libc", "num_threads", @@ -3424,9 +3424,9 @@ dependencies = [ [[package]] name = "time-macros" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6" +checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" [[package]] name = "tiny-bip39" diff --git a/relayer/src/keyring.rs b/relayer/src/keyring.rs index 741402dc42..1dee320b0c 100644 --- a/relayer/src/keyring.rs +++ b/relayer/src/keyring.rs @@ -360,25 +360,7 @@ impl KeyRing { ) -> Result, Error> { let key = self.get_key(key_name)?; - let private_key_bytes = key.private_key.private_key.to_bytes(); - match address_type { - AddressType::Ethermint { ref pk_type } if pk_type.ends_with(".ethsecp256k1.PubKey") => { - let hash = keccak256_hash(msg.as_slice()); - let s = Secp256k1::signing_only(); - // SAFETY: hash is 32 bytes, as expected in `Message::from_slice` -- see `keccak256_hash`, hence `unwrap` - let sign_msg = Message::from_slice(hash.as_slice()).unwrap(); - let key = SecretKey::from_slice(private_key_bytes.as_slice()) - .map_err(Error::invalid_key_raw)?; - let (_, sig_bytes) = s.sign_recoverable(&sign_msg, &key).serialize_compact(); - Ok(sig_bytes.to_vec()) - } - AddressType::Cosmos | AddressType::Ethermint { .. } => { - let signing_key = SigningKey::from_bytes(private_key_bytes.as_slice()) - .map_err(Error::invalid_key)?; - let signature: Signature = signing_key.sign(&msg); - Ok(signature.as_ref().to_vec()) - } - } + sign_message(&key, msg, address_type) } pub fn account_prefix(&self) -> &str { From 9262f003e862d70f0a27711c3cbec9cc7109c97b Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Tue, 5 Apr 2022 13:53:15 +0200 Subject: [PATCH 17/30] Refactor gas config --- relayer/src/chain/cosmos.rs | 28 ++++++++------------ relayer/src/chain/cosmos/batch.rs | 2 +- relayer/src/chain/cosmos/types/gas_config.rs | 6 ++--- 3 files changed, 15 insertions(+), 21 deletions(-) diff --git a/relayer/src/chain/cosmos.rs b/relayer/src/chain/cosmos.rs index 03aecf3c2d..359306e627 100644 --- a/relayer/src/chain/cosmos.rs +++ b/relayer/src/chain/cosmos.rs @@ -72,7 +72,7 @@ use crate::chain::cosmos::query::{ abci_query, fetch_version_specs, get_or_fetch_account, packet_query, }; use crate::chain::cosmos::types::account::Account; -use crate::chain::cosmos::types::gas_config::DEFAULT_MAX_GAS; +use crate::chain::cosmos::types::gas_config::{default_gas_from_config, max_gas_from_config}; use crate::chain::tx::TrackedMsgs; use crate::chain::{ChainEndpoint, HealthCheck}; use crate::chain::{QueryResponse, StatusResponse}; @@ -151,14 +151,17 @@ impl CosmosSdkChain { ); } + let max_gas = max_gas_from_config(&self.config); + let default_gas = default_gas_from_config(&self.config); + // If the default gas is strictly greater than the max gas and the tx simulation fails, // Hermes won't be able to ever submit that tx because the gas amount wanted will be // greater than the max gas. - if self.default_gas() > self.max_gas() { + if default_gas > max_gas { return Err(Error::config_validation_default_gas_too_high( self.id().clone(), - self.default_gas(), - self.max_gas(), + default_gas, + max_gas, )); } @@ -199,10 +202,12 @@ impl CosmosSdkChain { .try_into() .expect("cannot over or underflow because it is positive"); - if self.max_gas() > consensus_max_gas { + let max_gas = max_gas_from_config(&self.config); + + if max_gas > consensus_max_gas { return Err(Error::config_validation_max_gas_too_high( self.id().clone(), - self.max_gas(), + max_gas, result.consensus_params.block.max_gas, )); } @@ -266,17 +271,6 @@ impl CosmosSdkChain { self.rt.block_on(f) } - /// The default amount of gas the relayer is willing to pay for a transaction, - /// when it cannot simulate the tx and therefore estimate the gas amount needed. - fn default_gas(&self) -> u64 { - self.config.default_gas.unwrap_or_else(|| self.max_gas()) - } - - /// The maximum amount of gas the relayer is willing to pay for a transaction - fn max_gas(&self) -> u64 { - self.config.max_gas.unwrap_or(DEFAULT_MAX_GAS) - } - /// The maximum size of any transaction sent by the relayer to this chain fn max_tx_size(&self) -> usize { self.config.max_tx_size.into() diff --git a/relayer/src/chain/cosmos/batch.rs b/relayer/src/chain/cosmos/batch.rs index 772d3c46ba..ca90d171cb 100644 --- a/relayer/src/chain/cosmos/batch.rs +++ b/relayer/src/chain/cosmos/batch.rs @@ -94,7 +94,7 @@ pub async fn send_batched_messages_and_wait_check_tx( Ok(responses) } -pub async fn send_messages_as_batches( +async fn send_messages_as_batches( config: &ChainConfig, rpc_client: &HttpClient, rpc_address: &Url, diff --git a/relayer/src/chain/cosmos/types/gas_config.rs b/relayer/src/chain/cosmos/types/gas_config.rs index c8b2a5f0ac..4732726eb0 100644 --- a/relayer/src/chain/cosmos/types/gas_config.rs +++ b/relayer/src/chain/cosmos/types/gas_config.rs @@ -4,7 +4,7 @@ use crate::chain::cosmos::calculate_fee; use crate::config::{ChainConfig, GasPrice}; /// Default gas limit when submitting a transaction. -pub const DEFAULT_MAX_GAS: u64 = 400_000; +const DEFAULT_MAX_GAS: u64 = 400_000; /// Fraction of the estimated gas to add to the estimated gas amount when submitting a transaction. const DEFAULT_GAS_PRICE_ADJUSTMENT: f64 = 0.1; @@ -33,13 +33,13 @@ impl GasConfig { } } -fn default_gas_from_config(config: &ChainConfig) -> u64 { +pub fn default_gas_from_config(config: &ChainConfig) -> u64 { config .default_gas .unwrap_or_else(|| max_gas_from_config(config)) } -fn max_gas_from_config(config: &ChainConfig) -> u64 { +pub fn max_gas_from_config(config: &ChainConfig) -> u64 { config.max_gas.unwrap_or(DEFAULT_MAX_GAS) } From 5dad18c9fe4e4041a66a741cd0a5377cc9b44d24 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Tue, 5 Apr 2022 14:00:57 +0200 Subject: [PATCH 18/30] Move out query account module --- relayer/src/chain/cosmos.rs | 5 +- relayer/src/chain/cosmos/query.rs | 1 + relayer/src/chain/cosmos/query/account.rs | 75 +++++++++++++++++++++++ relayer/src/chain/cosmos/retry.rs | 2 +- 4 files changed, 79 insertions(+), 4 deletions(-) create mode 100644 relayer/src/chain/cosmos/query/account.rs diff --git a/relayer/src/chain/cosmos.rs b/relayer/src/chain/cosmos.rs index 359306e627..d7361a3838 100644 --- a/relayer/src/chain/cosmos.rs +++ b/relayer/src/chain/cosmos.rs @@ -67,10 +67,9 @@ use crate::chain::cosmos::batch::{ }; use crate::chain::cosmos::encode::encode_to_bech32; use crate::chain::cosmos::gas::{calculate_fee, mul_ceil}; +use crate::chain::cosmos::query::account::get_or_fetch_account; use crate::chain::cosmos::query::tx::query_txs; -use crate::chain::cosmos::query::{ - abci_query, fetch_version_specs, get_or_fetch_account, packet_query, -}; +use crate::chain::cosmos::query::{abci_query, fetch_version_specs, packet_query}; use crate::chain::cosmos::types::account::Account; use crate::chain::cosmos::types::gas_config::{default_gas_from_config, max_gas_from_config}; use crate::chain::tx::TrackedMsgs; diff --git a/relayer/src/chain/cosmos/query.rs b/relayer/src/chain/cosmos/query.rs index e86d820f1c..4ce3b53c4f 100644 --- a/relayer/src/chain/cosmos/query.rs +++ b/relayer/src/chain/cosmos/query.rs @@ -21,6 +21,7 @@ use crate::chain::cosmos::version::Specs; use crate::chain::QueryResponse; use crate::error::Error; +pub mod account; pub mod tx; pub async fn get_or_fetch_account<'a>( diff --git a/relayer/src/chain/cosmos/query/account.rs b/relayer/src/chain/cosmos/query/account.rs new file mode 100644 index 0000000000..9122947c09 --- /dev/null +++ b/relayer/src/chain/cosmos/query/account.rs @@ -0,0 +1,75 @@ +use http::uri::Uri; +use ibc_proto::cosmos::auth::v1beta1::query_client::QueryClient; +use ibc_proto::cosmos::auth::v1beta1::{BaseAccount, EthAccount, QueryAccountRequest}; +use prost::Message; +use tracing::info; + +use crate::chain::cosmos::types::account::Account; +use crate::error::Error; + +pub async fn get_or_fetch_account<'a>( + grpc_address: &Uri, + account_address: &str, + m_account: &'a mut Option, +) -> Result<&'a mut Account, Error> { + match m_account { + Some(account) => Ok(account), + None => { + let account = query_account(grpc_address, account_address).await?; + *m_account = Some(account.into()); + + Ok(m_account + .as_mut() + .expect("account was supposedly just cached")) + } + } +} + +pub async fn refresh_account<'a>( + grpc_address: &Uri, + account_address: &str, + m_account: &'a mut Account, +) -> Result<(), Error> { + let account = query_account(grpc_address, account_address).await?; + + info!( + sequence = %account.sequence, + number = %account.account_number, + "refresh: retrieved account", + ); + + *m_account = account.into(); + + Ok(()) +} + +/// Uses the GRPC client to retrieve the account sequence +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)?; + + let request = tonic::Request::new(QueryAccountRequest { + address: account_address.to_string(), + }); + + let response = client.account(request).await; + + // Querying for an account might fail, i.e. if the account doesn't actually exist + let resp_account = match response.map_err(Error::grpc_status)?.into_inner().account { + Some(account) => account, + None => return Err(Error::empty_query_account(account_address.to_string())), + }; + + if resp_account.type_url == "/cosmos.auth.v1beta1.BaseAccount" { + Ok(BaseAccount::decode(resp_account.value.as_slice()) + .map_err(|e| Error::protobuf_decode("BaseAccount".to_string(), e))?) + } else if resp_account.type_url.ends_with(".EthAccount") { + Ok(EthAccount::decode(resp_account.value.as_slice()) + .map_err(|e| Error::protobuf_decode("EthAccount".to_string(), e))? + .base_account + .ok_or_else(Error::empty_base_account)?) + } else { + Err(Error::unknown_account_type(resp_account.type_url)) + } +} diff --git a/relayer/src/chain/cosmos/retry.rs b/relayer/src/chain/cosmos/retry.rs index 450ddc419e..839631bbbb 100644 --- a/relayer/src/chain/cosmos/retry.rs +++ b/relayer/src/chain/cosmos/retry.rs @@ -9,7 +9,7 @@ use tendermint_rpc::{HttpClient, Url}; use tonic::codegen::http::Uri; use tracing::{debug, error, warn}; -use crate::chain::cosmos::query::refresh_account; +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::config::types::Memo; From 8d1040cca435cedeb05ee15b0e79ee47b99dcc18 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Tue, 5 Apr 2022 14:05:46 +0200 Subject: [PATCH 19/30] Reorganize types --- relayer/src/chain/cosmos.rs | 2 +- relayer/src/chain/cosmos/batch.rs | 2 +- relayer/src/chain/cosmos/encode.rs | 2 +- relayer/src/chain/cosmos/estimate.rs | 2 +- relayer/src/chain/cosmos/gas.rs | 2 +- relayer/src/chain/cosmos/types/{gas_config.rs => gas.rs} | 0 relayer/src/chain/cosmos/types/mod.rs | 8 ++------ relayer/src/chain/cosmos/types/signed_tx.rs | 9 --------- relayer/src/chain/cosmos/types/{tx_sync.rs => tx.rs} | 9 +++++++++ relayer/src/chain/cosmos/wait.rs | 2 +- 10 files changed, 17 insertions(+), 21 deletions(-) rename relayer/src/chain/cosmos/types/{gas_config.rs => gas.rs} (100%) delete mode 100644 relayer/src/chain/cosmos/types/signed_tx.rs rename relayer/src/chain/cosmos/types/{tx_sync.rs => tx.rs} (53%) diff --git a/relayer/src/chain/cosmos.rs b/relayer/src/chain/cosmos.rs index d7361a3838..de89c0d7f4 100644 --- a/relayer/src/chain/cosmos.rs +++ b/relayer/src/chain/cosmos.rs @@ -71,7 +71,7 @@ use crate::chain::cosmos::query::account::get_or_fetch_account; 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::gas_config::{default_gas_from_config, max_gas_from_config}; +use crate::chain::cosmos::types::gas::{default_gas_from_config, max_gas_from_config}; use crate::chain::tx::TrackedMsgs; use crate::chain::{ChainEndpoint, HealthCheck}; use crate::chain::{QueryResponse, StatusResponse}; diff --git a/relayer/src/chain/cosmos/batch.rs b/relayer/src/chain/cosmos/batch.rs index ca90d171cb..3e0074fd92 100644 --- a/relayer/src/chain/cosmos/batch.rs +++ b/relayer/src/chain/cosmos/batch.rs @@ -6,7 +6,7 @@ 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::tx_sync::TxSyncResult; +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; diff --git a/relayer/src/chain/cosmos/encode.rs b/relayer/src/chain/cosmos/encode.rs index 8ee61248ce..6bf36ba2cc 100644 --- a/relayer/src/chain/cosmos/encode.rs +++ b/relayer/src/chain/cosmos/encode.rs @@ -7,7 +7,7 @@ use ibc_proto::google::protobuf::Any; use tendermint::account::Id as AccountId; use crate::chain::cosmos::types::account::{AccountNumber, AccountSequence}; -use crate::chain::cosmos::types::SignedTx; +use crate::chain::cosmos::types::tx::SignedTx; use crate::config::types::Memo; use crate::config::AddressType; use crate::config::ChainConfig; diff --git a/relayer/src/chain/cosmos/estimate.rs b/relayer/src/chain/cosmos/estimate.rs index 03997cce80..ca5ce39b21 100644 --- a/relayer/src/chain/cosmos/estimate.rs +++ b/relayer/src/chain/cosmos/estimate.rs @@ -8,7 +8,7 @@ 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::{AccountNumber, AccountSequence}; -use crate::chain::cosmos::types::GasConfig; +use crate::chain::cosmos::types::gas::GasConfig; use crate::config::types::Memo; use crate::config::ChainConfig; use crate::error::Error; diff --git a/relayer/src/chain/cosmos/gas.rs b/relayer/src/chain/cosmos/gas.rs index 8cbd96a113..442d43b9a1 100644 --- a/relayer/src/chain/cosmos/gas.rs +++ b/relayer/src/chain/cosmos/gas.rs @@ -5,7 +5,7 @@ use ibc_proto::cosmos::tx::v1beta1::Fee; use num_bigint::BigInt; use num_rational::BigRational; -use crate::chain::cosmos::types::GasConfig; +use crate::chain::cosmos::types::gas::GasConfig; use crate::config::GasPrice; pub struct PrettyFee<'a>(pub &'a Fee); diff --git a/relayer/src/chain/cosmos/types/gas_config.rs b/relayer/src/chain/cosmos/types/gas.rs similarity index 100% rename from relayer/src/chain/cosmos/types/gas_config.rs rename to relayer/src/chain/cosmos/types/gas.rs diff --git a/relayer/src/chain/cosmos/types/mod.rs b/relayer/src/chain/cosmos/types/mod.rs index f40d9f6217..4d91a59966 100644 --- a/relayer/src/chain/cosmos/types/mod.rs +++ b/relayer/src/chain/cosmos/types/mod.rs @@ -1,7 +1,3 @@ pub mod account; -pub mod gas_config; -pub mod signed_tx; -pub mod tx_sync; - -pub use gas_config::GasConfig; -pub use signed_tx::SignedTx; +pub mod gas; +pub mod tx; diff --git a/relayer/src/chain/cosmos/types/signed_tx.rs b/relayer/src/chain/cosmos/types/signed_tx.rs deleted file mode 100644 index bef10ceee5..0000000000 --- a/relayer/src/chain/cosmos/types/signed_tx.rs +++ /dev/null @@ -1,9 +0,0 @@ -use ibc_proto::cosmos::tx::v1beta1::{AuthInfo, TxBody}; - -pub struct SignedTx { - pub body: TxBody, - pub body_bytes: Vec, - pub auth_info: AuthInfo, - pub auth_info_bytes: Vec, - pub signatures: Vec>, -} diff --git a/relayer/src/chain/cosmos/types/tx_sync.rs b/relayer/src/chain/cosmos/types/tx.rs similarity index 53% rename from relayer/src/chain/cosmos/types/tx_sync.rs rename to relayer/src/chain/cosmos/types/tx.rs index 9855fcb9ff..9509c9826c 100644 --- a/relayer/src/chain/cosmos/types/tx_sync.rs +++ b/relayer/src/chain/cosmos/types/tx.rs @@ -1,6 +1,15 @@ use ibc::events::IbcEvent; +use ibc_proto::cosmos::tx::v1beta1::{AuthInfo, TxBody}; use tendermint_rpc::endpoint::broadcast::tx_sync::Response; +pub struct SignedTx { + pub body: TxBody, + pub body_bytes: Vec, + pub auth_info: AuthInfo, + pub auth_info_bytes: Vec, + pub signatures: Vec>, +} + pub struct TxSyncResult { // the broadcast_tx_sync response pub response: Response, diff --git a/relayer/src/chain/cosmos/wait.rs b/relayer/src/chain/cosmos/wait.rs index b01e12c0ac..5a42f72fe5 100644 --- a/relayer/src/chain/cosmos/wait.rs +++ b/relayer/src/chain/cosmos/wait.rs @@ -9,7 +9,7 @@ use tendermint_rpc::{HttpClient, Url}; use tracing::{info, trace}; use crate::chain::cosmos::query::tx::query_txs; -use crate::chain::cosmos::types::tx_sync::TxSyncResult; +use crate::chain::cosmos::types::tx::TxSyncResult; use crate::error::Error; const WAIT_BACKOFF: Duration = Duration::from_millis(300); From 646da40e662b0c53101dfa4c67c5f527a46f96af Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Tue, 5 Apr 2022 14:09:05 +0200 Subject: [PATCH 20/30] Remove pub const --- relayer/src/chain/cosmos/retry.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/relayer/src/chain/cosmos/retry.rs b/relayer/src/chain/cosmos/retry.rs index 839631bbbb..c63fc31e7c 100644 --- a/relayer/src/chain/cosmos/retry.rs +++ b/relayer/src/chain/cosmos/retry.rs @@ -20,15 +20,15 @@ use crate::sdk_error::sdk_error_from_tx_sync_error_code; // Maximum number of retries for send_tx in the case of // an account sequence mismatch at broadcast step. -pub const MAX_ACCOUNT_SEQUENCE_RETRY: u64 = 1; +const MAX_ACCOUNT_SEQUENCE_RETRY: u64 = 1; // Backoff multiplier to apply while retrying in the case // of account sequence mismatch. -pub const BACKOFF_MULTIPLIER_ACCOUNT_SEQUENCE_RETRY: u64 = 300; +const BACKOFF_MULTIPLIER_ACCOUNT_SEQUENCE_RETRY: u64 = 300; // The error "incorrect account sequence" is defined as the unique error code 32 in cosmos-sdk: // https://github.com/cosmos/cosmos-sdk/blob/v0.44.0/types/errors/errors.go#L115-L117 -pub const INCORRECT_ACCOUNT_SEQUENCE_ERR: u32 = 32; +const INCORRECT_ACCOUNT_SEQUENCE_ERR: u32 = 32; /// Try to `send_tx` with retry on account sequence error. /// An account sequence error can occur if the account sequence that From 2dae99545337605a7ea66dffb3d21345ebf251c6 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Tue, 5 Apr 2022 14:51:08 +0200 Subject: [PATCH 21/30] Simplify arguments --- relayer/src/chain/cosmos.rs | 5 ++-- relayer/src/chain/cosmos/batch.rs | 17 +++++--------- relayer/src/chain/cosmos/encode.rs | 22 +++++------------ relayer/src/chain/cosmos/estimate.rs | 8 +++---- relayer/src/chain/cosmos/retry.rs | 18 +++++--------- relayer/src/chain/cosmos/tx.rs | 35 ++++++---------------------- 6 files changed, 30 insertions(+), 75 deletions(-) diff --git a/relayer/src/chain/cosmos.rs b/relayer/src/chain/cosmos.rs index de89c0d7f4..dd31fd03fa 100644 --- a/relayer/src/chain/cosmos.rs +++ b/relayer/src/chain/cosmos.rs @@ -405,8 +405,8 @@ impl CosmosSdkChain { &self.config.rpc_addr, &self.grpc_addr, &key_entry, - &self.config.memo_prefix, account, + &self.config.memo_prefix, proto_msgs, ) .await @@ -431,11 +431,10 @@ impl CosmosSdkChain { send_batched_messages_and_wait_check_tx( &self.config, &self.rpc_client, - &self.config.rpc_addr, &self.grpc_addr, &key_entry, - &self.config.memo_prefix, account, + &self.config.memo_prefix, proto_msgs, ) .await diff --git a/relayer/src/chain/cosmos/batch.rs b/relayer/src/chain/cosmos/batch.rs index 3e0074fd92..24583e11b0 100644 --- a/relayer/src/chain/cosmos/batch.rs +++ b/relayer/src/chain/cosmos/batch.rs @@ -19,8 +19,8 @@ pub async fn send_batched_messages_and_wait_commit( rpc_address: &Url, grpc_address: &Uri, key_entry: &KeyEntry, - tx_memo: &Memo, account: &mut Account, + tx_memo: &Memo, messages: Vec, ) -> Result, Error> { if messages.is_empty() { @@ -30,11 +30,10 @@ pub async fn send_batched_messages_and_wait_commit( let mut tx_sync_results = send_messages_as_batches( config, rpc_client, - rpc_address, grpc_address, key_entry, - tx_memo, account, + tx_memo, messages, ) .await?; @@ -59,11 +58,10 @@ pub async fn send_batched_messages_and_wait_commit( pub async fn send_batched_messages_and_wait_check_tx( config: &ChainConfig, rpc_client: &HttpClient, - rpc_address: &Url, grpc_address: &Uri, key_entry: &KeyEntry, - tx_memo: &Memo, account: &mut Account, + tx_memo: &Memo, messages: Vec, ) -> Result, Error> { if messages.is_empty() { @@ -78,11 +76,10 @@ pub async fn send_batched_messages_and_wait_check_tx( let response = send_tx_with_account_sequence_retry( config, rpc_client, - rpc_address, grpc_address, key_entry, - tx_memo, account, + tx_memo, batch, 0, ) @@ -97,11 +94,10 @@ pub async fn send_batched_messages_and_wait_check_tx( async fn send_messages_as_batches( config: &ChainConfig, rpc_client: &HttpClient, - rpc_address: &Url, grpc_address: &Uri, key_entry: &KeyEntry, - tx_memo: &Memo, account: &mut Account, + tx_memo: &Memo, messages: Vec, ) -> Result, Error> { if messages.is_empty() { @@ -118,11 +114,10 @@ async fn send_messages_as_batches( let response = send_tx_with_account_sequence_retry( config, rpc_client, - rpc_address, grpc_address, key_entry, - tx_memo, account, + tx_memo, batch, 0, ) diff --git a/relayer/src/chain/cosmos/encode.rs b/relayer/src/chain/cosmos/encode.rs index 6bf36ba2cc..b66265c4bc 100644 --- a/relayer/src/chain/cosmos/encode.rs +++ b/relayer/src/chain/cosmos/encode.rs @@ -6,7 +6,7 @@ use ibc_proto::cosmos::tx::v1beta1::{AuthInfo, Fee, ModeInfo, SignDoc, SignerInf use ibc_proto::google::protobuf::Any; use tendermint::account::Id as AccountId; -use crate::chain::cosmos::types::account::{AccountNumber, AccountSequence}; +use crate::chain::cosmos::types::account::{Account, AccountNumber, AccountSequence}; use crate::chain::cosmos::types::tx::SignedTx; use crate::config::types::Memo; use crate::config::AddressType; @@ -17,21 +17,12 @@ use crate::keyring::{sign_message, KeyEntry}; pub fn sign_and_encode_tx( config: &ChainConfig, key_entry: &KeyEntry, + account: &Account, tx_memo: &Memo, - account_number: AccountNumber, - account_sequence: AccountSequence, messages: Vec, fee: &Fee, ) -> Result, Error> { - let signed_tx = sign_tx( - config, - key_entry, - tx_memo, - account_number, - account_sequence, - messages, - fee, - )?; + let signed_tx = sign_tx(config, key_entry, account, tx_memo, messages, fee)?; let tx_raw = TxRaw { body_bytes: signed_tx.body_bytes, @@ -45,15 +36,14 @@ pub fn sign_and_encode_tx( pub fn sign_tx( config: &ChainConfig, key_entry: &KeyEntry, + account: &Account, tx_memo: &Memo, - account_number: AccountNumber, - account_sequence: AccountSequence, messages: Vec, fee: &Fee, ) -> Result { let key_bytes = encode_key_bytes(key_entry)?; - let signer = encode_signer_info(&config.address_type, account_sequence, key_bytes)?; + let signer = encode_signer_info(&config.address_type, account.sequence, key_bytes)?; let (body, body_bytes) = tx_body_and_bytes(messages, tx_memo)?; @@ -63,7 +53,7 @@ pub fn sign_tx( &config.id, key_entry, &config.address_type, - account_number, + account.number, auth_info_bytes.clone(), body_bytes.clone(), )?; diff --git a/relayer/src/chain/cosmos/estimate.rs b/relayer/src/chain/cosmos/estimate.rs index ca5ce39b21..206e453681 100644 --- a/relayer/src/chain/cosmos/estimate.rs +++ b/relayer/src/chain/cosmos/estimate.rs @@ -7,7 +7,7 @@ use tracing::{debug, error, span, warn, Level}; 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::{AccountNumber, AccountSequence}; +use crate::chain::cosmos::types::account::Account; use crate::chain::cosmos::types::gas::GasConfig; use crate::config::types::Memo; use crate::config::ChainConfig; @@ -18,9 +18,8 @@ pub async fn estimate_tx_fees( config: &ChainConfig, grpc_address: &Uri, key_entry: &KeyEntry, + account: &Account, tx_memo: &Memo, - account_number: AccountNumber, - account_sequence: AccountSequence, messages: Vec, ) -> Result { let gas_config = GasConfig::from_chain_config(config); @@ -33,9 +32,8 @@ pub async fn estimate_tx_fees( let signed_tx = sign_tx( config, key_entry, + account, tx_memo, - account_number, - account_sequence, messages, &gas_config.max_fee, )?; diff --git a/relayer/src/chain/cosmos/retry.rs b/relayer/src/chain/cosmos/retry.rs index c63fc31e7c..7c013dc84d 100644 --- a/relayer/src/chain/cosmos/retry.rs +++ b/relayer/src/chain/cosmos/retry.rs @@ -5,7 +5,7 @@ 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, Url}; +use tendermint_rpc::HttpClient; use tonic::codegen::http::Uri; use tracing::{debug, error, warn}; @@ -49,22 +49,20 @@ const INCORRECT_ACCOUNT_SEQUENCE_ERR: u32 = 32; pub async fn send_tx_with_account_sequence_retry( config: &ChainConfig, rpc_client: &HttpClient, - rpc_address: &Url, grpc_address: &Uri, key_entry: &KeyEntry, - tx_memo: &Memo, account: &mut Account, + tx_memo: &Memo, messages: Vec, retry_counter: u64, ) -> Result { do_send_tx_with_account_sequence_retry( config, rpc_client, - rpc_address, grpc_address, key_entry, - tx_memo, account, + tx_memo, messages, retry_counter, ) @@ -74,11 +72,10 @@ pub async fn send_tx_with_account_sequence_retry( fn do_send_tx_with_account_sequence_retry<'a>( config: &'a ChainConfig, rpc_client: &'a HttpClient, - rpc_address: &'a Url, grpc_address: &'a Uri, key_entry: &'a KeyEntry, - tx_memo: &'a Memo, account: &'a mut Account, + tx_memo: &'a Memo, messages: Vec, retry_counter: u64, ) -> Pin> + 'a>> { @@ -86,12 +83,10 @@ fn do_send_tx_with_account_sequence_retry<'a>( let tx_result = estimate_fee_and_send_tx( config, rpc_client, - rpc_address, grpc_address, key_entry, + account, tx_memo, - account.number, - account.sequence, messages.clone(), ) .await; @@ -128,11 +123,10 @@ fn do_send_tx_with_account_sequence_retry<'a>( do_send_tx_with_account_sequence_retry( config, rpc_client, - rpc_address, grpc_address, key_entry, - tx_memo, account, + tx_memo, messages, retry_counter + 1, ) diff --git a/relayer/src/chain/cosmos/tx.rs b/relayer/src/chain/cosmos/tx.rs index 477dea13e5..0ac651d270 100644 --- a/relayer/src/chain/cosmos/tx.rs +++ b/relayer/src/chain/cosmos/tx.rs @@ -6,7 +6,7 @@ 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::{AccountNumber, AccountSequence}; +use crate::chain::cosmos::types::account::Account; use crate::config::types::Memo; use crate::config::ChainConfig; use crate::error::Error; @@ -15,35 +15,24 @@ use crate::keyring::KeyEntry; pub async fn estimate_fee_and_send_tx( config: &ChainConfig, rpc_client: &HttpClient, - rpc_address: &Url, grpc_address: &Uri, key_entry: &KeyEntry, + account: &Account, tx_memo: &Memo, - account_number: AccountNumber, - account_sequence: AccountSequence, messages: Vec, ) -> Result { let fee = estimate_tx_fees( config, grpc_address, key_entry, + account, tx_memo, - account_number, - account_sequence, messages.clone(), ) .await?; send_tx_with_fee( - config, - rpc_client, - rpc_address, - key_entry, - tx_memo, - account_number, - account_sequence, - messages, - &fee, + config, rpc_client, key_entry, account, tx_memo, messages, &fee, ) .await } @@ -51,25 +40,15 @@ pub async fn estimate_fee_and_send_tx( async fn send_tx_with_fee( config: &ChainConfig, rpc_client: &HttpClient, - rpc_address: &Url, key_entry: &KeyEntry, + account: &Account, tx_memo: &Memo, - account_number: AccountNumber, - account_sequence: AccountSequence, messages: Vec, fee: &Fee, ) -> Result { - let tx_bytes = sign_and_encode_tx( - config, - key_entry, - tx_memo, - account_number, - account_sequence, - messages, - fee, - )?; + let tx_bytes = sign_and_encode_tx(config, key_entry, account, tx_memo, messages, fee)?; - let response = broadcast_tx_sync(rpc_client, rpc_address, tx_bytes).await?; + let response = broadcast_tx_sync(rpc_client, &config.rpc_addr, tx_bytes).await?; Ok(response) } From 7fe9488dae706f4c53efeb36ae9c4090ea7b2b09 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Tue, 5 Apr 2022 22:09:08 +0200 Subject: [PATCH 22/30] Remove redundant account query function --- relayer/src/chain/cosmos/query.rs | 75 ------------------------------- 1 file changed, 75 deletions(-) diff --git a/relayer/src/chain/cosmos/query.rs b/relayer/src/chain/cosmos/query.rs index 4ce3b53c4f..b6cb497af0 100644 --- a/relayer/src/chain/cosmos/query.rs +++ b/relayer/src/chain/cosmos/query.rs @@ -5,18 +5,13 @@ use ibc::core::ics04_channel::packet::Sequence; use ibc::core::ics23_commitment::merkle::convert_tm_to_ics_merkle_proof; use ibc::core::ics24_host::identifier::ChainId; use ibc::query::QueryTxHash; -use ibc_proto::cosmos::auth::v1beta1::query_client::QueryClient; -use ibc_proto::cosmos::auth::v1beta1::{BaseAccount, EthAccount, QueryAccountRequest}; use ibc_proto::cosmos::base::tendermint::v1beta1::service_client::ServiceClient; use ibc_proto::cosmos::base::tendermint::v1beta1::GetNodeInfoRequest; -use prost::Message; use tendermint::abci::Path as TendermintABCIPath; use tendermint::block::Height; use tendermint_rpc::query::Query; use tendermint_rpc::{Client, HttpClient, Url}; -use tracing::info; -use crate::chain::cosmos::types::account::Account; use crate::chain::cosmos::version::Specs; use crate::chain::QueryResponse; use crate::error::Error; @@ -24,76 +19,6 @@ use crate::error::Error; pub mod account; pub mod tx; -pub async fn get_or_fetch_account<'a>( - grpc_address: &Uri, - account_address: &str, - m_account: &'a mut Option, -) -> Result<&'a mut Account, Error> { - match m_account { - Some(account) => Ok(account), - None => { - let account = query_account(grpc_address, account_address).await?; - *m_account = Some(account.into()); - - Ok(m_account - .as_mut() - .expect("account was supposedly just cached")) - } - } -} - -pub async fn refresh_account<'a>( - grpc_address: &Uri, - account_address: &str, - m_account: &'a mut Account, -) -> Result<(), Error> { - let account = query_account(grpc_address, account_address).await?; - - info!( - sequence = %account.sequence, - number = %account.account_number, - "refresh: retrieved account", - ); - - *m_account = account.into(); - - Ok(()) -} - -/// Uses the GRPC client to retrieve the account sequence -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)?; - - let request = tonic::Request::new(QueryAccountRequest { - address: account_address.to_string(), - }); - - let response = client.account(request).await; - - // Querying for an account might fail, i.e. if the account doesn't actually exist - let resp_account = match response.map_err(Error::grpc_status)?.into_inner().account { - Some(account) => account, - None => return Err(Error::empty_query_account(account_address.to_string())), - }; - - if resp_account.type_url == "/cosmos.auth.v1beta1.BaseAccount" { - Ok(BaseAccount::decode(resp_account.value.as_slice()) - .map_err(|e| Error::protobuf_decode("BaseAccount".to_string(), e))?) - } else if resp_account.type_url.ends_with(".EthAccount") { - Ok(EthAccount::decode(resp_account.value.as_slice()) - .map_err(|e| Error::protobuf_decode("EthAccount".to_string(), e))? - .base_account - .ok_or_else(Error::empty_base_account)?) - } else { - Err(Error::unknown_account_type(resp_account.type_url)) - } -} - pub fn packet_query(request: &QueryPacketEventDataRequest, seq: Sequence) -> Query { tendermint_rpc::query::Query::eq( format!("{}.packet_src_channel", request.event_id.as_str()), From 206dfc247086f4d69950038e7a8ccec679d8446c Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Wed, 6 Apr 2022 12:18:33 +0200 Subject: [PATCH 23/30] Refactor query status --- relayer/src/chain.rs | 4 +-- relayer/src/chain/cosmos.rs | 34 ++++++++++------------ relayer/src/chain/cosmos/query.rs | 1 + relayer/src/chain/cosmos/query/status.rs | 36 ++++++++++++++++++++++++ relayer/src/chain/cosmos/transfer.rs | 24 ++++++++++++++++ relayer/src/chain/handle.rs | 6 ++-- relayer/src/chain/handle/base.rs | 4 +-- relayer/src/chain/handle/cache.rs | 4 +-- relayer/src/chain/handle/counting.rs | 4 +-- relayer/src/chain/mock.rs | 6 ++-- relayer/src/chain/runtime.rs | 4 +-- relayer/src/link/relay_path.rs | 6 ++-- 12 files changed, 95 insertions(+), 38 deletions(-) create mode 100644 relayer/src/chain/cosmos/query/status.rs create mode 100644 relayer/src/chain/cosmos/transfer.rs diff --git a/relayer/src/chain.rs b/relayer/src/chain.rs index 2b7237396b..f94a45fa77 100644 --- a/relayer/src/chain.rs +++ b/relayer/src/chain.rs @@ -67,7 +67,7 @@ pub enum HealthCheck { /// The result of a chain status query. #[derive(Clone, Debug)] -pub struct StatusResponse { +pub struct ChainStatus { pub height: ICSHeight, pub timestamp: Timestamp, } @@ -160,7 +160,7 @@ pub trait ChainEndpoint: Sized { } /// Query the latest height and timestamp the chain is at - fn query_status(&self) -> Result; + fn query_status(&self) -> Result; /// Performs a query to retrieve the state of all clients that a chain hosts. fn query_clients( diff --git a/relayer/src/chain/cosmos.rs b/relayer/src/chain/cosmos.rs index dd31fd03fa..df833fe5cb 100644 --- a/relayer/src/chain/cosmos.rs +++ b/relayer/src/chain/cosmos.rs @@ -68,13 +68,14 @@ use crate::chain::cosmos::batch::{ use crate::chain::cosmos::encode::encode_to_bech32; use crate::chain::cosmos::gas::{calculate_fee, mul_ceil}; use crate::chain::cosmos::query::account::get_or_fetch_account; +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::gas::{default_gas_from_config, max_gas_from_config}; use crate::chain::tx::TrackedMsgs; use crate::chain::{ChainEndpoint, HealthCheck}; -use crate::chain::{QueryResponse, StatusResponse}; +use crate::chain::{ChainStatus, QueryResponse}; use crate::config::ChainConfig; use crate::error::Error; use crate::event::monitor::{EventMonitor, EventReceiver, TxMonitorCmd}; @@ -91,6 +92,7 @@ pub mod gas; pub mod query; pub mod retry; pub mod simulate; +pub mod transfer; pub mod tx; pub mod types; pub mod version; @@ -375,12 +377,13 @@ impl CosmosSdkChain { crate::time!("query_latest_height"); crate::telemetry!(query, self.id(), "query_latest_height"); - let status = self.status()?; + let status = self.rt.block_on(query_status( + self.id(), + &self.rpc_client, + &self.config.rpc_addr, + ))?; - Ok(ICSHeight { - revision_number: ChainId::chain_version(status.node_info.network.as_str()), - revision_height: u64::from(status.sync_info.latest_block_height), - }) + Ok(status.height) } async fn do_send_messages_and_wait_commit( @@ -637,22 +640,15 @@ impl ChainEndpoint for CosmosSdkChain { } /// Query the chain status - fn query_status(&self) -> Result { + fn query_status(&self) -> Result { crate::time!("query_status"); crate::telemetry!(query, self.id(), "query_status"); - let status = self.status()?; - - let time = status.sync_info.latest_block_time; - let height = ICSHeight { - revision_number: ChainId::chain_version(status.node_info.network.as_str()), - revision_height: u64::from(status.sync_info.latest_block_height), - }; - - Ok(StatusResponse { - height, - timestamp: time.into(), - }) + self.rt.block_on(query_status( + self.id(), + &self.rpc_client, + &self.config.rpc_addr, + )) } fn query_clients( diff --git a/relayer/src/chain/cosmos/query.rs b/relayer/src/chain/cosmos/query.rs index b6cb497af0..5439292580 100644 --- a/relayer/src/chain/cosmos/query.rs +++ b/relayer/src/chain/cosmos/query.rs @@ -17,6 +17,7 @@ use crate::chain::QueryResponse; use crate::error::Error; pub mod account; +pub mod status; pub mod tx; pub fn packet_query(request: &QueryPacketEventDataRequest, seq: Sequence) -> Query { diff --git a/relayer/src/chain/cosmos/query/status.rs b/relayer/src/chain/cosmos/query/status.rs new file mode 100644 index 0000000000..125214800a --- /dev/null +++ b/relayer/src/chain/cosmos/query/status.rs @@ -0,0 +1,36 @@ +use ibc::core::ics24_host::identifier::ChainId; +use ibc::Height; +use tendermint_rpc::{Client, HttpClient, Url}; + +use crate::chain::ChainStatus; +use crate::error::Error; + +pub async fn query_status( + chain_id: &ChainId, + rpc_client: &HttpClient, + rpc_address: &Url, +) -> Result { + let response = rpc_client + .status() + .await + .map_err(|e| Error::rpc(rpc_address.clone(), e))?; + + if response.sync_info.catching_up { + return Err(Error::chain_not_caught_up( + rpc_address.to_string(), + chain_id.clone(), + )); + } + + let time = response.sync_info.latest_block_time; + + let height = Height { + revision_number: ChainId::chain_version(response.node_info.network.as_str()), + revision_height: u64::from(response.sync_info.latest_block_height), + }; + + Ok(ChainStatus { + height, + timestamp: time.into(), + }) +} diff --git a/relayer/src/chain/cosmos/transfer.rs b/relayer/src/chain/cosmos/transfer.rs new file mode 100644 index 0000000000..e9ee4f2b0c --- /dev/null +++ b/relayer/src/chain/cosmos/transfer.rs @@ -0,0 +1,24 @@ +// use ibc::Height; +// use ibc::timestamp::Timestamp; +// use ibc::core::ics24_host::identifier::{ChainId, ChannelId, PortId}; + +// use crate::transfer::Amount; + +// pub enum TransferTimeout { +// Height(Height), +// Timestamp(Timestamp), +// HeightAndTimestamp(Height, Timestamp), +// } + +// pub fn new_transfer_message_with_timeout_height( +// source_port_id: &PortId, +// source_channel_id: &ChannelId, +// denom: &str, +// amount: &Amount, +// sender: &Signer, +// receiver: &Signer, +// timeout: &TransferTimeout, +// ) -> MsgTransfer +// { +// todo!() +// } diff --git a/relayer/src/chain/handle.rs b/relayer/src/chain/handle.rs index d88e7c0825..100a1c249c 100644 --- a/relayer/src/chain/handle.rs +++ b/relayer/src/chain/handle.rs @@ -53,7 +53,7 @@ use crate::{ use super::client::ClientSettings; use super::tx::TrackedMsgs; -use super::{HealthCheck, StatusResponse}; +use super::{ChainStatus, HealthCheck}; mod base; mod cache; @@ -150,7 +150,7 @@ pub enum ChainRequest { }, QueryStatus { - reply_to: ReplyTo, + reply_to: ReplyTo, }, QueryClients { @@ -384,7 +384,7 @@ pub trait ChainHandle: Clone + Send + Sync + Serialize + Debug + 'static { /// Return the version of the IBC protocol that this chain is running, if known. fn ibc_version(&self) -> Result, Error>; - fn query_status(&self) -> Result; + fn query_status(&self) -> Result; fn query_latest_height(&self) -> Result { Ok(self.query_status()?.height) diff --git a/relayer/src/chain/handle/base.rs b/relayer/src/chain/handle/base.rs index 39cdcda106..bfd9597b09 100644 --- a/relayer/src/chain/handle/base.rs +++ b/relayer/src/chain/handle/base.rs @@ -37,7 +37,7 @@ use ibc_proto::ibc::core::connection::v1::QueryClientConnectionsRequest; use ibc_proto::ibc::core::connection::v1::QueryConnectionsRequest; use crate::{ - chain::{client::ClientSettings, tx::TrackedMsgs, StatusResponse}, + chain::{client::ClientSettings, tx::TrackedMsgs, ChainStatus}, config::ChainConfig, connection::ConnectionMsgType, error::Error, @@ -142,7 +142,7 @@ impl ChainHandle for BaseChainHandle { self.send(|reply_to| ChainRequest::IbcVersion { reply_to }) } - fn query_status(&self) -> Result { + fn query_status(&self) -> Result { self.send(|reply_to| ChainRequest::QueryStatus { reply_to }) } diff --git a/relayer/src/chain/handle/cache.rs b/relayer/src/chain/handle/cache.rs index 1eec45c81f..def29c8822 100644 --- a/relayer/src/chain/handle/cache.rs +++ b/relayer/src/chain/handle/cache.rs @@ -38,7 +38,7 @@ use crate::cache::{Cache, CacheStatus}; use crate::chain::client::ClientSettings; use crate::chain::handle::{ChainHandle, ChainRequest, Subscription}; use crate::chain::tx::TrackedMsgs; -use crate::chain::{HealthCheck, StatusResponse}; +use crate::chain::{ChainStatus, HealthCheck}; use crate::config::ChainConfig; use crate::error::Error; use crate::telemetry; @@ -127,7 +127,7 @@ impl ChainHandle for CachingChainHandle { self.inner().ibc_version() } - fn query_status(&self) -> Result { + fn query_status(&self) -> Result { self.inner().query_status() } diff --git a/relayer/src/chain/handle/counting.rs b/relayer/src/chain/handle/counting.rs index 8a8b192c97..d094d04c84 100644 --- a/relayer/src/chain/handle/counting.rs +++ b/relayer/src/chain/handle/counting.rs @@ -38,7 +38,7 @@ use tracing::debug; use crate::chain::client::ClientSettings; use crate::chain::handle::{ChainHandle, ChainRequest, Subscription}; use crate::chain::tx::TrackedMsgs; -use crate::chain::{HealthCheck, StatusResponse}; +use crate::chain::{ChainStatus, HealthCheck}; use crate::config::ChainConfig; use crate::error::Error; use crate::util::lock::LockExt; @@ -155,7 +155,7 @@ impl ChainHandle for CountingChainHandle { self.inner().ibc_version() } - fn query_status(&self) -> Result { + fn query_status(&self) -> Result { self.inc_metric("query_status"); self.inner().query_status() } diff --git a/relayer/src/chain/mock.rs b/relayer/src/chain/mock.rs index 52627d2c9c..f26f60ce36 100644 --- a/relayer/src/chain/mock.rs +++ b/relayer/src/chain/mock.rs @@ -40,7 +40,7 @@ use ibc_proto::ibc::core::connection::v1::{ }; use crate::chain::client::ClientSettings; -use crate::chain::{ChainEndpoint, StatusResponse}; +use crate::chain::{ChainEndpoint, ChainStatus}; use crate::config::ChainConfig; use crate::error::Error; use crate::event::monitor::{EventReceiver, EventSender, TxMonitorCmd}; @@ -170,8 +170,8 @@ impl ChainEndpoint for MockChain { unimplemented!() } - fn query_status(&self) -> Result { - Ok(StatusResponse { + fn query_status(&self) -> Result { + Ok(ChainStatus { height: self.context.host_height(), timestamp: self.context.host_timestamp(), }) diff --git a/relayer/src/chain/runtime.rs b/relayer/src/chain/runtime.rs index 09224c241f..e4f74aba8c 100644 --- a/relayer/src/chain/runtime.rs +++ b/relayer/src/chain/runtime.rs @@ -44,7 +44,7 @@ use ibc_proto::ibc::core::{ }; use crate::{ - chain::{client::ClientSettings, StatusResponse}, + chain::{client::ClientSettings, ChainStatus}, config::ChainConfig, connection::ConnectionMsgType, error::Error, @@ -465,7 +465,7 @@ where reply_to.send(result).map_err(Error::send) } - fn query_status(&self, reply_to: ReplyTo) -> Result<(), Error> { + fn query_status(&self, reply_to: ReplyTo) -> Result<(), Error> { let latest_timestamp = self.chain.query_status(); reply_to.send(latest_timestamp).map_err(Error::send) } diff --git a/relayer/src/link/relay_path.rs b/relayer/src/link/relay_path.rs index 7e286a9219..8fe11ac291 100644 --- a/relayer/src/link/relay_path.rs +++ b/relayer/src/link/relay_path.rs @@ -42,7 +42,7 @@ use crate::chain::counterparty::{ }; use crate::chain::handle::ChainHandle; use crate::chain::tx::TrackedMsgs; -use crate::chain::StatusResponse; +use crate::chain::ChainStatus; use crate::channel::error::ChannelError; use crate::channel::Channel; use crate::event::monitor::EventBatch; @@ -1319,7 +1319,7 @@ impl RelayPath { fn build_timeout_from_send_packet_event( &self, event: &SendPacket, - dst_info: &StatusResponse, + dst_info: &ChainStatus, ) -> Result, LinkError> { let packet = event.packet.clone(); if self @@ -1337,7 +1337,7 @@ impl RelayPath { fn build_recv_or_timeout_from_send_packet_event( &self, event: &SendPacket, - dst_info: &StatusResponse, + dst_info: &ChainStatus, ) -> Result<(Option, Option), LinkError> { let timeout = self.build_timeout_from_send_packet_event(event, dst_info)?; if timeout.is_some() { From 7f257655363b7643f07db7c821ea3a86aec14375 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Wed, 6 Apr 2022 13:02:03 +0200 Subject: [PATCH 24/30] Introduce TransferTimeout abstraction --- relayer-cli/src/commands/tx/transfer.rs | 5 +- relayer-cli/src/error.rs | 6 +- relayer/src/chain/cosmos.rs | 1 - relayer/src/chain/cosmos/transfer.rs | 24 ----- relayer/src/link/error.rs | 4 +- relayer/src/transfer.rs | 94 +++++++++++++++----- tools/test-framework/src/error.rs | 12 +-- tools/test-framework/src/relayer/chain.rs | 4 +- tools/test-framework/src/relayer/transfer.rs | 4 +- 9 files changed, 90 insertions(+), 64 deletions(-) delete mode 100644 relayer/src/chain/cosmos/transfer.rs diff --git a/relayer-cli/src/commands/tx/transfer.rs b/relayer-cli/src/commands/tx/transfer.rs index 5dca709156..dc5201a95b 100644 --- a/relayer-cli/src/commands/tx/transfer.rs +++ b/relayer-cli/src/commands/tx/transfer.rs @@ -1,6 +1,7 @@ use abscissa_core::clap::Parser; use abscissa_core::{config::Override, Command, FrameworkErrorKind, Runnable}; +use core::time::Duration; use ibc::{ core::{ ics02_client::client_state::ClientState, @@ -141,7 +142,7 @@ impl TxIcs20MsgTransferCmd { denom, receiver: self.receiver.clone(), timeout_height_offset: self.timeout_height_offset, - timeout_seconds: core::time::Duration::from_secs(self.timeout_seconds), + timeout_duration: Duration::from_secs(self.timeout_seconds), number_msgs, }; @@ -226,7 +227,7 @@ impl Runnable for TxIcs20MsgTransferCmd { // Checks pass, build and send the tx let res: Result, Error> = build_and_send_transfer_messages(&chains.src, &chains.dst, &opts) - .map_err(Error::packet); + .map_err(Error::transfer); match res { Ok(ev) => Output::success(ev).exit(), diff --git a/relayer-cli/src/error.rs b/relayer-cli/src/error.rs index fdec0e4287..b437df50a0 100644 --- a/relayer-cli/src/error.rs +++ b/relayer-cli/src/error.rs @@ -7,7 +7,7 @@ use ibc_relayer::error::Error as RelayerError; use ibc_relayer::foreign_client::ForeignClientError; use ibc_relayer::link::error::LinkError; use ibc_relayer::supervisor::Error as SupervisorError; -use ibc_relayer::transfer::PacketError; +use ibc_relayer::transfer::TransferError; use ibc_relayer::upgrade_chain::UpgradeChainError; use tendermint::Error as TendermintError; @@ -69,8 +69,8 @@ define_error! { [ ConnectionError ] |_| { "connection error" }, - Packet - [ PacketError ] + Transfer + [ TransferError ] |_| { "packet error" }, Channel diff --git a/relayer/src/chain/cosmos.rs b/relayer/src/chain/cosmos.rs index df833fe5cb..290f387c8c 100644 --- a/relayer/src/chain/cosmos.rs +++ b/relayer/src/chain/cosmos.rs @@ -92,7 +92,6 @@ pub mod gas; pub mod query; pub mod retry; pub mod simulate; -pub mod transfer; pub mod tx; pub mod types; pub mod version; diff --git a/relayer/src/chain/cosmos/transfer.rs b/relayer/src/chain/cosmos/transfer.rs deleted file mode 100644 index e9ee4f2b0c..0000000000 --- a/relayer/src/chain/cosmos/transfer.rs +++ /dev/null @@ -1,24 +0,0 @@ -// use ibc::Height; -// use ibc::timestamp::Timestamp; -// use ibc::core::ics24_host::identifier::{ChainId, ChannelId, PortId}; - -// use crate::transfer::Amount; - -// pub enum TransferTimeout { -// Height(Height), -// Timestamp(Timestamp), -// HeightAndTimestamp(Height, Timestamp), -// } - -// pub fn new_transfer_message_with_timeout_height( -// source_port_id: &PortId, -// source_channel_id: &ChannelId, -// denom: &str, -// amount: &Amount, -// sender: &Signer, -// receiver: &Signer, -// timeout: &TransferTimeout, -// ) -> MsgTransfer -// { -// todo!() -// } diff --git a/relayer/src/link/error.rs b/relayer/src/link/error.rs index 67b11869bc..bf2f0803b2 100644 --- a/relayer/src/link/error.rs +++ b/relayer/src/link/error.rs @@ -9,7 +9,7 @@ use crate::connection::ConnectionError; use crate::error::Error; use crate::foreign_client::{ForeignClientError, HasExpiredOrFrozenError}; use crate::supervisor::Error as SupervisorError; -use crate::transfer::PacketError; +use crate::transfer::TransferError; define_error! { LinkError { @@ -64,7 +64,7 @@ define_error! { |_| { "failed during a client operation" }, Packet - [ PacketError ] + [ TransferError ] |_| { "packet error" }, OldPacketClearingFailed diff --git a/relayer/src/transfer.rs b/relayer/src/transfer.rs index e924f9742c..6fc3bed415 100644 --- a/relayer/src/transfer.rs +++ b/relayer/src/transfer.rs @@ -13,11 +13,12 @@ use uint::FromStrRadixErr; use crate::chain::handle::ChainHandle; use crate::chain::tx::TrackedMsgs; +use crate::chain::ChainStatus; use crate::error::Error; use crate::util::bigint::U256; define_error! { - PacketError { + TransferError { Relayer [ Error ] |_| { "relayer error" }, @@ -51,6 +52,9 @@ define_error! { format!("internal error, expected IBCEvent::ChainError, got {:?}", e.event) }, + + ZeroTimeout + | _ | { "packet timeout height and packet timeout timestamp cannot both be 0" }, } } @@ -71,6 +75,57 @@ impl FromStr for Amount { } } +#[derive(Copy, Clone)] +pub enum TransferTimeout { + Height(Height), + Timestamp(Timestamp), + HeightAndTimestamp(Height, Timestamp), +} + +impl TransferTimeout { + pub fn new( + timeout_height_offset: u64, + timeout: Duration, + destination_chain_status: &ChainStatus, + ) -> Result { + let offset_is_zero = timeout_height_offset == 0; + let timeout_is_zero = timeout == Duration::ZERO; + + let timeout_height = destination_chain_status.height.add(timeout_height_offset); + + let timeout_timestamp = (destination_chain_status.timestamp + timeout) + .map_err(TransferError::timestamp_overflow)?; + + if offset_is_zero && timeout_is_zero { + Err(TransferError::zero_timeout()) + } else if timeout_is_zero { + // timeout height offset is non zero + Ok(Self::Height(timeout_height)) + } else if offset_is_zero { + // timeout is non zero + Ok(Self::Timestamp(timeout_timestamp)) + } else { + Ok(Self::HeightAndTimestamp(timeout_height, timeout_timestamp)) + } + } + + pub fn timeout_timestamp(&self) -> Timestamp { + match self { + Self::Height(_) => Timestamp::none(), + Self::Timestamp(timestamp) => *timestamp, + Self::HeightAndTimestamp(_, timestamp) => *timestamp, + } + } + + pub fn timeout_height(&self) -> Height { + match self { + Self::Height(height) => *height, + Self::Timestamp(_) => Height::zero(), + Self::HeightAndTimestamp(height, _) => *height, + } + } +} + #[derive(Clone, Debug)] pub struct TransferOptions { pub packet_src_port_id: PortId, @@ -79,7 +134,7 @@ pub struct TransferOptions { pub denom: String, pub receiver: Option, pub timeout_height_offset: u64, - pub timeout_seconds: Duration, + pub timeout_duration: Duration, pub number_msgs: usize, } @@ -87,28 +142,23 @@ pub fn build_and_send_transfer_messages Result, PacketError> { +) -> Result, TransferError> { let receiver = match &opts.receiver { - None => packet_dst_chain.get_signer().map_err(PacketError::key)?, + None => packet_dst_chain.get_signer().map_err(TransferError::key)?, Some(r) => r.clone().into(), }; - let sender = packet_src_chain.get_signer().map_err(PacketError::key)?; + let sender = packet_src_chain.get_signer().map_err(TransferError::key)?; - let timeout_timestamp = if opts.timeout_seconds == Duration::from_secs(0) { - Timestamp::none() - } else { - (Timestamp::now() + opts.timeout_seconds).map_err(PacketError::timestamp_overflow)? - }; + let chain_status = packet_dst_chain + .query_status() + .map_err(TransferError::relayer)?; - let timeout_height = if opts.timeout_height_offset == 0 { - Height::zero() - } else { - packet_dst_chain - .query_latest_height() - .map_err(PacketError::relayer)? - .add(opts.timeout_height_offset) - }; + let timeout = TransferTimeout::new( + opts.timeout_height_offset, + opts.timeout_duration, + &chain_status, + )?; let msg = MsgTransfer { source_port: opts.packet_src_port_id.clone(), @@ -119,8 +169,8 @@ pub fn build_and_send_transfer_messages Ok(events), Some(err) => { if let IbcEvent::ChainError(err) = err { - Err(PacketError::tx_response(err.clone())) + Err(TransferError::tx_response(err.clone())) } else { panic!( "internal error, expected IBCEvent::ChainError, got {:?}", diff --git a/tools/test-framework/src/error.rs b/tools/test-framework/src/error.rs index dbb7ece3ec..792f06ee4a 100644 --- a/tools/test-framework/src/error.rs +++ b/tools/test-framework/src/error.rs @@ -7,7 +7,7 @@ use ibc_relayer::channel::error::ChannelError; use ibc_relayer::connection::ConnectionError; use ibc_relayer::error::Error as RelayerError; use ibc_relayer::supervisor::error::Error as SupervisorError; -use ibc_relayer::transfer::PacketError; +use ibc_relayer::transfer::TransferError; use std::io::{Error as IoError, ErrorKind as IoErrorKind}; define_error! { @@ -45,8 +45,8 @@ define_error! { [ ConnectionError ] | _ | { "connection error"}, - Packet - [ PacketError ] + Transfer + [ TransferError ] | _ | { "packet error"}, } } @@ -98,8 +98,8 @@ impl From for Error { } } -impl From for Error { - fn from(e: PacketError) -> Self { - Error::packet(e) +impl From for Error { + fn from(e: TransferError) -> Self { + Error::transfer(e) } } diff --git a/tools/test-framework/src/relayer/chain.rs b/tools/test-framework/src/relayer/chain.rs index bca4a5bf34..3d2c00be6b 100644 --- a/tools/test-framework/src/relayer/chain.rs +++ b/tools/test-framework/src/relayer/chain.rs @@ -57,7 +57,7 @@ use ibc_proto::ibc::core::connection::v1::QueryConnectionsRequest; use ibc_relayer::chain::client::ClientSettings; use ibc_relayer::chain::handle::{ChainHandle, ChainRequest, Subscription}; use ibc_relayer::chain::tx::TrackedMsgs; -use ibc_relayer::chain::{HealthCheck, StatusResponse}; +use ibc_relayer::chain::{ChainStatus, HealthCheck}; use ibc_relayer::config::ChainConfig; use ibc_relayer::error::Error; use ibc_relayer::{connection::ConnectionMsgType, keyring::KeyEntry}; @@ -128,7 +128,7 @@ where self.value().ibc_version() } - fn query_status(&self) -> Result { + fn query_status(&self) -> Result { self.value().query_status() } diff --git a/tools/test-framework/src/relayer/transfer.rs b/tools/test-framework/src/relayer/transfer.rs index 1ed1741a91..d3b380e14e 100644 --- a/tools/test-framework/src/relayer/transfer.rs +++ b/tools/test-framework/src/relayer/transfer.rs @@ -46,7 +46,7 @@ pub fn tx_raw_ft_transfer( denom: &MonoTagged, amount: u64, timeout_height_offset: u64, - timeout_seconds: Duration, + timeout_duration: Duration, number_messages: usize, ) -> Result, Error> { let transfer_options = TransferOptions { @@ -56,7 +56,7 @@ pub fn tx_raw_ft_transfer( denom: denom.value().to_string(), receiver: Some(recipient.value().0.clone()), timeout_height_offset, - timeout_seconds, + timeout_duration, number_msgs: number_messages, }; From d245cdb384c6e5e97e778ffdfda967c46ae4f514 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Thu, 7 Apr 2022 10:59:22 +0200 Subject: [PATCH 25/30] Use prost::Message::encoded_len() to compute encoded message length --- relayer/src/chain/cosmos/batch.rs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/relayer/src/chain/cosmos/batch.rs b/relayer/src/chain/cosmos/batch.rs index 24583e11b0..6a36fb8639 100644 --- a/relayer/src/chain/cosmos/batch.rs +++ b/relayer/src/chain/cosmos/batch.rs @@ -1,5 +1,6 @@ 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; @@ -146,7 +147,7 @@ fn batch_messages(config: &ChainConfig, messages: Vec) -> Result= max_message_count || current_size >= max_tx_size { @@ -163,12 +164,3 @@ fn batch_messages(config: &ChainConfig, messages: Vec) -> Result Result { - let mut buf = Vec::new(); - - prost::Message::encode(message, &mut buf) - .map_err(|e| Error::protobuf_encode("Message".into(), e))?; - - Ok(buf.len()) -} From 52992f63a7d66fb500fe356f868cccdd9ce74e60 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Thu, 7 Apr 2022 11:12:15 +0200 Subject: [PATCH 26/30] Address review feedback --- relayer/src/chain/cosmos/query.rs | 6 +++--- relayer/src/chain/cosmos/retry.rs | 3 +++ relayer/src/transfer.rs | 6 +++--- tools/test-framework/src/error.rs | 2 +- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/relayer/src/chain/cosmos/query.rs b/relayer/src/chain/cosmos/query.rs index 5439292580..15d700b173 100644 --- a/relayer/src/chain/cosmos/query.rs +++ b/relayer/src/chain/cosmos/query.rs @@ -21,7 +21,7 @@ pub mod status; pub mod tx; pub fn packet_query(request: &QueryPacketEventDataRequest, seq: Sequence) -> Query { - tendermint_rpc::query::Query::eq( + Query::eq( format!("{}.packet_src_channel", request.event_id.as_str()), request.source_channel_id.to_string(), ) @@ -44,7 +44,7 @@ pub fn packet_query(request: &QueryPacketEventDataRequest, seq: Sequence) -> Que } pub fn header_query(request: &QueryClientEventRequest) -> Query { - tendermint_rpc::query::Query::eq( + Query::eq( format!("{}.client_id", request.event_id.as_str()), request.client_id.to_string(), ) @@ -58,7 +58,7 @@ pub fn header_query(request: &QueryClientEventRequest) -> Query { } pub fn tx_hash_query(request: &QueryTxHash) -> Query { - tendermint_rpc::query::Query::eq("tx.hash", request.0.to_string()) + Query::eq("tx.hash", request.0.to_string()) } /// Perform a generic `abci_query`, and return the corresponding deserialized response data. diff --git a/relayer/src/chain/cosmos/retry.rs b/relayer/src/chain/cosmos/retry.rs index 7c013dc84d..922f016beb 100644 --- a/relayer/src/chain/cosmos/retry.rs +++ b/relayer/src/chain/cosmos/retry.rs @@ -69,6 +69,9 @@ pub async fn send_tx_with_account_sequence_retry( .await } +// We have to do explicit return of `Box` because Rust +// 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, diff --git a/relayer/src/transfer.rs b/relayer/src/transfer.rs index 6fc3bed415..759236aa08 100644 --- a/relayer/src/transfer.rs +++ b/relayer/src/transfer.rs @@ -85,15 +85,15 @@ pub enum TransferTimeout { impl TransferTimeout { pub fn new( timeout_height_offset: u64, - timeout: Duration, + timeout_duration: Duration, destination_chain_status: &ChainStatus, ) -> Result { let offset_is_zero = timeout_height_offset == 0; - let timeout_is_zero = timeout == Duration::ZERO; + let timeout_is_zero = timeout_duration == Duration::ZERO; let timeout_height = destination_chain_status.height.add(timeout_height_offset); - let timeout_timestamp = (destination_chain_status.timestamp + timeout) + let timeout_timestamp = (destination_chain_status.timestamp + timeout_duration) .map_err(TransferError::timestamp_overflow)?; if offset_is_zero && timeout_is_zero { diff --git a/tools/test-framework/src/error.rs b/tools/test-framework/src/error.rs index 792f06ee4a..c9c4b1ce4f 100644 --- a/tools/test-framework/src/error.rs +++ b/tools/test-framework/src/error.rs @@ -47,7 +47,7 @@ define_error! { Transfer [ TransferError ] - | _ | { "packet error"}, + | _ | { "transfer error"}, } } From 2d101f32f022dab1241235728661aa4f121c0418 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Thu, 7 Apr 2022 11:40:00 +0200 Subject: [PATCH 27/30] Re-add missing comments --- relayer-cli/src/error.rs | 2 +- relayer/src/chain/cosmos/estimate.rs | 8 ++++++++ relayer/src/chain/cosmos/gas.rs | 1 + relayer/src/chain/cosmos/query/account.rs | 5 +++++ relayer/src/chain/cosmos/query/status.rs | 4 ++++ relayer/src/chain/cosmos/retry.rs | 12 +++++++++++- relayer/src/chain/cosmos/types/gas.rs | 8 ++++++++ 7 files changed, 38 insertions(+), 2 deletions(-) diff --git a/relayer-cli/src/error.rs b/relayer-cli/src/error.rs index b437df50a0..8565a1f7b8 100644 --- a/relayer-cli/src/error.rs +++ b/relayer-cli/src/error.rs @@ -71,7 +71,7 @@ define_error! { Transfer [ TransferError ] - |_| { "packet error" }, + |_| { "transfer error" }, Channel [ ChannelError ] diff --git a/relayer/src/chain/cosmos/estimate.rs b/relayer/src/chain/cosmos/estimate.rs index 206e453681..22faf937ea 100644 --- a/relayer/src/chain/cosmos/estimate.rs +++ b/relayer/src/chain/cosmos/estimate.rs @@ -82,6 +82,14 @@ async fn estimate_fee_with_tx( Ok(adjusted_fee) } +/// Try to simulate the given tx in order to estimate how much gas will be needed to submit it. +/// +/// It is possible that a batch of messages are fragmented by the caller (`send_msgs`) such that +/// they do not individually verify. For example for the following batch: +/// [`MsgUpdateClient`, `MsgRecvPacket`, ..., `MsgRecvPacket`] +/// +/// If the batch is split in two TX-es, the second one will fail the simulation in `deliverTx` check. +/// In this case we use the `default_gas` param. async fn estimate_gas_with_tx( gas_config: &GasConfig, grpc_address: &Uri, diff --git a/relayer/src/chain/cosmos/gas.rs b/relayer/src/chain/cosmos/gas.rs index 442d43b9a1..9b881845db 100644 --- a/relayer/src/chain/cosmos/gas.rs +++ b/relayer/src/chain/cosmos/gas.rs @@ -13,6 +13,7 @@ pub struct PrettyFee<'a>(pub &'a Fee); pub fn gas_amount_to_fees(config: &GasConfig, gas_amount: u64) -> Fee { let adjusted_gas_limit = adjust_gas_with_simulated_fees(config, gas_amount); + // The fee in coins based on gas amount let amount = calculate_fee(adjusted_gas_limit, &config.gas_price); Fee { diff --git a/relayer/src/chain/cosmos/query/account.rs b/relayer/src/chain/cosmos/query/account.rs index 9122947c09..2cc7579481 100644 --- a/relayer/src/chain/cosmos/query/account.rs +++ b/relayer/src/chain/cosmos/query/account.rs @@ -7,6 +7,9 @@ use tracing::info; use crate::chain::cosmos::types::account::Account; use crate::error::Error; +/// Get a `&mut Account` from an `&mut Option` if it is `Some(Account)`. +/// Otherwise query for the account information, update the `Option` to `Some`, +/// and return the underlying `&mut` reference. pub async fn get_or_fetch_account<'a>( grpc_address: &Uri, account_address: &str, @@ -25,6 +28,8 @@ pub async fn get_or_fetch_account<'a>( } } +/// Refresh the account sequence behind the `&mut Account` by refetching the +/// account and updating the `&mut` reference. pub async fn refresh_account<'a>( grpc_address: &Uri, account_address: &str, diff --git a/relayer/src/chain/cosmos/query/status.rs b/relayer/src/chain/cosmos/query/status.rs index 125214800a..8efc5c57c4 100644 --- a/relayer/src/chain/cosmos/query/status.rs +++ b/relayer/src/chain/cosmos/query/status.rs @@ -5,6 +5,10 @@ use tendermint_rpc::{Client, HttpClient, Url}; use crate::chain::ChainStatus; use crate::error::Error; +/// Query the chain status via an RPC query. +/// +/// Returns an error if the node is still syncing and has not caught up, +/// ie. if `sync_info.catching_up` is `true`. pub async fn query_status( chain_id: &ChainId, rpc_client: &HttpClient, diff --git a/relayer/src/chain/cosmos/retry.rs b/relayer/src/chain/cosmos/retry.rs index 922f016beb..9960455969 100644 --- a/relayer/src/chain/cosmos/retry.rs +++ b/relayer/src/chain/cosmos/retry.rs @@ -7,7 +7,7 @@ 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, warn}; +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; @@ -56,6 +56,10 @@ pub async fn send_tx_with_account_sequence_retry( messages: Vec, retry_counter: u64, ) -> Result { + crate::time!("send_tx_with_account_sequence_retry"); + let _span = + span!(Level::ERROR, "send_tx_with_account_sequence_retry", id = %config.id).entered(); + do_send_tx_with_account_sequence_retry( config, rpc_client, @@ -83,6 +87,12 @@ fn do_send_tx_with_account_sequence_retry<'a>( retry_counter: u64, ) -> Pin> + 'a>> { Box::pin(async move { + debug!( + "sending {} messages using account sequence {}", + messages.len(), + account.sequence, + ); + let tx_result = estimate_fee_and_send_tx( config, rpc_client, diff --git a/relayer/src/chain/cosmos/types/gas.rs b/relayer/src/chain/cosmos/types/gas.rs index 4732726eb0..55625dab3c 100644 --- a/relayer/src/chain/cosmos/types/gas.rs +++ b/relayer/src/chain/cosmos/types/gas.rs @@ -33,22 +33,27 @@ impl GasConfig { } } +/// The default amount of gas the relayer is willing to pay for a transaction, +/// when it cannot simulate the tx and therefore estimate the gas amount needed. pub fn default_gas_from_config(config: &ChainConfig) -> u64 { config .default_gas .unwrap_or_else(|| max_gas_from_config(config)) } +/// The maximum amount of gas the relayer is willing to pay for a transaction pub fn max_gas_from_config(config: &ChainConfig) -> u64 { config.max_gas.unwrap_or(DEFAULT_MAX_GAS) } +/// The gas price adjustment fn gas_adjustment_from_config(config: &ChainConfig) -> f64 { config .gas_adjustment .unwrap_or(DEFAULT_GAS_PRICE_ADJUSTMENT) } +/// Get the fee granter address fn fee_granter_from_config(config: &ChainConfig) -> String { config .fee_granter @@ -59,7 +64,10 @@ fn fee_granter_from_config(config: &ChainConfig) -> String { fn max_fee_from_config(config: &ChainConfig) -> Fee { let max_gas = max_gas_from_config(config); + + // The maximum fee the relayer pays for a transaction let max_fee_in_coins = calculate_fee(max_gas, &config.gas_price); + let fee_granter = fee_granter_from_config(config); Fee { From 17ade0d22fd578b9b50ad953c43a52678a2518e2 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Fri, 8 Apr 2022 11:46:13 +0200 Subject: [PATCH 28/30] Fix clippy error --- relayer/src/chain/cosmos/wait.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/relayer/src/chain/cosmos/wait.rs b/relayer/src/chain/cosmos/wait.rs index 5a42f72fe5..953395fef2 100644 --- a/relayer/src/chain/cosmos/wait.rs +++ b/relayer/src/chain/cosmos/wait.rs @@ -22,7 +22,7 @@ pub async fn wait_for_block_commits( rpc_client: &HttpClient, rpc_address: &Url, rpc_timeout: &Duration, - tx_sync_results: &mut Vec, + tx_sync_results: &mut [TxSyncResult], ) -> Result<(), Error> { let start_time = Instant::now(); From 073b0572b9834905795546a15226b3544a48577e Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Mon, 11 Apr 2022 22:12:15 +0200 Subject: [PATCH 29/30] Remove check for both timeout height offset and duration being zero --- relayer/src/transfer.rs | 45 +++++++++-------------------------------- 1 file changed, 9 insertions(+), 36 deletions(-) diff --git a/relayer/src/transfer.rs b/relayer/src/transfer.rs index 3c672132a3..8c653ca19f 100644 --- a/relayer/src/transfer.rs +++ b/relayer/src/transfer.rs @@ -76,10 +76,9 @@ impl FromStr for Amount { } #[derive(Copy, Clone)] -pub enum TransferTimeout { - Height(Height), - Timestamp(Timestamp), - HeightAndTimestamp(Height, Timestamp), +pub struct TransferTimeout { + pub timeout_height: Height, + pub timeout_timestamp: Timestamp, } impl TransferTimeout { @@ -88,41 +87,15 @@ impl TransferTimeout { timeout_duration: Duration, destination_chain_status: &ChainStatus, ) -> Result { - let offset_is_zero = timeout_height_offset == 0; - let timeout_is_zero = timeout_duration == Duration::ZERO; - let timeout_height = destination_chain_status.height.add(timeout_height_offset); let timeout_timestamp = (destination_chain_status.timestamp + timeout_duration) .map_err(TransferError::timestamp_overflow)?; - if offset_is_zero && timeout_is_zero { - Err(TransferError::zero_timeout()) - } else if timeout_is_zero { - // timeout height offset is non zero - Ok(Self::Height(timeout_height)) - } else if offset_is_zero { - // timeout is non zero - Ok(Self::Timestamp(timeout_timestamp)) - } else { - Ok(Self::HeightAndTimestamp(timeout_height, timeout_timestamp)) - } - } - - pub fn timeout_timestamp(&self) -> Timestamp { - match self { - Self::Height(_) => Timestamp::none(), - Self::Timestamp(timestamp) => *timestamp, - Self::HeightAndTimestamp(_, timestamp) => *timestamp, - } - } - - pub fn timeout_height(&self) -> Height { - match self { - Self::Height(height) => *height, - Self::Timestamp(_) => Height::zero(), - Self::HeightAndTimestamp(height, _) => *height, - } + Ok(TransferTimeout { + timeout_height, + timeout_timestamp, + }) } } @@ -169,8 +142,8 @@ pub fn build_and_send_transfer_messages Date: Tue, 12 Apr 2022 11:41:51 +0200 Subject: [PATCH 30/30] Do not set timeout height or time when input is zero --- relayer/src/transfer.rs | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/relayer/src/transfer.rs b/relayer/src/transfer.rs index 8c653ca19f..e9d950e1e5 100644 --- a/relayer/src/transfer.rs +++ b/relayer/src/transfer.rs @@ -82,15 +82,33 @@ pub struct TransferTimeout { } impl TransferTimeout { + /** + Construct the transfer timeout parameters from the given timeout + height offset, timeout duration, and the latest chain status + containing the latest time of the destination chain. + + The height offset and duration are optional, with zero indicating + that the packet do not get expired at the given height or time. + If both height offset and duration are zero, then the packet will + never expire. + */ pub fn new( timeout_height_offset: u64, timeout_duration: Duration, destination_chain_status: &ChainStatus, ) -> Result { - let timeout_height = destination_chain_status.height.add(timeout_height_offset); - - let timeout_timestamp = (destination_chain_status.timestamp + timeout_duration) - .map_err(TransferError::timestamp_overflow)?; + let timeout_height = if timeout_height_offset == 0 { + Height::zero() + } else { + destination_chain_status.height.add(timeout_height_offset) + }; + + let timeout_timestamp = if timeout_duration == Duration::ZERO { + Timestamp::none() + } else { + (destination_chain_status.timestamp + timeout_duration) + .map_err(TransferError::timestamp_overflow)? + }; Ok(TransferTimeout { timeout_height,