Expand description

Ordered Channel Test

This walkthrough illustrates the behavior or an integration test that makes use of the BinaryChannelTest trait. This trait is used for test cases that require two running full nodes connected via IBC channels with completed handshakes. The relayer is initialized with chain handles and foreign clients for interfacing with the running full nodes.

The test itself checks that transactions sent over an ordered channel are successfully relayed and received by the intended recipient, even when the transaction was queued up to be sent before the relayer was started.

The test in most of its entirety (some parts omitted for brevity) looks like this:


#[test]
fn test_ordered_channel() -> Result<(), Error> {
    run_binary_channel_test(&OrderedChannelTest)
}

pub struct OrderedChannelTest;

impl TestOverrides for OrderedChannelTest {
    fn modify_relayer_config(&self, config: &mut Config) {
        config.mode.packets.clear_on_start = false;
        config.mode.packets.clear_interval = 0;
    }

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

    fn channel_order(&self) -> Ordering {
        Ordering::Ordered
    }
}

impl BinaryChannelTest for OrderedChannelTest {
    fn run<ChainA: ChainHandle, ChainB: ChainHandle>(
        &self,
        _config: &TestConfig,
        relayer: RelayerDriver,
        chains: ConnectedChains<ChainA, ChainB>,
        channel: ConnectedChannel<ChainA, ChainB>,
    ) -> Result<(), Error> {
        let denom_a = chains.node_a.denom();

        let wallet_a = chains.node_a.wallets().user1().cloned();
        let wallet_b = chains.node_b.wallets().user1().cloned();

        let balance_a = chains
            .node_a
            .chain_driver()
            .query_balance(&wallet_a.address(), &denom_a)?;

        let amount1 = random_u128_range(1000, 5000);

        info!(
            "Performing IBC transfer with amount {}, which should be relayed because it's an ordered channel",
            amount1
        );

        chains.node_a.chain_driver().ibc_transfer_token(
            &channel.port_a.as_ref(),
            &channel.channel_id_a.as_ref(),
            &wallet_a.as_ref(),
            &wallet_b.address(),
            &denom_a.with_amount(amount1).as_ref(),
        )?;

        sleep(Duration::from_secs(1));

        relayer.with_supervisor(|| {
            sleep(Duration::from_secs(1));

            let amount2 = random_u128_range(1000, 5000);

            info!(
                "Performing IBC transfer with amount {}, which should be relayed",
                amount2
            );

            chains.node_a.chain_driver().ibc_transfer_token(
                &channel.port_a.as_ref(),
                &channel.channel_id_a.as_ref(),
                &wallet_a.as_ref(),
                &wallet_b.address(),
                &denom_a.with_amount(amount2).as_ref(),
            )?;

            sleep(Duration::from_secs(1));

            let denom_b = derive_ibc_denom(
                &channel.port_b.as_ref(),
                &channel.channel_id_b.as_ref(),
                &denom_a,
            )?;

            chains.node_a.chain_driver().assert_eventual_wallet_amount(
                &wallet_a.address(),
                &(balance_a - amount1 - amount2).as_ref(),
            )?;

            chains.node_b.chain_driver().assert_eventual_wallet_amount(
                &wallet_b.address(),
                &denom_b.with_amount(amount1 + amount2).as_ref(),
            )?;

            Ok(())
        })
    }
}

The test is run by calling the run_binary_channel_test function, passing it a struct, OrderdChannelTest, upon which we implement the TestOverrides trait in order to configure the behavior of the test. We define the should_spawn_supervisor function to have it return false in order to not automatically spawn a supervisor when the relayer is initialized; this is necessary in order to queue up an IBC transaction such that it is pending until the relayer is initialized, not before that. We also define the channel_order function in order to set the initialized channels to the ordered variant; by default, the test will initialize unordered channels. Lastly, we define the modify_relayer_config function in order to toggle off the clear_on_start option, as well as set the clear_interval option to 0. Setting these options means the relayer itself will not relay any packets that were pending before the relayer started; we want to ensure that the behavior of the ordered channel is what is causing the pending transaction to be relayed.

The logic of the test itself is defined in the run function of the BinaryChannelTest trait. In this function, we first set up the two wallets, the sending wallet, wallet_a, which is associated with chain A, and the receiving wallet, wallet_b, which is associated iwth chain B. The balance of wallet_a is also saved. An IBC transfer is then made from chain A to chain B. At this point, because no relayer has been initialized yet, the transaction is in a pending state.

At this point, a relayer instance is initialized. The first thing it does is perform another IBC transfer from chain A to chain B. The test then asserts that wallet_a was indeed debited appropriately, that both transactions went through due to the behavior of the ordered channel. It then checks wallet_b’s balance to ensure that it was credited with the expected amount. If the assertions pass, we can confident that the ordered channel is indeed exhibiting the expected behavior of picking up pending transactions that were queued up before the relayer was started.

You can find the file containing this test at tools/integration-test/src/tests/ordered_channel.rs.