From 908c09aad2763f33edafdcd2a82ad433e184ba43 Mon Sep 17 00:00:00 2001 From: yaruwangway <69694322+yaruwangway@users.noreply.github.com> Date: Tue, 6 Jun 2023 18:12:40 +0200 Subject: [PATCH] test: add e2e test for ibc bypass msg (#2532) * test: setup a new hermes instance * test: add executeHermesCommand * test: add hermesTransfer * test: add ibc-bypass-msg test * test: add hermes config * test: enable all e2e test * fix: tearDownSuite in e2e test * Apply suggestions from code review Co-authored-by: Simon Noetzlin * update according to comments * chore: add comments to code * chore: correct typo * refactor bypass msg test * fix: parse query pending packet result * fix: format print error * fix: pending packets parsing * Apply suggestions from code review Co-authored-by: Philip Offtermatt <57488781+p-offtermatt@users.noreply.github.com> * docs: fix dead link * chore: fix lint * chore: lint fix * chore: lint fix1 * chore: remove nolint * Update tests/e2e/e2e_ibc_test.go --------- Co-authored-by: Simon Noetzlin Co-authored-by: Philip Offtermatt <57488781+p-offtermatt@users.noreply.github.com> --- tests/e2e/e2e_bypassminfee_test.go | 87 ++++++++++ tests/e2e/e2e_exec_test.go | 29 ++++ tests/e2e/e2e_ibc_test.go | 196 ++++++++++------------ tests/e2e/e2e_setup_test.go | 221 ++++++++++++++++++++++--- tests/e2e/e2e_test.go | 5 + tests/e2e/scripts/hermes1_bootstrap.sh | 152 +++++++++++++++++ 6 files changed, 564 insertions(+), 126 deletions(-) create mode 100755 tests/e2e/scripts/hermes1_bootstrap.sh diff --git a/tests/e2e/e2e_bypassminfee_test.go b/tests/e2e/e2e_bypassminfee_test.go index 61be12f0b6a..2901c557fef 100644 --- a/tests/e2e/e2e_bypassminfee_test.go +++ b/tests/e2e/e2e_bypassminfee_test.go @@ -1,9 +1,13 @@ package e2e import ( + "time" + "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + ibcclienttypes "github.com/cosmos/ibc-go/v4/modules/core/02-client/types" + ibcchanneltypes "github.com/cosmos/ibc-go/v4/modules/core/04-channel/types" ) func (s *IntegrationTestSuite) testBypassMinFeeWithdrawReward(endpoint string) { @@ -95,3 +99,86 @@ func (s *IntegrationTestSuite) testBypassMinFeeWithdrawReward(endpoint string) { } } } + +func (s *IntegrationTestSuite) testIBCBypassMsg() { + // submit gov proposal to change bypass-msg param to + // ["/ibc.core.channel.v1.MsgRecvPacket", + // "/ibc.core.channel.v1.MsgAcknowledgement", + // "/ibc.core.client.v1.MsgUpdateClient"] + submitterAddr := s.chainA.validators[0].keyInfo.GetAddress() + submitter := submitterAddr.String() + proposalCounter++ + s.govProposeNewBypassMsgs([]string{ + sdk.MsgTypeURL(&ibcchanneltypes.MsgRecvPacket{}), + sdk.MsgTypeURL(&ibcchanneltypes.MsgAcknowledgement{}), + sdk.MsgTypeURL(&ibcclienttypes.MsgUpdateClient{}), + }, proposalCounter, submitter, standardFees.String()) + + // use hermes1 to test default ibc bypass-msg + // + // test 1: transaction only contains bypass-msgs, pass + s.testTxContainsOnlyIBCBypassMsg() + // test 2: test transactions contains both bypass and non-bypass msgs (sdk.MsgTypeURL(&ibcchanneltypes.MsgTimeout{}) + s.testTxContainsMixBypassNonBypassMsg() + // test 3: test bypass-msgs exceed the MaxBypassGasUsage + s.testBypassMsgsExceedMaxBypassGasLimit() + + // set the default bypass-msg back + proposalCounter++ + s.govProposeNewBypassMsgs([]string{ + sdk.MsgTypeURL(&ibcchanneltypes.MsgRecvPacket{}), + sdk.MsgTypeURL(&ibcchanneltypes.MsgAcknowledgement{}), + sdk.MsgTypeURL(&ibcclienttypes.MsgUpdateClient{}), + sdk.MsgTypeURL(&ibcchanneltypes.MsgTimeout{}), + sdk.MsgTypeURL(&ibcchanneltypes.MsgTimeoutOnClose{}), + }, proposalCounter, submitter, standardFees.String()) +} + +func (s *IntegrationTestSuite) testTxContainsOnlyIBCBypassMsg() { + s.T().Logf("testing transaction contains only ibc bypass messages") + ok := s.hermesTransfer(hermesConfigWithGasPrices, s.chainA.id, s.chainB.id, transferChannel, uatomDenom, 100, 1000, 1) + s.Require().True(ok) + + scrRelayerBalanceBefore, dstRelayerBalanceBefore := s.queryRelayerWalletsBalances() + + pass := s.hermesClearPacket(hermesConfigNoGasPrices, s.chainA.id, transferChannel) + s.Require().True(pass) + pendingPacketsExist := s.hermesPendingPackets(hermesConfigNoGasPrices, s.chainA.id, transferChannel) + s.Require().False(pendingPacketsExist) + + // confirm relayer wallets do not pay fees + scrRelayerBalanceAfter, dstRelayerBalanceAfter := s.queryRelayerWalletsBalances() + s.Require().Equal(scrRelayerBalanceBefore.String(), scrRelayerBalanceAfter.String()) + s.Require().Equal(dstRelayerBalanceBefore.String(), dstRelayerBalanceAfter.String()) +} + +func (s *IntegrationTestSuite) testTxContainsMixBypassNonBypassMsg() { + s.T().Logf("testing transaction contains both bypass and non-bypass messages") + // hermesTransfer with --timeout-height-offset=1 + ok := s.hermesTransfer(hermesConfigWithGasPrices, s.chainA.id, s.chainB.id, transferChannel, uatomDenom, 100, 1, 1) + s.Require().True(ok) + // make sure that the transaction is timeout + time.Sleep(3 * time.Second) + pendingPacketsExist := s.hermesPendingPackets(hermesConfigNoGasPrices, s.chainA.id, transferChannel) + s.Require().True(pendingPacketsExist) + + pass := s.hermesClearPacket(hermesConfigNoGasPrices, s.chainA.id, transferChannel) + s.Require().False(pass) + // clear packets with paying fee, to not influence the next transaction + pass = s.hermesClearPacket(hermesConfigWithGasPrices, s.chainA.id, transferChannel) + s.Require().True(pass) +} + +func (s *IntegrationTestSuite) testBypassMsgsExceedMaxBypassGasLimit() { + s.T().Logf("testing bypass messages exceed MaxBypassGasUsage") + ok := s.hermesTransfer(hermesConfigWithGasPrices, s.chainA.id, s.chainB.id, transferChannel, uatomDenom, 100, 1000, 12) + s.Require().True(ok) + pass := s.hermesClearPacket(hermesConfigNoGasPrices, s.chainA.id, transferChannel) + s.Require().False(pass) + + pendingPacketsExist := s.hermesPendingPackets(hermesConfigNoGasPrices, s.chainA.id, transferChannel) + s.Require().True(pendingPacketsExist) + + pass = s.hermesClearPacket(hermesConfigWithGasPrices, s.chainA.id, transferChannel) + s.Require().True(pass) +} diff --git a/tests/e2e/e2e_exec_test.go b/tests/e2e/e2e_exec_test.go index 6b007ef7c0b..4f16db42d88 100644 --- a/tests/e2e/e2e_exec_test.go +++ b/tests/e2e/e2e_exec_test.go @@ -617,6 +617,35 @@ func (s *IntegrationTestSuite) executeGaiaTxCommand(ctx context.Context, c *chai } } +func (s *IntegrationTestSuite) executeHermesCommand(ctx context.Context, hermesCmd []string) (string, string) { + var ( + outBuf bytes.Buffer + errBuf bytes.Buffer + ) + exec, err := s.dkrPool.Client.CreateExec(docker.CreateExecOptions{ + Context: ctx, + AttachStdout: true, + AttachStderr: true, + Container: s.hermesResource1.Container.ID, + User: "root", + Cmd: hermesCmd, + }) + s.Require().NoError(err) + + err = s.dkrPool.Client.StartExec(exec.ID, docker.StartExecOptions{ + Context: ctx, + Detach: false, + OutputStream: &outBuf, + ErrorStream: &errBuf, + }) + s.Require().NoError(err) + + stdOut := outBuf.Bytes() + stdErr := errBuf.Bytes() + + return string(stdOut), string(stdErr) +} + func (s *IntegrationTestSuite) expectErrExecValidation(chain *chain, valIdx int, expectErr bool) func([]byte, []byte) bool { return func(stdOut []byte, stdErr []byte) bool { var txResp sdk.TxResponse diff --git a/tests/e2e/e2e_ibc_test.go b/tests/e2e/e2e_ibc_test.go index 4c9d3c7454a..76a5bd7d406 100644 --- a/tests/e2e/e2e_ibc_test.go +++ b/tests/e2e/e2e_ibc_test.go @@ -5,18 +5,12 @@ import ( "context" "encoding/json" "fmt" - "io" - "net/http" - "os" - "path" - "path/filepath" "strconv" "strings" "time" "github.com/cosmos/cosmos-sdk/client/flags" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/ory/dockertest/v3" "github.com/ory/dockertest/v3/docker" ) @@ -34,102 +28,6 @@ type PacketMetadata struct { Forward *ForwardMetadata `json:"forward"` } -func (s *IntegrationTestSuite) runIBCRelayer() { - s.T().Log("starting Hermes relayer container...") - - tmpDir, err := os.MkdirTemp("", "gaia-e2e-testnet-hermes-") - s.Require().NoError(err) - s.tmpDirs = append(s.tmpDirs, tmpDir) - - gaiaAVal := s.chainA.validators[0] - gaiaBVal := s.chainB.validators[0] - - gaiaARly := s.chainA.genesisAccounts[relayerAccountIndex] - gaiaBRly := s.chainB.genesisAccounts[relayerAccountIndex] - - hermesCfgPath := path.Join(tmpDir, "hermes") - - s.Require().NoError(os.MkdirAll(hermesCfgPath, 0o755)) - _, err = copyFile( - filepath.Join("./scripts/", "hermes_bootstrap.sh"), - filepath.Join(hermesCfgPath, "hermes_bootstrap.sh"), - ) - s.Require().NoError(err) - - s.hermesResource, err = s.dkrPool.RunWithOptions( - &dockertest.RunOptions{ - Name: fmt.Sprintf("%s-%s-relayer", s.chainA.id, s.chainB.id), - Repository: "ghcr.io/cosmos/hermes-e2e", - Tag: "1.0.0", - NetworkID: s.dkrNet.Network.ID, - Mounts: []string{ - fmt.Sprintf("%s/:/root/hermes", hermesCfgPath), - }, - PortBindings: map[docker.Port][]docker.PortBinding{ - "3031/tcp": {{HostIP: "", HostPort: "3031"}}, - }, - Env: []string{ - fmt.Sprintf("GAIA_A_E2E_CHAIN_ID=%s", s.chainA.id), - fmt.Sprintf("GAIA_B_E2E_CHAIN_ID=%s", s.chainB.id), - fmt.Sprintf("GAIA_A_E2E_VAL_MNEMONIC=%s", gaiaAVal.mnemonic), - fmt.Sprintf("GAIA_B_E2E_VAL_MNEMONIC=%s", gaiaBVal.mnemonic), - fmt.Sprintf("GAIA_A_E2E_RLY_MNEMONIC=%s", gaiaARly.mnemonic), - fmt.Sprintf("GAIA_B_E2E_RLY_MNEMONIC=%s", gaiaBRly.mnemonic), - fmt.Sprintf("GAIA_A_E2E_VAL_HOST=%s", s.valResources[s.chainA.id][0].Container.Name[1:]), - fmt.Sprintf("GAIA_B_E2E_VAL_HOST=%s", s.valResources[s.chainB.id][0].Container.Name[1:]), - }, - Entrypoint: []string{ - "sh", - "-c", - "chmod +x /root/hermes/hermes_bootstrap.sh && /root/hermes/hermes_bootstrap.sh", - }, - }, - noRestart, - ) - s.Require().NoError(err) - - endpoint := fmt.Sprintf("http://%s/state", s.hermesResource.GetHostPort("3031/tcp")) - s.Require().Eventually( - func() bool { - resp, err := http.Get(endpoint) //nolint:gosec // this is a test - if err != nil { - return false - } - - defer resp.Body.Close() - - bz, err := io.ReadAll(resp.Body) - if err != nil { - return false - } - - var respBody map[string]interface{} - if err := json.Unmarshal(bz, &respBody); err != nil { - return false - } - - status := respBody["status"].(string) - result := respBody["result"].(map[string]interface{}) - - return status == "success" && len(result["chains"].([]interface{})) == 2 - }, - 5*time.Minute, - time.Second, - "hermes relayer not healthy", - ) - - s.T().Logf("started Hermes relayer container: %s", s.hermesResource.Container.ID) - - // XXX: Give time to both networks to start, otherwise we might see gRPC - // transport errors. - time.Sleep(10 * time.Second) - - // create the client, connection and channel between the two Gaia chains - s.createConnection() - time.Sleep(10 * time.Second) - s.createChannel() -} - func (s *IntegrationTestSuite) sendIBC(c *chain, valIdx int, sender, recipient, token, fees, note string) { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() @@ -158,6 +56,95 @@ func (s *IntegrationTestSuite) sendIBC(c *chain, valIdx int, sender, recipient, s.T().Log("successfully sent IBC tokens") } +func (s *IntegrationTestSuite) hermesTransfer(configPath, srcChainID, dstChainID, srcChannelID, denom string, sendAmt, timeOutOffset, numMsg int) (success bool) { + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + hermesCmd := []string{ + hermesBinary, + fmt.Sprintf("--config=%s", configPath), + "tx", + "ft-transfer", + fmt.Sprintf("--dst-chain=%s", dstChainID), + fmt.Sprintf("--src-chain=%s", srcChainID), + fmt.Sprintf("--src-channel=%s", srcChannelID), + fmt.Sprintf("--src-port=%s", "transfer"), + fmt.Sprintf("--amount=%v", sendAmt), + fmt.Sprintf("--denom=%s", denom), + fmt.Sprintf("--timeout-height-offset=%v", timeOutOffset), + fmt.Sprintf("--number-msgs=%v", numMsg), + } + + stdout, stderr := s.executeHermesCommand(ctx, hermesCmd) + if strings.Contains(stdout, "ERROR") || strings.Contains(stderr, "ERROR") { + return false + } + + return true +} + +func (s *IntegrationTestSuite) hermesClearPacket(configPath, chainID, channelID string) bool { //nolint:unparam + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + hermesCmd := []string{ + hermesBinary, + fmt.Sprintf("--config=%s", configPath), + "clear", + "packets", + fmt.Sprintf("--chain=%s", chainID), + fmt.Sprintf("--channel=%s", channelID), + fmt.Sprintf("--port=%s", "transfer"), + } + + stdout, stderr := s.executeHermesCommand(ctx, hermesCmd) + if strings.Contains(stdout, "ERROR") || strings.Contains(stderr, "ERROR") { + return false + } + + return true +} + +func (s *IntegrationTestSuite) hermesPendingPackets(configPath, chainID, channelID string) (pendingPackets bool) { + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + hermesCmd := []string{ + hermesBinary, + fmt.Sprintf("--config=%s", configPath), + "query", + "packet", + "pending", + fmt.Sprintf("--chain=%s", chainID), + fmt.Sprintf("--channel=%s", channelID), + fmt.Sprintf("--port=%s", "transfer"), + } + + stdout, _ := s.executeHermesCommand(ctx, hermesCmd) + stdout = strings.ReplaceAll(stdout, " ", "") + stdout = strings.ReplaceAll(stdout, "\n", "") + + // Check if "unreceived_packets" exists in "src" + return !strings.Contains(stdout, "src:pendingPackets{unreceived_packets:[]") +} + +func (s *IntegrationTestSuite) queryRelayerWalletsBalances() (sdk.Coin, sdk.Coin) { + chainAAPIEndpoint := fmt.Sprintf("http://%s", s.valResources[s.chainA.id][0].GetHostPort("1317/tcp")) + scrRelayerBalance, err := getSpecificBalance( + chainAAPIEndpoint, + s.chainA.genesisAccounts[relayerAccountIndexHermes1].keyInfo.GetAddress().String(), + uatomDenom) + s.Require().NoError(err) + + chainBAPIEndpoint := fmt.Sprintf("http://%s", s.valResources[s.chainB.id][0].GetHostPort("1317/tcp")) + dstRelayerBalance, err := getSpecificBalance( + chainBAPIEndpoint, + s.chainB.genesisAccounts[relayerAccountIndexHermes1].keyInfo.GetAddress().String(), + uatomDenom) + s.Require().NoError(err) + + return scrRelayerBalance, dstRelayerBalance +} + func (s *IntegrationTestSuite) createConnection() { s.T().Logf("connecting %s and %s chains via IBC", s.chainA.id, s.chainB.id) @@ -168,7 +155,7 @@ func (s *IntegrationTestSuite) createConnection() { Context: ctx, AttachStdout: true, AttachStderr: true, - Container: s.hermesResource.Container.ID, + Container: s.hermesResource0.Container.ID, User: "root", Cmd: []string{ "hermes", @@ -211,7 +198,7 @@ func (s *IntegrationTestSuite) createChannel() { Context: ctx, AttachStdout: true, AttachStderr: true, - Container: s.hermesResource.Container.ID, + Container: s.hermesResource0.Container.ID, User: "root", Cmd: []string{ "hermes", @@ -249,7 +236,6 @@ func (s *IntegrationTestSuite) createChannel() { } func (s *IntegrationTestSuite) testIBCTokenTransfer() { - time.Sleep(30 * time.Second) s.Run("send_uatom_to_chainB", func() { // require the recipient account receives the IBC tokens (IBC packets ACKd) var ( diff --git a/tests/e2e/e2e_setup_test.go b/tests/e2e/e2e_setup_test.go index b7b46e86ec0..bc3d54b68e5 100644 --- a/tests/e2e/e2e_setup_test.go +++ b/tests/e2e/e2e_setup_test.go @@ -4,8 +4,11 @@ import ( "context" "encoding/json" "fmt" + "io" + "net/http" "os" "os/exec" + "path" "path/filepath" "strconv" "strings" @@ -58,7 +61,8 @@ const ( maxTotalBypassMinFeeMsgGasUsage = "1" gas = 200000 govProposalBlockBuffer = 35 - relayerAccountIndex = 0 + relayerAccountIndexHermes0 = 0 + relayerAccountIndexHermes1 = 1 numberOfEvidences = 10 slashingShares int64 = 10000 @@ -68,29 +72,37 @@ const ( proposalCommunitySpendFilename = "proposal_community_spend.json" proposalAddConsumerChainFilename = "proposal_add_consumer.json" proposalRemoveConsumerChainFilename = "proposal_remove_consumer.json" + + hermesBinary = "hermes" + hermesConfigWithGasPrices = "/root/.hermes/config.toml" + hermesConfigNoGasPrices = "/root/.hermes/config-zero.toml" + transferChannel = "channel-0" ) var ( - gaiaConfigPath = filepath.Join(gaiaHomePath, "config") - stakingAmount = sdk.NewInt(100000000000) - stakingAmountCoin = sdk.NewCoin(uatomDenom, stakingAmount) - tokenAmount = sdk.NewCoin(uatomDenom, sdk.NewInt(3300000000)) // 3,300uatom - standardFees = sdk.NewCoin(uatomDenom, sdk.NewInt(330000)) // 0.33uatom - depositAmount = sdk.NewCoin(uatomDenom, sdk.NewInt(330000000)) // 3,300uatom - distModuleAddress = authtypes.NewModuleAddress(distrtypes.ModuleName).String() - proposalCounter = 0 + gaiaConfigPath = filepath.Join(gaiaHomePath, "config") + stakingAmount = sdk.NewInt(100000000000) + stakingAmountCoin = sdk.NewCoin(uatomDenom, stakingAmount) + tokenAmount = sdk.NewCoin(uatomDenom, sdk.NewInt(3300000000)) // 3,300uatom + standardFees = sdk.NewCoin(uatomDenom, sdk.NewInt(330000)) // 0.33uatom + depositAmount = sdk.NewCoin(uatomDenom, sdk.NewInt(330000000)) // 3,300uatom + distModuleAddress = authtypes.NewModuleAddress(distrtypes.ModuleName).String() + proposalCounter = 0 + HermesResource0Purged = false ) type IntegrationTestSuite struct { suite.Suite - tmpDirs []string - chainA *chain - chainB *chain - dkrPool *dockertest.Pool - dkrNet *dockertest.Network - hermesResource *dockertest.Resource - valResources map[string][]*dockertest.Resource + tmpDirs []string + chainA *chain + chainB *chain + dkrPool *dockertest.Pool + dkrNet *dockertest.Network + hermesResource0 *dockertest.Resource + hermesResource1 *dockertest.Resource + + valResources map[string][]*dockertest.Resource } type AddressResponse struct { @@ -148,7 +160,8 @@ func (s *IntegrationTestSuite) SetupSuite() { s.runValidators(s.chainB, 10) time.Sleep(10 * time.Second) - s.runIBCRelayer() + s.runIBCRelayer0() + s.runIBCRelayer1() } func (s *IntegrationTestSuite) TearDownSuite() { @@ -163,8 +176,12 @@ func (s *IntegrationTestSuite) TearDownSuite() { s.T().Log("tearing down e2e integration test suite...") - if runIBCTest { - s.Require().NoError(s.dkrPool.Purge(s.hermesResource)) + s.Require().NoError(s.dkrPool.Purge(s.hermesResource1)) + // if runIBCTest, s.hermesResource0 already purged in TestIBC() + // in GovSoftwareUpgrade test, s.TearDownSuite() then s.SetupSuite() + // if IBCTest runs before GovSoftwareUpgrade, s.hermesResource0 is already purged. + if !HermesResource0Purged { + s.Require().NoError(s.dkrPool.Purge(s.hermesResource0)) } for _, vr := range s.valResources { @@ -186,12 +203,13 @@ func (s *IntegrationTestSuite) TearDownSuite() { func (s *IntegrationTestSuite) initNodes(c *chain) { s.Require().NoError(c.createAndInitValidators(2)) /* Adding 4 accounts to val0 local directory - c.genesisAccounts[0]: Relayer Wallet + c.genesisAccounts[0]: Relayer0 Wallet c.genesisAccounts[1]: ICA Owner c.genesisAccounts[2]: Test Account 1 c.genesisAccounts[3]: Test Account 2 + c.genesisAccounts[4]: Relayer1 Wallet */ - s.Require().NoError(c.addAccountFromMnemonic(4)) + s.Require().NoError(c.addAccountFromMnemonic(5)) // Initialize a genesis file for the first validator val0ConfigDir := c.validators[0].configDir() var addrAll []sdk.AccAddress @@ -583,6 +601,167 @@ func noRestart(config *docker.HostConfig) { } } +// hermes0 is for ibc and packet-forward-middleware(PFM) test, hermes0 is keep running during the ibc and PFM test. +func (s *IntegrationTestSuite) runIBCRelayer0() { + s.T().Log("starting Hermes relayer container 0...") + + tmpDir, err := os.MkdirTemp("", "gaia-e2e-testnet-hermes-") + s.Require().NoError(err) + s.tmpDirs = append(s.tmpDirs, tmpDir) + + gaiaAVal := s.chainA.validators[0] + gaiaBVal := s.chainB.validators[0] + + gaiaARly := s.chainA.genesisAccounts[relayerAccountIndexHermes0] + gaiaBRly := s.chainB.genesisAccounts[relayerAccountIndexHermes0] + + hermesCfgPath := path.Join(tmpDir, "hermes") + + s.Require().NoError(os.MkdirAll(hermesCfgPath, 0o755)) + _, err = copyFile( + filepath.Join("./scripts/", "hermes_bootstrap.sh"), + filepath.Join(hermesCfgPath, "hermes_bootstrap.sh"), + ) + s.Require().NoError(err) + + s.hermesResource0, err = s.dkrPool.RunWithOptions( + &dockertest.RunOptions{ + Name: fmt.Sprintf("%s-%s-relayer-0", s.chainA.id, s.chainB.id), + Repository: "ghcr.io/cosmos/hermes-e2e", + Tag: "1.0.0", + NetworkID: s.dkrNet.Network.ID, + Mounts: []string{ + fmt.Sprintf("%s/:/root/hermes", hermesCfgPath), + }, + PortBindings: map[docker.Port][]docker.PortBinding{ + "3031/tcp": {{HostIP: "", HostPort: "3031"}}, + }, + Env: []string{ + fmt.Sprintf("GAIA_A_E2E_CHAIN_ID=%s", s.chainA.id), + fmt.Sprintf("GAIA_B_E2E_CHAIN_ID=%s", s.chainB.id), + fmt.Sprintf("GAIA_A_E2E_VAL_MNEMONIC=%s", gaiaAVal.mnemonic), + fmt.Sprintf("GAIA_B_E2E_VAL_MNEMONIC=%s", gaiaBVal.mnemonic), + fmt.Sprintf("GAIA_A_E2E_RLY_MNEMONIC=%s", gaiaARly.mnemonic), + fmt.Sprintf("GAIA_B_E2E_RLY_MNEMONIC=%s", gaiaBRly.mnemonic), + fmt.Sprintf("GAIA_A_E2E_VAL_HOST=%s", s.valResources[s.chainA.id][0].Container.Name[1:]), + fmt.Sprintf("GAIA_B_E2E_VAL_HOST=%s", s.valResources[s.chainB.id][0].Container.Name[1:]), + }, + Entrypoint: []string{ + "sh", + "-c", + "chmod +x /root/hermes/hermes_bootstrap.sh && /root/hermes/hermes_bootstrap.sh", + }, + }, + noRestart, + ) + s.Require().NoError(err) + + endpoint := fmt.Sprintf("http://%s/state", s.hermesResource0.GetHostPort("3031/tcp")) + s.Require().Eventually( + func() bool { + resp, err := http.Get(endpoint) //nolint:gosec // this is a test + if err != nil { + return false + } + + defer resp.Body.Close() + + bz, err := io.ReadAll(resp.Body) + if err != nil { + return false + } + + var respBody map[string]interface{} + if err := json.Unmarshal(bz, &respBody); err != nil { + return false + } + + status := respBody["status"].(string) + result := respBody["result"].(map[string]interface{}) + + return status == "success" && len(result["chains"].([]interface{})) == 2 + }, + 5*time.Minute, + time.Second, + "hermes relayer not healthy", + ) + + s.T().Logf("started Hermes relayer 0 container: %s", s.hermesResource0.Container.ID) + + // XXX: Give time to both networks to start, otherwise we might see gRPC + // transport errors. + time.Sleep(10 * time.Second) + + // create the client, connection and channel between the two Gaia chains + s.createConnection() + time.Sleep(10 * time.Second) + s.createChannel() +} + +// hermes1 is for bypass-msg test. Hermes1 is to process asynchronous transactions, +// Hermes1 has access to two Hermes configurations: one configuration allows paying fees, while the other does not. +// With Hermes1, better control can be achieved regarding whether fees are paid when clearing transactions. +func (s *IntegrationTestSuite) runIBCRelayer1() { + s.T().Log("starting Hermes relayer container 1...") + + tmpDir, err := os.MkdirTemp("", "gaia-e2e-testnet-hermes-") + s.Require().NoError(err) + s.tmpDirs = append(s.tmpDirs, tmpDir) + + gaiaAVal := s.chainA.validators[0] + gaiaBVal := s.chainB.validators[0] + + gaiaARly := s.chainA.genesisAccounts[relayerAccountIndexHermes1] + gaiaBRly := s.chainB.genesisAccounts[relayerAccountIndexHermes1] + + hermesCfgPath := path.Join(tmpDir, "hermes") + + s.Require().NoError(os.MkdirAll(hermesCfgPath, 0o755)) + _, err = copyFile( + filepath.Join("./scripts/", "hermes1_bootstrap.sh"), + filepath.Join(hermesCfgPath, "hermes1_bootstrap.sh"), + ) + s.Require().NoError(err) + + s.hermesResource1, err = s.dkrPool.RunWithOptions( + &dockertest.RunOptions{ + Name: fmt.Sprintf("%s-%s-relayer-1", s.chainA.id, s.chainB.id), + Repository: "ghcr.io/cosmos/hermes-e2e", + Tag: "1.0.0", + NetworkID: s.dkrNet.Network.ID, + Mounts: []string{ + fmt.Sprintf("%s/:/root/hermes", hermesCfgPath), + }, + PortBindings: map[docker.Port][]docker.PortBinding{ + "3032/tcp": {{HostIP: "", HostPort: "3032"}}, + }, + Env: []string{ + fmt.Sprintf("GAIA_A_E2E_CHAIN_ID=%s", s.chainA.id), + fmt.Sprintf("GAIA_B_E2E_CHAIN_ID=%s", s.chainB.id), + fmt.Sprintf("GAIA_A_E2E_VAL_MNEMONIC=%s", gaiaAVal.mnemonic), + fmt.Sprintf("GAIA_B_E2E_VAL_MNEMONIC=%s", gaiaBVal.mnemonic), + fmt.Sprintf("GAIA_A_E2E_RLY_MNEMONIC=%s", gaiaARly.mnemonic), + fmt.Sprintf("GAIA_B_E2E_RLY_MNEMONIC=%s", gaiaBRly.mnemonic), + fmt.Sprintf("GAIA_A_E2E_VAL_HOST=%s", s.valResources[s.chainA.id][0].Container.Name[1:]), + fmt.Sprintf("GAIA_B_E2E_VAL_HOST=%s", s.valResources[s.chainB.id][0].Container.Name[1:]), + }, + Entrypoint: []string{ + "sh", + "-c", + "chmod +x /root/hermes/hermes1_bootstrap.sh && /root/hermes/hermes1_bootstrap.sh && tail -f /dev/null", + }, + }, + noRestart, + ) + s.Require().NoError(err) + + s.T().Logf("started Hermes relayer 1 container: %s", s.hermesResource1.Container.ID) + + // XXX: Give time to both networks to start, otherwise we might see gRPC + // transport errors. + time.Sleep(10 * time.Second) +} + func (s *IntegrationTestSuite) writeGovParamChangeProposalGlobalFees(c *chain, coins sdk.DecCoins) { type ParamInfo struct { Subspace string `json:"subspace"` diff --git a/tests/e2e/e2e_test.go b/tests/e2e/e2e_test.go index 58cba8a906d..c2edd641fc5 100644 --- a/tests/e2e/e2e_test.go +++ b/tests/e2e/e2e_test.go @@ -88,6 +88,11 @@ func (s *IntegrationTestSuite) TestIBC() { s.testIBCTokenTransfer() s.testMultihopIBCTokenTransfer() s.testFailedMultihopIBCTokenTransfer() + + // stop hermes0 to prevent hermes0 relaying transactions + s.Require().NoError(s.dkrPool.Purge(s.hermesResource0)) + HermesResource0Purged = true + s.testIBCBypassMsg() } func (s *IntegrationTestSuite) TestSlashing() { diff --git a/tests/e2e/scripts/hermes1_bootstrap.sh b/tests/e2e/scripts/hermes1_bootstrap.sh new file mode 100755 index 00000000000..049c61b8ad4 --- /dev/null +++ b/tests/e2e/scripts/hermes1_bootstrap.sh @@ -0,0 +1,152 @@ +#!/bin/bash + +set -ex + +# initialize Hermes relayer configuration +mkdir -p /root/.hermes/ +touch /root/.hermes/config.toml + +echo $GAIA_B_E2E_RLY_MNEMONIC > /root/.hermes/GAIA_B_E2E_RLY_MNEMONIC.txt +echo $GAIA_A_E2E_RLY_MNEMONIC > /root/.hermes/GAIA_A_E2E_RLY_MNEMONIC.txt + +# setup Hermes relayer configuration with non-zero gas_price +tee /root/.hermes/config.toml <