-
Notifications
You must be signed in to change notification settings - Fork 129
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: cosmwasm contract build flow during e2e test execution (#782)
* Initial cosmwasm build flow * Add example cosmwasm test * Add comment * Move cosmwasm.go to interchaintest package * Rename intro to rust-optimizer * Allow cw contract to compile with custom docker image and version * Scaffold workspace-optimizer and move cosmwasm to own package * workspace-optimizer working, updated comments * Return channels when compiling contracts so that chain setup can proceed in parallel. * Cleanup waiting for compilation * fix lint errors * Add example cosmwasm tests to ci * Revert "Add example cosmwasm tests to ci" This reverts commit ecd6e62. * Check for rust/workspace-optimizer version and set the cache directory appropriately * Fix comment Co-authored-by: Reece Williams <[email protected]> --------- Co-authored-by: Reece Williams <[email protected]>
- Loading branch information
1 parent
fcfcac6
commit 08b20f6
Showing
38 changed files
with
3,859 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
package cosmwasm | ||
|
||
import ( | ||
"context" | ||
"path/filepath" | ||
"fmt" | ||
"runtime" | ||
"io" | ||
"os" | ||
|
||
"github.com/docker/docker/api/types" | ||
"github.com/docker/docker/api/types/container" | ||
"github.com/docker/docker/api/types/mount" | ||
"github.com/docker/docker/client" | ||
"github.com/docker/docker/errdefs" | ||
"github.com/docker/docker/pkg/stdcopy" | ||
"github.com/hashicorp/go-version" | ||
) | ||
|
||
// compile will compile the specified repo using the specified docker image and version | ||
func compile(image string, optVersion string, repoPath string) (string, error) { | ||
// Set the image to pull/use | ||
arch := "" | ||
if runtime.GOARCH == "arm64" { | ||
arch = "-arm64" | ||
} | ||
imageFull := image + arch + ":" + optVersion | ||
|
||
// Check if version is less than 0.13.0, if so, use old cache directory | ||
cacheDir := "/target" | ||
versionThresh, err := version.NewVersion("0.13.0") | ||
if err != nil { | ||
return "", fmt.Errorf("version threshold 0.13.0: %w", err) | ||
} | ||
myVersion, err := version.NewVersion(optVersion) | ||
if err != nil { | ||
return "", fmt.Errorf("version %s: %w", optVersion, err) | ||
} | ||
if myVersion.LessThan(versionThresh) { | ||
cacheDir = "/code/target" | ||
} | ||
|
||
// Get absolute path of contract project | ||
pwd, err := os.Getwd() | ||
if err != nil { | ||
return "", fmt.Errorf("getwd: %w", err) | ||
} | ||
repoPathFull := filepath.Join(pwd, repoPath) | ||
|
||
ctx := context.Background() | ||
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) | ||
if err != nil { | ||
return "", fmt.Errorf("new client with opts: %w", err) | ||
} | ||
defer cli.Close() | ||
|
||
reader, err := cli.ImagePull(ctx, imageFull, types.ImagePullOptions{}) | ||
if err != nil { | ||
return "", fmt.Errorf("pull image %s: %w", imageFull, err) | ||
} | ||
|
||
defer reader.Close() | ||
_, err = io.Copy(os.Stdout, reader) | ||
if err != nil { | ||
return "", fmt.Errorf("io copy %s: %w", imageFull, err) | ||
} | ||
|
||
resp, err := cli.ContainerCreate(ctx, &container.Config{ | ||
Image: imageFull, | ||
Tty: false, | ||
}, &container.HostConfig{ | ||
Mounts: []mount.Mount{ | ||
{ | ||
Type: mount.TypeBind, | ||
Source: repoPathFull, | ||
Target: "/code", | ||
}, | ||
{ | ||
Type: mount.TypeVolume, | ||
Source: filepath.Base(repoPathFull)+"_cache", | ||
Target: cacheDir, | ||
}, | ||
{ | ||
Type: mount.TypeVolume, | ||
Source: "registry_cache", | ||
Target: "/usr/local/cargo/registry", | ||
}, | ||
}, | ||
}, nil, nil, "") | ||
if err != nil { | ||
return "", fmt.Errorf("create container %s: %w", imageFull, err) | ||
} | ||
|
||
if err := cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil { | ||
return "", fmt.Errorf("start container %s: %w", imageFull, err) | ||
} | ||
|
||
statusCh, errCh := cli.ContainerWait(ctx, resp.ID, container.WaitConditionNotRunning) | ||
select { | ||
case err := <-errCh: | ||
if err != nil { | ||
return "", fmt.Errorf("wait container %s: %w", imageFull, err) | ||
} | ||
case <-statusCh: | ||
} | ||
|
||
out, err := cli.ContainerLogs(ctx, resp.ID, types.ContainerLogsOptions{ShowStdout: true}) | ||
if err != nil { | ||
return "", fmt.Errorf("logs container %s: %w", imageFull, err) | ||
} | ||
|
||
_, err = stdcopy.StdCopy(os.Stdout, os.Stderr, out) | ||
if err != nil { | ||
return "", fmt.Errorf("std copy %s: %w", imageFull, err) | ||
} | ||
|
||
err = cli.ContainerStop(ctx, resp.ID, container.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 "", fmt.Errorf("stop container %s: %w", imageFull, err) | ||
} | ||
} | ||
|
||
err = cli.ContainerRemove(ctx, resp.ID, types.ContainerRemoveOptions{ | ||
Force: true, | ||
RemoveVolumes: true, | ||
}) | ||
if err != nil && !errdefs.IsNotFound(err) { | ||
return "", fmt.Errorf("remove container %s: %w", imageFull, err) | ||
} | ||
|
||
return repoPathFull, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
package cosmwasm | ||
|
||
import ( | ||
"path/filepath" | ||
"fmt" | ||
"os" | ||
"strings" | ||
) | ||
|
||
type Contract struct { | ||
DockerImage string | ||
Version string | ||
RelativePath string | ||
wasmBinPathChan chan string | ||
errChan chan error | ||
} | ||
|
||
// NewContract return a contract struct, populated with defaults and its relative path | ||
// relativePath is the relative path to the contract on local machine | ||
func NewContract(relativePath string) *Contract { | ||
return &Contract{ | ||
DockerImage: "cosmwasm/rust-optimizer", | ||
Version: "0.14.0", | ||
RelativePath: relativePath, | ||
} | ||
} | ||
|
||
// WithDockerImage sets a custom docker image to use | ||
func (c *Contract) WithDockerImage(image string) *Contract { | ||
c.DockerImage = image | ||
return c | ||
} | ||
|
||
// WithVersion sets a custom version to use | ||
func (c *Contract) WithVersion(version string) *Contract { | ||
c.Version = version | ||
return c | ||
} | ||
|
||
// Compile will compile the contract | ||
// cosmwasm/rust-optimizer is the expected docker image | ||
func (c *Contract) Compile() *Contract { | ||
c.wasmBinPathChan = make(chan string) | ||
c.errChan = make(chan error, 1) | ||
|
||
go func() { | ||
repoPathFull, err := compile(c.DockerImage, c.Version, c.RelativePath) | ||
if err != nil { | ||
c.errChan <- err | ||
return | ||
} | ||
|
||
// Form the path to the artifacts directory, used for checksum.txt and package.wasm | ||
artifactsPath := filepath.Join(repoPathFull, "artifacts") | ||
|
||
// Parse the checksums.txt for the crate/wasm binary name | ||
checksumsPath := filepath.Join(artifactsPath, "checksums.txt") | ||
checksumsBz, err := os.ReadFile(checksumsPath) | ||
if err != nil { | ||
c.errChan <- fmt.Errorf("checksums read: %w", err) | ||
return | ||
} | ||
_, wasmBin, found := strings.Cut(string(checksumsBz), " ") | ||
if !found { | ||
c.errChan <- fmt.Errorf("wasm binary name not found") | ||
return | ||
} | ||
|
||
// Form the path to the wasm binary | ||
c.wasmBinPathChan <- filepath.Join(artifactsPath, strings.TrimSpace(wasmBin)) | ||
}() | ||
|
||
return c | ||
} | ||
|
||
// WaitForCompile will wait until compilation is complete, this can be called after chain setup | ||
// Successful compilation will return the binary location in a channel | ||
func (c *Contract) WaitForCompile() (string, error) { | ||
contractBinary := "" | ||
select { | ||
case err := <-c.errChan: | ||
return "", err | ||
case contractBinary = <-c.wasmBinPathChan: | ||
} | ||
return contractBinary, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
package cosmwasm | ||
|
||
import ( | ||
"bufio" | ||
"path/filepath" | ||
"fmt" | ||
"os" | ||
"strings" | ||
) | ||
|
||
type Workspace struct { | ||
DockerImage string | ||
Version string | ||
RelativePath string | ||
wasmBinariesChan chan map[string]string | ||
errChan chan error | ||
} | ||
|
||
// NewWorkspace returns a workspace struct, populated with defaults and its relative path | ||
// relativePath is the relative path to the workspace on local machine | ||
func NewWorkspace(relativePath string) *Workspace { | ||
return &Workspace{ | ||
DockerImage: "cosmwasm/workspace-optimizer", | ||
Version: "0.14.0", | ||
RelativePath: relativePath, | ||
} | ||
} | ||
|
||
// WithDockerImage sets a custom docker image to use | ||
func (w *Workspace) WithDockerImage(image string) *Workspace { | ||
w.DockerImage = image | ||
return w | ||
} | ||
|
||
// WithVersion sets a custom version to use | ||
func (w *Workspace) WithVersion(version string) *Workspace { | ||
w.Version = version | ||
return w | ||
} | ||
|
||
// Compile will compile the workspace's contracts | ||
// cosmwasm/workspace-optimizer is the expected docker image | ||
// The workspace object is returned, call WaitForCompile() to get results | ||
func (w *Workspace) Compile() *Workspace { | ||
w.wasmBinariesChan = make(chan map[string]string) | ||
w.errChan = make(chan error, 1) | ||
|
||
go func() { | ||
repoPathFull, err := compile(w.DockerImage, w.Version, w.RelativePath) | ||
if err != nil { | ||
w.errChan <- err | ||
return | ||
} | ||
|
||
// Form the path to the artifacts directory, used for checksum.txt and package.wasm | ||
artifactsPath := filepath.Join(repoPathFull, "artifacts") | ||
|
||
// Parse the checksums.txt for the crate/wasm binary names | ||
wasmBinaries := make(map[string]string) | ||
checksumsPath := filepath.Join(artifactsPath, "checksums.txt") | ||
file, err := os.Open(checksumsPath) | ||
if err != nil { | ||
w.errChan <- fmt.Errorf("checksums open: %w", err) | ||
return | ||
} | ||
scanner := bufio.NewScanner(file) | ||
for scanner.Scan() { | ||
line := scanner.Text() | ||
_, wasmBin, found := strings.Cut(line, " ") | ||
if !found { | ||
w.errChan <- fmt.Errorf("wasm binary name not found") | ||
return | ||
} | ||
wasmBin = strings.TrimSpace(wasmBin) | ||
crateName, _, found := strings.Cut(wasmBin, ".") | ||
if !found { | ||
w.errChan <- fmt.Errorf("wasm binary name invalid") | ||
return | ||
} | ||
wasmBinaries[crateName] = filepath.Join(artifactsPath, wasmBin) | ||
} | ||
w.wasmBinariesChan <- wasmBinaries | ||
}() | ||
|
||
return w | ||
} | ||
|
||
// WaitForCompile will wait until coyympilation is complete, this can be called after chain setup | ||
// Successful compilation will return a map of crate names to binary locations in a channel | ||
func (w *Workspace) WaitForCompile() (map[string]string, error) { | ||
contractBinaries := make(map[string]string) | ||
select { | ||
case err := <-w.errChan: | ||
return contractBinaries, err | ||
case contractBinaries = <-w.wasmBinariesChan: | ||
} | ||
return contractBinaries, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
The rust-optimizer example contains a simple contract that performs minimal functionality. | ||
The single contract uses cosmwasm/rust-optimizer to compile during test execution. | ||
The test case shows how the contract source can integrate with interchaintest: building the contract, spinning up a chain, storing it on-chain, and instantiating/querying/executing against it. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
[alias] | ||
wasm = "build --target wasm32-unknown-unknown --release --lib" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
target/ | ||
artifacts/ |
Oops, something went wrong.