From 2b887d7f1efabd42256464aa501f440a981e4eed Mon Sep 17 00:00:00 2001 From: Cian Hatton Date: Wed, 12 Jun 2024 14:32:28 +0100 Subject: [PATCH] Update E2E infra to support more than 2 chains (#6524) * wip: adding additional function to return all chains associated with a test * chore: existing tests passing * chore: replace chain spec references with chain spec list indexing * chore: refactor SetupChainsRelayerAndChannel to construct interchain based on arbitrary number of chains * chore: add basic test to verify 3 chain setup * chore: add e2e build flag * chore: corrected proposal id init * chore: fix build errors * chore: extract setup fn into testsuite package for re-use * chore: fix chain construction error * chore: addressing PR feedback --- e2e/tests/transfer/forwarding_test.go | 93 +++++++++++ e2e/tests/upgrades/genesis_test.go | 2 +- e2e/tests/wasm/grandpa_test.go | 60 ++++---- e2e/testsuite/testconfig.go | 28 ++-- e2e/testsuite/testsuite.go | 212 +++++++++++++++++++------- 5 files changed, 292 insertions(+), 103 deletions(-) create mode 100644 e2e/tests/transfer/forwarding_test.go diff --git a/e2e/tests/transfer/forwarding_test.go b/e2e/tests/transfer/forwarding_test.go new file mode 100644 index 00000000000..dbebfab1e9c --- /dev/null +++ b/e2e/tests/transfer/forwarding_test.go @@ -0,0 +1,93 @@ +//go:build !test_e2e + +package transfer + +import ( + "context" + "testing" + + testifysuite "github.com/stretchr/testify/suite" + + "github.com/cosmos/ibc-go/e2e/testsuite" + "github.com/cosmos/ibc-go/e2e/testsuite/query" + "github.com/cosmos/ibc-go/e2e/testvalues" +) + +func TestTransferForwardingTestSuite(t *testing.T) { + testifysuite.Run(t, new(TransferForwardingTestSuite)) +} + +type TransferForwardingTestSuite struct { + testsuite.E2ETestSuite +} + +// TODO: replace this with actual tests https://github.com/cosmos/ibc-go/issues/6578 +// this test verifies that three chains can be set up, and the relayer will relay +// packets between them as configured in the newInterchain function. +func (s *TransferForwardingTestSuite) TestThreeChainSetup() { + ctx := context.TODO() + t := s.T() + + // TODO: note the ThreeChainSetup fn needs to be passed to TransferChannelOptions since it calls + // GetChains which will requires those settings. We should be able to update the function to accept + // the chain version rather than calling GetChains. + // https://github.com/cosmos/ibc-go/issues/6577 + relayer, channelA := s.SetupChainsRelayerAndChannel(ctx, s.TransferChannelOptions(testsuite.ThreeChainSetup()), testsuite.ThreeChainSetup()) + chains := s.GetAllChains() + + chainA, chainB, chainC := chains[0], chains[1], chains[2] + + chainAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + chainAAddress := chainAWallet.FormattedAddress() + chainADenom := chainA.Config().Denom + + chainBWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) + chainBAddress := chainBWallet.FormattedAddress() + chainBDenom := chainB.Config().Denom + + chainCWallet := s.CreateUserOnChainC(ctx, testvalues.StartingTokenAmount) + chainCAddress := chainCWallet.FormattedAddress() + + t.Run("IBC transfer from A to B", func(t *testing.T) { + transferTxResp := s.Transfer(ctx, chainA, chainAWallet, channelA.PortID, channelA.ChannelID, testvalues.DefaultTransferCoins(chainADenom), chainAAddress, chainBAddress, s.GetTimeoutHeight(ctx, chainB), 0, "") + s.AssertTxSuccess(transferTxResp) + }) + + chainBChannels, err := relayer.GetChannels(ctx, s.GetRelayerExecReporter(), chainB.Config().ChainID) + s.Require().NoError(err) + // channel between A and B and channel between B and C + s.Require().Len(chainBChannels, 2) + + chainBtoCChannel := chainBChannels[0] + + t.Run("IBC transfer from B to C", func(t *testing.T) { + transferTxResp := s.Transfer(ctx, chainB, chainBWallet, chainBtoCChannel.PortID, chainBtoCChannel.ChannelID, testvalues.DefaultTransferCoins(chainBDenom), chainBAddress, chainCAddress, s.GetTimeoutHeight(ctx, chainC), 0, "") + s.AssertTxSuccess(transferTxResp) + }) + + t.Run("start relayer", func(t *testing.T) { + s.StartRelayer(relayer) + }) + + chainBIBCToken := testsuite.GetIBCToken(chainADenom, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID) + t.Run("packets are relayed from A to B", func(t *testing.T) { + s.AssertPacketRelayed(ctx, chainA, channelA.PortID, channelA.ChannelID, 1) + + actualBalance, err := query.Balance(ctx, chainB, chainBAddress, chainBIBCToken.IBCDenom()) + s.Require().NoError(err) + + expected := testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance.Int64()) + }) + + chainCIBCToken := testsuite.GetIBCToken(chainBDenom, chainBtoCChannel.Counterparty.PortID, chainBtoCChannel.Counterparty.ChannelID) + t.Run("packets are relayed from B to C", func(t *testing.T) { + s.AssertPacketRelayed(ctx, chainB, chainBtoCChannel.PortID, chainBtoCChannel.ChannelID, 1) + + actualBalance, err := query.Balance(ctx, chainC, chainCAddress, chainCIBCToken.IBCDenom()) + s.Require().NoError(err) + + expected := testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance.Int64()) + }) +} diff --git a/e2e/tests/upgrades/genesis_test.go b/e2e/tests/upgrades/genesis_test.go index 7c1a761916a..e8f606df85f 100644 --- a/e2e/tests/upgrades/genesis_test.go +++ b/e2e/tests/upgrades/genesis_test.go @@ -46,7 +46,7 @@ func (s *GenesisTestSuite) TestIBCGenesis() { appTomlOverrides["halt-height"] = haltHeight configFileOverrides["config/app.toml"] = appTomlOverrides chainOpts := func(options *testsuite.ChainOptions) { - options.ChainASpec.ConfigFileOverrides = configFileOverrides + options.ChainSpecs[0].ConfigFileOverrides = configFileOverrides } // create chains with specified chain configuration options diff --git a/e2e/tests/wasm/grandpa_test.go b/e2e/tests/wasm/grandpa_test.go index 227529e95a3..63488f79d79 100644 --- a/e2e/tests/wasm/grandpa_test.go +++ b/e2e/tests/wasm/grandpa_test.go @@ -710,11 +710,11 @@ func getConfigOverrides() map[string]any { func (s *GrandpaTestSuite) GetGrandpaTestChains() (ibc.Chain, ibc.Chain) { return s.GetChains(func(options *testsuite.ChainOptions) { // configure chain A (polkadot) - options.ChainASpec.ChainName = composable - options.ChainASpec.Type = "polkadot" - options.ChainASpec.ChainID = "rococo-local" - options.ChainASpec.Name = "composable" - options.ChainASpec.Images = []ibc.DockerImage{ + options.ChainSpecs[0].ChainName = composable + options.ChainSpecs[0].Type = "polkadot" + options.ChainSpecs[0].ChainID = "rococo-local" + options.ChainSpecs[0].Name = "composable" + options.ChainSpecs[0].Images = []ibc.DockerImage{ // TODO: https://github.com/cosmos/ibc-go/issues/4965 { Repository: "ghcr.io/misko9/polkadot-node", @@ -727,37 +727,37 @@ func (s *GrandpaTestSuite) GetGrandpaTestChains() (ibc.Chain, ibc.Chain) { UidGid: "1000:1000", }, } - options.ChainASpec.Bin = "polkadot" - options.ChainASpec.Bech32Prefix = composable - options.ChainASpec.Denom = "uDOT" - options.ChainASpec.GasPrices = "" - options.ChainASpec.GasAdjustment = 0 - options.ChainASpec.TrustingPeriod = "" - options.ChainASpec.CoinType = "354" + options.ChainSpecs[0].Bin = "polkadot" + options.ChainSpecs[0].Bech32Prefix = composable + options.ChainSpecs[0].Denom = "uDOT" + options.ChainSpecs[0].GasPrices = "" + options.ChainSpecs[0].GasAdjustment = 0 + options.ChainSpecs[0].TrustingPeriod = "" + options.ChainSpecs[0].CoinType = "354" // these values are set by default for our cosmos chains, we need to explicitly remove them here. - options.ChainASpec.ModifyGenesis = nil - options.ChainASpec.ConfigFileOverrides = nil - options.ChainASpec.EncodingConfig = nil + options.ChainSpecs[0].ModifyGenesis = nil + options.ChainSpecs[0].ConfigFileOverrides = nil + options.ChainSpecs[0].EncodingConfig = nil // configure chain B (cosmos) - options.ChainBSpec.ChainName = simd // Set chain name so that a suffix with a "dash" is not appended (required for hyperspace) - options.ChainBSpec.Type = "cosmos" - options.ChainBSpec.Name = "simd" - options.ChainBSpec.ChainID = simd - options.ChainBSpec.Bin = simd - options.ChainBSpec.Bech32Prefix = "cosmos" + options.ChainSpecs[1].ChainName = simd // Set chain name so that a suffix with a "dash" is not appended (required for hyperspace) + options.ChainSpecs[1].Type = "cosmos" + options.ChainSpecs[1].Name = "simd" + options.ChainSpecs[1].ChainID = simd + options.ChainSpecs[1].Bin = simd + options.ChainSpecs[1].Bech32Prefix = "cosmos" // TODO: hyperspace relayer assumes a denom of "stake", hard code this here right now. // https://github.com/cosmos/ibc-go/issues/4964 - options.ChainBSpec.Denom = "stake" - options.ChainBSpec.GasPrices = "0.00stake" - options.ChainBSpec.GasAdjustment = 1 - options.ChainBSpec.TrustingPeriod = "504h" - options.ChainBSpec.CoinType = "118" - - options.ChainBSpec.ChainConfig.NoHostMount = false - options.ChainBSpec.ConfigFileOverrides = getConfigOverrides() - options.ChainBSpec.EncodingConfig = testsuite.SDKEncodingConfig() + options.ChainSpecs[1].Denom = "stake" + options.ChainSpecs[1].GasPrices = "0.00stake" + options.ChainSpecs[1].GasAdjustment = 1 + options.ChainSpecs[1].TrustingPeriod = "504h" + options.ChainSpecs[1].CoinType = "118" + + options.ChainSpecs[1].ChainConfig.NoHostMount = false + options.ChainSpecs[1].ConfigFileOverrides = getConfigOverrides() + options.ChainSpecs[1].EncodingConfig = testsuite.SDKEncodingConfig() }) } diff --git a/e2e/testsuite/testconfig.go b/e2e/testsuite/testconfig.go index b1050388e77..0aec930be62 100644 --- a/e2e/testsuite/testconfig.go +++ b/e2e/testsuite/testconfig.go @@ -71,7 +71,7 @@ const ( ) // defaultChainNames contains the default name for chainA and chainB. -var defaultChainNames = []string{"simapp-a", "simapp-b"} +var defaultChainNames = []string{"simapp-a", "simapp-b", "simapp-c"} func getChainImage(binary string) string { if binary == "" { @@ -525,8 +525,7 @@ func IsFork() bool { // created for the tests. They can be modified by passing ChainOptionConfiguration // to E2ETestSuite.GetChains. type ChainOptions struct { - ChainASpec *interchaintest.ChainSpec - ChainBSpec *interchaintest.ChainSpec + ChainSpecs []*interchaintest.ChainSpec SkipPathCreation bool } @@ -544,17 +543,20 @@ func DefaultChainOptions() ChainOptions { chainAVal, chainAFn := getValidatorsAndFullNodes(0) chainBVal, chainBFn := getValidatorsAndFullNodes(1) + chainASpec := &interchaintest.ChainSpec{ + ChainConfig: chainACfg, + NumFullNodes: &chainAFn, + NumValidators: &chainAVal, + } + + chainBSpec := &interchaintest.ChainSpec{ + ChainConfig: chainBCfg, + NumFullNodes: &chainBFn, + NumValidators: &chainBVal, + } + return ChainOptions{ - ChainASpec: &interchaintest.ChainSpec{ - ChainConfig: chainACfg, - NumFullNodes: &chainAFn, - NumValidators: &chainAVal, - }, - ChainBSpec: &interchaintest.ChainSpec{ - ChainConfig: chainBCfg, - NumFullNodes: &chainBFn, - NumValidators: &chainBVal, - }, + ChainSpecs: []*interchaintest.ChainSpec{chainASpec, chainBSpec}, } } diff --git a/e2e/testsuite/testsuite.go b/e2e/testsuite/testsuite.go index b678850084b..0924ce0f4b2 100644 --- a/e2e/testsuite/testsuite.go +++ b/e2e/testsuite/testsuite.go @@ -49,7 +49,7 @@ type E2ETestSuite struct { // proposalIDs keeps track of the active proposal ID for each chain. proposalIDs map[string]uint64 - paths map[string]pathPair + paths map[string][]ibc.Chain relayers relayer.Map logger *zap.Logger DockerClient *dockerclient.Client @@ -60,19 +60,6 @@ type E2ETestSuite struct { pathNameIndex int64 } -// pathPair is a pairing of two chains which will be used in a test. -type pathPair struct { - chainA, chainB ibc.Chain -} - -// newPath returns a path built from the given chains. -func newPath(chainA, chainB ibc.Chain) pathPair { - return pathPair{ - chainA: chainA, - chainB: chainB, - } -} - func (s *E2ETestSuite) SetupTest() { s.configureGenesisDebugExport() } @@ -119,7 +106,8 @@ func (s *E2ETestSuite) configureGenesisDebugExport() { // GetRelayerUsers returns two ibc.Wallet instances which can be used for the relayer users // on the two chains. func (s *E2ETestSuite) GetRelayerUsers(ctx context.Context, chainOpts ...ChainOptionConfiguration) (ibc.Wallet, ibc.Wallet) { - chainA, chainB := s.GetChains(chainOpts...) + chains := s.GetAllChains(chainOpts...) + chainA, chainB := chains[0], chains[1] chainAAccountBytes, err := chainA.GetAddress(ctx, ChainARelayerName) s.Require().NoError(err) @@ -143,31 +131,80 @@ func (s *E2ETestSuite) GetRelayerUsers(ctx context.Context, chainOpts ...ChainOp // with E2ETestSuite.StartRelayer if needed. // This should be called at the start of every test, unless fine grained control is required. func (s *E2ETestSuite) SetupChainsRelayerAndChannel(ctx context.Context, channelOpts func(*ibc.CreateChannelOptions), chainSpecOpts ...ChainOptionConfiguration) (ibc.Relayer, ibc.ChannelOutput) { - chainA, chainB := s.GetChains(chainSpecOpts...) - r := s.ConfigureRelayer(ctx, chainA, chainB, channelOpts) - chainAChannels, err := r.GetChannels(ctx, s.GetRelayerExecReporter(), chainA.Config().ChainID) + chains := s.GetAllChains(chainSpecOpts...) + + r := relayer.New(s.T(), *LoadConfig().GetActiveRelayerConfig(), s.logger, s.DockerClient, s.network) + + ic := s.newInterchain(ctx, r, chains, channelOpts) + + buildOpts := interchaintest.InterchainBuildOptions{ + TestName: s.T().Name(), + Client: s.DockerClient, + NetworkID: s.network, + } + + s.Require().NoError(ic.Build(ctx, s.GetRelayerExecReporter(), buildOpts)) + + chainAChannels, err := r.GetChannels(ctx, s.GetRelayerExecReporter(), chains[0].Config().ChainID) s.Require().NoError(err) return r, chainAChannels[len(chainAChannels)-1] } +// newInterchain constructs a new interchain instance that creates channels between the chains. +func (s *E2ETestSuite) newInterchain(ctx context.Context, r ibc.Relayer, chains []ibc.Chain, channelOpts func(*ibc.CreateChannelOptions)) *interchaintest.Interchain { + channelOptions := defaultChannelOpts(chains) + if channelOpts != nil { + channelOpts(&channelOptions) + } + + ic := interchaintest.NewInterchain() + for _, chain := range chains { + ic.AddChain(chain) + } + ic.AddRelayer(r, "r") + + var pathNames []string + + // iterate through all chains, and create links such that there is a channel between + // - chainA and chainB + // - chainB and chainC + // - chainC and chainD etc + for i := 0; i < len(chains)-1; i++ { + pathName := s.generatePathName() + pathNames = append(pathNames, pathName) + ic.AddLink(interchaintest.InterchainLink{ + Chain1: chains[i], + Chain2: chains[i+1], + Relayer: r, + Path: pathName, + CreateChannelOpts: channelOptions, + }) + } + + s.startRelayerFn = func(relayer ibc.Relayer) { + err := relayer.StartRelayer(ctx, s.GetRelayerExecReporter(), pathNames...) + s.Require().NoError(err, fmt.Sprintf("failed to start relayer: %s", err)) + + var chainHeighters []test.ChainHeighter + for _, c := range chains { + chainHeighters = append(chainHeighters, c) + } + + // wait for every chain to produce some blocks before using the relayer. + s.Require().NoError(test.WaitForBlocks(ctx, 10, chainHeighters...), "failed to wait for blocks") + } + + return ic +} + func (s *E2ETestSuite) ConfigureRelayer(ctx context.Context, chainA, chainB ibc.Chain, channelOpts func(*ibc.CreateChannelOptions), buildOptions ...func(options *interchaintest.InterchainBuildOptions)) ibc.Relayer { r := relayer.New(s.T(), *LoadConfig().GetActiveRelayerConfig(), s.logger, s.DockerClient, s.network) pathName := s.generatePathName() - channelOptions := ibc.DefaultChannelOpts() + channelOptions := defaultChannelOpts([]ibc.Chain{chainA, chainB}) if channelOpts != nil { channelOpts(&channelOptions) - } else { - chainAVersion := chainA.Config().Images[0].Version - chainBVersion := chainB.Config().Images[0].Version - - // select the transfer version based on the chains' version - transferVersion := transfertypes.V1 - if testvalues.ICS20v2FeatureReleases.IsSupported(chainAVersion) && testvalues.ICS20v2FeatureReleases.IsSupported(chainBVersion) { - transferVersion = transfertypes.V2 - } - channelOptions.Version = transferVersion } ic := interchaintest.NewInterchain(). @@ -210,7 +247,8 @@ func (s *E2ETestSuite) ConfigureRelayer(ctx context.Context, chainA, chainB ibc. // TODO: Actually setup a single chain. Seeing panic: runtime error: index out of range [0] with length 0 when using a single chain. // issue: https://github.com/strangelove-ventures/interchaintest/issues/401 func (s *E2ETestSuite) SetupSingleChain(ctx context.Context) ibc.Chain { - chainA, chainB := s.GetChains() + chains := s.GetAllChains() + chainA, chainB := chains[0], chains[1] ic := interchaintest.NewInterchain().AddChain(chainA).AddChain(chainB) @@ -239,9 +277,12 @@ func (s *E2ETestSuite) GetPathName(idx int64) string { return strings.ReplaceAll(pathName, "/", "-") } -// generatePath generates the path name using the test suites name -func (s *E2ETestSuite) generatePath(ctx context.Context, ibcrelayer ibc.Relayer) string { - chainA, chainB := s.GetChains() +// generatePath generates the path name using the test suites name. The indices provided specify which chains should be +// used. E.g. to generate a path between chain A and B, you would use 0 and 1, to specify between A and C, you would +// use 0 and 2 etc. +func (s *E2ETestSuite) generatePath(ctx context.Context, ibcrelayer ibc.Relayer, chainAIdx, chainBIdx int) string { + chains := s.GetAllChains() + chainA, chainB := chains[chainAIdx], chains[chainBIdx] chainAID := chainA.Config().ChainID chainBID := chainB.Config().ChainID @@ -255,7 +296,7 @@ func (s *E2ETestSuite) generatePath(ctx context.Context, ibcrelayer ibc.Relayer) // SetupClients creates clients on chainA and chainB using the provided create client options func (s *E2ETestSuite) SetupClients(ctx context.Context, ibcrelayer ibc.Relayer, opts ibc.CreateClientOptions) { - pathName := s.generatePath(ctx, ibcrelayer) + pathName := s.generatePath(ctx, ibcrelayer, 0, 1) err := ibcrelayer.CreateClients(ctx, s.GetRelayerExecReporter(), pathName, opts) s.Require().NoError(err) } @@ -269,13 +310,19 @@ func (s *E2ETestSuite) UpdateClients(ctx context.Context, ibcrelayer ibc.Relayer // GetChains returns two chains that can be used in a test. The pair returned // is unique to the current test being run. Note: this function does not create containers. func (s *E2ETestSuite) GetChains(chainOpts ...ChainOptionConfiguration) (ibc.Chain, ibc.Chain) { + chains := s.GetAllChains(chainOpts...) + return chains[0], chains[1] +} + +// GetAllChains returns all chains that can be used in a test. The chains returned +// are unique to the current test being run. Note: this function does not create containers. +func (s *E2ETestSuite) GetAllChains(chainOpts ...ChainOptionConfiguration) []ibc.Chain { if s.paths == nil { - s.paths = map[string]pathPair{} + s.paths = map[string][]ibc.Chain{} } - suitePath, ok := s.paths[s.T().Name()] - if ok { - return suitePath.chainA, suitePath.chainB + if chains, ok := s.paths[s.T().Name()]; ok { + return chains } chainOptions := DefaultChainOptions() @@ -283,23 +330,25 @@ func (s *E2ETestSuite) GetChains(chainOpts ...ChainOptionConfiguration) (ibc.Cha opt(&chainOptions) } - chainA, chainB := s.createChains(chainOptions) - suitePath = newPath(chainA, chainB) - s.paths[s.T().Name()] = suitePath + chains := s.createChains(chainOptions) + s.paths[s.T().Name()] = chains if s.proposalIDs == nil { s.proposalIDs = map[string]uint64{} } - s.proposalIDs[chainA.Config().ChainID] = 1 - s.proposalIDs[chainB.Config().ChainID] = 1 + // initialise proposal ids for all chains. + for _, chain := range chains { + s.proposalIDs[chain.Config().ChainID] = 1 + } - return suitePath.chainA, suitePath.chainB + return chains } // GetRelayerWallets returns the ibcrelayer wallets associated with the chains. func (s *E2ETestSuite) GetRelayerWallets(ibcrelayer ibc.Relayer) (ibc.Wallet, ibc.Wallet, error) { - chainA, chainB := s.GetChains() + chains := s.GetAllChains() + chainA, chainB := chains[0], chains[1] chainARelayerWallet, ok := ibcrelayer.GetWallet(chainA.Config().ChainID) if !ok { return nil, nil, fmt.Errorf("unable to find chain A relayer wallet") @@ -320,7 +369,8 @@ func (s *E2ETestSuite) RecoverRelayerWallets(ctx context.Context, ibcrelayer ibc return err } - chainA, chainB := s.GetChains() + chains := s.GetAllChains() + chainA, chainB := chains[0], chains[1] if err := chainA.RecoverKey(ctx, ChainARelayerName, chainARelayerWallet.Mnemonic()); err != nil { return fmt.Errorf("could not recover relayer wallet on chain A: %s", err) @@ -354,19 +404,28 @@ func (s *E2ETestSuite) RestartRelayer(ctx context.Context, ibcrelayer ibc.Relaye // CreateUserOnChainA creates a user with the given amount of funds on chain A. func (s *E2ETestSuite) CreateUserOnChainA(ctx context.Context, amount int64) ibc.Wallet { - chainA, _ := s.GetChains() - return interchaintest.GetAndFundTestUsers(s.T(), ctx, strings.ReplaceAll(s.T().Name(), " ", "-"), sdkmath.NewInt(amount), chainA)[0] + return s.createWalletOnChainIndex(ctx, amount, 0) } // CreateUserOnChainB creates a user with the given amount of funds on chain B. func (s *E2ETestSuite) CreateUserOnChainB(ctx context.Context, amount int64) ibc.Wallet { - _, chainB := s.GetChains() - return interchaintest.GetAndFundTestUsers(s.T(), ctx, strings.ReplaceAll(s.T().Name(), " ", "-"), sdkmath.NewInt(amount), chainB)[0] + return s.createWalletOnChainIndex(ctx, amount, 1) +} + +// CreateUserOnChainC creates a user with the given amount of funds on chain C. +func (s *E2ETestSuite) CreateUserOnChainC(ctx context.Context, amount int64) ibc.Wallet { + return s.createWalletOnChainIndex(ctx, amount, 2) +} + +// createWalletOnChainIndex creates a wallet with the given amount of funds on the chain of the given index. +func (s *E2ETestSuite) createWalletOnChainIndex(ctx context.Context, amount, chainIndex int64) ibc.Wallet { + chain := s.GetAllChains()[chainIndex] + return interchaintest.GetAndFundTestUsers(s.T(), ctx, strings.ReplaceAll(s.T().Name(), " ", "-"), sdkmath.NewInt(amount), chain)[0] } // GetChainANativeBalance gets the balance of a given user on chain A. func (s *E2ETestSuite) GetChainANativeBalance(ctx context.Context, user ibc.Wallet) (int64, error) { - chainA, _ := s.GetChains() + chainA := s.GetAllChains()[0] balanceResp, err := query.GRPCQuery[banktypes.QueryBalanceResponse](ctx, chainA, &banktypes.QueryBalanceRequest{ Address: user.FormattedAddress(), @@ -381,7 +440,7 @@ func (s *E2ETestSuite) GetChainANativeBalance(ctx context.Context, user ibc.Wall // GetChainBNativeBalance gets the balance of a given user on chain B. func (s *E2ETestSuite) GetChainBNativeBalance(ctx context.Context, user ibc.Wallet) (int64, error) { - _, chainB := s.GetChains() + chainB := s.GetAllChains()[1] balanceResp, err := query.GRPCQuery[banktypes.QueryBalanceResponse](ctx, chainB, &banktypes.QueryBalanceRequest{ Address: user.FormattedAddress(), @@ -426,7 +485,7 @@ func (s *E2ETestSuite) AssertHumanReadableDenom(ctx context.Context, chain ibc.C // createChains creates two separate chains in docker containers. // test and can be retrieved with GetChains. -func (s *E2ETestSuite) createChains(chainOptions ChainOptions) (ibc.Chain, ibc.Chain) { +func (s *E2ETestSuite) createChains(chainOptions ChainOptions) []ibc.Chain { client, network := interchaintest.DockerSetup(s.T()) t := s.T() @@ -434,21 +493,24 @@ func (s *E2ETestSuite) createChains(chainOptions ChainOptions) (ibc.Chain, ibc.C s.DockerClient = client s.network = network - cf := interchaintest.NewBuiltinChainFactory(s.logger, []*interchaintest.ChainSpec{chainOptions.ChainASpec, chainOptions.ChainBSpec}) + cf := interchaintest.NewBuiltinChainFactory(s.logger, chainOptions.ChainSpecs) // this is intentionally called after the interchaintest.DockerSetup function. The above function registers a // cleanup task which deletes all containers. By registering a cleanup function afterwards, it is executed first // this allows us to process the logs before the containers are removed. t.Cleanup(func() { dumpLogs := LoadConfig().DebugConfig.DumpLogs - chains := []string{chainOptions.ChainASpec.ChainConfig.Name, chainOptions.ChainBSpec.ChainConfig.Name} - diagnostics.Collect(t, s.DockerClient, dumpLogs, chains...) + var chainNames []string + for _, chain := range chainOptions.ChainSpecs { + chainNames = append(chainNames, chain.Name) + } + diagnostics.Collect(t, s.DockerClient, dumpLogs, chainNames...) }) chains, err := cf.Chains(t.Name()) s.Require().NoError(err) - return chains[0], chains[1] + return chains } // GetRelayerExecReporter returns a testreporter.RelayerExecReporter instances @@ -459,8 +521,8 @@ func (s *E2ETestSuite) GetRelayerExecReporter() *testreporter.RelayerExecReporte } // TransferChannelOptions configures both of the chains to have non-incentivized transfer channels. -func (s *E2ETestSuite) TransferChannelOptions() func(options *ibc.CreateChannelOptions) { - chainA, chainB := s.GetChains() +func (s *E2ETestSuite) TransferChannelOptions(chainOpts ...ChainOptionConfiguration) func(options *ibc.CreateChannelOptions) { + chainA, chainB := s.GetChains(chainOpts...) chainAVersion := chainA.Config().Images[0].Version chainBVersion := chainB.Config().Images[0].Version @@ -590,3 +652,35 @@ func GetMsgTransfer(portID, channelID, version string, tokens sdk.Coins, sender, return msg } + +// ThreeChainSetup provides the default behaviour to wire up 3 chains in the tests. +func ThreeChainSetup() ChainOptionConfiguration { + // copy all values of existing chains and tweak to make unique to new chain. + return func(options *ChainOptions) { + chainCSpec := *options.ChainSpecs[0] // nolint + + chainCSpec.ChainID = "chainC-1" + chainCSpec.Name = "simapp-c" + + options.ChainSpecs = append(options.ChainSpecs, &chainCSpec) + } +} + +// DefaultChainOptions returns the default chain options for the test suite based on the provided chains. +func defaultChannelOpts(chains []ibc.Chain) ibc.CreateChannelOptions { + channelOptions := ibc.DefaultChannelOpts() + channelOptions.Version = determineDefaultTransferVersion(chains) + return channelOptions +} + +// determineDefaultTransferVersion determines the version of transfer that should be used with an arbitrary number of chains. +// the default is V2, but if any chain does not support V2, then V1 is used. +func determineDefaultTransferVersion(chains []ibc.Chain) string { + for _, chain := range chains { + chainVersion := chain.Config().Images[0].Version + if !testvalues.ICS20v2FeatureReleases.IsSupported(chainVersion) { + return transfertypes.V1 + } + } + return transfertypes.V2 +}