Skip to content

Commit

Permalink
Add channel close correlation (#1145)
Browse files Browse the repository at this point in the history
* Add channel close correlation

* Switch to pre-close key

* make tx channel-close cli command work, add test coverage

* more sweet code removals

* update comment
  • Loading branch information
agouin authored Mar 27, 2023
1 parent 048dfa4 commit f29a2c7
Show file tree
Hide file tree
Showing 8 changed files with 652 additions and 114 deletions.
315 changes: 315 additions & 0 deletions interchaintest/ica_channel_close_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,315 @@
package interchaintest_test

import (
"context"
"encoding/json"
"strconv"
"testing"
"time"

"github.com/cosmos/cosmos-sdk/crypto/keyring"
relayerinterchaintest "github.com/cosmos/relayer/v2/interchaintest"
interchaintest "github.com/strangelove-ventures/interchaintest/v7"
"github.com/strangelove-ventures/interchaintest/v7/chain/cosmos"
"github.com/strangelove-ventures/interchaintest/v7/ibc"
"github.com/strangelove-ventures/interchaintest/v7/testreporter"
"github.com/strangelove-ventures/interchaintest/v7/testutil"
"github.com/stretchr/testify/require"
"go.uber.org/zap/zaptest"
)

// TestScenarioICAChannelClose is very similar to the TestScenarioInterchainAccounts,
// but instead it tests manually closing the channel using the relayer CLI.
func TestScenarioICAChannelClose(t *testing.T) {
if testing.Short() {
t.Skip("skipping in short mode")
}

t.Parallel()

client, network := interchaintest.DockerSetup(t)

rep := testreporter.NewNopReporter()
eRep := rep.RelayerExecReporter(t)

ctx := context.Background()

// Get both chains
nf := 0
nv := 1
cf := interchaintest.NewBuiltinChainFactory(zaptest.NewLogger(t), []*interchaintest.ChainSpec{
{
Name: "icad",
NumValidators: &nv,
NumFullNodes: &nf,
ChainConfig: ibc.ChainConfig{
Images: []ibc.DockerImage{{Repository: "ghcr.io/cosmos/ibc-go-icad", Version: "v0.5.0"}},
UsingNewGenesisCommand: true,
},
},
{
Name: "icad",
NumValidators: &nv,
NumFullNodes: &nf,
ChainConfig: ibc.ChainConfig{
Images: []ibc.DockerImage{{Repository: "ghcr.io/cosmos/ibc-go-icad", Version: "v0.5.0"}},
UsingNewGenesisCommand: true,
},
},
})

chains, err := cf.Chains(t.Name())
require.NoError(t, err)

chain1, chain2 := chains[0], chains[1]

// Get a relayer instance
r := relayerinterchaintest.
NewRelayerFactory(relayerinterchaintest.RelayerConfig{}).
Build(t, client, network)

// Build the network; spin up the chains and configure the relayer
const pathName = "test-path"
const relayerName = "relayer"

ic := interchaintest.NewInterchain().
AddChain(chain1).
AddChain(chain2).
AddRelayer(r, relayerName).
AddLink(interchaintest.InterchainLink{
Chain1: chain1,
Chain2: chain2,
Relayer: r,
Path: pathName,
})

require.NoError(t, ic.Build(ctx, eRep, interchaintest.InterchainBuildOptions{
TestName: t.Name(),
Client: client,
NetworkID: network,
SkipPathCreation: true,
BlockDatabaseFile: interchaintest.DefaultBlockDatabaseFilepath(),
}))

// Fund a user account on chain1 and chain2
const userFunds = int64(10_000_000_000)
users := interchaintest.GetAndFundTestUsers(t, ctx, t.Name(), userFunds, chain1, chain2)
chain1User := users[0]
chain2User := users[1]

// Generate a new IBC path
err = r.GeneratePath(ctx, eRep, chain1.Config().ChainID, chain2.Config().ChainID, pathName)
require.NoError(t, err)

// Create new clients
err = r.CreateClients(ctx, eRep, pathName, ibc.CreateClientOptions{TrustingPeriod: "330h"})
require.NoError(t, err)

err = testutil.WaitForBlocks(ctx, 5, chain1, chain2)
require.NoError(t, err)

// Create a new connection
err = r.CreateConnections(ctx, eRep, pathName)
require.NoError(t, err)

err = testutil.WaitForBlocks(ctx, 5, chain1, chain2)
require.NoError(t, err)

// Query for the newly created connection
connections, err := r.GetConnections(ctx, eRep, chain1.Config().ChainID)
require.NoError(t, err)
require.Equal(t, 1, len(connections))

// Register a new interchain account on chain2, on behalf of the user acc on chain1
chain1Addr := chain1User.(*cosmos.CosmosWallet).FormattedAddressWithPrefix(chain1.Config().Bech32Prefix)

registerICA := []string{
chain1.Config().Bin, "tx", "intertx", "register",
"--from", chain1Addr,
"--connection-id", connections[0].ID,
"--chain-id", chain1.Config().ChainID,
"--home", chain1.HomeDir(),
"--node", chain1.GetRPCAddress(),
"--keyring-backend", keyring.BackendTest,
"-y",
}
_, _, err = chain1.Exec(ctx, registerICA, nil)
require.NoError(t, err)

// Start the relayer and set the cleanup function.
err = r.StartRelayer(ctx, eRep, pathName)
require.NoError(t, err)

t.Cleanup(
func() {
err := r.StopRelayer(ctx, eRep)
if err != nil {
t.Logf("an error occured while stopping the relayer: %s", err)
}
},
)

// Wait for relayer to start up and finish channel handshake
err = testutil.WaitForBlocks(ctx, 15, chain1, chain2)
require.NoError(t, err)

// Query for the newly registered interchain account
queryICA := []string{
chain1.Config().Bin, "query", "intertx", "interchainaccounts", connections[0].ID, chain1Addr,
"--chain-id", chain1.Config().ChainID,
"--home", chain1.HomeDir(),
"--node", chain1.GetRPCAddress(),
}
stdout, _, err := chain1.Exec(ctx, queryICA, nil)
require.NoError(t, err)

icaAddr := parseInterchainAccountField(stdout)
require.NotEmpty(t, icaAddr)

// Get initial account balances
chain2Addr := chain2User.(*cosmos.CosmosWallet).FormattedAddressWithPrefix(chain2.Config().Bech32Prefix)

chain2OrigBal, err := chain2.GetBalance(ctx, chain2Addr, chain2.Config().Denom)
require.NoError(t, err)

icaOrigBal, err := chain2.GetBalance(ctx, icaAddr, chain2.Config().Denom)
require.NoError(t, err)

// Send funds to ICA from user account on chain2
const transferAmount = 10000
transfer := ibc.WalletAmount{
Address: icaAddr,
Denom: chain2.Config().Denom,
Amount: transferAmount,
}
err = chain2.SendFunds(ctx, chain2User.KeyName(), transfer)
require.NoError(t, err)

// Wait for transfer to be complete and assert balances
err = testutil.WaitForBlocks(ctx, 5, chain2)
require.NoError(t, err)

chain2Bal, err := chain2.GetBalance(ctx, chain2Addr, chain2.Config().Denom)
require.NoError(t, err)
require.Equal(t, chain2OrigBal-transferAmount, chain2Bal)

icaBal, err := chain2.GetBalance(ctx, icaAddr, chain2.Config().Denom)
require.NoError(t, err)
require.Equal(t, icaOrigBal+transferAmount, icaBal)

// Build bank transfer msg
rawMsg, err := json.Marshal(map[string]any{
"@type": "/cosmos.bank.v1beta1.MsgSend",
"from_address": icaAddr,
"to_address": chain2Addr,
"amount": []map[string]any{
{
"denom": chain2.Config().Denom,
"amount": strconv.Itoa(transferAmount),
},
},
})
require.NoError(t, err)

// Send bank transfer msg to ICA on chain2 from the user account on chain1
sendICATransfer := []string{
chain1.Config().Bin, "tx", "intertx", "submit", string(rawMsg),
"--connection-id", connections[0].ID,
"--from", chain1Addr,
"--chain-id", chain1.Config().ChainID,
"--home", chain1.HomeDir(),
"--node", chain1.GetRPCAddress(),
"--keyring-backend", keyring.BackendTest,
"-y",
}
_, _, err = chain1.Exec(ctx, sendICATransfer, nil)
require.NoError(t, err)

// Wait for tx to be relayed
err = testutil.WaitForBlocks(ctx, 10, chain2)
require.NoError(t, err)

// Assert that the funds have been received by the user account on chain2
chain2Bal, err = chain2.GetBalance(ctx, chain2Addr, chain2.Config().Denom)
require.NoError(t, err)
require.Equal(t, chain2OrigBal, chain2Bal)

// Assert that the funds have been removed from the ICA on chain2
icaBal, err = chain2.GetBalance(ctx, icaAddr, chain2.Config().Denom)
require.NoError(t, err)
require.Equal(t, icaOrigBal, icaBal)

// Stop the relayer and wait for the process to terminate
err = r.StopRelayer(ctx, eRep)
require.NoError(t, err)

err = testutil.WaitForBlocks(ctx, 5, chain1, chain2)
require.NoError(t, err)

// Send another bank transfer msg to ICA on chain2 from the user account on chain1.
// This message should timeout and the channel will be closed when we re-start the relayer.
_, _, err = chain1.Exec(ctx, sendICATransfer, nil)
require.NoError(t, err)

// Wait for approximately one minute to allow packet timeout threshold to be hit
time.Sleep(70 * time.Second)

chain1Chans, err := r.GetChannels(ctx, eRep, chain1.Config().ChainID)
require.NoError(t, err)
require.Equal(t, 1, len(chain1Chans))

// Close the channel using the channel close CLI method
res := r.Exec(ctx, eRep, []string{"tx", "channel-close", pathName, chain1Chans[0].ChannelID, chain1Chans[0].PortID}, nil)
require.NoError(t, res.Err)
require.Zero(t, res.ExitCode)

// Assert that the packet timed out and that the acc balances are correct
chain2Bal, err = chain2.GetBalance(ctx, chain2Addr, chain2.Config().Denom)
require.NoError(t, err)
require.Equal(t, chain2OrigBal, chain2Bal)

icaBal, err = chain2.GetBalance(ctx, icaAddr, chain2.Config().Denom)
require.NoError(t, err)
require.Equal(t, icaOrigBal, icaBal)

// Assert that the channel ends are both closed
chain1Chans, err = r.GetChannels(ctx, eRep, chain1.Config().ChainID)
require.NoError(t, err)
require.Equal(t, 1, len(chain1Chans))
require.Subset(t, []string{"STATE_CLOSED", "Closed"}, []string{chain1Chans[0].State})

chain2Chans, err := r.GetChannels(ctx, eRep, chain2.Config().ChainID)
require.NoError(t, err)
require.Equal(t, 1, len(chain2Chans))
require.Subset(t, []string{"STATE_CLOSED", "Closed"}, []string{chain2Chans[0].State})

// Restart the relayer for the next channel handshake
err = r.StartRelayer(ctx, eRep, pathName)
require.NoError(t, err)

// Attempt to open another channel for the same ICA
_, _, err = chain1.Exec(ctx, registerICA, nil)
require.NoError(t, err)

// Wait for channel handshake to finish
err = testutil.WaitForBlocks(ctx, 15, chain1, chain2)
require.NoError(t, err)

// Assert that a new channel has been opened and the same ICA is in use
stdout, _, err = chain1.Exec(ctx, queryICA, nil)
require.NoError(t, err)

newICA := parseInterchainAccountField(stdout)
require.NotEmpty(t, newICA)
require.Equal(t, icaAddr, newICA)

chain1Chans, err = r.GetChannels(ctx, eRep, chain1.Config().ChainID)
require.NoError(t, err)
require.Equal(t, 2, len(chain1Chans))
require.Subset(t, []string{"STATE_OPEN", "Open"}, []string{chain1Chans[1].State})

chain2Chans, err = r.GetChannels(ctx, eRep, chain2.Config().ChainID)
require.NoError(t, err)
require.Equal(t, 2, len(chain2Chans))
require.Subset(t, []string{"STATE_OPEN", "Open"}, []string{chain2Chans[1].State})
}
6 changes: 4 additions & 2 deletions interchaintest/interchain_accounts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,17 @@ func TestScenarioInterchainAccounts(t *testing.T) {
NumValidators: &nv,
NumFullNodes: &nf,
ChainConfig: ibc.ChainConfig{
Images: []ibc.DockerImage{{Repository: "ghcr.io/cosmos/ibc-go-icad", Version: "v0.3.5"}},
Images: []ibc.DockerImage{{Repository: "ghcr.io/cosmos/ibc-go-icad", Version: "v0.5.0"}},
UsingNewGenesisCommand: true,
},
},
{
Name: "icad",
NumValidators: &nv,
NumFullNodes: &nf,
ChainConfig: ibc.ChainConfig{
Images: []ibc.DockerImage{{Repository: "ghcr.io/cosmos/ibc-go-icad", Version: "v0.3.5"}},
Images: []ibc.DockerImage{{Repository: "ghcr.io/cosmos/ibc-go-icad", Version: "v0.5.0"}},
UsingNewGenesisCommand: true,
},
},
})
Expand Down
Loading

0 comments on commit f29a2c7

Please sign in to comment.