Skip to content

Commit

Permalink
test: add e2e test for ibc bypass msg (#2532)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>

* 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 <[email protected]>

* 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 <[email protected]>
Co-authored-by: Philip Offtermatt <[email protected]>
  • Loading branch information
3 people authored Jun 6, 2023
1 parent 4d96f85 commit 908c09a
Show file tree
Hide file tree
Showing 6 changed files with 564 additions and 126 deletions.
87 changes: 87 additions & 0 deletions tests/e2e/e2e_bypassminfee_test.go
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down Expand Up @@ -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)
}
29 changes: 29 additions & 0 deletions tests/e2e/e2e_exec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
196 changes: 91 additions & 105 deletions tests/e2e/e2e_ibc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand All @@ -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()
Expand Down Expand Up @@ -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)

Expand All @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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 (
Expand Down
Loading

0 comments on commit 908c09a

Please sign in to comment.