diff --git a/crates/relayer/src/chain/cosmos/gas.rs b/crates/relayer/src/chain/cosmos/gas.rs index 07b4b6a20f..a9b6ccdeac 100644 --- a/crates/relayer/src/chain/cosmos/gas.rs +++ b/crates/relayer/src/chain/cosmos/gas.rs @@ -117,16 +117,16 @@ pub fn mul_floor(a: u64, f: f64) -> BigInt { (f * a).floor().to_integer() } -struct AdjustGas { - gas_multiplier: f64, - max_gas: u64, - gas_amount: u64, +pub struct AdjustGas { + pub gas_multiplier: f64, + pub max_gas: u64, + pub gas_amount: u64, } /// Adjusts the fee based on the configured `gas_multiplier` 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_estimated_gas( +pub fn adjust_estimated_gas( AdjustGas { gas_multiplier, max_gas, diff --git a/crates/relayer/src/chain/namada/error.rs b/crates/relayer/src/chain/namada/error.rs index 85f5d46527..5c3a76f941 100644 --- a/crates/relayer/src/chain/namada/error.rs +++ b/crates/relayer/src/chain/namada/error.rs @@ -21,6 +21,10 @@ define_error! { BorshDecode [ TraceError ] |_| { "borsh decoding failed" }, + + DryRun + { tx_result: namada_sdk::tx::data::TxResult } + |e| { format!("Dry run to simulate a transaction failed: {}", e.tx_result) }, } } diff --git a/crates/relayer/src/chain/namada/tx.rs b/crates/relayer/src/chain/namada/tx.rs index defa6e6e78..9cc8454d7c 100644 --- a/crates/relayer/src/chain/namada/tx.rs +++ b/crates/relayer/src/chain/namada/tx.rs @@ -6,6 +6,7 @@ use std::time::Instant; use ibc_proto::google::protobuf::Any; use itertools::Itertools; +use namada_ibc::{MsgAcknowledgement, MsgRecvPacket, MsgTimeout}; use namada_sdk::address::{Address, ImplicitAddress}; use namada_sdk::args::{self, TxBuilder}; use namada_sdk::args::{InputAmount, Tx as TxArgs, TxCustom}; @@ -19,15 +20,17 @@ use namada_sdk::ibc::core::channel::types::msgs::{ MsgTimeout as IbcMsgTimeout, ACKNOWLEDGEMENT_TYPE_URL, RECV_PACKET_TYPE_URL, TIMEOUT_TYPE_URL, }; use namada_sdk::ibc::core::host::types::identifiers::{ChannelId, PortId}; -use namada_ibc::{MsgAcknowledgement, MsgRecvPacket, MsgTimeout}; use namada_sdk::masp::{PaymentAddress, TransferTarget}; use namada_sdk::masp_primitives::transaction::Transaction as MaspTransaction; +use namada_sdk::tx::{prepare_tx, ProcessTxResponse}; use namada_sdk::{signing, tx, Namada}; use namada_token::ShieldingTransfer; use tendermint_proto::Protobuf; use tendermint_rpc::endpoint::broadcast::tx_sync::Response; -use tracing::{debug, debug_span, trace}; +use tracing::{debug, debug_span, trace, warn}; +use crate::chain::cosmos::gas::{adjust_estimated_gas, AdjustGas}; +use crate::chain::cosmos::types::gas::default_gas_from_config; use crate::chain::cosmos::types::tx::{TxStatus, TxSyncResult}; use crate::chain::cosmos::wait::all_tx_results_found; use crate::chain::endpoint::ChainEndpoint; @@ -44,9 +47,11 @@ impl NamadaChain { return Err(Error::send_tx("No message to be batched".to_string())); } - let tx_args = self.make_tx_args()?; + let mut tx_args = self.make_tx_args()?; + + let relayer_key = self.get_key()?; + let relayer_addr = relayer_key.address; - let relayer_addr = self.get_key()?.address; let rt = self.rt.clone(); rt.block_on(self.submit_reveal_aux(&tx_args, &relayer_addr))?; @@ -57,7 +62,6 @@ impl NamadaChain { serialized_tx: None, owner: relayer_addr.clone(), }; - let mut txs = Vec::new(); for msg in msgs { let (mut tx, signing_data) = rt @@ -67,17 +71,46 @@ impl NamadaChain { txs.push((tx, signing_data)); } let (mut tx, signing_data) = tx::build_batch(txs).map_err(NamadaError::namada)?; + let signing_data = signing_data.first().expect("SigningData should exist"); + + // Estimate the fee with dry-run + if let Some((fee_token, gas_limit, fee_amount)) = + self.estimate_fee(tx.clone(), &tx_args, signing_data)? + { + // Set the estimated fee + tx_args = tx_args + .fee_token(fee_token) + .gas_limit(gas_limit.into()) + .fee_amount( + fee_amount + .to_string() + .parse() + .expect("Fee should be parsable"), + ); + let fee_amount = rt + .block_on(signing::validate_fee(&self.ctx, &tx_args)) + .map_err(NamadaError::namada)?; + rt.block_on(prepare_tx( + &tx_args, + &mut tx, + fee_amount, + relayer_key.secret_key.to_public(), + )) + .map_err(NamadaError::namada)?; + } + rt.block_on(self.ctx.sign( &mut tx, - &args.tx, - signing_data.first().unwrap().clone(), + &tx_args, + signing_data.clone(), signing::default_sign, (), )) .map_err(NamadaError::namada)?; + let tx_header_hash = tx.header_hash().to_string(); let response = rt - .block_on(self.ctx.submit(tx, &args.tx)) + .block_on(self.ctx.submit(tx, &tx_args)) .map_err(NamadaError::namada)?; match response { @@ -284,6 +317,87 @@ impl NamadaChain { } } + fn estimate_fee( + &self, + mut tx: tx::Tx, + args: &TxArgs, + signing_data: &signing::SigningTxData, + ) -> Result, Error> { + let chain_id = self.config().id.clone(); + + let args = args.clone().dry_run_wrapper(true); + self.rt + .block_on(self.ctx.sign( + &mut tx, + &args, + signing_data.clone(), + signing::default_sign, + (), + )) + .map_err(NamadaError::namada)?; + + let response = match self.rt.block_on(self.ctx.submit(tx, &args)) { + Ok(resp) => resp, + Err(_) => { + warn!( + id = %chain_id, + "send_tx: gas estimation failed, using the default gas limit" + ); + return Ok(None); + } + }; + let estimated_gas = match response { + ProcessTxResponse::DryRun(result) => { + if result + .batch_results + .0 + .iter() + .all(|(_, r)| matches!(&r, Ok(result) if result.is_accepted())) + { + // Convert with the decimal scale of Gas units + u64::from_str(&result.gas_used.to_string()).expect("Gas should be parsable") + } else { + return Err(Error::namada(NamadaError::dry_run(result))); + } + } + _ => unreachable!("Unexpected response"), + }; + let max_gas = default_gas_from_config(self.config()); + if estimated_gas > max_gas { + debug!( + id = %chain_id, estimated = ?estimated_gas, max_gas, + "send_tx: estimated gas is higher than max gas" + ); + + return Err(Error::tx_simulate_gas_estimate_exceeded( + chain_id, + estimated_gas, + max_gas, + )); + } + + let fee_token_str = self.config().gas_price.denom.clone(); + let fee_token = Address::from_str(&fee_token_str) + .map_err(|_| NamadaError::address_decode(fee_token_str.clone()))?; + let gas_price = self.config().gas_price.price; + let gas_multiplier = self.config().gas_multiplier.unwrap_or_default().to_f64(); + + let adjusted_gas = adjust_estimated_gas(AdjustGas { + gas_multiplier, + max_gas, + gas_amount: estimated_gas, + }); + + debug!( + id = %chain_id, + "send_tx: using {} gas, gas_price {:?}", + estimated_gas, + gas_price, + ); + + Ok(Some((fee_token, adjusted_gas, gas_price))) + } + pub fn wait_for_block_commits( &self, tx_sync_results: &mut [TxSyncResult], diff --git a/e2e/namada-simple-transfers b/e2e/namada-simple-transfers index abba5f10da..935217127c 100755 --- a/e2e/namada-simple-transfers +++ b/e2e/namada-simple-transfers @@ -4,7 +4,7 @@ # `make build` and `make build-wasm-scripts` on Namada directory in advance # Run with `namada-simple-transfers ${namada_dir}` -set -ex +set -e NAMADA_DIR=$1 if [ -z "${NAMADA_DIR}" ] @@ -45,10 +45,34 @@ function init_relayer_balance() { --node ${ledger_addr} } +function wait_for_relaying() { + local chain_id=$1 + + for i in {1..20} + do + result=$(cargo run --bin hermes -- --config config_for_namada.toml \ + query packet pending \ + --chain ${chain_id} \ + --channel channel-0 \ + --port transfer) + + echo ${result} + if [[ "${result}" =~ "=" ]]; + then + echo "Waiting for packet relaying..." + sleep 5 + else + echo "All packets have been relayed!" + break + fi + done +} + # ==== main ==== # Run 2 Namada chains ${HERMES_DIR}/scripts/setup-namada ${NAMADA_DIR} +sleep 5 cd ${HERMES_DIR} ids=$(grep "id" config_for_namada.toml | awk -F"'" '/^id/ {print $2}') @@ -238,8 +262,7 @@ ${NAMADAC} --base-dir ${base_dir_b} ibc-transfer \ --channel-id channel-0 \ --node ${LEDGER_ADDR_B} -# wait for relaying -sleep 15 +wait_for_relaying ${chain_a} echo "==== Balances on chain A ====" balance=$(${NAMADAC} --base-dir ${base_dir_a} balance \ @@ -295,8 +318,7 @@ ${NAMADAC} --base-dir ${base_dir_a} ibc-transfer \ --gas-payer relayer \ --node ${LEDGER_ADDR_A} -# wait for relaying -sleep 40 +wait_for_relaying ${chain_a} echo "==== Balance of shielded_a on chain A ====" ${NAMADAC} --base-dir ${base_dir_a} shielded-sync \ @@ -373,8 +395,7 @@ ${NAMADAC} --base-dir ${base_dir_b} ibc-transfer \ --channel-id channel-0 \ --node ${LEDGER_ADDR_B} -# wait for relaying -sleep 40 +wait_for_relaying ${chain_a} echo "==== Balances of shielded_a on chain A ====" ${NAMADAC} --base-dir ${base_dir_a} shielded-sync \ diff --git a/scripts/setup-namada b/scripts/setup-namada index 313a568c72..024f706ae8 100755 --- a/scripts/setup-namada +++ b/scripts/setup-namada @@ -32,7 +32,7 @@ LEDGER_ADDR_B="http://127.0.0.1:28657" HERMES_CONFIG_TEMPLATE=" [global] -log_level = 'info' +log_level = 'debug' [mode] @@ -67,7 +67,7 @@ event_source = { mode = 'push', url = 'ws://127.0.0.1:27657/websocket', batch_de account_prefix = '' key_name = 'relayer' store_prefix = 'ibc' -gas_price = { price = 0.001, denom = '_FEE_TOKEN_A_' } +gas_price = { price = 0.000001, denom = '_FEE_TOKEN_A_' } [[chains]] id = '_CHAIN_ID_B_' @@ -78,7 +78,7 @@ event_source = { mode = 'push', url = 'ws://127.0.0.1:28657/websocket', batch_de account_prefix = '' key_name = 'relayer' store_prefix = 'ibc' -gas_price = { price = 0.001, denom = '_FEE_TOKEN_B_' } +gas_price = { price = 0.000001, denom = '_FEE_TOKEN_B_' } " function make_genesis() { diff --git a/scripts/setup-namada-single-node b/scripts/setup-namada-single-node index 9c33c15cd7..e21d662030 100755 --- a/scripts/setup-namada-single-node +++ b/scripts/setup-namada-single-node @@ -27,7 +27,7 @@ LEDGER_ADDR="http://127.0.0.1:27657" HERMES_CONFIG_TEMPLATE=" [global] -log_level = 'info' +log_level = 'debug' [mode] @@ -62,7 +62,7 @@ event_source = { mode = 'push', url = 'ws://127.0.0.1:27657/websocket', batch_de account_prefix = '' key_name = 'relayer' store_prefix = 'ibc' -gas_price = { price = 0.001, denom = '_FEE_TOKEN_' } +gas_price = { price = 0.000001, denom = '_FEE_TOKEN_' } " function make_genesis() {