Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ICA channel close test after packet timeout #3779

Merged
merged 3 commits into from
Jan 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- Add an ICA test to assert a channel correctly closes after a packet time-outs
([\#3778](https://github.com/informalsystems/hermes/issues/3778))
201 changes: 171 additions & 30 deletions tools/integration-test/src/tests/ica.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use ibc_test_framework::chain::ext::ica::register_interchain_account;
use ibc_test_framework::ibc::denom::Denom;
use ibc_test_framework::prelude::*;
use ibc_test_framework::relayer::channel::{
assert_eventually_channel_established, query_channel_end,
assert_eventually_channel_closed, assert_eventually_channel_established, query_channel_end,
};

#[test]
Expand All @@ -43,6 +43,16 @@ fn test_ica_filter_allow() -> Result<(), Error> {
)))
}

#[test]
fn test_ica_filter_deny() -> Result<(), Error> {
run_binary_connection_test(&IcaFilterTestDeny)
}

#[test]
fn test_ica_close_channel() -> Result<(), Error> {
run_binary_connection_test(&ICACloseChannelTest)
}

pub struct IcaFilterTestAllow {
packet_filter: PacketFilter,
}
Expand Down Expand Up @@ -177,35 +187,6 @@ impl BinaryConnectionTest for IcaFilterTestAllow {
Ok(())
}
}

fn interchain_send_tx<ChainA: ChainHandle>(
chain: &ChainA,
from: &Signer,
connection: &ConnectionId,
msg: InterchainAccountPacketData,
relative_timeout: Timestamp,
) -> Result<Vec<IbcEventWithHeight>, Error> {
let msg = MsgSendTx {
owner: from.clone(),
connection_id: connection.clone(),
packet_data: msg,
relative_timeout,
};

let msg_any = msg.to_any();

let tm = TrackedMsgs::new_static(vec![msg_any], "SendTx");

chain
.send_messages_and_wait_commit(tm)
.map_err(Error::relayer)
}

#[test]
fn test_ica_filter_deny() -> Result<(), Error> {
run_binary_connection_test(&IcaFilterTestDeny)
}

pub struct IcaFilterTestDeny;

impl TestOverrides for IcaFilterTestDeny {
Expand Down Expand Up @@ -255,3 +236,163 @@ impl BinaryConnectionTest for IcaFilterTestDeny {
)
}
}

pub struct ICACloseChannelTest;

impl TestOverrides for ICACloseChannelTest {
fn modify_relayer_config(&self, config: &mut Config) {
config.mode.channels.enabled = true;

config.mode.clients.misbehaviour = false;
}

fn should_spawn_supervisor(&self) -> bool {
false
}
}

impl BinaryConnectionTest for ICACloseChannelTest {
fn run<Controller: ChainHandle, Host: ChainHandle>(
&self,
_config: &TestConfig,
relayer: RelayerDriver,
chains: ConnectedChains<Controller, Host>,
connection: ConnectedConnection<Controller, Host>,
) -> Result<(), Error> {
let stake_denom: MonoTagged<Host, Denom> = MonoTagged::new(Denom::base("stake"));
let (wallet, ica_address, controller_channel_id, controller_port_id) = relayer
.with_supervisor(|| {
// Register an interchain account on behalf of
// controller wallet `user1` where the counterparty chain is the interchain accounts host.
let (wallet, controller_channel_id, controller_port_id) =
register_interchain_account(&chains.node_a, chains.handle_a(), &connection)?;

// Check that the corresponding ICA channel is eventually established.
let _counterparty_channel_id = assert_eventually_channel_established(
chains.handle_a(),
chains.handle_b(),
&controller_channel_id.as_ref(),
&controller_port_id.as_ref(),
)?;

// Query the controller chain for the address of the ICA wallet on the host chain.
let ica_address = chains.node_a.chain_driver().query_interchain_account(
&wallet.address(),
&connection.connection_id_a.as_ref(),
)?;

chains.node_b.chain_driver().assert_eventual_wallet_amount(
&ica_address.as_ref(),
&stake_denom.with_amount(0u64).as_ref(),
)?;

Ok((
wallet,
ica_address,
controller_channel_id,
controller_port_id,
))
})?;

// Send funds to the interchain account.
let ica_fund = 42000u64;

chains.node_b.chain_driver().local_transfer_token(
&chains.node_b.wallets().user1(),
&ica_address.as_ref(),
&stake_denom.with_amount(ica_fund).as_ref(),
)?;

chains.node_b.chain_driver().assert_eventual_wallet_amount(
&ica_address.as_ref(),
&stake_denom.with_amount(ica_fund).as_ref(),
)?;

let amount = 12345u64;

let msg = MsgSend {
from_address: ica_address.to_string(),
to_address: chains.node_b.wallets().user2().address().to_string(),
amount: vec![Coin {
denom: stake_denom.to_string(),
amount: Amount(U256::from(amount)),
}],
};

let raw_msg = msg.to_any();

let cosmos_tx = CosmosTx {
messages: vec![raw_msg],
};

let raw_cosmos_tx = cosmos_tx.to_any();

let interchain_account_packet_data = InterchainAccountPacketData::new(raw_cosmos_tx.value);

let signer = Signer::from_str(&wallet.address().to_string()).unwrap();

let balance_user2 = chains.node_b.chain_driver().query_balance(
&chains.node_b.wallets().user2().address(),
&stake_denom.as_ref(),
)?;

interchain_send_tx(
chains.handle_a(),
&signer,
&connection.connection_id_a.0,
interchain_account_packet_data,
Timestamp::from_nanoseconds(1000000000).unwrap(),
)?;

sleep(Duration::from_nanos(3000000000));

relayer.with_supervisor(|| {
// Check that user2 has not received the sent amount.
chains.node_b.chain_driver().assert_eventual_wallet_amount(
&chains.node_b.wallets().user2().address(),
&(balance_user2).as_ref(),
)?;
sleep(Duration::from_secs(5));

// Check that the ICA account's balance has not been debited the sent amount.
chains.node_b.chain_driver().assert_eventual_wallet_amount(
&ica_address.as_ref(),
&stake_denom.with_amount(ica_fund).as_ref(),
)?;

info!("Check that the channel closed after packet timeout...");

assert_eventually_channel_closed(
&chains.handle_a,
&chains.handle_b,
&controller_channel_id.as_ref(),
&controller_port_id.as_ref(),
)?;

Ok(())
})
}
}

fn interchain_send_tx<ChainA: ChainHandle>(
chain: &ChainA,
from: &Signer,
connection: &ConnectionId,
msg: InterchainAccountPacketData,
relative_timeout: Timestamp,
) -> Result<Vec<IbcEventWithHeight>, Error> {
let msg = MsgSendTx {
owner: from.clone(),
connection_id: connection.clone(),
packet_data: msg,
relative_timeout,
};

let msg_any = msg.to_any();

let tm = TrackedMsgs::new_static(vec![msg_any], "SendTx");

chain
.send_messages_and_wait_commit(tm)
.map_err(Error::relayer)
}
42 changes: 42 additions & 0 deletions tools/test-framework/src/relayer/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,3 +247,45 @@ pub fn assert_eventually_channel_established<ChainA: ChainHandle, ChainB: ChainH
},
)
}

pub fn assert_eventually_channel_closed<ChainA: ChainHandle, ChainB: ChainHandle>(
handle_a: &ChainA,
handle_b: &ChainB,
channel_id_a: &TaggedChannelIdRef<ChainA, ChainB>,
port_id_a: &TaggedPortIdRef<ChainA, ChainB>,
) -> Result<TaggedChannelId<ChainB, ChainA>, Error> {
assert_eventually_succeed(
"channel should eventually closed",
20,
Duration::from_secs(2),
|| {
let channel_end_a = query_channel_end(handle_a, channel_id_a, port_id_a)?;

if !channel_end_a.value().state_matches(&ChannelState::Closed) {
return Err(Error::generic(eyre!(
"expected channel end A to be in closed state, but it is instead `{}",
channel_end_a.value().state()
)));
}

let channel_id_b = channel_end_a
.tagged_counterparty_channel_id()
.ok_or_else(|| {
eyre!("expected counterparty channel id to present on closed channel")
})?;

let port_id_b = channel_end_a.tagged_counterparty_port_id();

let channel_end_b =
query_channel_end(handle_b, &channel_id_b.as_ref(), &port_id_b.as_ref())?;

if !channel_end_b.value().state_matches(&ChannelState::Closed) {
return Err(Error::generic(eyre!(
"expected channel end B to be in closed state"
)));
}

Ok(channel_id_b)
},
)
}
Loading