diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml index efa8e03f8f..3bf3914c6c 100644 --- a/.github/workflows/integration.yaml +++ b/.github/workflows/integration.yaml @@ -45,11 +45,11 @@ jobs: fail-fast: false matrix: chain: - - package: gaia20 + - package: gaia23 command: gaiad account_prefix: cosmos native_token: stake - features: forward-packet,clean-workers,ica,ics29-fee + features: forward-packet,clean-workers,ica,no-denom-trace - package: ibc-go-v6-simapp command: simd account_prefix: cosmos @@ -186,10 +186,10 @@ jobs: fail-fast: false matrix: chain: - - package: .#gaia20 .#stride + - package: .#gaia23 .#stride command: gaiad,strided account_prefix: cosmos,stride - - package: .#gaia20 .#neutron + - package: .#gaia23 .#neutron command: gaiad,neutrond account_prefix: cosmos,neutron steps: @@ -214,7 +214,7 @@ jobs: - uses: actions-rs/cargo@v1 with: command: test - args: -p ibc-integration-test --features interchain-security,ica --no-fail-fast --no-run + args: -p ibc-integration-test --features interchain-security,ica,no-denom-trace --no-fail-fast --no-run - name: Install cargo-nextest run: curl -LsSf https://get.nexte.st/latest/linux | tar zxf - -C ${CARGO_HOME:-~/.cargo}/bin - env: @@ -227,7 +227,7 @@ jobs: run: | nix shell ${{ matrix.chain.package }} -c \ cargo nextest run -p ibc-integration-test --no-fail-fast --failure-output final --test-threads=2 \ - --features interchain-security,ica interchain_security:: + --features interchain-security,ica,no-denom-trace interchain_security:: interchain-security-icq: if: false # Disable CCQ test @@ -236,7 +236,7 @@ jobs: fail-fast: false matrix: chain: - - package: .#gaia20 .#stride-no-admin + - package: .#gaia23 .#stride-no-admin command: gaiad,strided account_prefix: cosmos,stride steps: @@ -261,7 +261,7 @@ jobs: - uses: actions-rs/cargo@v1 with: command: test - args: -p ibc-integration-test --features interchain-security --no-fail-fast --no-run + args: -p ibc-integration-test --features interchain-security,ics31,no-denom-trace --no-fail-fast --no-run - name: Install cargo-nextest run: curl -LsSf https://get.nexte.st/latest/linux | tar zxf - -C ${CARGO_HOME:-~/.cargo}/bin - env: @@ -273,7 +273,7 @@ jobs: run: | nix shell ${{ matrix.chain.package }} -c \ cargo nextest run -p ibc-integration-test --no-fail-fast --failure-output final --test-threads=2 \ - --features interchain-security,ics31 interchain_security:: + --features interchain-security,ics31,no-denom-trace interchain_security:: celestia-to-gaia: runs-on: ubuntu-24.04 @@ -281,7 +281,7 @@ jobs: fail-fast: false matrix: chain: - - package: .#celestia .#gaia20 + - package: .#celestia .#gaia23 command: celestia-appd,gaiad account_prefix: celestia,cosmos native_token: utia,stake @@ -307,7 +307,7 @@ jobs: - uses: actions-rs/cargo@v1 with: command: test - args: -p ibc-integration-test --features celestia --no-fail-fast --no-run + args: -p ibc-integration-test --features celestia,no-denom-trace --no-fail-fast --no-run - name: Install cargo-nextest run: curl -LsSf https://get.nexte.st/latest/linux | tar zxf - -C ${CARGO_HOME:-~/.cargo}/bin - env: @@ -321,7 +321,7 @@ jobs: run: | nix shell .#python ${{ matrix.chain.package }} -c \ cargo nextest run -p ibc-integration-test --no-fail-fast --failure-output final --test-threads=2 \ - --features celestia + --features celestia,no-denom-trace # Run the transfer test using IPv6 for gRPC endpoint ipv6-grpc-endpoint: @@ -330,7 +330,7 @@ jobs: fail-fast: false matrix: chain: - - package: .#gaia20 + - package: .#gaia23 command: gaiad account_prefix: cosmos steps: @@ -355,7 +355,7 @@ jobs: - uses: actions-rs/cargo@v1 with: command: test - args: -p ibc-integration-test --no-fail-fast --no-run + args: -p ibc-integration-test --features no-denom-trace --no-fail-fast --no-run - name: Install cargo-nextest run: curl -LsSf https://get.nexte.st/latest/linux | tar zxf - -C ${CARGO_HOME:-~/.cargo}/bin - env: @@ -368,4 +368,4 @@ jobs: run: | nix shell ${{ matrix.chain.package }} -c \ cargo nextest run -p ibc-integration-test --no-fail-fast --failure-output final --test-threads=2 \ - tests::transfer::test_ibc_transfer + --features no-denom-trace tests::transfer::test_ibc_transfer diff --git a/.github/workflows/misbehaviour.yml b/.github/workflows/misbehaviour.yml index a7649e9330..58fca8a386 100644 --- a/.github/workflows/misbehaviour.yml +++ b/.github/workflows/misbehaviour.yml @@ -43,7 +43,7 @@ jobs: fail-fast: false matrix: chain: - - package: gaia20 + - package: gaia23 command: gaiad account_prefix: cosmos steps: diff --git a/.github/workflows/multi-chains.yaml b/.github/workflows/multi-chains.yaml index 615b291ca3..1bc930b5e6 100644 --- a/.github/workflows/multi-chains.yaml +++ b/.github/workflows/multi-chains.yaml @@ -58,7 +58,7 @@ jobs: fail-fast: false matrix: first-package: - - package: gaia20 + - package: gaia23 command: gaiad account_prefix: cosmos - package: ibc-go-v7-simapp diff --git a/.github/workflows/namada.yaml b/.github/workflows/namada.yaml index 5ae59f7da4..56c9325cd5 100644 --- a/.github/workflows/namada.yaml +++ b/.github/workflows/namada.yaml @@ -34,7 +34,7 @@ jobs: fail-fast: false matrix: chain: - - package: .#gaia20 + - package: .#gaia23 command: gaiad,namada account_prefix: cosmos,'' native_token: stake,nam diff --git a/crates/relayer/src/foreign_client.rs b/crates/relayer/src/foreign_client.rs index 99a316e525..7237d9102d 100644 --- a/crates/relayer/src/foreign_client.rs +++ b/crates/relayer/src/foreign_client.rs @@ -1659,13 +1659,14 @@ impl ForeignClient io.fetch_validator_set(height, proposer_address), } } + + pub fn fetch_signed_header( + &self, + height: AtHeight, + ) -> Result { + match self { + AnyIo::Prod(io) => io.fetch_signed_header(height), + AnyIo::RestartAware(io) => io.fetch_signed_header(height), + } + } } impl Io for AnyIo { @@ -71,6 +81,18 @@ impl RestartAwareIo { io.fetch_validator_set(height, proposer_address) } + + pub fn fetch_signed_header( + &self, + height: AtHeight, + ) -> Result { + let io = match height { + AtHeight::At(height) if height <= self.restart_height => &self.archive_io, + _ => &self.live_io, + }; + + io.fetch_signed_header(height) + } } impl Io for RestartAwareIo { diff --git a/crates/relayer/src/light_client/tendermint.rs b/crates/relayer/src/light_client/tendermint.rs index a62ead4e5f..d7a5363358 100644 --- a/crates/relayer/src/light_client/tendermint.rs +++ b/crates/relayer/src/light_client/tendermint.rs @@ -3,7 +3,7 @@ mod detector; use std::time::Duration; use itertools::Itertools; -use tendermint::Time; +use tendermint::{validator, Time}; use tracing::{debug, error, trace, warn}; use tendermint_light_client::{ @@ -18,7 +18,7 @@ use tendermint_light_client::{ verifier::ProdVerifier, }; use tendermint_light_client_detector::Divergence; -use tendermint_rpc as rpc; +use tendermint_rpc::{self as rpc, Client, Paging}; use ibc_relayer_types::clients::ics07_tendermint::header::Header as TmHeader; use ibc_relayer_types::clients::ics07_tendermint::misbehaviour::Misbehaviour as TmMisbehaviour; @@ -28,11 +28,14 @@ use ibc_relayer_types::core::ics24_host::identifier::ChainId; use ibc_relayer_types::Height as ICSHeight; use crate::{ - chain::cosmos::{config::CosmosSdkConfig, CosmosSdkChain}, - chain::penumbra::config::PenumbraConfig, + chain::{ + cosmos::{config::CosmosSdkConfig, CosmosSdkChain}, + penumbra::config::PenumbraConfig, + }, client_state::AnyClientState, error::Error, misbehaviour::{AnyMisbehaviour, MisbehaviourEvidence}, + util::block_on, HERMES_VERSION, }; @@ -147,12 +150,62 @@ impl super::LightClient for LightClient { } ); - let any_header = update.header.as_ref().ok_or_else(|| { + let any_header = match update.header.as_ref() { + Some(header) => header.clone(), + None => { + let rpc_client = self.io.rpc_client(); + + let height = update.consensus_height(); + + // Doesn't work + let signed_header = self + .io + .fetch_signed_header(AtHeight::At(height.into())) + .unwrap(); + + // Doesn't work either + /*let signed_header = block_on(rpc_client.commit(update.consensus_height())) + .unwrap() + .signed_header;*/ + + let validators = block_on(rpc_client.validators(height, Paging::All)) + .unwrap() + .validators; + + let validator_set = validator::Set::with_proposer( + validators, + signed_header.header.proposer_address, + ) + .unwrap(); + + let trusted_header = block_on(rpc_client.commit(height)).unwrap().signed_header; + + let trusted_validators = block_on(rpc_client.validators(height, Paging::All)) + .unwrap() + .validators; + + let trusted_validator_set = validator::Set::with_proposer( + trusted_validators, + trusted_header.header.proposer_address, + ) + .unwrap(); + + let tm_header = TmHeader { + signed_header, + validator_set, + trusted_height: height, + trusted_validator_set, + }; + tm_header.into() + } + }; + + /*let any_header = update.header.as_ref().ok_or_else(|| { Error::misbehaviour(format!( "missing header in update client event {}", self.chain_id )) - })?; + })?;*/ let update_header = match any_header { AnyHeader::Tendermint(header) => Ok::<_, Error>(header), diff --git a/flake.lock b/flake.lock index c5ad02d3fd..e3d149bce8 100644 --- a/flake.lock +++ b/flake.lock @@ -144,6 +144,7 @@ "gaia18-src": "gaia18-src", "gaia19-src": "gaia19-src", "gaia20-src": "gaia20-src", + "gaia23-src": "gaia23-src", "gaia5-src": "gaia5-src", "gaia6-ordered-src": "gaia6-ordered-src", "gaia6-src": "gaia6-src", @@ -212,14 +213,15 @@ "wasmvm_2_1_0-src": "wasmvm_2_1_0-src", "wasmvm_2_1_2-src": "wasmvm_2_1_2-src", "wasmvm_2_1_3-src": "wasmvm_2_1_3-src", - "wasmvm_2_1_4-src": "wasmvm_2_1_4-src" + "wasmvm_2_1_4-src": "wasmvm_2_1_4-src", + "wasmvm_2_2_3-src": "wasmvm_2_2_3-src" }, "locked": { - "lastModified": 1747387017, - "narHash": "sha256-hgZvWvXG57RaDjDkTNVRJ9t1S49s2IbLPhC3pmnzNPY=", + "lastModified": 1747829676, + "narHash": "sha256-zxAPAfFjDT0bJFtuRuJ7mJd+wXN83tUqrQrb44GIih8=", "owner": "informalsystems", "repo": "cosmos.nix", - "rev": "58f8b59ec52fa69a82349d40597fa2bfa601ec3a", + "rev": "f027e5b304e8a1593a0da8677dfea5bef1a95f02", "type": "github" }, "original": { @@ -680,6 +682,23 @@ "type": "github" } }, + "gaia23-src": { + "flake": false, + "locked": { + "lastModified": 1746478993, + "narHash": "sha256-mO4rCiSqFZZiDYvHEby083viOvJVjefqjz38Pt/xJ20=", + "owner": "cosmos", + "repo": "gaia", + "rev": "875b68be0df1e9e8940c568e93e7478edc55a27f", + "type": "github" + }, + "original": { + "owner": "cosmos", + "ref": "v23.3.0", + "repo": "gaia", + "type": "github" + } + }, "gaia5-src": { "flake": false, "locked": { @@ -1486,11 +1505,11 @@ }, "nixpkgs_5": { "locked": { - "lastModified": 1747312588, - "narHash": "sha256-MmJvj6mlWzeRwKGLcwmZpKaOPZ5nJb/6al5CXqJsgjo=", + "lastModified": 1747728033, + "narHash": "sha256-NnXFQu7g4LnvPIPfJmBuZF7LFy/fey2g2+LCzjQhTUk=", "owner": "nixos", "repo": "nixpkgs", - "rev": "b1bebd0fe266bbd1820019612ead889e96a8fa2d", + "rev": "2f9173bde1d3fbf1ad26ff6d52f952f9e9da52ea", "type": "github" }, "original": { @@ -2170,6 +2189,23 @@ "repo": "wasmvm", "type": "github" } + }, + "wasmvm_2_2_3-src": { + "flake": false, + "locked": { + "lastModified": 1741173688, + "narHash": "sha256-pCmKTnYtPBlSBMC8EpfDww2JTBwAWRQ6uMEdLKP+TM0=", + "owner": "CosmWasm", + "repo": "wasmvm", + "rev": "215b331a6ec8041e8b421afbc9eaa0a6182cfae3", + "type": "github" + }, + "original": { + "owner": "CosmWasm", + "ref": "v2.2.3", + "repo": "wasmvm", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 7e32e416f4..c7c7b7655e 100644 --- a/flake.nix +++ b/flake.nix @@ -32,8 +32,7 @@ cometbft evmos gaia6-ordered - gaia18 - gaia20 + gaia23 ibc-go-v2-simapp ibc-go-v3-simapp ibc-go-v4-simapp diff --git a/tools/integration-test/src/tests/forward/forward_hop_transfer.rs b/tools/integration-test/src/tests/forward/forward_hop_transfer.rs index b86211233d..6c317c46f6 100644 --- a/tools/integration-test/src/tests/forward/forward_hop_transfer.rs +++ b/tools/integration-test/src/tests/forward/forward_hop_transfer.rs @@ -116,9 +116,11 @@ impl NaryChannelTest<4> for IbcForwardHopTransferTest { wallet_c.address().value().to_string(), channel_b_to_c.port_a.to_string(), channel_b_to_c.channel.a_channel_id().unwrap().to_string(), + "60m".to_owned(), wallet_d.address().value().to_string(), channel_c_to_d.port_a.to_string(), channel_c_to_d.channel.a_channel_id().unwrap().to_string(), + "60m".to_owned(), ); let memo = serde_json::to_string(&memo_field).unwrap(); @@ -241,9 +243,11 @@ impl NaryChannelTest<4> for AtomicIbcForwardHopTransferTest { wallet_c.address().value().to_string(), channel_b_to_c.port_a.to_string(), channel_b_to_c.channel.a_channel_id().unwrap().to_string(), + "60m".to_owned(), wallet_d.address().value().to_string(), channel_c_to_d.port_a.to_string(), "InvalidChannelFromCtoD".to_owned(), + "60m".to_owned(), ); let memo = serde_json::to_string(&memo_field).unwrap(); diff --git a/tools/integration-test/src/tests/forward/forward_transfer.rs b/tools/integration-test/src/tests/forward/forward_transfer.rs index acef77d626..3450ee1380 100644 --- a/tools/integration-test/src/tests/forward/forward_transfer.rs +++ b/tools/integration-test/src/tests/forward/forward_transfer.rs @@ -124,6 +124,7 @@ impl NaryChannelTest<3> for IbcForwardTransferTest { wallet_c.address().value().to_string(), channel_b_to_c.port_a.to_string(), channel_b_to_c.channel.a_channel_id().unwrap().to_string(), + 6000000000, ); let memo = serde_json::to_string(&memo_field).unwrap(); @@ -235,6 +236,7 @@ impl NaryChannelTest<3> for MisspelledMemoFieldsIbcForwardTransferTest { wallet_c.address().value().to_string(), channel_b_to_c.port_a.to_string(), channel_b_to_c.channel.a_channel_id().unwrap().to_string(), + 6000000000, ); let memo1 = serde_json::to_string(&memo_invalid_field).unwrap(); @@ -243,6 +245,7 @@ impl NaryChannelTest<3> for MisspelledMemoFieldsIbcForwardTransferTest { wallet_c.address().value().to_string(), channel_b_to_c.port_a.to_string(), channel_b_to_c.channel.a_channel_id().unwrap().to_string(), + 6000000000, ); let memo2 = serde_json::to_string(&memo_invalid_field).unwrap(); @@ -251,6 +254,7 @@ impl NaryChannelTest<3> for MisspelledMemoFieldsIbcForwardTransferTest { wallet_c.address().value().to_string(), channel_b_to_c.port_a.to_string(), channel_b_to_c.channel.a_channel_id().unwrap().to_string(), + 6000000000, ); let memo3 = serde_json::to_string(&memo_invalid_field).unwrap(); @@ -259,6 +263,7 @@ impl NaryChannelTest<3> for MisspelledMemoFieldsIbcForwardTransferTest { wallet_c.address().value().to_string(), channel_b_to_c.port_a.to_string(), channel_b_to_c.channel.a_channel_id().unwrap().to_string(), + 6000000000, ); let memo4 = serde_json::to_string(&memo_invalid_field).unwrap(); @@ -480,6 +485,7 @@ impl NaryChannelTest<3> for MisspelledMemoContentIbcForwardTransferTest { wallet_c.address().value().to_string(), "misspelledPort".to_owned(), channel_b_to_c.channel.a_channel_id().unwrap().to_string(), + 6000000000, ); let memo1 = serde_json::to_string(&memo_misspelled_port).unwrap(); @@ -488,6 +494,7 @@ impl NaryChannelTest<3> for MisspelledMemoContentIbcForwardTransferTest { wallet_c.address().value().to_string(), channel_b_to_c.port_a.to_string(), "misspelledChannel".to_owned(), + 6000000000, ); let memo2 = serde_json::to_string(&memo_misspelled_channel).unwrap(); @@ -496,6 +503,7 @@ impl NaryChannelTest<3> for MisspelledMemoContentIbcForwardTransferTest { "misspelledReceiver".to_owned(), channel_b_to_c.port_a.to_string(), channel_b_to_c.channel.a_channel_id().unwrap().to_string(), + 6000000000, ); let memo3 = serde_json::to_string(&memo_misspelled_receiver).unwrap(); diff --git a/tools/integration-test/src/tests/forward/memo.rs b/tools/integration-test/src/tests/forward/memo.rs index 6f5b173592..3a1b9c0095 100644 --- a/tools/integration-test/src/tests/forward/memo.rs +++ b/tools/integration-test/src/tests/forward/memo.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; pub trait HasForwardMemoInfo { - fn new_memo(receiver: String, port: String, channel: String) -> Self; + fn new_memo(receiver: String, port: String, channel: String, timeout: u64) -> Self; } #[derive(Serialize, Deserialize)] @@ -14,8 +14,8 @@ pub struct MemoField { } impl MemoField { - pub fn new(receiver: String, port: String, channel: String) -> Self { - let forward = M::new_memo(receiver, port, channel); + pub fn new(receiver: String, port: String, channel: String, timeout: u64) -> Self { + let forward = M::new_memo(receiver, port, channel, timeout); MemoField { forward } } } @@ -26,8 +26,8 @@ pub struct MemoMisspelledField { } impl MemoMisspelledField { - pub fn new(receiver: String, port: String, channel: String) -> Self { - let fwd = M::new_memo(receiver, port, channel); + pub fn new(receiver: String, port: String, channel: String, timeout: u64) -> Self { + let fwd = M::new_memo(receiver, port, channel, timeout); MemoMisspelledField { fwd } } } @@ -37,14 +37,16 @@ pub struct MemoInfo { receiver: String, port: String, channel: String, + timeout: u64, } impl HasForwardMemoInfo for MemoInfo { - fn new_memo(receiver: String, port: String, channel: String) -> Self { + fn new_memo(receiver: String, port: String, channel: String, timeout: u64) -> Self { Self { receiver, port, channel, + timeout, } } } @@ -54,14 +56,16 @@ pub struct MisspelledReceiverMemoInfo { recv: String, port: String, channel: String, + timeout: u64, } impl HasForwardMemoInfo for MisspelledReceiverMemoInfo { - fn new_memo(receiver: String, port: String, channel: String) -> Self { + fn new_memo(receiver: String, port: String, channel: String, timeout: u64) -> Self { Self { recv: receiver, port, channel, + timeout, } } } @@ -71,14 +75,16 @@ pub struct MisspelledPortMemoInfo { receiver: String, fort: String, channel: String, + timeout: u64, } impl HasForwardMemoInfo for MisspelledPortMemoInfo { - fn new_memo(receiver: String, port: String, channel: String) -> Self { + fn new_memo(receiver: String, port: String, channel: String, timeout: u64) -> Self { Self { receiver, fort: port, channel, + timeout, } } } @@ -88,14 +94,16 @@ pub struct MisspelledChannelMemoInfo { receiver: String, port: String, xhannel: String, + timeout: u64, } impl HasForwardMemoInfo for MisspelledChannelMemoInfo { - fn new_memo(receiver: String, port: String, channel: String) -> Self { + fn new_memo(receiver: String, port: String, channel: String, timeout: u64) -> Self { Self { receiver, port, xhannel: channel, + timeout, } } } @@ -110,16 +118,19 @@ impl HopMemoField { intermediary_receiver: String, intermediary_port: String, intermediary_channel: String, + intermediary_timeout: String, final_receiver: String, final_port: String, final_channel: String, + final_timeout: String, ) -> Self { - let hop_field = HopField::new(final_receiver, final_port, final_channel); + let hop_field = HopField::new(final_receiver, final_port, final_channel, final_timeout); let hop_field_string = serde_json::to_string(&hop_field).unwrap(); let memo_content = HopMemoInfo::new( intermediary_receiver, intermediary_port, intermediary_channel, + intermediary_timeout, hop_field_string, ); Self { @@ -133,15 +144,23 @@ pub struct HopMemoInfo { receiver: String, port: String, channel: String, + timeout: String, next: String, } impl HopMemoInfo { - pub fn new(receiver: String, port: String, channel: String, next: String) -> Self { + pub fn new( + receiver: String, + port: String, + channel: String, + timeout: String, + next: String, + ) -> Self { Self { receiver, port, channel, + timeout, next, } } @@ -153,8 +172,8 @@ pub struct HopField { } impl HopField { - pub fn new(receiver: String, port: String, channel: String) -> Self { - let hop = Hop::new(receiver, port, channel); + pub fn new(receiver: String, port: String, channel: String, timeout: String) -> Self { + let hop = Hop::new(receiver, port, channel, timeout); Self { forward: hop } } } @@ -164,14 +183,16 @@ pub struct Hop { receiver: String, port: String, channel: String, + timeout: String, } impl Hop { - pub fn new(receiver: String, port: String, channel: String) -> Self { + pub fn new(receiver: String, port: String, channel: String, timeout: String) -> Self { Self { receiver, port, channel, + timeout, } } } diff --git a/tools/integration-test/src/tests/forward/mod.rs b/tools/integration-test/src/tests/forward/mod.rs index 743813ca0b..792a00597d 100644 --- a/tools/integration-test/src/tests/forward/mod.rs +++ b/tools/integration-test/src/tests/forward/mod.rs @@ -1,3 +1,4 @@ pub mod forward_hop_transfer; pub mod forward_transfer; pub mod memo; +pub mod timeout; diff --git a/tools/integration-test/src/tests/forward/timeout.rs b/tools/integration-test/src/tests/forward/timeout.rs new file mode 100644 index 0000000000..b5a8c4e857 --- /dev/null +++ b/tools/integration-test/src/tests/forward/timeout.rs @@ -0,0 +1,523 @@ +//! This test tests TODO different cases: +//! +//! - The `IbcForwardTimeoutTransferTest` tests the case a timedout packet before being +//! forwarded. +//! +//! - The `IbcForwardHopTimeoutTransferTest` tests the case a timedout packet after the first +//! hop being forwarded. + +use ibc_relayer::chain::counterparty::pending_packet_summary; +use ibc_relayer::chain::requests::Paginate; +use ibc_relayer::config::{self, ModeConfig}; +use ibc_relayer::link::Link; +use ibc_relayer::link::LinkParameters; +use ibc_relayer_types::events::IbcEventType; +use ibc_test_framework::prelude::*; +use ibc_test_framework::relayer::channel::query_identified_channel_end; + +use crate::tests::forward::memo::{HopMemoField, MemoField, MemoInfo}; + +#[test] +fn test_ibc_forward_timeout_transfer() -> Result<(), Error> { + run_nary_channel_test(&IbcForwardTimeoutTransferTest { + timeout_in_ns: 30000000000, // 30 seconds + should_timeout: true, + }) +} + +#[test] +fn test_ibc_forward_no_timeout_transfer() -> Result<(), Error> { + run_nary_channel_test(&IbcForwardTimeoutTransferTest { + timeout_in_ns: 90000000000, // 90 seconds + should_timeout: false, + }) +} + +#[test] +fn test_ibc_forward_hop_timeout_transfer() -> Result<(), Error> { + run_nary_channel_test(&IbcForwardHopTimeoutTransferTest) +} + +struct IbcForwardTimeoutTransferTestOverrides; + +impl TestOverrides for IbcForwardTimeoutTransferTestOverrides { + fn modify_relayer_config(&self, config: &mut Config) { + config.mode = ModeConfig { + connections: config::Connections { enabled: false }, + channels: config::Channels { enabled: false }, + ..Default::default() + }; + + config.mode.clients.misbehaviour = false; + } + + fn modify_test_config(&self, config: &mut TestConfig) { + config.bootstrap_with_random_ids = false; + } + + fn should_spawn_supervisor(&self) -> bool { + false + } +} + +impl PortsOverride<3> for IbcForwardTimeoutTransferTestOverrides {} + +struct IbcForwardHopTimeoutTransferTestOverrides; + +impl TestOverrides for IbcForwardHopTimeoutTransferTestOverrides { + fn modify_relayer_config(&self, config: &mut Config) { + config.mode = ModeConfig { + connections: config::Connections { enabled: false }, + channels: config::Channels { enabled: false }, + ..Default::default() + }; + + config.mode.clients.misbehaviour = false; + } + + fn modify_test_config(&self, config: &mut TestConfig) { + config.bootstrap_with_random_ids = false; + } + + fn should_spawn_supervisor(&self) -> bool { + false + } +} + +impl PortsOverride<4> for IbcForwardHopTimeoutTransferTestOverrides {} + +struct IbcForwardTimeoutTransferTest { + pub timeout_in_ns: u64, + pub should_timeout: bool, +} + +impl NaryChannelTest<3> for IbcForwardTimeoutTransferTest { + fn run( + &self, + _config: &TestConfig, + relayer: RelayerDriver, + chains: NaryConnectedChains, + channels: NaryConnectedChannels, + ) -> Result<(), Error> { + let packet_config = relayer.config.mode.packets; + + let connected_chains = chains.connected_chains_at::<0, 1>()?; + + let node_a = chains.full_node_at::<0>()?; + let node_b = chains.full_node_at::<1>()?; + let node_c = chains.full_node_at::<2>()?; + + let handle_a = chains.chain_handle_at::<0>()?; + let handle_b = chains.chain_handle_at::<1>()?; + let handle_c = chains.chain_handle_at::<2>()?; + + let channel_a_to_b = channels.channel_at::<0, 1>()?; + let channel_b_to_c = channels.channel_at::<1, 2>()?; + + let denom_a = connected_chains.node_a.denom(); + + let denom_b = derive_ibc_denom( + &node_b.chain_driver().value().chain_type, + &channel_a_to_b.port_b.as_ref(), + &channel_a_to_b.channel_id_b.as_ref(), + &denom_a, + )?; + + let denom_a_to_b = derive_ibc_denom( + &node_b.chain_driver().value().chain_type, + &channel_a_to_b.port_b.as_ref(), + &channel_a_to_b.channel_id_b.as_ref(), + &denom_a, + )?; + + let denom_a_to_c = derive_ibc_denom( + &node_c.chain_driver().value().chain_type, + &channel_b_to_c.port_b.as_ref(), + &channel_b_to_c.channel_id_b.as_ref(), + &denom_b.as_ref(), + )?; + + let wallets_a = node_a.wallets(); + let wallet_a = wallets_a.user1(); + + let wallets_b = node_b.wallets(); + let wallet_b = wallets_b.user1(); + + let wallets_c = node_c.wallets(); + let wallet_c = wallets_c.user1(); + + let balance_a = node_a + .chain_driver() + .query_balance(&wallet_a.address(), &denom_a)?; + + let a_to_c_amount = 4000_u128; + + let memo_field: MemoField = MemoField::new( + wallet_c.address().value().to_string(), + channel_b_to_c.port_a.to_string(), + channel_b_to_c.channel.a_channel_id().unwrap().to_string(), + self.timeout_in_ns, + ); + let memo = serde_json::to_string(&memo_field).unwrap(); + + node_a + .chain_driver() + .ibc_transfer_token_with_memo_and_timeout( + &channel_a_to_b.port_a.as_ref(), + &channel_a_to_b.channel_id_a.as_ref(), + &wallet_a, + &wallet_b.address(), + &denom_a.with_amount(a_to_c_amount).as_ref(), + Some(memo), + Some(Duration::from_secs(50)), + )?; + + node_a.chain_driver().assert_eventual_wallet_amount( + &wallet_a.address(), + &(balance_a.clone() - a_to_c_amount).as_ref(), + )?; + + // Wait for packet to timeout + std::thread::sleep(Duration::from_secs(35)); + + let channel_end_a = query_identified_channel_end( + &handle_a, + channel_a_to_b.channel_id_a.as_ref(), + channel_a_to_b.port_a.as_ref(), + )?; + + let pending_packets_a = + pending_packet_summary(&handle_a, &handle_b, channel_end_a.value(), Paginate::All)?; + + let to_clear = pending_packets_a + .unreceived_packets + .iter() + .map(|&seq| seq..=seq) + .collect::>(); + + let opts = LinkParameters { + src_port_id: channel_a_to_b.port_a.clone().into_value(), + src_channel_id: channel_a_to_b.channel_id_a.clone().into_value(), + max_memo_size: packet_config.ics20_max_memo_size, + max_receiver_size: packet_config.ics20_max_receiver_size, + exclude_src_sequences: vec![], + }; + + let link = Link::new_from_opts(handle_a, handle_b.clone(), opts, false, false)?; + + assert!( + to_clear.len() == 1, + "expected exactly one packet to clear from A to B" + ); + + let result = link + .relay_recv_packet_and_timeout_messages(to_clear) + .unwrap(); + + assert!( + result + .iter() + .any(|event| event.event_type() == IbcEventType::SendPacket), + "expected to relay send packet from A to B" + ); + + // Wait for packet to timeout + std::thread::sleep(Duration::from_secs(35)); + + let channel_end_b = query_identified_channel_end( + &handle_b, + channel_b_to_c.channel_id_a.as_ref(), + channel_b_to_c.port_a.as_ref(), + )?; + + let pending_packets_b = + pending_packet_summary(&handle_b, &handle_c, channel_end_b.value(), Paginate::All)?; + + let to_clear = pending_packets_b + .unreceived_packets + .iter() + .map(|&seq| seq..=seq) + .collect::>(); + + assert!( + to_clear.len() == 1, + "expected exactly one packet to clear from B to C" + ); + + node_a.chain_driver().assert_eventual_wallet_amount( + &wallet_a.address(), + &(balance_a.clone() - a_to_c_amount).as_ref(), + )?; + + info!( + "waiting for user on chain A to be refunded the amount of {}", + a_to_c_amount + ); + + relayer.with_supervisor(||{ + if self.should_timeout{ + node_a + .chain_driver() + .assert_eventual_wallet_amount(&wallet_a.address(), &balance_a.as_ref())?; + + node_c.chain_driver().assert_eventual_wallet_amount( + &wallet_c.address(), + &denom_a_to_c.with_amount(0u128).as_ref(), + )?; + } else { + node_a + .chain_driver() + .assert_eventual_wallet_amount(&wallet_a.address(), &(balance_a.clone() - a_to_c_amount).as_ref())?; + + node_c.chain_driver().assert_eventual_wallet_amount( + &wallet_c.address(), + &denom_a_to_c.with_amount(a_to_c_amount).as_ref(), + )?; + } + + node_b.chain_driver().assert_eventual_wallet_amount( + &wallet_b.address(), + &denom_a_to_b.with_amount(0_u128).as_ref(), + )?; + + info!( + "successfully performed IBC timeout transfer for PFM transfer from chain {} to chain {}", + chains.chain_handle_at::<0>().unwrap().value(), + chains.chain_handle_at::<2>().unwrap().value(), + ); + + Ok(()) + })?; + + let pending_packets_b = + pending_packet_summary(&handle_b, &handle_c, channel_end_b.value(), Paginate::All)?; + + let to_clear = pending_packets_b + .unreceived_packets + .iter() + .map(|&seq| seq..=seq) + .collect::>(); + + assert!( + to_clear.is_empty(), + "expected all packets to have been cleared from B to C" + ); + + Ok(()) + } +} + +struct IbcForwardHopTimeoutTransferTest; + +impl NaryChannelTest<4> for IbcForwardHopTimeoutTransferTest { + fn run( + &self, + _config: &TestConfig, + relayer: RelayerDriver, + chains: NaryConnectedChains, + channels: NaryConnectedChannels, + ) -> Result<(), Error> { + let packet_config = relayer.config.mode.packets; + + let connected_chains = chains.connected_chains_at::<0, 1>()?; + + let node_a = chains.full_node_at::<0>()?; + let node_b = chains.full_node_at::<1>()?; + let node_c = chains.full_node_at::<2>()?; + let node_d = chains.full_node_at::<3>()?; + + let handle_a = chains.chain_handle_at::<0>()?; + let handle_b = chains.chain_handle_at::<1>()?; + let handle_c = chains.chain_handle_at::<2>()?; + + let channel_a_to_b = channels.channel_at::<0, 1>()?; + let channel_b_to_c = channels.channel_at::<1, 2>()?; + let channel_c_to_d = channels.channel_at::<2, 3>()?; + + let denom_a = connected_chains.node_a.denom(); + + let denom_a_to_b = derive_ibc_denom( + &node_b.chain_driver().value().chain_type, + &channel_a_to_b.port_b.as_ref(), + &channel_a_to_b.channel_id_b.as_ref(), + &denom_a, + )?; + + let denom_b_to_c = derive_ibc_denom( + &node_c.chain_driver().value().chain_type, + &channel_b_to_c.port_b.as_ref(), + &channel_b_to_c.channel_id_b.as_ref(), + &denom_a_to_b.as_ref(), + )?; + + let denom_a_to_d = derive_ibc_denom( + &node_d.chain_driver().value().chain_type, + &channel_c_to_d.port_b.as_ref(), + &channel_c_to_d.channel_id_b.as_ref(), + &denom_b_to_c.as_ref(), + )?; + + let wallets_a = node_a.wallets(); + let wallet_a = wallets_a.user1(); + + let wallets_b = node_b.wallets(); + let wallet_b = wallets_b.user1(); + + let wallets_c = node_c.wallets(); + let wallet_c = wallets_c.user1(); + + let wallets_d = node_d.wallets(); + let wallet_d = wallets_d.user1(); + + let balance_a = node_a + .chain_driver() + .query_balance(&wallet_a.address(), &denom_a)?; + + let a_to_d_amount = 4000_u128; + + let memo_field = HopMemoField::new( + wallet_c.address().value().to_string(), + channel_b_to_c.port_a.to_string(), + channel_b_to_c.channel.a_channel_id().unwrap().to_string(), + "20s".to_owned(), + wallet_d.address().value().to_string(), + channel_c_to_d.port_a.to_string(), + channel_c_to_d.channel.a_channel_id().unwrap().to_string(), + "20s".to_owned(), + ); + let memo = serde_json::to_string(&memo_field).unwrap(); + + node_a + .chain_driver() + .ibc_transfer_token_with_memo_and_timeout( + &channel_a_to_b.port_a.as_ref(), + &channel_a_to_b.channel_id_a.as_ref(), + &wallet_a, + &wallet_b.address(), + &denom_a.with_amount(a_to_d_amount).as_ref(), + Some(memo), + Some(Duration::from_secs(300)), + )?; + + std::thread::sleep(Duration::from_secs(30)); + + node_a.chain_driver().assert_eventual_wallet_amount( + &wallet_a.address(), + &(balance_a.clone() - a_to_d_amount).as_ref(), + )?; + + // First hop transfer + let channel_end_a = query_identified_channel_end( + &handle_a, + channel_a_to_b.channel_id_a.as_ref(), + channel_a_to_b.port_a.as_ref(), + )?; + + let pending_packets_a = + pending_packet_summary(&handle_a, &handle_b, channel_end_a.value(), Paginate::All)?; + + let to_clear = pending_packets_a + .unreceived_packets + .iter() + .map(|&seq| seq..=seq) + .collect::>(); + + let opts = LinkParameters { + src_port_id: channel_a_to_b.port_a.clone().into_value(), + src_channel_id: channel_a_to_b.channel_id_a.clone().into_value(), + max_memo_size: packet_config.ics20_max_memo_size, + max_receiver_size: packet_config.ics20_max_receiver_size, + exclude_src_sequences: vec![], + }; + + let link = Link::new_from_opts(handle_a.clone(), handle_b.clone(), opts, false, false)?; + + info!("Clearing all packets ({})", to_clear.len()); + + link.relay_recv_packet_and_timeout_messages(to_clear) + .unwrap(); + + node_a.chain_driver().assert_eventual_wallet_amount( + &wallet_a.address(), + &(balance_a.clone() - a_to_d_amount).as_ref(), + )?; + + std::thread::sleep(Duration::from_secs(30)); + + // Second hop transfer + let channel_end_a = query_identified_channel_end( + &handle_b, + channel_b_to_c.channel_id_a.as_ref(), + channel_b_to_c.port_a.as_ref(), + )?; + + let pending_packets_a = + pending_packet_summary(&handle_b, &handle_c, channel_end_a.value(), Paginate::All)?; + + let to_clear = pending_packets_a + .unreceived_packets + .iter() + .map(|&seq| seq..=seq) + .collect::>(); + + let opts = LinkParameters { + src_port_id: channel_b_to_c.port_a.clone().into_value(), + src_channel_id: channel_b_to_c.channel_id_a.clone().into_value(), + max_memo_size: packet_config.ics20_max_memo_size, + max_receiver_size: packet_config.ics20_max_receiver_size, + exclude_src_sequences: vec![], + }; + + let link = Link::new_from_opts(handle_b, handle_c, opts, false, false)?; + + info!("Clearing all packets ({})", to_clear.len()); + + link.relay_recv_packet_and_timeout_messages(to_clear) + .unwrap(); + + relayer.with_supervisor(|| { + node_a + .chain_driver() + .assert_eventual_wallet_amount(&wallet_a.address(), &balance_a.as_ref())?; + + node_d.chain_driver().assert_eventual_wallet_amount( + &wallet_d.address(), + &denom_a_to_d.with_amount(0u128).as_ref(), + )?; + + node_c.chain_driver().assert_eventual_wallet_amount( + &wallet_c.address(), + &denom_b_to_c.with_amount(0u128).as_ref(), + )?; + + node_b.chain_driver().assert_eventual_wallet_amount( + &wallet_b.address(), + &denom_a_to_b.with_amount(0_u128).as_ref(), + )?; + + info!( + "successfully performed IBC timeout transfer for PFM transfer from chain {} to chain {}", + chains.chain_handle_at::<0>().unwrap().value(), + chains.chain_handle_at::<3>().unwrap().value(), + ); + + Ok(()) + }) + } +} + +impl HasOverrides for IbcForwardTimeoutTransferTest { + type Overrides = IbcForwardTimeoutTransferTestOverrides; + + fn get_overrides(&self) -> &IbcForwardTimeoutTransferTestOverrides { + &IbcForwardTimeoutTransferTestOverrides + } +} + +impl HasOverrides for IbcForwardHopTimeoutTransferTest { + type Overrides = IbcForwardHopTimeoutTransferTestOverrides; + + fn get_overrides(&self) -> &IbcForwardHopTimeoutTransferTestOverrides { + &IbcForwardHopTimeoutTransferTestOverrides + } +} diff --git a/tools/test-framework/src/bootstrap/consumer.rs b/tools/test-framework/src/bootstrap/consumer.rs index e5acabc87a..3efac160ef 100644 --- a/tools/test-framework/src/bootstrap/consumer.rs +++ b/tools/test-framework/src/bootstrap/consumer.rs @@ -90,6 +90,22 @@ pub fn bootstrap_consumer_node( config::cosmos::consensus_params_max_gas(genesis, "3000000")?; config::cosmos::globalfee_minimum_gas_prices(genesis, globalfee_minimum_gas)?; config::cosmos::set_retry_delay_period(genesis, "100s")?; + + // TODO: This is a temporary solution to be compatible with current Stride and Neutron versions used. + // Both panic due to unknown fields "consumer_id" and "connection_id". + let ccvconsumer = genesis + .get_mut("app_state") + .and_then(|app_state| app_state.get_mut("ccvconsumer")) + .and_then(|ccvconsumer| ccvconsumer.as_object_mut()) + .ok_or_else(|| eyre!("failed to get ccvconsumer in genesis file"))?; + ccvconsumer.remove("connection_id"); + + let params = ccvconsumer + .get_mut("params") + .and_then(|params| params.as_object_mut()) + .ok_or_else(|| eyre!("failed to get ccvconsumer params in genesis file"))?; + params.remove("consumer_id"); + Ok(()) })?;