From 7d2925a80fba234c09e690b30e4c1f8f5a90a185 Mon Sep 17 00:00:00 2001 From: Vladyslav Budichenko Date: Fri, 8 Nov 2024 12:20:52 -0500 Subject: [PATCH] wip: removed one debug log --- app/app.go | 1 + go.mod | 4 +- go.sum | 8 +- modules checked.md | 2 +- tests/e2e-ibc/chainconfig.go | 1 + tests/e2e-ibc/dockerutil/busybox.go | 56 +++ .../e2e-ibc/dockerutil/container_lifecycle.go | 181 +++++++++ tests/e2e-ibc/dockerutil/doc.go | 2 + tests/e2e-ibc/dockerutil/file.go | 32 ++ tests/e2e-ibc/dockerutil/fileretriever.go | 100 +++++ .../e2e-ibc/dockerutil/fileretriever_test.go | 69 ++++ tests/e2e-ibc/dockerutil/filewriter.go | 142 +++++++ tests/e2e-ibc/dockerutil/filewriter_test.go | 68 ++++ tests/e2e-ibc/dockerutil/image.go | 378 ++++++++++++++++++ tests/e2e-ibc/dockerutil/image_test.go | 191 +++++++++ tests/e2e-ibc/dockerutil/keyring.go | 63 +++ tests/e2e-ibc/dockerutil/ports.go | 91 +++++ tests/e2e-ibc/dockerutil/setup.go | 251 ++++++++++++ tests/e2e-ibc/dockerutil/setup_test.go | 80 ++++ tests/e2e-ibc/dockerutil/startcontainer.go | 26 ++ tests/e2e-ibc/dockerutil/strings.go | 96 +++++ tests/e2e-ibc/dockerutil/strings_test.go | 88 ++++ tests/e2e-ibc/dockerutil/volumeowner.go | 109 +++++ tests/e2e-ibc/erc20_test.go | 34 +- tests/e2e-ibc/go.mod | 8 +- tests/e2e/e2e_community_update_params_test.go | 22 +- tests/e2e/e2e_convert_cosmos_coins_test.go | 7 +- x/community/types/tx.pb.go | 4 - 28 files changed, 2075 insertions(+), 39 deletions(-) create mode 100644 tests/e2e-ibc/dockerutil/busybox.go create mode 100644 tests/e2e-ibc/dockerutil/container_lifecycle.go create mode 100644 tests/e2e-ibc/dockerutil/doc.go create mode 100644 tests/e2e-ibc/dockerutil/file.go create mode 100644 tests/e2e-ibc/dockerutil/fileretriever.go create mode 100644 tests/e2e-ibc/dockerutil/fileretriever_test.go create mode 100644 tests/e2e-ibc/dockerutil/filewriter.go create mode 100644 tests/e2e-ibc/dockerutil/filewriter_test.go create mode 100644 tests/e2e-ibc/dockerutil/image.go create mode 100644 tests/e2e-ibc/dockerutil/image_test.go create mode 100644 tests/e2e-ibc/dockerutil/keyring.go create mode 100644 tests/e2e-ibc/dockerutil/ports.go create mode 100644 tests/e2e-ibc/dockerutil/setup.go create mode 100644 tests/e2e-ibc/dockerutil/setup_test.go create mode 100644 tests/e2e-ibc/dockerutil/startcontainer.go create mode 100644 tests/e2e-ibc/dockerutil/strings.go create mode 100644 tests/e2e-ibc/dockerutil/strings_test.go create mode 100644 tests/e2e-ibc/dockerutil/volumeowner.go diff --git a/app/app.go b/app/app.go index de3f0e20b..fff65dc0f 100644 --- a/app/app.go +++ b/app/app.go @@ -931,6 +931,7 @@ func NewApp( paramsclient.ProposalHandler, }, ), + ibctm.ModuleName: ibctm.NewAppModule(), }) app.BasicModuleManager.RegisterLegacyAminoCodec(legacyAmino) app.BasicModuleManager.RegisterInterfaces(interfaceRegistry) diff --git a/go.mod b/go.mod index 47822da61..6c4fce193 100644 --- a/go.mod +++ b/go.mod @@ -261,7 +261,7 @@ replace ( // Use cosmos-sdk fork with backported fix for unsafe-reset-all, staking transfer events, and custom tally handler support //github.com/cosmos/cosmos-sdk => github.com/kava-labs/cosmos-sdk v0.47.10-iavl-v1-kava.1 //github.com/cosmos/cosmos-sdk => github.com/kava-labs/cosmos-sdk v0.50.10-test-patch 5f9239e3147358ef034bfc4d19aacb34e5ea2064 - github.com/cosmos/cosmos-sdk => github.com/kava-labs/cosmos-sdk v0.0.0-20241107161140-9121bca395e2 + github.com/cosmos/cosmos-sdk => github.com/kava-labs/cosmos-sdk v0.0.0-20241107170058-27e0ccccc208 //github.com/cosmos/cosmos-sdk => ../cosmos-sdk //github.com/cosmos/cosmos-sdk/store => cosmossdk.io/store v1.1.1 @@ -281,7 +281,7 @@ replace ( // Tracking kava-labs/etheremint master branch // TODO: Tag before release //github.com/evmos/ethermint => github.com/kava-labs/ethermint v0.21.1-0.20240802224012-586960857184 - github.com/evmos/ethermint => github.com/kava-labs/ethermint v0.0.0-20241107030905-d910f315f8c7 + github.com/evmos/ethermint => github.com/kava-labs/ethermint v0.0.0-20241107232649-d8ee91e8450e //github.com/evmos/ethermint => ../ethermint // See https://github.com/cosmos/cosmos-sdk/pull/10401, https://github.com/cosmos/cosmos-sdk/commit/0592ba6158cd0bf49d894be1cef4faeec59e8320 github.com/gin-gonic/gin => github.com/gin-gonic/gin v1.9.0 diff --git a/go.sum b/go.sum index d2fe77805..8e649929b 100644 --- a/go.sum +++ b/go.sum @@ -832,10 +832,10 @@ github.com/kava-labs/cometbft v0.0.0-20241024200036-527d8df9ff12 h1:RqnpnEGEuykj github.com/kava-labs/cometbft v0.0.0-20241024200036-527d8df9ff12/go.mod h1:5awmm7t/X8LJ+Wd7/TXHcv4hLfFLcexy6fdS9WvOepA= github.com/kava-labs/cometbft-db v0.0.0-20241007145430-b2740b2e4bed h1:3FNJ3fcD9aA9oOwDphrSEJ8u2kBNj9YoYAwl16UKyv4= github.com/kava-labs/cometbft-db v0.0.0-20241007145430-b2740b2e4bed/go.mod h1:buPRZKyVp+u5fmwN7tDtOk1zc5xA2z9BJJTy61tNnws= -github.com/kava-labs/cosmos-sdk v0.0.0-20241107161140-9121bca395e2 h1:YUetmupzuNF+w7yrQYG6xzkA87wzamiq3KjzORohj8Y= -github.com/kava-labs/cosmos-sdk v0.0.0-20241107161140-9121bca395e2/go.mod h1:Yf8jj8R5+9NWCbdot0IMId92WeAIF22Thc/pRR2hSoI= -github.com/kava-labs/ethermint v0.0.0-20241107030905-d910f315f8c7 h1:OfO2y640jBDjXC5K399dZsSkryx3B61s4BDpcCggjDY= -github.com/kava-labs/ethermint v0.0.0-20241107030905-d910f315f8c7/go.mod h1:dFh15Ndobz1zWqWcJm9ML+0BnMt5m8gS+MhVUcAGLpg= +github.com/kava-labs/cosmos-sdk v0.0.0-20241107170058-27e0ccccc208 h1:0Z/vypO858xcywSSzxuDsuFW+CuuxeE+8XR1jyMM7qQ= +github.com/kava-labs/cosmos-sdk v0.0.0-20241107170058-27e0ccccc208/go.mod h1:Yf8jj8R5+9NWCbdot0IMId92WeAIF22Thc/pRR2hSoI= +github.com/kava-labs/ethermint v0.0.0-20241107232649-d8ee91e8450e h1:79mJSoWItAG2gPLOzVfdRJnMWy3vwAPdnahjh3jmUno= +github.com/kava-labs/ethermint v0.0.0-20241107232649-d8ee91e8450e/go.mod h1:Vulovod3ZhLTCfs9RkILIDK8WSDyYhnEp665kTRv530= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= diff --git a/modules checked.md b/modules checked.md index 550b98056..a5d2c48ab 100644 --- a/modules checked.md +++ b/modules checked.md @@ -35,7 +35,7 @@ E2E tests: | File | Status | Notes |----------------------------------------------------------------------------------------|-----|-------------------------------------------------------------------------------------------------| -| e2e_community_update_params_test.go | | TestCommunityUpdateParams_Authority | +| e2e_community_update_params_test.go | ✅ | | | e2e_convert_cosmos_coins_test.go | | TestConvertCosmosCoins_ForbiddenERC20Calls, TestConvertCosmosCoins_ERC20Magic | | e2e_evm_contracts_test.go | ✅ | | | e2e_grpc_client_query_test.go | ✅ | | diff --git a/tests/e2e-ibc/chainconfig.go b/tests/e2e-ibc/chainconfig.go index d52f0a375..27e96da25 100644 --- a/tests/e2e-ibc/chainconfig.go +++ b/tests/e2e-ibc/chainconfig.go @@ -17,6 +17,7 @@ func DefaultKavaChainConfig(chainId string) ibc.ChainConfig { kavaImageTag := os.Getenv("KAVA_TAG") if kavaImageTag == "" { kavaImageTag = "v0.26.0-rocksdb" + //kavaImageTag = "local" } // app.toml overrides diff --git a/tests/e2e-ibc/dockerutil/busybox.go b/tests/e2e-ibc/dockerutil/busybox.go new file mode 100644 index 000000000..2b68b27ed --- /dev/null +++ b/tests/e2e-ibc/dockerutil/busybox.go @@ -0,0 +1,56 @@ +package dockerutil + +import ( + "context" + "fmt" + "io" + "sync" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/client" +) + +// Allow multiple goroutines to check for busybox +// by using a protected package-level variable. +// +// A mutex allows for retries upon error, if we ever need that; +// whereas a sync.Once would not be simple to retry. +var ( + ensureBusyboxMu sync.Mutex + hasBusybox bool +) + +const busyboxRef = "busybox:stable" + +func ensureBusybox(ctx context.Context, cli *client.Client) error { + ensureBusyboxMu.Lock() + defer ensureBusyboxMu.Unlock() + + if hasBusybox { + return nil + } + + images, err := cli.ImageList(ctx, types.ImageListOptions{ + Filters: filters.NewArgs(filters.Arg("reference", busyboxRef)), + }) + if err != nil { + return fmt.Errorf("listing images to check busybox presence: %w", err) + } + + if len(images) > 0 { + hasBusybox = true + return nil + } + + rc, err := cli.ImagePull(ctx, busyboxRef, types.ImagePullOptions{}) + if err != nil { + return err + } + + _, _ = io.Copy(io.Discard, rc) + _ = rc.Close() + + hasBusybox = true + return nil +} diff --git a/tests/e2e-ibc/dockerutil/container_lifecycle.go b/tests/e2e-ibc/dockerutil/container_lifecycle.go new file mode 100644 index 000000000..3f138b759 --- /dev/null +++ b/tests/e2e-ibc/dockerutil/container_lifecycle.go @@ -0,0 +1,181 @@ +package dockerutil + +import ( + "context" + "fmt" + "net" + "strings" + + dockertypes "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/mount" + "github.com/docker/docker/api/types/network" + dockerclient "github.com/docker/docker/client" + "github.com/docker/docker/errdefs" + "github.com/docker/go-connections/nat" + "go.uber.org/zap" + + "github.com/strangelove-ventures/interchaintest/v8/ibc" +) + +type ContainerLifecycle struct { + log *zap.Logger + client *dockerclient.Client + containerName string + id string + preStartListeners Listeners +} + +func NewContainerLifecycle(log *zap.Logger, client *dockerclient.Client, containerName string) *ContainerLifecycle { + return &ContainerLifecycle{ + log: log, + client: client, + containerName: containerName, + } +} + +func (c *ContainerLifecycle) CreateContainer( + ctx context.Context, + testName string, + networkID string, + image ibc.DockerImage, + ports nat.PortMap, + volumeBinds []string, + mounts []mount.Mount, + hostName string, + cmd []string, + env []string, +) error { + imageRef := image.Ref() + c.log.Info( + "Will run command", + zap.String("image", imageRef), + zap.String("container", c.containerName), + zap.String("command", strings.Join(cmd, " ")), + ) + + pS := nat.PortSet{} + for k := range ports { + pS[k] = struct{}{} + } + + pb, listeners, err := GeneratePortBindings(ports) + if err != nil { + return fmt.Errorf("failed to generate port bindings: %w", err) + } + + c.preStartListeners = listeners + + cc, err := c.client.ContainerCreate( + ctx, + &container.Config{ + Image: imageRef, + + Entrypoint: []string{}, + Cmd: cmd, + Env: env, + + Hostname: hostName, + + Labels: map[string]string{CleanupLabel: testName}, + + ExposedPorts: pS, + }, + &container.HostConfig{ + Binds: volumeBinds, + PortBindings: pb, + PublishAllPorts: true, + AutoRemove: false, + DNS: []string{}, + Mounts: mounts, + }, + &network.NetworkingConfig{ + EndpointsConfig: map[string]*network.EndpointSettings{ + networkID: {}, + }, + }, + nil, + c.containerName, + ) + if err != nil { + listeners.CloseAll() + c.preStartListeners = []net.Listener{} + return err + } + c.id = cc.ID + return nil +} + +func (c *ContainerLifecycle) StartContainer(ctx context.Context) error { + // lock port allocation for the time between freeing the ports from the + // temporary listeners to the consumption of the ports by the container + mu.RLock() + defer mu.RUnlock() + + c.preStartListeners.CloseAll() + c.preStartListeners = []net.Listener{} + + if err := StartContainer(ctx, c.client, c.id); err != nil { + return err + } + + c.log.Info("Container started", zap.String("container", c.containerName)) + + return nil +} + +func (c *ContainerLifecycle) PauseContainer(ctx context.Context) error { + return c.client.ContainerPause(ctx, c.id) +} + +func (c *ContainerLifecycle) UnpauseContainer(ctx context.Context) error { + return c.client.ContainerUnpause(ctx, c.id) +} + +func (c *ContainerLifecycle) StopContainer(ctx context.Context) error { + var timeout container.StopOptions + timeoutSec := 30 + timeout.Timeout = &timeoutSec + + return c.client.ContainerStop(ctx, c.id, timeout) +} + +func (c *ContainerLifecycle) RemoveContainer(ctx context.Context) error { + err := c.client.ContainerRemove(ctx, c.id, dockertypes.ContainerRemoveOptions{ + Force: true, + RemoveVolumes: true, + }) + if err != nil && !errdefs.IsNotFound(err) { + return fmt.Errorf("remove container %s: %w", c.containerName, err) + } + return nil +} + +func (c *ContainerLifecycle) ContainerID() string { + return c.id +} + +func (c *ContainerLifecycle) GetHostPorts(ctx context.Context, portIDs ...string) ([]string, error) { + cjson, err := c.client.ContainerInspect(ctx, c.id) + if err != nil { + return nil, err + } + ports := make([]string, len(portIDs)) + for i, p := range portIDs { + ports[i] = GetHostPort(cjson, p) + } + return ports, nil +} + +// Running will inspect the container and check its state to determine if it is currently running. +// If the container is running nil will be returned, otherwise an error is returned. +func (c *ContainerLifecycle) Running(ctx context.Context) error { + cjson, err := c.client.ContainerInspect(ctx, c.id) + if err != nil { + return err + } + if cjson.State.Running { + return nil + } + return fmt.Errorf("container with name %s and id %s is not running", c.containerName, c.id) +} diff --git a/tests/e2e-ibc/dockerutil/doc.go b/tests/e2e-ibc/dockerutil/doc.go new file mode 100644 index 000000000..7a9a80a2f --- /dev/null +++ b/tests/e2e-ibc/dockerutil/doc.go @@ -0,0 +1,2 @@ +// Package dockerutil contains helpers for interacting with Docker containers. +package dockerutil diff --git a/tests/e2e-ibc/dockerutil/file.go b/tests/e2e-ibc/dockerutil/file.go new file mode 100644 index 000000000..0780030b7 --- /dev/null +++ b/tests/e2e-ibc/dockerutil/file.go @@ -0,0 +1,32 @@ +package dockerutil + +import ( + "fmt" + "io" + "os" +) + +func CopyFile(src, dst string) (int64, error) { + sourceFileStat, err := os.Stat(src) + if err != nil { + return 0, err + } + + if !sourceFileStat.Mode().IsRegular() { + return 0, fmt.Errorf("%s is not a regular file", src) + } + + source, err := os.Open(src) + if err != nil { + return 0, err + } + defer source.Close() + + destination, err := os.Create(dst) + if err != nil { + return 0, err + } + defer destination.Close() + nBytes, err := io.Copy(destination, source) + return nBytes, err +} diff --git a/tests/e2e-ibc/dockerutil/fileretriever.go b/tests/e2e-ibc/dockerutil/fileretriever.go new file mode 100644 index 000000000..3e4725fb8 --- /dev/null +++ b/tests/e2e-ibc/dockerutil/fileretriever.go @@ -0,0 +1,100 @@ +package dockerutil + +import ( + "archive/tar" + "context" + "fmt" + "io" + "path" + "time" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/client" + "go.uber.org/zap" +) + +// FileRetriever allows retrieving a single file from a Docker volume. +// In the future it may allow retrieving an entire directory. +type FileRetriever struct { + log *zap.Logger + + cli *client.Client + + testName string +} + +// NewFileRetriever returns a new FileRetriever. +func NewFileRetriever(log *zap.Logger, cli *client.Client, testName string) *FileRetriever { + return &FileRetriever{log: log, cli: cli, testName: testName} +} + +// SingleFileContent returns the content of the file named at relPath, +// inside the volume specified by volumeName. +func (r *FileRetriever) SingleFileContent(ctx context.Context, volumeName, relPath string) ([]byte, error) { + const mountPath = "/mnt/dockervolume" + + if err := ensureBusybox(ctx, r.cli); err != nil { + return nil, err + } + + containerName := fmt.Sprintf("interchaintest-getfile-%d-%s", time.Now().UnixNano(), RandLowerCaseLetterString(5)) + + cc, err := r.cli.ContainerCreate( + ctx, + &container.Config{ + Image: busyboxRef, + + // Use root user to avoid permission issues when reading files from the volume. + User: GetRootUserString(), + + Labels: map[string]string{CleanupLabel: r.testName}, + }, + &container.HostConfig{ + Binds: []string{volumeName + ":" + mountPath}, + AutoRemove: true, + }, + nil, // No networking necessary. + nil, + containerName, + ) + if err != nil { + return nil, fmt.Errorf("creating container: %w", err) + } + + defer func() { + if err := r.cli.ContainerRemove(ctx, cc.ID, types.ContainerRemoveOptions{ + Force: true, + }); err != nil { + r.log.Warn("Failed to remove file content container", zap.String("container_id", cc.ID), zap.Error(err)) + } + }() + + rc, _, err := r.cli.CopyFromContainer(ctx, cc.ID, path.Join(mountPath, relPath)) + if err != nil { + return nil, fmt.Errorf("copying from container: %w", err) + } + defer func() { + _ = rc.Close() + }() + + wantPath := path.Base(relPath) + tr := tar.NewReader(rc) + for { + hdr, err := tr.Next() + if err == io.EOF { + break + } + if err != nil { + return nil, fmt.Errorf("reading tar from container: %w", err) + } + if hdr.Name != wantPath { + r.log.Debug("Unexpected path", zap.String("want", relPath), zap.String("got", hdr.Name)) + continue + } + + return io.ReadAll(tr) + } + + return nil, fmt.Errorf("path %q not found in tar from container", relPath) +} diff --git a/tests/e2e-ibc/dockerutil/fileretriever_test.go b/tests/e2e-ibc/dockerutil/fileretriever_test.go new file mode 100644 index 000000000..e9157faf8 --- /dev/null +++ b/tests/e2e-ibc/dockerutil/fileretriever_test.go @@ -0,0 +1,69 @@ +package dockerutil_test + +import ( + "context" + "testing" + + volumetypes "github.com/docker/docker/api/types/volume" + interchaintest "github.com/strangelove-ventures/interchaintest/v8" + "github.com/strangelove-ventures/interchaintest/v8/internal/dockerutil" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zaptest" +) + +func TestFileRetriever(t *testing.T) { + if testing.Short() { + t.Skip("skipping due to short mode") + } + + t.Parallel() + + cli, network := interchaintest.DockerSetup(t) + + ctx := context.Background() + v, err := cli.VolumeCreate(ctx, volumetypes.CreateOptions{ + Labels: map[string]string{dockerutil.CleanupLabel: t.Name()}, + }) + require.NoError(t, err) + + img := dockerutil.NewImage( + zaptest.NewLogger(t), + cli, + network, + t.Name(), + "busybox", "stable", + ) + + res := img.Run( + ctx, + []string{"sh", "-c", "chmod 0700 /mnt/test && printf 'hello world' > /mnt/test/hello.txt"}, + dockerutil.ContainerOptions{ + Binds: []string{v.Name + ":/mnt/test"}, + User: dockerutil.GetRootUserString(), + }, + ) + require.NoError(t, res.Err) + res = img.Run( + ctx, + []string{"sh", "-c", "mkdir -p /mnt/test/foo/bar/ && printf 'test' > /mnt/test/foo/bar/baz.txt"}, + dockerutil.ContainerOptions{ + Binds: []string{v.Name + ":/mnt/test"}, + User: dockerutil.GetRootUserString(), + }, + ) + require.NoError(t, res.Err) + + fr := dockerutil.NewFileRetriever(zaptest.NewLogger(t), cli, t.Name()) + + t.Run("top-level file", func(t *testing.T) { + b, err := fr.SingleFileContent(ctx, v.Name, "hello.txt") + require.NoError(t, err) + require.Equal(t, string(b), "hello world") + }) + + t.Run("nested file", func(t *testing.T) { + b, err := fr.SingleFileContent(ctx, v.Name, "foo/bar/baz.txt") + require.NoError(t, err) + require.Equal(t, string(b), "test") + }) +} diff --git a/tests/e2e-ibc/dockerutil/filewriter.go b/tests/e2e-ibc/dockerutil/filewriter.go new file mode 100644 index 000000000..7b66ca2f2 --- /dev/null +++ b/tests/e2e-ibc/dockerutil/filewriter.go @@ -0,0 +1,142 @@ +package dockerutil + +import ( + "archive/tar" + "bytes" + "context" + "fmt" + "time" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/client" + "go.uber.org/zap" +) + +// FileWriter allows retrieving a single file from a Docker volume. +// In the future it may allow retrieving an entire directory. +type FileWriter struct { + log *zap.Logger + + cli *client.Client + + testName string +} + +// NewFileWriter returns a new FileWriter. +func NewFileWriter(log *zap.Logger, cli *client.Client, testName string) *FileWriter { + return &FileWriter{log: log, cli: cli, testName: testName} +} + +// WriteFile writes the single file containing content, at relPath within the given volume. +func (w *FileWriter) WriteFile(ctx context.Context, volumeName, relPath string, content []byte) error { + const mountPath = "/mnt/dockervolume" + + if err := ensureBusybox(ctx, w.cli); err != nil { + return err + } + + containerName := fmt.Sprintf("interchaintest-writefile-%d-%s", time.Now().UnixNano(), RandLowerCaseLetterString(5)) + + cc, err := w.cli.ContainerCreate( + ctx, + &container.Config{ + Image: busyboxRef, + + Entrypoint: []string{"sh", "-c"}, + Cmd: []string{ + // Take the uid and gid of the mount path, + // and set that as the owner of the new relative path. + `chown -R "$(stat -c '%u:%g' "$1")" "$2"`, + "_", // Meaningless arg0 for sh -c with positional args. + mountPath, + mountPath, + }, + + // Use root user to avoid permission issues when reading files from the volume. + User: GetRootUserString(), + + Labels: map[string]string{CleanupLabel: w.testName}, + }, + &container.HostConfig{ + Binds: []string{volumeName + ":" + mountPath}, + AutoRemove: true, + }, + nil, // No networking necessary. + nil, + containerName, + ) + if err != nil { + return fmt.Errorf("creating container: %w", err) + } + + autoRemoved := false + defer func() { + if autoRemoved { + // No need to attempt removing the container if we successfully started and waited for it to complete. + return + } + + if err := w.cli.ContainerRemove(ctx, cc.ID, types.ContainerRemoveOptions{ + Force: true, + }); err != nil { + w.log.Warn("Failed to remove file content container", zap.String("container_id", cc.ID), zap.Error(err)) + } + }() + + var buf bytes.Buffer + tw := tar.NewWriter(&buf) + if err := tw.WriteHeader(&tar.Header{ + Name: relPath, + + Size: int64(len(content)), + Mode: 0600, + // Not setting uname because the container will chown it anyway. + + ModTime: time.Now(), + + Format: tar.FormatPAX, + }); err != nil { + return fmt.Errorf("writing tar header: %w", err) + } + if _, err := tw.Write(content); err != nil { + return fmt.Errorf("writing content to tar: %w", err) + } + if err := tw.Close(); err != nil { + return fmt.Errorf("closing tar writer: %w", err) + } + + if err := w.cli.CopyToContainer( + ctx, + cc.ID, + mountPath, + &buf, + types.CopyToContainerOptions{}, + ); err != nil { + return fmt.Errorf("copying tar to container: %w", err) + } + + if err := w.cli.ContainerStart(ctx, cc.ID, types.ContainerStartOptions{}); err != nil { + return fmt.Errorf("starting write-file container: %w", err) + } + + waitCh, errCh := w.cli.ContainerWait(ctx, cc.ID, container.WaitConditionNotRunning) + select { + case <-ctx.Done(): + return ctx.Err() + case err := <-errCh: + return err + case res := <-waitCh: + autoRemoved = true + + if res.Error != nil { + return fmt.Errorf("waiting for write-file container: %s", res.Error.Message) + } + + if res.StatusCode != 0 { + return fmt.Errorf("chown on new file exited %d", res.StatusCode) + } + } + + return nil +} diff --git a/tests/e2e-ibc/dockerutil/filewriter_test.go b/tests/e2e-ibc/dockerutil/filewriter_test.go new file mode 100644 index 000000000..bf5001540 --- /dev/null +++ b/tests/e2e-ibc/dockerutil/filewriter_test.go @@ -0,0 +1,68 @@ +package dockerutil_test + +import ( + "context" + "testing" + + volumetypes "github.com/docker/docker/api/types/volume" + interchaintest "github.com/strangelove-ventures/interchaintest/v8" + "github.com/strangelove-ventures/interchaintest/v8/internal/dockerutil" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zaptest" +) + +func TestFileWriter(t *testing.T) { + if testing.Short() { + t.Skip("skipping due to short mode") + } + + t.Parallel() + + cli, network := interchaintest.DockerSetup(t) + + ctx := context.Background() + v, err := cli.VolumeCreate(ctx, volumetypes.CreateOptions{ + Labels: map[string]string{dockerutil.CleanupLabel: t.Name()}, + }) + require.NoError(t, err) + + img := dockerutil.NewImage( + zaptest.NewLogger(t), + cli, + network, + t.Name(), + "busybox", "stable", + ) + + fw := dockerutil.NewFileWriter(zaptest.NewLogger(t), cli, t.Name()) + + t.Run("top-level file", func(t *testing.T) { + require.NoError(t, fw.WriteFile(context.Background(), v.Name, "hello.txt", []byte("hello world"))) + res := img.Run( + ctx, + []string{"sh", "-c", "cat /mnt/test/hello.txt"}, + dockerutil.ContainerOptions{ + Binds: []string{v.Name + ":/mnt/test"}, + User: dockerutil.GetRootUserString(), + }, + ) + require.NoError(t, res.Err) + + require.Equal(t, string(res.Stdout), "hello world") + }) + + t.Run("create nested file", func(t *testing.T) { + require.NoError(t, fw.WriteFile(context.Background(), v.Name, "a/b/c/d.txt", []byte(":D"))) + res := img.Run( + ctx, + []string{"sh", "-c", "cat /mnt/test/a/b/c/d.txt"}, + dockerutil.ContainerOptions{ + Binds: []string{v.Name + ":/mnt/test"}, + User: dockerutil.GetRootUserString(), + }, + ) + require.NoError(t, err) + + require.Equal(t, string(res.Stdout), ":D") + }) +} diff --git a/tests/e2e-ibc/dockerutil/image.go b/tests/e2e-ibc/dockerutil/image.go new file mode 100644 index 000000000..32245acd9 --- /dev/null +++ b/tests/e2e-ibc/dockerutil/image.go @@ -0,0 +1,378 @@ +package dockerutil + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "strconv" + "strings" + "time" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/mount" + "github.com/docker/docker/api/types/network" + "github.com/docker/docker/client" + "github.com/docker/docker/errdefs" + "github.com/docker/docker/pkg/stdcopy" + "go.uber.org/zap" +) + +// Image is a docker image. +type Image struct { + log *zap.Logger + client *client.Client + + // NOTE: it might make sense for Image to have an ibc.DockerImage field, + // but for now it is probably better to not have internal/dockerutil depend on ibc. + repository, tag string + + networkID string + testName string +} + +// NewImage returns a valid Image. +// +// "pool" and "networkID" are likely from DockerSetup. +// "testName" is from a (*testing.T).Name() and should match the t.Name() from DockerSetup to ensure proper cleanup. +// +// Most arguments (except tag) must be non-zero values or this function panics. +// If tag is absent, defaults to "latest". +// Currently, only public docker images are supported. +func NewImage(logger *zap.Logger, cli *client.Client, networkID string, testName string, repository, tag string) *Image { + if logger == nil { + panic(errors.New("nil logger")) + } + if cli == nil { + panic(errors.New("client cannot be nil")) + } + if networkID == "" { + panic(errors.New("networkID cannot be empty")) + } + if testName == "" { + panic("testName cannot be empty") + } + if repository == "" { + panic(errors.New("repository cannot be empty")) + } + if tag == "" { + tag = "latest" + } + + i := &Image{ + client: cli, + networkID: networkID, + repository: repository, + tag: tag, + testName: testName, + } + // Assign log after creating, so the imageRef method can be used. + i.log = logger.With( + zap.String("image", i.imageRef()), + zap.String("test_name", testName), + ) + return i +} + +// ContainerOptions optionally configures starting a Container. +type ContainerOptions struct { + // bind mounts: https://docs.docker.com/storage/bind-mounts/ + Binds []string + + // Environment variables + Env []string + + // If blank, defaults to the container's default user. + User string + + // If non-zero, will limit the amount of log lines returned. + LogTail uint64 + + // mounts directories + Mounts []mount.Mount + + // working directory to launch cmd from + WorkingDir string +} + +// ContainerExecResult is a wrapper type that wraps an exit code and associated output from stderr & stdout, along with +// an error in the case of some error occurring during container execution. +type ContainerExecResult struct { + Err error // Err is nil, unless some error occurs during the container lifecycle. + ExitCode int + Stdout, Stderr []byte +} + +// Run creates and runs a container invoking "cmd". The container resources are removed after exit. +// +// Run blocks until the command completes. Thus, Run is not suitable for daemons or servers. Use Start instead. +// A non-zero status code returns an error. +func (image *Image) Run(ctx context.Context, cmd []string, opts ContainerOptions) ContainerExecResult { + c, err := image.Start(ctx, cmd, opts) + if err != nil { + return ContainerExecResult{ + Err: err, + ExitCode: -1, + Stdout: nil, + Stderr: nil, + } + } + return c.Wait(ctx, opts.LogTail) +} + +func (image *Image) imageRef() string { + return image.repository + ":" + image.tag +} + +// ensurePulled can only pull public images. +func (image *Image) ensurePulled(ctx context.Context) error { + ref := image.imageRef() + _, _, err := image.client.ImageInspectWithRaw(ctx, ref) + if err != nil { + rc, err := image.client.ImagePull(ctx, ref, types.ImagePullOptions{}) + if err != nil { + return fmt.Errorf("pull image %s: %w", ref, err) + } + _, _ = io.Copy(io.Discard, rc) + _ = rc.Close() + } + return nil +} + +func (image *Image) createContainer(ctx context.Context, containerName, hostName string, cmd []string, opts ContainerOptions) (string, error) { + // Although this shouldn't happen because the name includes randomness, in reality there seems to intermittent + // chances of collisions. + + containers, err := image.client.ContainerList(ctx, types.ContainerListOptions{ + All: true, + Filters: filters.NewArgs(filters.Arg("name", containerName)), + }) + if err != nil { + return "", fmt.Errorf("unable to list containers: %w", err) + } + + for _, c := range containers { + if err := image.client.ContainerRemove(ctx, c.ID, types.ContainerRemoveOptions{ + RemoveVolumes: true, + Force: true, + }); err != nil { + return "", fmt.Errorf("unable to remove container %s: %w", containerName, err) + } + } + + cc, err := image.client.ContainerCreate( + ctx, + &container.Config{ + Image: image.imageRef(), + + Entrypoint: []string{}, + WorkingDir: opts.WorkingDir, + Cmd: cmd, + + Env: opts.Env, + + Hostname: hostName, + User: opts.User, + + Labels: map[string]string{CleanupLabel: image.testName}, + }, + &container.HostConfig{ + Binds: opts.Binds, + PublishAllPorts: true, // Because we publish all ports, no need to expose specific ports. + AutoRemove: false, + Mounts: opts.Mounts, + }, + &network.NetworkingConfig{ + EndpointsConfig: map[string]*network.EndpointSettings{ + image.networkID: {}, + }, + }, + nil, + containerName, + ) + if err != nil { + return "", err + } + return cc.ID, nil +} + +// Start pulls the image if not present, creates a container, and runs it. +func (image *Image) Start(ctx context.Context, cmd []string, opts ContainerOptions) (*Container, error) { + if len(cmd) == 0 { + panic(errors.New("cmd cannot be empty")) + } + + if err := image.ensurePulled(ctx); err != nil { + return nil, image.wrapErr(err) + } + + var ( + containerName = SanitizeContainerName(image.testName + "-" + RandLowerCaseLetterString(6)) + hostName = CondenseHostName(containerName) + logger = image.log.With( + zap.String("command", strings.Join(cmd, " ")), + zap.String("hostname", hostName), + zap.String("container", containerName), + ) + ) + + cID, err := image.createContainer(ctx, containerName, hostName, cmd, opts) + if err != nil { + return nil, image.wrapErr(fmt.Errorf("create container %s: %w", containerName, err)) + } + + logger.Info("About to start container") + + err = StartContainer(ctx, image.client, cID) + if err != nil { + return nil, image.wrapErr(fmt.Errorf("start container %s: %w", containerName, err)) + } + + return &Container{ + Name: containerName, + Hostname: hostName, + log: logger, + image: image, + containerID: cID, + }, nil +} + +func (image *Image) wrapErr(err error) error { + return fmt.Errorf("image %s:%s: %w", image.repository, image.tag, err) +} + +// Container is a docker container. Use (*Image).Start to create a new container. +type Container struct { + Name string + Hostname string + + log *zap.Logger + image *Image + containerID string +} + +// Wait blocks until the container exits. Calling wait is not suitable for daemons and servers. +// A non-zero status code returns an error. +// +// Wait implicitly calls Stop. +// If logTail is non-zero, the stdout and stderr logs will be truncated at the end to that number of lines. +func (c *Container) Wait(ctx context.Context, logTail uint64) ContainerExecResult { + waitCh, errCh := c.image.client.ContainerWait(ctx, c.containerID, container.WaitConditionNotRunning) + var exitCode int + select { + case <-ctx.Done(): + return ContainerExecResult{ + Err: ctx.Err(), + ExitCode: 1, + Stdout: nil, + Stderr: nil, + } + case err := <-errCh: + return ContainerExecResult{ + Err: err, + ExitCode: 1, + Stdout: nil, + Stderr: nil, + } + case res := <-waitCh: + exitCode = int(res.StatusCode) + if res.Error != nil { + return ContainerExecResult{ + Err: errors.New(res.Error.Message), + ExitCode: exitCode, + Stdout: nil, + Stderr: nil, + } + } + } + + var ( + stdoutBuf = new(bytes.Buffer) + stderrBuf = new(bytes.Buffer) + ) + + logOpts := types.ContainerLogsOptions{ + ShowStdout: true, + ShowStderr: true, + } + if logTail != 0 { + logOpts.Tail = strconv.FormatUint(logTail, 10) + } + + rc, err := c.image.client.ContainerLogs(ctx, c.containerID, logOpts) + if err != nil { + return ContainerExecResult{ + Err: err, + ExitCode: exitCode, + Stdout: nil, + Stderr: nil, + } + } + defer func() { _ = rc.Close() }() + + // Logs are multiplexed into one stream; see docs for ContainerLogs. + _, err = stdcopy.StdCopy(stdoutBuf, stderrBuf, rc) + if err != nil { + return ContainerExecResult{ + Err: err, + ExitCode: exitCode, + Stdout: nil, + Stderr: nil, + } + } + _ = rc.Close() + + err = c.Stop(10 * time.Second) + if err != nil { + c.log.Error("Failed to stop and remove container", zap.Error(err), zap.String("container_id", c.containerID)) + } + + if exitCode != 0 { + out := strings.Join([]string{stdoutBuf.String(), stderrBuf.String()}, " ") + return ContainerExecResult{ + Err: fmt.Errorf("exit code %d: %s", exitCode, out), + ExitCode: exitCode, + Stdout: nil, + Stderr: nil, + } + } + + return ContainerExecResult{ + Err: nil, + ExitCode: exitCode, + Stdout: stdoutBuf.Bytes(), + Stderr: stderrBuf.Bytes(), + } +} + +// Stop gives the container up to timeout to stop and remove itself from the network. +func (c *Container) Stop(timeout time.Duration) error { + // Use timeout*2 to give both stop and remove container operations a chance to complete. + ctx, cancel := context.WithTimeout(context.Background(), timeout*2) + defer cancel() + + var stopOptions container.StopOptions + timeoutRound := int(timeout.Round(time.Second)) + stopOptions.Timeout = &timeoutRound + err := c.image.client.ContainerStop(ctx, c.containerID, stopOptions) + if err != nil { + // Only return the error if it didn't match an already stopped, or a missing container. + if !(errdefs.IsNotModified(err) || errdefs.IsNotFound(err)) { + return c.image.wrapErr(fmt.Errorf("stop container %s: %w", c.Name, err)) + } + } + + // RemoveContainerOptions duplicates (*dockertest.Resource).Prune. + err = c.image.client.ContainerRemove(ctx, c.containerID, types.ContainerRemoveOptions{ + Force: true, + RemoveVolumes: true, + }) + if err != nil && !errdefs.IsNotFound(err) { + return c.image.wrapErr(fmt.Errorf("remove container %s: %w", c.Name, err)) + } + + return nil +} diff --git a/tests/e2e-ibc/dockerutil/image_test.go b/tests/e2e-ibc/dockerutil/image_test.go new file mode 100644 index 000000000..de0850d92 --- /dev/null +++ b/tests/e2e-ibc/dockerutil/image_test.go @@ -0,0 +1,191 @@ +package dockerutil + +import ( + "context" + "os" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/client" + "github.com/stretchr/testify/require" + "go.uber.org/zap" +) + +const ( + testDockerImage = "busybox" + testDockerTag = "latest" +) + +func TestNewImage(t *testing.T) { + if testing.Short() { + t.Skip("skipping in short mode") + } + t.Parallel() + + cl, networkID := DockerSetup(t) + + for _, tt := range []struct { + Client *client.Client + NetworkID string + Repository string + TestName string + }{ + {nil, networkID, "repo", t.Name()}, + {cl, "", "repo", t.Name()}, + {cl, networkID, "", t.Name()}, + {cl, networkID, "repo", ""}, + } { + require.Panics(t, func() { + NewImage(zap.NewNop(), tt.Client, tt.NetworkID, tt.TestName, tt.Repository, "") + }, tt) + } +} + +func TestImage_Run(t *testing.T) { + if testing.Short() { + t.Skip("skipping in short mode") + } + t.Parallel() + + ctx := context.Background() + client, networkID := DockerSetup(t) + image := NewImage(zap.NewNop(), client, networkID, t.Name(), testDockerImage, testDockerTag) + + t.Run("happy path", func(t *testing.T) { + res := image.Run(ctx, []string{"echo", "-n", "hello"}, ContainerOptions{}) + stdout, stderr, err := res.Stdout, res.Stderr, res.Err + + require.NoError(t, err) + require.Equal(t, "hello", string(stdout)) + require.Empty(t, string(stderr)) + }) + + t.Run("binds", func(t *testing.T) { + const scriptBody = `#!/bin/sh +echo -n hi from stderr >> /dev/stderr +` + tmpDir := t.TempDir() + err := os.WriteFile(filepath.Join(tmpDir, "test.sh"), []byte(scriptBody), 0777) + require.NoError(t, err) + + opts := ContainerOptions{ + Binds: []string{tmpDir + ":/test"}, + } + + res := image.Run(ctx, []string{"/test/test.sh"}, opts) + stdout, stderr, err := res.Stdout, res.Stderr, res.Err + + require.NoError(t, err) + require.Empty(t, string(stdout)) + require.Equal(t, "hi from stderr", string(stderr)) + }) + + t.Run("env vars", func(t *testing.T) { + opts := ContainerOptions{Env: []string{"MY_ENV_VAR=foo"}} + res := image.Run(ctx, []string{"printenv", "MY_ENV_VAR"}, opts) + stdout, stderr, err := res.Stdout, res.Stderr, res.Err + + require.NoError(t, err) + require.Equal(t, "foo", strings.TrimSpace(string(stdout))) + require.Empty(t, string(stderr)) + }) + + t.Run("context cancelled", func(t *testing.T) { + cctx, cancel := context.WithCancel(ctx) + cancel() + res := image.Run(cctx, []string{"sleep", "100"}, ContainerOptions{}) + err := res.Err + + require.Error(t, err) + require.ErrorIs(t, err, context.Canceled) + }) + + t.Run("errors", func(t *testing.T) { + for _, tt := range []struct { + Args []string + WantErr string + }{ + {[]string{"program-does-not-exist"}, "executable file not found"}, + {[]string{"sleep", "not-valid-arg"}, "sleep: invalid"}, + } { + res := image.Run(ctx, tt.Args, ContainerOptions{}) + err := res.Err + + require.Error(t, err, tt) + require.Contains(t, err.Error(), tt.WantErr, tt) + } + }) + + t.Run("missing required args", func(t *testing.T) { + require.PanicsWithError(t, "cmd cannot be empty", func() { + _ = image.Run(ctx, nil, ContainerOptions{}) + }) + }) +} + +func TestContainer(t *testing.T) { + if testing.Short() { + t.Skip("skipping in short mode") + } + t.Parallel() + + ctx := context.Background() + cl, networkID := DockerSetup(t) + image := NewImage(zap.NewNop(), cl, networkID, t.Name(), testDockerImage, testDockerTag) + + t.Run("wait", func(t *testing.T) { + c, err := image.Start(ctx, []string{"echo", "-n", "started"}, ContainerOptions{}) + + require.NoError(t, err) + require.NotEmpty(t, c.Name) + require.NotEmpty(t, c.Hostname) + + res := c.Wait(ctx, 0) + stdout, stderr, err := res.Stdout, res.Stderr, res.Err + + require.NoError(t, err) + require.Equal(t, "started", string(stdout)) + require.Empty(t, stderr) + + containers, err := image.client.ContainerList(ctx, types.ContainerListOptions{ + All: true, + Filters: filters.NewArgs(filters.Arg("name", c.Name)), + }) + require.NoError(t, err) + require.Empty(t, containers, "container was not removed") + + require.NoError(t, c.Stop(5*time.Second)) + }) + + t.Run("stop long running container", func(t *testing.T) { + c, err := image.Start(ctx, []string{"sleep", "100"}, ContainerOptions{}) + require.NoError(t, err) + require.NoError(t, c.Stop(10*time.Second)) + require.NoError(t, c.Stop(10*time.Second)) // assert idempotent + + containers, err := image.client.ContainerList(ctx, types.ContainerListOptions{ + All: true, + Filters: filters.NewArgs(filters.Arg("name", c.Name)), + }) + require.NoError(t, err) + require.Empty(t, containers, "container was not removed") + }) + + t.Run("start error", func(t *testing.T) { + c, err := image.Start(ctx, []string{"sleep", "not valid arg"}, ContainerOptions{}) + require.NoError(t, err) + + res := c.Wait(ctx, 0) + require.Error(t, res.Err) + }) + + t.Run("missing command", func(t *testing.T) { + require.Panics(t, func() { + _, _ = image.Start(ctx, nil, ContainerOptions{}) + }) + }) +} diff --git a/tests/e2e-ibc/dockerutil/keyring.go b/tests/e2e-ibc/dockerutil/keyring.go new file mode 100644 index 000000000..9d54393bf --- /dev/null +++ b/tests/e2e-ibc/dockerutil/keyring.go @@ -0,0 +1,63 @@ +package dockerutil + +import ( + "archive/tar" + "bytes" + "context" + "io" + "os" + "path" + "path/filepath" + + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + "github.com/docker/docker/client" +) + +// NewLocalKeyringFromDockerContainer copies the contents of the given container directory into a specified local directory. +// This allows test hosts to sign transactions on behalf of test users. +func NewLocalKeyringFromDockerContainer(ctx context.Context, dc *client.Client, localDirectory, containerKeyringDir, containerId string) (keyring.Keyring, error) { + reader, _, err := dc.CopyFromContainer(ctx, containerId, containerKeyringDir) + if err != nil { + return nil, err + } + + if err := os.Mkdir(filepath.Join(localDirectory, "keyring-test"), os.ModePerm); err != nil { + return nil, err + } + tr := tar.NewReader(reader) + for { + hdr, err := tr.Next() + if err == io.EOF { + break // End of archive + } + if err != nil { + return nil, err + } + + var fileBuff bytes.Buffer + if _, err := io.Copy(&fileBuff, tr); err != nil { + return nil, err + } + + name := hdr.Name + extractedFileName := path.Base(name) + isDirectory := extractedFileName == "" + if isDirectory { + continue + } + + filePath := filepath.Join(localDirectory, "keyring-test", extractedFileName) + if err := os.WriteFile(filePath, fileBuff.Bytes(), os.ModePerm); err != nil { + return nil, err + } + } + + registry := codectypes.NewInterfaceRegistry() + cryptocodec.RegisterInterfaces(registry) + cdc := codec.NewProtoCodec(registry) + + return keyring.New("", keyring.BackendTest, localDirectory, os.Stdin, cdc) +} diff --git a/tests/e2e-ibc/dockerutil/ports.go b/tests/e2e-ibc/dockerutil/ports.go new file mode 100644 index 000000000..7aedc13b5 --- /dev/null +++ b/tests/e2e-ibc/dockerutil/ports.go @@ -0,0 +1,91 @@ +package dockerutil + +import ( + "fmt" + "net" + "strconv" + "sync" + + "github.com/docker/go-connections/nat" +) + +var mu sync.RWMutex + +type Listeners []net.Listener + +func (l Listeners) CloseAll() { + for _, listener := range l { + listener.Close() + } +} + +// openListener opens a listener on a port. Set to 0 to get a random port. +func openListener(port int) (*net.TCPListener, error) { + addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("127.0.0.1:%d", port)) + if err != nil { + return nil, err + } + + mu.Lock() + defer mu.Unlock() + l, err := net.ListenTCP("tcp", addr) + if err != nil { + return nil, err + } + + return l, nil +} + +// getPort generates a docker PortBinding by using the port provided. +// If port is set to 0, the next avaliable port will be used. +// The listener will be closed in the case of an error, otherwise it will be left open. +// This allows multiple getPort calls to find multiple available ports +// before closing them so they are available for the PortBinding. +func getPort(port int) (nat.PortBinding, *net.TCPListener, error) { + l, err := openListener(port) + if err != nil { + l.Close() + return nat.PortBinding{}, nil, err + } + + return nat.PortBinding{ + HostIP: "0.0.0.0", + HostPort: fmt.Sprint(l.Addr().(*net.TCPAddr).Port), + }, l, nil +} + +// GeneratePortBindings will find open ports on the local +// machine and create a PortBinding for every port in the portSet. +// If a port is already bound, it will use that port as an override. +func GeneratePortBindings(pairs nat.PortMap) (nat.PortMap, Listeners, error) { + m := make(nat.PortMap) + listeners := make(Listeners, 0, len(pairs)) + + var pb nat.PortBinding + var l *net.TCPListener + var err error + + for p, bind := range pairs { + if len(bind) == 0 { + // random port + pb, l, err = getPort(0) + } else { + var pNum int + if pNum, err = strconv.Atoi(bind[0].HostPort); err != nil { + return nat.PortMap{}, nil, err + } + + pb, l, err = getPort(pNum) + } + + if err != nil { + listeners.CloseAll() + return nat.PortMap{}, nil, err + } + + listeners = append(listeners, l) + m[p] = []nat.PortBinding{pb} + } + + return m, listeners, nil +} diff --git a/tests/e2e-ibc/dockerutil/setup.go b/tests/e2e-ibc/dockerutil/setup.go new file mode 100644 index 000000000..517e1503b --- /dev/null +++ b/tests/e2e-ibc/dockerutil/setup.go @@ -0,0 +1,251 @@ +package dockerutil + +import ( + "bytes" + "context" + "fmt" + "os" + "strings" + "time" + + "github.com/avast/retry-go/v4" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/client" + "github.com/docker/docker/errdefs" +) + +// DockerSetupTestingT is a subset of testing.T required for DockerSetup. +type DockerSetupTestingT interface { + Helper() + + Name() string + + Failed() bool + Cleanup(func()) + + Logf(format string, args ...any) +} + +// CleanupLabel is a docker label key targeted by DockerSetup when it cleans up docker resources. +// +// "interchaintest" is perhaps a better name. However, for backwards compatability we preserve the original name of "ibc-test" +// with the hyphen. Otherwise, we run the risk of causing "container already exists" errors because DockerSetup +// is unable to clean old resources from docker engine. +const CleanupLabel = "ibc-test" + +// CleanupLabel is the "old" format. +// Note that any new labels should follow the reverse DNS format suggested at +// https://docs.docker.com/config/labels-custom-metadata/#key-format-recommendations. + +const ( + // LabelPrefix is the reverse DNS format "namespace" for interchaintest Docker labels. + LabelPrefix = "ventures.strangelove.interchaintest." + + // NodeOwnerLabel indicates the logical node owning a particular object (probably a volume). + NodeOwnerLabel = LabelPrefix + "node-owner" +) + +// KeepVolumesOnFailure determines whether volumes associated with a test +// using DockerSetup are retained or deleted following a test failure. +// +// The value is false by default, but can be initialized to true by setting the +// environment variable IBCTEST_SKIP_FAILURE_CLEANUP to a non-empty value. +// Alternatively, importers of the dockerutil package may set the variable to true. +// Because dockerutil is an internal package, the public API for setting this value +// is interchaintest.KeepDockerVolumesOnFailure(bool). +var KeepVolumesOnFailure = os.Getenv("IBCTEST_SKIP_FAILURE_CLEANUP") != "" + +// DockerSetup returns a new Docker Client and the ID of a configured network, associated with t. +// +// If any part of the setup fails, DockerSetup panics because the test cannot continue. +func DockerSetup(t DockerSetupTestingT) (*client.Client, string) { + t.Helper() + + cli, err := client.NewClientWithOpts(client.FromEnv) + if err != nil { + panic(fmt.Errorf("failed to create docker client: %v", err)) + } + + // Clean up docker resources at end of test. + t.Cleanup(dockerCleanup(t, cli)) + + // Also eagerly clean up any leftover resources from a previous test run, + // e.g. if the test was interrupted. + dockerCleanup(t, cli)() + + name := fmt.Sprintf("interchaintest-%s", RandLowerCaseLetterString(8)) + network, err := cli.NetworkCreate(context.TODO(), name, types.NetworkCreate{ + CheckDuplicate: true, + + Labels: map[string]string{CleanupLabel: t.Name()}, + }) + if err != nil { + panic(fmt.Errorf("failed to create docker network: %v", err)) + } + + return cli, network.ID +} + +// dockerCleanup will clean up Docker containers, networks, and the other various config files generated in testing +func dockerCleanup(t DockerSetupTestingT, cli *client.Client) func() { + return func() { + showContainerLogs := os.Getenv("SHOW_CONTAINER_LOGS") + containerLogTail := os.Getenv("CONTAINER_LOG_TAIL") + keepContainers := os.Getenv("KEEP_CONTAINERS") != "" + + ctx := context.TODO() + cli.NegotiateAPIVersion(ctx) + cs, err := cli.ContainerList(ctx, types.ContainerListOptions{ + All: true, + Filters: filters.NewArgs( + filters.Arg("label", CleanupLabel+"="+t.Name()), + ), + }) + if err != nil { + t.Logf("Failed to list containers during docker cleanup: %v", err) + return + } + + for _, c := range cs { + if (t.Failed() && showContainerLogs == "") || showContainerLogs == "always" { + logTail := "50" + if containerLogTail != "" { + logTail = containerLogTail + } + rc, err := cli.ContainerLogs(ctx, c.ID, types.ContainerLogsOptions{ + ShowStdout: true, + ShowStderr: true, + Tail: logTail, + }) + if err == nil { + b := new(bytes.Buffer) + _, err := b.ReadFrom(rc) + if err == nil { + t.Logf("\n\nContainer logs - {%s}\n%s", strings.Join(c.Names, " "), b.String()) + } + } + } + if !keepContainers { + var stopTimeout container.StopOptions + timeout := 10 + timeoutDur := time.Duration(timeout * int(time.Second)) + deadline := time.Now().Add(timeoutDur) + stopTimeout.Timeout = &timeout + if err := cli.ContainerStop(ctx, c.ID, stopTimeout); isLoggableStopError(err) { + t.Logf("Failed to stop container %s during docker cleanup: %v", c.ID, err) + } + + waitCtx, cancel := context.WithDeadline(ctx, deadline.Add(500*time.Millisecond)) + waitCh, errCh := cli.ContainerWait(waitCtx, c.ID, container.WaitConditionNotRunning) + select { + case <-waitCtx.Done(): + t.Logf("Timed out waiting for container %s", c.ID) + case err := <-errCh: + t.Logf("Failed to wait for container %s during docker cleanup: %v", c.ID, err) + case res := <-waitCh: + if res.Error != nil { + t.Logf("Error while waiting for container %s during docker cleanup: %s", c.ID, res.Error.Message) + } + // Ignoring statuscode for now. + } + cancel() + + if err := cli.ContainerRemove(ctx, c.ID, types.ContainerRemoveOptions{ + // Not removing volumes with the container, because we separately handle them conditionally. + Force: true, + }); err != nil { + t.Logf("Failed to remove container %s during docker cleanup: %v", c.ID, err) + } + } + } + + if !keepContainers { + pruneVolumesWithRetry(ctx, t, cli) + pruneNetworksWithRetry(ctx, t, cli) + } else { + t.Logf("Keeping containers - Docker cleanup skipped") + } + } +} + +func pruneVolumesWithRetry(ctx context.Context, t DockerSetupTestingT, cli *client.Client) { + if KeepVolumesOnFailure && t.Failed() { + return + } + + var msg string + err := retry.Do( + func() error { + res, err := cli.VolumesPrune(ctx, filters.NewArgs(filters.Arg("label", CleanupLabel+"="+t.Name()))) + if err != nil { + if errdefs.IsConflict(err) { + // Prune is already in progress; try again. + return err + } + + // Give up on any other error. + return retry.Unrecoverable(err) + } + + if len(res.VolumesDeleted) > 0 { + msg = fmt.Sprintf("Pruned %d volumes, reclaiming approximately %.1f MB", len(res.VolumesDeleted), float64(res.SpaceReclaimed)/(1024*1024)) + } + + return nil + }, + retry.Context(ctx), + retry.DelayType(retry.FixedDelay), + ) + + if err != nil { + t.Logf("Failed to prune volumes during docker cleanup: %v", err) + return + } + + if msg != "" { + // Odd to Logf %s, but this is a defensive way to keep the DockerSetupTestingT interface + // with only Logf and not need to add Log. + t.Logf("%s", msg) + } +} + +func pruneNetworksWithRetry(ctx context.Context, t DockerSetupTestingT, cli *client.Client) { + var deleted []string + err := retry.Do( + func() error { + res, err := cli.NetworksPrune(ctx, filters.NewArgs(filters.Arg("label", CleanupLabel+"="+t.Name()))) + if err != nil { + if errdefs.IsConflict(err) { + // Prune is already in progress; try again. + return err + } + + // Give up on any other error. + return retry.Unrecoverable(err) + } + + deleted = res.NetworksDeleted + return nil + }, + retry.Context(ctx), + retry.DelayType(retry.FixedDelay), + ) + + if err != nil { + t.Logf("Failed to prune networks during docker cleanup: %v", err) + return + } + + if len(deleted) > 0 { + t.Logf("Pruned unused networks: %v", deleted) + } +} + +func isLoggableStopError(err error) bool { + if err == nil { + return false + } + return !(errdefs.IsNotModified(err) || errdefs.IsNotFound(err)) +} diff --git a/tests/e2e-ibc/dockerutil/setup_test.go b/tests/e2e-ibc/dockerutil/setup_test.go new file mode 100644 index 000000000..020a45073 --- /dev/null +++ b/tests/e2e-ibc/dockerutil/setup_test.go @@ -0,0 +1,80 @@ +package dockerutil_test + +import ( + "context" + "fmt" + "testing" + + volumetypes "github.com/docker/docker/api/types/volume" + "github.com/docker/docker/errdefs" + "github.com/strangelove-ventures/interchaintest/v8/internal/dockerutil" + "github.com/strangelove-ventures/interchaintest/v8/internal/mocktesting" + "github.com/stretchr/testify/require" +) + +func TestDockerSetup_KeepVolumes(t *testing.T) { + if testing.Short() { + t.Skip("skipping due to short mode") + } + + cli, _ := dockerutil.DockerSetup(t) + + origKeep := dockerutil.KeepVolumesOnFailure + defer func() { + dockerutil.KeepVolumesOnFailure = origKeep + }() + + ctx := context.Background() + + for _, tc := range []struct { + keep bool + passed bool + volumeKept bool + }{ + {keep: false, passed: false, volumeKept: false}, + {keep: true, passed: false, volumeKept: true}, + {keep: false, passed: true, volumeKept: false}, + {keep: true, passed: true, volumeKept: false}, + } { + tc := tc + state := "failed" + if tc.passed { + state = "passed" + } + + testName := fmt.Sprintf("keep=%t, test %s", tc.keep, state) + t.Run(testName, func(t *testing.T) { + dockerutil.KeepVolumesOnFailure = tc.keep + mt := mocktesting.NewT(t.Name()) + + var volumeName string + mt.Simulate(func() { + cli, _ := dockerutil.DockerSetup(mt) + + v, err := cli.VolumeCreate(ctx, volumetypes.CreateOptions{ + Labels: map[string]string{dockerutil.CleanupLabel: mt.Name()}, + }) + require.NoError(t, err) + + volumeName = v.Name + + if !tc.passed { + mt.Fail() + } + }) + + require.Equal(t, !tc.passed, mt.Failed()) + + _, err := cli.VolumeInspect(ctx, volumeName) + if !tc.volumeKept { + require.Truef(t, errdefs.IsNotFound(err), "expected not found error, got %v", err) + return + } + + require.NoError(t, err) + if err := cli.VolumeRemove(ctx, volumeName, true); err != nil { + t.Logf("failed to remove volume %s: %v", volumeName, err) + } + }) + } +} diff --git a/tests/e2e-ibc/dockerutil/startcontainer.go b/tests/e2e-ibc/dockerutil/startcontainer.go new file mode 100644 index 000000000..a4a3153c5 --- /dev/null +++ b/tests/e2e-ibc/dockerutil/startcontainer.go @@ -0,0 +1,26 @@ +package dockerutil + +import ( + "context" + "time" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/client" +) + +// StartContainer attempts to start the container with the given ID. +func StartContainer(ctx context.Context, cli *client.Client, id string) error { + // add a deadline for the request if the calling context does not provide one + if _, hasDeadline := ctx.Deadline(); !hasDeadline { + var cancel func() + ctx, cancel = context.WithTimeout(ctx, 30*time.Second) + defer cancel() + } + + err := cli.ContainerStart(ctx, id, types.ContainerStartOptions{}) + if err != nil { + return err + } + + return nil +} diff --git a/tests/e2e-ibc/dockerutil/strings.go b/tests/e2e-ibc/dockerutil/strings.go new file mode 100644 index 000000000..1c0adba99 --- /dev/null +++ b/tests/e2e-ibc/dockerutil/strings.go @@ -0,0 +1,96 @@ +package dockerutil + +import ( + "fmt" + "math/rand" + "net" + "os" + "regexp" + "runtime" + "time" + + "github.com/docker/docker/api/types" + "github.com/docker/go-connections/nat" +) + +// GetHostPort returns a resource's published port with an address. +// cont is the type returned by the Docker client's ContainerInspect method. +func GetHostPort(cont types.ContainerJSON, portID string) string { + if cont.NetworkSettings == nil { + return "" + } + + m, ok := cont.NetworkSettings.Ports[nat.Port(portID)] + if !ok || len(m) == 0 { + return "" + } + + return net.JoinHostPort(m[0].HostIP, m[0].HostPort) +} + +// Ensure that the global RNG is seeded when this package is imported. +// Otherwise, each importer would need to seed explicitly on their own. +// +// Without pre-seeding, it is possible for two independent test binaries +// to attempt to create a Docker network with the same random suffix +// due to unintentionally both using the default seed. +func init() { + rand.Seed(time.Now().UnixNano()) +} + +var chars = []byte("abcdefghijklmnopqrstuvwxyz") + +// RandLowerCaseLetterString returns a lowercase letter string of given length +func RandLowerCaseLetterString(length int) string { + b := make([]byte, length) + for i := range b { + b[i] = chars[rand.Intn(len(chars))] + } + return string(b) +} + +func GetDockerUserString() string { + uid := os.Getuid() + var usr string + if runtime.GOOS == "darwin" { + usr = "" + } else { + usr = fmt.Sprintf("%d:%d", uid, uid) + } + return usr +} + +func GetHeighlinerUserString() string { + return "1025:1025" +} + +func GetRootUserString() string { + return "0:0" +} + +// CondenseHostName truncates the middle of the given name +// if it is 64 characters or longer. +// +// Without this helper, you may see an error like: +// +// API error (500): failed to create shim: OCI runtime create failed: container_linux.go:380: starting container process caused: process_linux.go:545: container init caused: sethostname: invalid argument: unknown +func CondenseHostName(name string) string { + if len(name) < 64 { + return name + } + + // I wanted to use ... as the middle separator, + // but that causes resolution problems for other hosts. + // Instead, use _._ which will be okay if there is a . on either end. + return name[:30] + "_._" + name[len(name)-30:] +} + +var validContainerCharsRE = regexp.MustCompile(`[^a-zA-Z0-9_.-]`) + +// SanitizeContainerName returns name with any +// invalid characters replaced with underscores. +// Subtests will include slashes, and there may be other +// invalid characters too. +func SanitizeContainerName(name string) string { + return validContainerCharsRE.ReplaceAllLiteralString(name, "_") +} diff --git a/tests/e2e-ibc/dockerutil/strings_test.go b/tests/e2e-ibc/dockerutil/strings_test.go new file mode 100644 index 000000000..ff806ae7c --- /dev/null +++ b/tests/e2e-ibc/dockerutil/strings_test.go @@ -0,0 +1,88 @@ +package dockerutil + +import ( + "math/rand" + "testing" + + "github.com/docker/docker/api/types" + "github.com/docker/go-connections/nat" + "github.com/stretchr/testify/require" +) + +func TestGetHostPort(t *testing.T) { + for _, tt := range []struct { + Container types.ContainerJSON + PortID string + Want string + }{ + { + types.ContainerJSON{ + NetworkSettings: &types.NetworkSettings{ + NetworkSettingsBase: types.NetworkSettingsBase{ + Ports: nat.PortMap{ + nat.Port("test"): []nat.PortBinding{ + {HostIP: "1.2.3.4", HostPort: "8080"}, + {HostIP: "0.0.0.0", HostPort: "9999"}, + }, + }, + }, + }, + }, "test", "1.2.3.4:8080", + }, + { + types.ContainerJSON{ + NetworkSettings: &types.NetworkSettings{ + NetworkSettingsBase: types.NetworkSettingsBase{ + Ports: nat.PortMap{ + nat.Port("test"): []nat.PortBinding{ + {HostIP: "0.0.0.0", HostPort: "3000"}, + }, + }, + }, + }, + }, "test", "0.0.0.0:3000", + }, + + {types.ContainerJSON{}, "", ""}, + {types.ContainerJSON{NetworkSettings: &types.NetworkSettings{}}, "does-not-matter", ""}, + } { + require.Equal(t, tt.Want, GetHostPort(tt.Container, tt.PortID), tt) + } +} + +func TestRandLowerCaseLetterString(t *testing.T) { + require.Empty(t, RandLowerCaseLetterString(0)) + + rand.Seed(1) + require.Equal(t, "xvlbzgbaicmr", RandLowerCaseLetterString(12)) + + rand.Seed(1) + require.Equal(t, "xvlbzgbaicmrajwwhthctcuaxhxkqf", RandLowerCaseLetterString(30)) +} + +func TestCondenseHostName(t *testing.T) { + for _, tt := range []struct { + HostName, Want string + }{ + {"", ""}, + {"test", "test"}, + {"some-really-very-incredibly-long-hostname-that-is-greater-than-64-characters", "some-really-very-incredibly-lo_._-is-greater-than-64-characters"}, + } { + require.Equal(t, tt.Want, CondenseHostName(tt.HostName), tt) + } +} + +func TestSanitizeContainerName(t *testing.T) { + for _, tt := range []struct { + Name, Want string + }{ + {"hello-there", "hello-there"}, + {"hello@there", "hello_there"}, + {"hello@/there", "hello__there"}, + // edge cases + {"?", "_"}, + {"", ""}, + } { + require.Equal(t, tt.Want, SanitizeContainerName(tt.Name), tt) + } +} diff --git a/tests/e2e-ibc/dockerutil/volumeowner.go b/tests/e2e-ibc/dockerutil/volumeowner.go new file mode 100644 index 000000000..6227194ee --- /dev/null +++ b/tests/e2e-ibc/dockerutil/volumeowner.go @@ -0,0 +1,109 @@ +package dockerutil + +import ( + "context" + "fmt" + "time" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/client" + "go.uber.org/zap" +) + +// VolumeOwnerOptions contain the configuration for the SetVolumeOwner function. +type VolumeOwnerOptions struct { + Log *zap.Logger + + Client *client.Client + + VolumeName string + ImageRef string + TestName string + UidGid string +} + +// SetVolumeOwner configures the owner of a volume to match the default user in the supplied image reference. +func SetVolumeOwner(ctx context.Context, opts VolumeOwnerOptions) error { + owner := opts.UidGid + if owner == "" { + owner = GetRootUserString() + } + + // Start a one-off container to chmod and chown the volume. + + containerName := fmt.Sprintf("interchaintest-volumeowner-%d-%s", time.Now().UnixNano(), RandLowerCaseLetterString(5)) + + if err := ensureBusybox(ctx, opts.Client); err != nil { + return err + } + + const mountPath = "/mnt/dockervolume" + cc, err := opts.Client.ContainerCreate( + ctx, + &container.Config{ + Image: busyboxRef, // Using busybox image which has chown and chmod. + + Entrypoint: []string{"sh", "-c"}, + Cmd: []string{ + `chown "$2" "$1" && chmod 0700 "$1"`, + "_", // Meaningless arg0 for sh -c with positional args. + mountPath, + owner, + }, + + // Root user so we have permissions to set ownership and mode. + User: GetRootUserString(), + + Labels: map[string]string{CleanupLabel: opts.TestName}, + }, + &container.HostConfig{ + Binds: []string{opts.VolumeName + ":" + mountPath}, + AutoRemove: true, + }, + nil, // No networking necessary. + nil, + containerName, + ) + if err != nil { + return fmt.Errorf("creating container: %w", err) + } + + autoRemoved := false + defer func() { + if autoRemoved { + // No need to attempt removing the container if we successfully started and waited for it to complete. + return + } + + if err := opts.Client.ContainerRemove(ctx, cc.ID, types.ContainerRemoveOptions{ + Force: true, + }); err != nil { + opts.Log.Warn("Failed to remove volume-owner container", zap.String("container_id", cc.ID), zap.Error(err)) + } + }() + + if err := opts.Client.ContainerStart(ctx, cc.ID, types.ContainerStartOptions{}); err != nil { + return fmt.Errorf("starting volume-owner container: %w", err) + } + + waitCh, errCh := opts.Client.ContainerWait(ctx, cc.ID, container.WaitConditionNotRunning) + select { + case <-ctx.Done(): + return ctx.Err() + case err := <-errCh: + return err + case res := <-waitCh: + autoRemoved = true + + if res.Error != nil { + return fmt.Errorf("waiting for volume-owner container: %s", res.Error.Message) + } + + if res.StatusCode != 0 { + return fmt.Errorf("configuring volume exited %d", res.StatusCode) + } + } + + return nil +} diff --git a/tests/e2e-ibc/erc20_test.go b/tests/e2e-ibc/erc20_test.go index 4528a9681..9bd3bbecd 100644 --- a/tests/e2e-ibc/erc20_test.go +++ b/tests/e2e-ibc/erc20_test.go @@ -5,9 +5,11 @@ import ( "crypto/sha256" "encoding/json" "fmt" + "github.com/kava-labs/kava/tests/interchain/dockerutil" "github.com/strangelove-ventures/interchaintest/v8" "math/big" "path/filepath" + "reflect" "testing" "time" @@ -85,7 +87,8 @@ func TestInterchainErc20(t *testing.T) { TestName: t.Name(), Client: client, NetworkID: network, - SkipPathCreation: false}, + SkipPathCreation: false, + }, ) require.NoError(t, err) @@ -117,14 +120,33 @@ func TestInterchainErc20(t *testing.T) { // we need to fund an account and then all of kava's e2e testutil chain management will work. //rpcUrl, err := ictKava.FullNodes[0].GetHostAddress(ctx, "26657/tcp") - rpcUrl := ictKava.FullNodes[0].HostName() + ":26657" - require.NoError(t, err, "failed to find rpc URL") + //require.NoError(t, err, "failed to find rpc URL") + rpcUrl := ictKava.GetHostRPCAddress() //grpcUrl, err := ictKava.FullNodes[0].GetHostAddress(ctx, "9090/tcp") - grpcUrl := ictKava.FullNodes[0].HostName() + ":9090" - require.NoError(t, err, "failed to find grpc URL") + //require.NoError(t, err, "failed to find grpc URL") + grpcUrl := ictKava.GetHostGRPCAddress() //evmUrl, err := ictKava.FullNodes[0].GetHostAddress(ctx, "8545/tcp") - evmUrl := ictKava.FullNodes[0].HostName() + ":8545" require.NoError(t, err, "failed to find evm URL") + evmUrl := ictKava.GetHostAPIAddress() + fmt.Println("evmUrl 1: ", evmUrl) + + // lifecycle is hidden for package, so we will need fork with an expose method for the particular port. + // at the moment such unsafe workaround + value := reflect.ValueOf(ictKava).FieldByName("containerLifecycle") + + var containerLifecycle *dockerutil.ContainerLifecycle + + if value.IsValid() && value.CanInterface() { + fmt.Println(value.Interface().(*dockerutil.ContainerLifecycle)) + containerLifecycle = value.Interface().(*dockerutil.ContainerLifecycle) + } else { + panic("containerLifecycle is not valid") + } + + hostPorts, err := containerLifecycle.GetHostPorts(ctx, "8545/tcp") + require.NoError(t, err, "failed to find evm URL") + evmUrl = hostPorts[0] + fmt.Println("evmUrl 2: ", evmUrl) evmClient, err := ethclient.Dial(evmUrl) require.NoError(t, err, "failed to connect to evm") diff --git a/tests/e2e-ibc/go.mod b/tests/e2e-ibc/go.mod index b0a267cc3..7b7d5dc31 100644 --- a/tests/e2e-ibc/go.mod +++ b/tests/e2e-ibc/go.mod @@ -4,8 +4,11 @@ go 1.21.9 require ( cosmossdk.io/math v1.3.0 + github.com/avast/retry-go/v4 v4.5.1 github.com/cosmos/cosmos-sdk v0.53.0 github.com/cosmos/ibc-go/v8 v8.5.1 + github.com/docker/docker v24.0.7+incompatible + github.com/docker/go-connections v0.5.0 github.com/ethereum/go-ethereum v1.13.14 github.com/kava-labs/kava v0.0.0-00010101000000-000000000000 github.com/strangelove-ventures/interchaintest/v8 v8.2.0 @@ -49,7 +52,6 @@ require ( github.com/StackExchange/wmi v1.2.1 // indirect github.com/StirlingMarketingGroup/go-namecase v1.0.0 // indirect github.com/VictoriaMetrics/fastcache v1.12.1 // indirect - github.com/avast/retry-go/v4 v4.5.1 // indirect github.com/aws/aws-sdk-go v1.55.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect @@ -93,8 +95,6 @@ require ( github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect github.com/docker/distribution v2.8.2+incompatible // indirect - github.com/docker/docker v24.0.7+incompatible // indirect - github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/dvsekhvalnov/jose2go v1.6.0 // indirect @@ -314,6 +314,8 @@ replace ( //github.com/misko9/go-substrate-rpc-client/v4 => github.com/faddat/go-substrate-rpc-client/v4 v4.0.1-0.20240402155230-48db8c110afe github.com/misko9/go-substrate-rpc-client/v4 => github.com/misko9/go-substrate-rpc-client/v4 v4.0.0-20230913220906-b988ea7da0c2 + github.com/strangelove-ventures/interchaintest/v8 => ../../../interchaintest + ) replace github.com/kava-labs/kava => ../../ diff --git a/tests/e2e/e2e_community_update_params_test.go b/tests/e2e/e2e_community_update_params_test.go index ed8c13bd7..c02a0c388 100644 --- a/tests/e2e/e2e_community_update_params_test.go +++ b/tests/e2e/e2e_community_update_params_test.go @@ -3,7 +3,6 @@ package e2e_test import ( "context" "encoding/hex" - "fmt" "time" sdkmath "cosmossdk.io/math" @@ -57,7 +56,6 @@ func (suite *IntegrationTestSuite) TestCommunityUpdateParams_Authority() { ParamsType: govv1.ParamDeposit, }) suite.NoError(err) - fmt.Println("govParamsRes", govParamsRes) // Check initial params communityParamsResInitial, err := suite.Kava.Grpc.Query.Community.Params( @@ -66,15 +64,16 @@ func (suite *IntegrationTestSuite) TestCommunityUpdateParams_Authority() { ) suite.Require().NoError(err) - fmt.Println("communityParamsResInitial", communityParamsResInitial) - // setup kava account // .1 KAVA + min deposit amount for proposal - funds := sdk.NewCoins(ukava(1e5)).Add(govParamsRes.DepositParams.MinDeposit...) + //funds := sdk.NewCoins(ukava(1e5)).Add(govParamsRes.DepositParams.MinDeposit...) + funds := sdk.NewCoins(ukava(1e6)).Add(govParamsRes.DepositParams.MinDeposit...) kavaAcc := suite.Kava.NewFundedAccount("community-update-params", funds) - gasLimit := int64(2e5) - fee := ukava(200) + //gasLimit := int64(2e5) + gasLimit := int64(3e5) + //fee := ukava(200) + fee := ukava(300) // Wait until switchover actually happens - When testing without the upgrade // handler that sets a relative switchover time, the switchover time in @@ -99,8 +98,6 @@ func (suite *IntegrationTestSuite) TestCommunityUpdateParams_Authority() { StakingRewardsPerSecond. Add(sdkmath.LegacyNewDec(1)) - fmt.Println("newStakingRewardsPerSecond", newStakingRewardsPerSecond) - // 1. Proposal // Only modify stakingRewardsPerSecond, as to not re-run the switchover and // to not influence other tests @@ -112,7 +109,6 @@ func (suite *IntegrationTestSuite) TestCommunityUpdateParams_Authority() { communityParamsResInitial.Params.UpgradeTimeSetStakingRewardsPerSecond, ), ) - fmt.Println("updateParamsMsg", updateParamsMsg) // Make sure we're actually changing the params suite.NotEqual( @@ -133,8 +129,6 @@ func (suite *IntegrationTestSuite) TestCommunityUpdateParams_Authority() { ) suite.NoError(err) - fmt.Println("proposalMsg", proposalMsg) - req := util.KavaMsgRequest{ Msgs: []sdk.Msg{proposalMsg}, GasLimit: uint64(gasLimit), @@ -142,13 +136,9 @@ func (suite *IntegrationTestSuite) TestCommunityUpdateParams_Authority() { Memo: "this is a proposal please accept me", } - fmt.Println("test req", req) - res := kavaAcc.SignAndBroadcastKavaTx(req) suite.Require().NoError(res.Err) - fmt.Println("test res", res) - // Wait for proposal to be submitted txRes, err := util.WaitForSdkTxCommit(suite.Kava.Grpc.Query.Tx, res.Result.TxHash, 6*time.Second) suite.Require().NoError(err) diff --git a/tests/e2e/e2e_convert_cosmos_coins_test.go b/tests/e2e/e2e_convert_cosmos_coins_test.go index 5c0ee6a7b..3b6c29e17 100644 --- a/tests/e2e/e2e_convert_cosmos_coins_test.go +++ b/tests/e2e/e2e_convert_cosmos_coins_test.go @@ -65,9 +65,10 @@ func (suite *IntegrationTestSuite) setupAccountWithCosmosCoinERC20Balance( convertAmount, ) tx := util.KavaMsgRequest{ - Msgs: []sdk.Msg{&msg}, - GasLimit: 4e5, - FeeAmount: sdk.NewCoins(ukava(400)), + Msgs: []sdk.Msg{&msg}, + GasLimit: 10e6, + //FeeAmount: sdk.NewCoins(ukava(400)), + FeeAmount: sdk.NewCoins(ukava(10000)), Data: "converting sdk coin to erc20", } res := user.SignAndBroadcastKavaTx(tx) diff --git a/x/community/types/tx.pb.go b/x/community/types/tx.pb.go index ff57f680a..806fba66e 100644 --- a/x/community/types/tx.pb.go +++ b/x/community/types/tx.pb.go @@ -414,10 +414,6 @@ func _Msg_FundCommunityPool_Handler(srv interface{}, ctx context.Context, dec fu } func _Msg_UpdateParams_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - fmt.Println("_Msg_UpdateParams_Handler invoked srv", srv) - fmt.Println("_Msg_UpdateParams_Handler invoked ctx", ctx) - fmt.Println("_Msg_UpdateParams_Handler invoked dec", dec) - fmt.Println("_Msg_UpdateParams_Handler invoked interceptor", interceptor) in := new(MsgUpdateParams) if err := dec(in); err != nil { return nil, err