-
Notifications
You must be signed in to change notification settings - Fork 326
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
Make ordered channels more resilient in the face of failing packets #3610
Changes from 37 commits
f0281a8
92c2ec7
27f762a
d54a891
54abddf
7eefadc
bc16d41
94f7f83
083b81a
f649204
98c89b6
3facd4e
08b479d
1781c20
ed173a2
e1d48d2
7480c1f
6fc48b4
53df768
0538481
7d9d367
25f0974
f927d0f
9eeb84e
3cd60e2
fca4417
85466b0
ccf329f
6f9f691
112c4f8
1dbe79c
fdc352b
50379f9
a7836e1
cc81934
7cbad88
19d1065
8e85859
107a175
a6506bf
11abdd7
7acbfa4
3a5acfc
a364e6e
b2c7fea
92fe20e
00745da
dd579ec
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
- Improve resilience when relaying on ordered channels. When a packet | ||
fails to be relayed on an ordered channel, this used to block the | ||
channel until either another relayer relayed the packet successfully or | ||
until packet clearing kicked off. Hermes will now detect the failure | ||
and attempt to clear packets on the channel in order to unblock it. | ||
([\#3540](https://github.com/informalsystems/hermes/issues/3540)) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,217 @@ | ||
//! Verifies the behaviour of ordered channels with Interchain Accounts. | ||
//! | ||
//! In order to ensure that ordered channels correctly clear packets on ICA | ||
//! channels, this test sends some sequential packets with the supervisor enabled, | ||
//! sends the next packet *without* the supervisor enabled, then sends additional | ||
//! packets with the supervisor enabled again. The pending packet that was sent | ||
//! without the supervisor enabled should be relayed in order along with the | ||
//! other packets, as expected of ordered channel behaviour. | ||
|
||
use std::str::FromStr; | ||
|
||
use ibc_relayer_types::applications::ics27_ica::cosmos_tx::CosmosTx; | ||
use ibc_relayer_types::applications::ics27_ica::packet_data::InterchainAccountPacketData; | ||
use ibc_relayer_types::applications::transfer::msgs::send::MsgSend; | ||
use ibc_relayer_types::applications::transfer::{Amount, Coin}; | ||
use ibc_relayer_types::bigint::U256; | ||
use ibc_relayer_types::signer::Signer; | ||
use ibc_relayer_types::timestamp::Timestamp; | ||
use ibc_relayer_types::tx_msg::Msg; | ||
use ibc_test_framework::chain::ext::ica::register_interchain_account; | ||
use ibc_test_framework::framework::binary::channel::run_binary_interchain_security_channel_test; | ||
use ibc_test_framework::prelude::*; | ||
use ibc_test_framework::relayer::channel::assert_eventually_channel_established; | ||
use ibc_test_framework::relayer::channel::query_channel_end; | ||
use ibc_test_framework::util::interchain_security::{ | ||
interchain_send_tx, update_genesis_for_consumer_chain, update_relayer_config_for_consumer_chain, | ||
}; | ||
|
||
#[test] | ||
fn test_ica_ordered_channel() -> Result<(), Error> { | ||
run_binary_interchain_security_channel_test(&IcaOrderedChannelTest) | ||
} | ||
|
||
struct IcaOrderedChannelTest; | ||
|
||
impl TestOverrides for IcaOrderedChannelTest { | ||
fn modify_genesis_file(&self, genesis: &mut serde_json::Value) -> Result<(), Error> { | ||
use serde_json::Value; | ||
|
||
// Allow MsgSend messages over ICA | ||
let allow_messages = genesis | ||
.get_mut("app_state") | ||
.and_then(|app_state| app_state.get_mut("interchainaccounts")) | ||
.and_then(|ica| ica.get_mut("host_genesis_state")) | ||
.and_then(|state| state.get_mut("params")) | ||
.and_then(|params| params.get_mut("allow_messages")) | ||
.and_then(|allow_messages| allow_messages.as_array_mut()); | ||
|
||
if let Some(allow_messages) = allow_messages { | ||
allow_messages.push(Value::String("/cosmos.bank.v1beta1.MsgSend".to_string())); | ||
} else { | ||
return Err(Error::generic(eyre!("failed to update genesis file"))); | ||
} | ||
|
||
update_genesis_for_consumer_chain(genesis)?; | ||
|
||
Ok(()) | ||
} | ||
|
||
fn modify_relayer_config(&self, config: &mut Config) { | ||
config.mode.channels.enabled = true; | ||
|
||
// Disable packet clearing so that packets sent without the supervisor | ||
// enabled enter a pending state. | ||
config.mode.packets.enabled = true; | ||
config.mode.packets.clear_on_start = false; | ||
config.mode.packets.clear_interval = 0; | ||
|
||
update_relayer_config_for_consumer_chain(config); | ||
} | ||
|
||
fn should_spawn_supervisor(&self) -> bool { | ||
false | ||
} | ||
} | ||
|
||
impl BinaryChannelTest for IcaOrderedChannelTest { | ||
fn run<ChainA: ChainHandle, ChainB: ChainHandle>( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we can remove most of the sleeps in the test |
||
&self, | ||
_config: &TestConfig, | ||
relayer: RelayerDriver, | ||
chains: ConnectedChains<ChainA, ChainB>, | ||
channel: ConnectedChannel<ChainA, ChainB>, | ||
) -> Result<(), Error> { | ||
let connection_b_to_a = channel.connection.clone().flip(); | ||
let (wallet, channel_id, port_id) = | ||
register_interchain_account(&chains.node_b, chains.handle_b(), &connection_b_to_a)?; | ||
|
||
relayer.with_supervisor(|| { | ||
// Check that the corresponding ICA channel is eventually established. | ||
let _counterparty_channel_id = assert_eventually_channel_established( | ||
chains.handle_b(), | ||
chains.handle_a(), | ||
&channel_id.as_ref(), | ||
&port_id.as_ref(), | ||
)?; | ||
|
||
Ok(()) | ||
})?; | ||
|
||
// Assert that the channel returned by `register_interchain_account` is an ordered channel | ||
let channel_end = | ||
query_channel_end(chains.handle_b(), &channel_id.as_ref(), &port_id.as_ref())?; | ||
|
||
assert_eq!(channel_end.value().ordering(), &Ordering::Ordered); | ||
|
||
// Query the controller chain for the address of the ICA wallet on the host chain. | ||
let ica_address = chains.node_b.chain_driver().query_interchain_account( | ||
&wallet.address(), | ||
&channel.connection.connection_id_b.as_ref(), | ||
)?; | ||
|
||
let stake_denom: MonoTagged<ChainA, Denom> = MonoTagged::new(Denom::base("stake")); | ||
|
||
chains.node_a.chain_driver().assert_eventual_wallet_amount( | ||
&ica_address.as_ref(), | ||
&stake_denom.with_amount(0u64).as_ref(), | ||
)?; | ||
|
||
// Send funds to the interchain account. | ||
let ica_fund = 42000u64; | ||
|
||
chains.node_a.chain_driver().local_transfer_token( | ||
&chains.node_a.wallets().user1(), | ||
&ica_address.as_ref(), | ||
&stake_denom.with_amount(ica_fund).as_ref(), | ||
)?; | ||
|
||
chains.node_a.chain_driver().assert_eventual_wallet_amount( | ||
&ica_address.as_ref(), | ||
&stake_denom.with_amount(ica_fund).as_ref(), | ||
)?; | ||
|
||
let amount = 1200; | ||
|
||
let msg = MsgSend { | ||
from_address: ica_address.to_string(), | ||
to_address: chains.node_a.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(); | ||
|
||
sleep(Duration::from_secs(5)); | ||
|
||
relayer.with_supervisor(|| { | ||
sleep(Duration::from_secs(1)); | ||
|
||
let ica_events = interchain_send_tx( | ||
chains.handle_b(), | ||
&signer, | ||
&channel.connection.connection_id_b.0, | ||
interchain_account_packet_data.clone(), | ||
Timestamp::from_nanoseconds(120000000000).unwrap(), | ||
)?; | ||
|
||
info!("First ICA transfer made with supervisor: {ica_events:#?}"); | ||
|
||
sleep(Duration::from_secs(5)); | ||
|
||
Ok(()) | ||
})?; | ||
|
||
sleep(Duration::from_secs(1)); | ||
|
||
let ica_events = interchain_send_tx( | ||
chains.handle_b(), | ||
&signer, | ||
&channel.connection.connection_id_b.0, | ||
interchain_account_packet_data.clone(), | ||
Timestamp::from_nanoseconds(120000000000).unwrap(), | ||
)?; | ||
|
||
info!("Second ICA transfer made without supervisor: {ica_events:#?}"); | ||
|
||
sleep(Duration::from_secs(5)); | ||
|
||
relayer.with_supervisor(|| { | ||
sleep(Duration::from_secs(1)); | ||
|
||
let ica_events = interchain_send_tx( | ||
chains.handle_b(), | ||
&signer, | ||
&channel.connection.connection_id_b.0, | ||
interchain_account_packet_data, | ||
Timestamp::from_nanoseconds(120000000000).unwrap(), | ||
)?; | ||
|
||
info!("Third ICA transfer made with supervisor: {ica_events:#?}"); | ||
|
||
sleep(Duration::from_secs(5)); | ||
|
||
Ok(()) | ||
})?; | ||
|
||
// Check that the ICA account's balance has been debited the sent amount. | ||
chains.node_a.chain_driver().assert_eventual_wallet_amount( | ||
&ica_address.as_ref(), | ||
&stake_denom.with_amount(ica_fund - 3 * amount).as_ref(), | ||
)?; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should move this in the supervisor block to insure that the clearing is done, and maybe add an assertion that the amount is correctly received |
||
|
||
Ok(()) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
imo, we should not check on error but rather when we attempt to clear a packet on an IBC event.
Here are some tentative diffs: