From 735fa0ecdb084be772355b54813ff6d96c7b8d96 Mon Sep 17 00:00:00 2001 From: Jacob Gadikian Date: Tue, 20 May 2025 22:08:54 +0700 Subject: [PATCH 01/19] Document CGO interface --- .gitignore | 1 + README.md | 1 + docs/CGO_INTERFACE.md | 61 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 docs/CGO_INTERFACE.md diff --git a/.gitignore b/.gitignore index b8bff339e..83622b14a 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ a.out # direnv Nix stuff .direnv/ +reference-code/ diff --git a/README.md b/README.md index f123c27a6..b4fcdabd8 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,7 @@ CGO_ENABLED=0 go build ./types This package contains the code binding the libwasmvm build to the Go code. All low level FFI handling code belongs there. This package can only be built using cgo. Using the `internal/` convention makes this package fully private. +For an overview of the exported C functions and their Go wrappers see [docs/CGO_INTERFACE.md](docs/CGO_INTERFACE.md). #### Package github.com/CosmWasm/wasmvm diff --git a/docs/CGO_INTERFACE.md b/docs/CGO_INTERFACE.md new file mode 100644 index 000000000..eea2170db --- /dev/null +++ b/docs/CGO_INTERFACE.md @@ -0,0 +1,61 @@ +# CGO interface + +This document summarises the functions exported by the `libwasmvm` C library and their +wrappers implemented in the Go package `internal/api`. It also lists the Go data +structures from `types/` that cross the cgo boundary. + +## Exported functions and Go wrappers + +| C function (bindings.h) | Go wrapper | +| --- | --- | +| `init_cache` | `api.InitCache` | +| `store_code` | `api.StoreCode`/`api.StoreCodeUnchecked` | +| `remove_wasm` | `api.RemoveCode` | +| `load_wasm` | `api.GetCode` | +| `pin` | `api.Pin` | +| `unpin` | `api.Unpin` | +| `analyze_code` | `api.AnalyzeCode` | +| `get_metrics` | `api.GetMetrics` | +| `get_pinned_metrics` | `api.GetPinnedMetrics` | +| `release_cache` | `api.ReleaseCache` | +| `instantiate` | `api.Instantiate` | +| `execute` | `api.Execute` | +| `migrate` | `api.Migrate` | +| `migrate_with_info` | `api.MigrateWithInfo` | +| `sudo` | `api.Sudo` | +| `reply` | `api.Reply` | +| `query` | `api.Query` | +| `ibc_channel_open` | `api.IBCChannelOpen` | +| `ibc_channel_connect` | `api.IBCChannelConnect` | +| `ibc_channel_close` | `api.IBCChannelClose` | +| `ibc_packet_receive` | `api.IBCPacketReceive` | +| `ibc_packet_ack` | `api.IBCPacketAck` | +| `ibc_packet_timeout` | `api.IBCPacketTimeout` | +| `ibc_source_callback` | `api.IBCSourceCallback` | +| `ibc_destination_callback` | `api.IBCDestinationCallback` | +| `ibc2_packet_receive` | `api.IBC2PacketReceive` | +| `ibc2_packet_ack` | `api.IBC2PacketAck` | +| `ibc2_packet_timeout` | `api.IBC2PacketTimeout` | +| `ibc2_packet_send` | `api.IBC2PacketSend` | +| `new_unmanaged_vector` | internal helpers in `memory.go` | +| `destroy_unmanaged_vector` | internal helpers in `memory.go` | +| `version_str` | `api.LibwasmvmVersion` | + +## Data structures crossing the boundary + +Several types defined in the `types` package are used when calling into +`libwasmvm` or when reading its results: + +- `VMConfig` – configuration passed to `InitCache`. +- `GasMeter` – interface supplying gas information for execution. +- `KVStore` and `Iterator` – database interface used by the VM. +- `GoAPI` – set of callbacks for address handling. +- `Querier` – interface for custom queries. +- `AnalysisReport` – returned from `AnalyzeCode`. +- `Metrics` and `PinnedMetrics` – returned from metric queries. +- `GasReport` – returned from execution functions. + +All other data (such as contract messages, environment and info values) is +passed as byte slices and therefore does not rely on additional exported Go +structures. + From 6e03e6b42dde6b62935eac4a09cd2bd0fdf45455 Mon Sep 17 00:00:00 2001 From: Jacob Gadikian Date: Tue, 20 May 2025 22:33:48 +0700 Subject: [PATCH 02/19] Add AGENTS instructions --- AGENTS.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..4796846d1 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,15 @@ +# AGENTS instructions + +## Formatting + +- Run `gofmt -w` and `gofumpt -w` on any modified Go files. +- Run `cargo fmt` on any modified Rust files. +- Run `prettier -w` on any modified Markdown files. + +## Testing + +- When Go or Rust code is changed, run `make test` before committing. + +## PR message + +- Summarize the changes and mention any test commands that were executed. From 03cc9ab0b68b4a8c69a4084fd1b43d773b1c7090 Mon Sep 17 00:00:00 2001 From: Jacob Gadikian Date: Tue, 20 May 2025 22:34:56 +0700 Subject: [PATCH 03/19] Update AGENTS.md --- AGENTS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AGENTS.md b/AGENTS.md index 4796846d1..ee0da6053 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,6 +8,7 @@ ## Testing +- Use `golangci-lint run ./... --fix` before running `make test` and make sure that all lint issues are fixed before running tests. - When Go or Rust code is changed, run `make test` before committing. ## PR message From 7cc9cbe7b687e19eb7679a1128c04810c1782d2c Mon Sep 17 00:00:00 2001 From: Jacob Gadikian Date: Tue, 20 May 2025 22:38:00 +0700 Subject: [PATCH 04/19] Fix wazero runtime implementation --- .gitignore | 2 + go.mod | 13 +-- internal/wazeroimpl/runtime.go | 144 +++++++++++++++++++++++++++++++++ 3 files changed, 154 insertions(+), 5 deletions(-) create mode 100644 internal/wazeroimpl/runtime.go diff --git a/.gitignore b/.gitignore index b8bff339e..0c71e4711 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,5 @@ a.out # direnv Nix stuff .direnv/ + +reference-code/ diff --git a/go.mod b/go.mod index 9dffc99b7..5a6ab9790 100644 --- a/go.mod +++ b/go.mod @@ -1,12 +1,15 @@ module github.com/CosmWasm/wasmvm/v3 -go 1.22 +go 1.22.0 + +toolchain go1.23.8 require ( - github.com/google/btree v1.0.0 - github.com/shamaton/msgpack/v2 v2.2.0 - github.com/stretchr/testify v1.8.1 - golang.org/x/sys v0.16.0 + github.com/google/btree v1.0.0 + github.com/shamaton/msgpack/v2 v2.2.0 + github.com/stretchr/testify v1.8.1 + github.com/tetratelabs/wazero v1.9.0 + golang.org/x/sys v0.16.0 ) require ( diff --git a/internal/wazeroimpl/runtime.go b/internal/wazeroimpl/runtime.go new file mode 100644 index 000000000..dca31ea86 --- /dev/null +++ b/internal/wazeroimpl/runtime.go @@ -0,0 +1,144 @@ +package wazeroimpl + +import ( + "context" + "encoding/hex" + "fmt" + "unsafe" + + "github.com/tetratelabs/wazero" + "github.com/tetratelabs/wazero/api" + + "github.com/CosmWasm/wasmvm/v3/types" +) + +// Cache manages a wazero runtime and compiled modules. +type Cache struct { + runtime wazero.Runtime + modules map[string]wazero.CompiledModule +} + +// InitCache creates a new wazero Runtime with memory limits similar to api.InitCache. +func InitCache(config types.VMConfig) (*Cache, error) { + ctx := context.Background() + limitBytes := *(*uint32)(unsafe.Pointer(&config.Cache.InstanceMemoryLimitBytes)) + r := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfig().WithMemoryLimitPages(limitBytes/65536)) + return &Cache{ + runtime: r, + modules: make(map[string]wazero.CompiledModule), + }, nil +} + +// Close releases all resources of the runtime. +func (c *Cache) Close(ctx context.Context) error { + if c.runtime != nil { + return c.runtime.Close(ctx) + } + return nil +} + +// Compile stores a compiled module under the given checksum. +func (c *Cache) Compile(ctx context.Context, checksum types.Checksum, wasm []byte) error { + mod, err := c.runtime.CompileModule(ctx, wasm) + if err != nil { + return err + } + c.modules[hex.EncodeToString(checksum)] = mod + return nil +} + +// getModule returns the compiled module for the checksum. +func (c *Cache) getModule(checksum types.Checksum) (wazero.CompiledModule, bool) { + mod, ok := c.modules[hex.EncodeToString(checksum)] + return mod, ok +} + +// registerHost builds an env module with callbacks for the given state. +func (c *Cache) registerHost(ctx context.Context, store types.KVStore, apiImpl *types.GoAPI, q *types.Querier, gm types.GasMeter) (api.Module, error) { + builder := c.runtime.NewHostModuleBuilder("env") + + // db_read + builder.NewFunctionBuilder().WithGoModuleFunction(api.GoModuleFunc(func(ctx context.Context, m api.Module, stack []uint64) { + keyPtr := uint32(stack[0]) + keyLen := uint32(stack[1]) + outPtr := uint32(stack[2]) + mem := m.Memory() + key, _ := mem.Read(keyPtr, keyLen) + value := store.Get(key) + if value == nil { + _ = mem.WriteUint32Le(outPtr, 0) + return + } + _ = mem.WriteUint32Le(outPtr, uint32(len(value))) + mem.Write(outPtr+4, value) + }), []api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{}).Export("db_read") + + // db_write + builder.NewFunctionBuilder().WithGoModuleFunction(api.GoModuleFunc(func(ctx context.Context, m api.Module, stack []uint64) { + keyPtr := uint32(stack[0]) + keyLen := uint32(stack[1]) + valPtr := uint32(stack[2]) + valLen := uint32(stack[3]) + mem := m.Memory() + key, _ := mem.Read(keyPtr, keyLen) + val, _ := mem.Read(valPtr, valLen) + store.Set(key, val) + }), []api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{}).Export("db_write") + + // db_remove + builder.NewFunctionBuilder().WithGoModuleFunction(api.GoModuleFunc(func(ctx context.Context, m api.Module, stack []uint64) { + keyPtr := uint32(stack[0]) + keyLen := uint32(stack[1]) + mem := m.Memory() + key, _ := mem.Read(keyPtr, keyLen) + store.Delete(key) + }), []api.ValueType{api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{}).Export("db_remove") + + // query_external - simplified: returns 0 length + builder.NewFunctionBuilder().WithGoModuleFunction(api.GoModuleFunc(func(ctx context.Context, m api.Module, stack []uint64) { + resPtr := uint32(stack[2]) + _ = m.Memory().WriteUint32Le(resPtr, 0) + }), []api.ValueType{api.ValueTypeI64, api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{}).Export("query_external") + + return builder.Instantiate(ctx) +} + +// Instantiate loads and runs the contract's instantiate function. +func (c *Cache) Instantiate(ctx context.Context, checksum types.Checksum, env, info, msg []byte, store types.KVStore, apiImpl *types.GoAPI, q *types.Querier, gm types.GasMeter) error { + compiled, ok := c.getModule(checksum) + if !ok { + return fmt.Errorf("module not found") + } + _, err := c.registerHost(ctx, store, apiImpl, q, gm) + if err != nil { + return err + } + mod, err := c.runtime.InstantiateModule(ctx, compiled, wazero.NewModuleConfig()) + if err != nil { + return err + } + if fn := mod.ExportedFunction("instantiate"); fn != nil { + _, err = fn.Call(ctx) + } + return err +} + +// Execute runs the contract's execute function. +func (c *Cache) Execute(ctx context.Context, checksum types.Checksum, env, info, msg []byte, store types.KVStore, apiImpl *types.GoAPI, q *types.Querier, gm types.GasMeter) error { + compiled, ok := c.getModule(checksum) + if !ok { + return fmt.Errorf("module not found") + } + _, err := c.registerHost(ctx, store, apiImpl, q, gm) + if err != nil { + return err + } + mod, err := c.runtime.InstantiateModule(ctx, compiled, wazero.NewModuleConfig()) + if err != nil { + return err + } + if fn := mod.ExportedFunction("execute"); fn != nil { + _, err = fn.Call(ctx) + } + return err +} From 9915cd5affbb04947acb0ae9e8969a5e85d77be7 Mon Sep 17 00:00:00 2001 From: Jacob Gadikian Date: Tue, 20 May 2025 22:50:00 +0700 Subject: [PATCH 05/19] Split CI workflow into separate Go and Rust jobs --- .circleci/config.yml | 515 ------------------------------------- .github/workflows/go.yml | 55 ++++ .github/workflows/rust.yml | 50 ++++ 3 files changed, 105 insertions(+), 515 deletions(-) delete mode 100644 .circleci/config.yml create mode 100644 .github/workflows/go.yml create mode 100644 .github/workflows/rust.yml diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 61e2b4a8f..000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,515 +0,0 @@ -version: 2.1 - -orbs: - win: circleci/windows@5.0 - -jobs: - # All checks on the codebase that can run in parallel to build_shared_library - libwasmvm_sanity: - docker: - - image: cimg/rust:1.81.0 - steps: - - checkout - - run: - name: Show Rust version information - command: rustc --version; cargo --version; rustup --version - - run: - name: Add Rust components - command: rustup component add rustfmt - - restore_cache: - keys: - - cargocache-v3-libwasmvm_sanity-rust:1.81.0-{{ checksum "libwasmvm/Cargo.lock" }} - - cargocache-v3-libwasmvm_sanity-rust:1.81.0- - - run: - name: Ensure libwasmvm/bindings.h is up-to-date - working_directory: libwasmvm - command: | - cargo check - CHANGES_IN_REPO=$(git status --porcelain bindings.h) - if [[ -n "$CHANGES_IN_REPO" ]]; then - echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" - git status && git --no-pager diff - exit 1 - fi - - run: - # It is easy to update libwasmvm/bindings.h as part of the development but forget `make update-bindings` - name: Ensure internal/api/bindings.h is up to date - command: diff libwasmvm/bindings.h internal/api/bindings.h - - run: - name: Check Rust formatting - working_directory: libwasmvm - command: cargo fmt -- --check - - run: - name: Run unit tests - working_directory: libwasmvm - command: cargo test - - run: - name: Build docs - working_directory: libwasmvm - command: cargo doc --no-deps - - run: - name: Test docs - working_directory: libwasmvm - command: | - sed -i '/^crate-type = \["cdylib"\]/d' Cargo.toml - cargo test --doc - - save_cache: - paths: - - ~/.cargo/registry - - libwasmvm/target/debug/.fingerprint - - libwasmvm/target/debug/build - - libwasmvm/target/debug/deps - - libwasmvm/target/release/.fingerprint - - libwasmvm/target/release/build - - libwasmvm/target/release/deps - key: cargocache-v3-libwasmvm_sanity-rust:1.81.0-{{ checksum "libwasmvm/Cargo.lock" }} - - libwasmvm_clippy: - parameters: - rust-version: - type: string - docker: - - image: rust:<< parameters.rust-version >> - steps: - - checkout - - run: - name: Version information - command: rustc --version && cargo --version - - restore_cache: - keys: - - v3-libwasmvm_clippy-rust:<< parameters.rust-version >>-{{ checksum "libwasmvm/Cargo.lock" }} - - v3-libwasmvm_clippy-rust:<< parameters.rust-version >>- - - run: - name: Add clippy component - command: rustup component add clippy - - run: - name: Run clippy - working_directory: libwasmvm - command: cargo clippy --all-targets -- -D warnings - - save_cache: - paths: - - ~/.cargo/registry - - libwasmvm/target/debug/.fingerprint - - libwasmvm/target/debug/build - - libwasmvm/target/debug/deps - - libwasmvm/target/release/.fingerprint - - libwasmvm/target/release/build - - libwasmvm/target/release/deps - key: v3-libwasmvm_clippy-rust:<< parameters.rust-version >>-{{ checksum "libwasmvm/Cargo.lock" }} - - # This performs all the Rust debug builds on Windows. Similar to libwasmvm_sanity - # but avoids duplicating things that are not platform dependent. - libwasmvm_sanity_windows: - executor: - name: win/default - shell: bash.exe - steps: - - checkout - - run: - name: Reset git config set by CircleCI to make Cargo work - command: git config --global --unset url.ssh://git@github.com.insteadof - - run: - name: Install Rust - command: | - set -o errexit - curl -sS --output rustup-init.exe https://static.rust-lang.org/rustup/dist/x86_64-pc-windows-msvc/rustup-init.exe - ./rustup-init.exe --no-modify-path --profile minimal --default-toolchain 1.81.0 -y - echo 'export PATH="$PATH;$USERPROFILE/.cargo/bin"' >> "$BASH_ENV" - - run: - name: Show Rust version information - command: rustc --version; cargo --version; rustup --version - - restore_cache: - keys: - - cachev4-libwasmvm_sanity_windows-rust:1.81.0-{{ checksum "libwasmvm/Cargo.lock" }} - - cachev4-libwasmvm_sanity_windows-rust:1.81.0- - - run: - name: Run unit tests - working_directory: libwasmvm - command: cargo test - - save_cache: - paths: - # ".." is the easiest way to get $HOME here (pwd is $HOME\project) - - ../.cargo/registry - - libwasmvm/target/debug/.fingerprint - - libwasmvm/target/debug/build - - libwasmvm/target/debug/deps - key: cachev4-libwasmvm_sanity_windows-rust:1.81.0-{{ checksum "libwasmvm/Cargo.lock" }} - - libwasmvm_audit: - docker: - # The audit tool might use a more modern Rust version than the build jobs. See - # "Tooling Rust compiler" in docs/COMPILER_VERSIONS.md - - image: cimg/rust:1.81.0 - steps: - - checkout - - run: - name: Install OpenSSL - command: | - sudo apt update - sudo apt install libssl-dev - - run: - name: Show Rust version information - command: rustc --version; cargo --version; rustup --version - - restore_cache: - keys: - - v3-libwasmvm_audit-rust:1.81.0-{{ checksum "libwasmvm/Cargo.lock" }} - - v3-libwasmvm_audit-rust:1.81.0- - - run: - name: Install cargo-audit - command: cargo install --debug cargo-audit --version 0.21.0 --locked - - run: - name: Run cargo-audit - working_directory: libwasmvm - command: cargo audit - - save_cache: - paths: - - ~/.cargo/registry - key: v3-libwasmvm_audit-rust:1.81.0-{{ checksum "libwasmvm/Cargo.lock" }} - - format-go: - docker: - - image: cimg/go:1.22.12 - steps: - - run: - name: Install gofumpt - command: go install mvdan.cc/gofumpt@v0.4.0 - - checkout - - run: - name: Check Go formatting with gofmt - command: | - [ "$(gofmt -l .)" = "" ] || (gofmt -d . && exit 1) - - run: - name: Check Go formatting with gofumpt - command: | - [ "$(gofumpt -l .)" = "" ] || (gofumpt -d . && exit 1) - - # Build types and cosmwam package without cgo - wasmvm_no_cgo: - docker: - - image: cimg/go:1.22.12 - steps: - - checkout - - run: - name: Build package "types" without cgo - command: CGO_ENABLED=0 go build ./types - - run: - name: Build package "cosmwasm" without cgo - command: CGO_ENABLED=0 go build . - - run: - name: Test package "types" without cgo - command: CGO_ENABLED=0 go test ./types - - run: - name: Test package "cosmwasm" without cgo - command: CGO_ENABLED=0 go test . - - # Build types and cosmwasm with libwasmvm linking disabled - nolink_libwasmvm: - docker: - - image: cimg/go:1.22.12 - steps: - - checkout - - run: - name: Build package "types" with libwasmvm linking disabled - command: go build -tags "nolink_libwasmvm" ./types - - run: - name: Build package "cosmwasm" with libwasmvm linking disabled - command: go build -tags "nolink_libwasmvm" . - - run: - name: Test package "types" with libwasmvm linking disabled - command: go test -tags "nolink_libwasmvm" ./types - - run: - name: Test package "cosmwasm" with libwasmvm linking disabled - command: go test -tags "nolink_libwasmvm" . - - tidy-go: - docker: - - image: cimg/go:1.22.12 - steps: - - checkout - - run: - name: Check go mod tidy - # Use --check or --exit-code when available (Go 1.22?) - # https://github.com/golang/go/issues/27005 - command: | - go mod tidy - CHANGES_IN_REPO=$(git status --porcelain) - if [[ -n "$CHANGES_IN_REPO" ]]; then - echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" - git status && git --no-pager diff - exit 1 - fi - - format-scripts: - docker: - - image: cimg/go:1.22.12 - steps: - - run: - name: Install shfmt - command: GO111MODULE=on go install mvdan.cc/sh/v3/cmd/shfmt@v3.7.0 - - checkout - - run: - name: Run shfmt - command: shfmt --diff . - - lint-scripts: - docker: - - image: ubuntu:20.04 - steps: - - run: - name: Install packages - command: | - apt update - apt install -y git shellcheck - - checkout - - run: - name: Run shellcheck - command: find . -name "*.sh" -exec shellcheck {} + - - build_shared_library: - docker: - # libwasmvm versions built with 1.81 are broken, so we use 1.82 here - - image: cimg/rust:1.82.0 - steps: - - checkout - - run: - name: Show version information - command: rustc --version; cargo --version; rustup --version - - restore_cache: - keys: - - cargocache-v3-build_shared_library-rust:1.82.0-{{ checksum "libwasmvm/Cargo.lock" }} - - cargocache-v3-build_shared_library-rust:1.82.0- - - run: - name: Create release build of libwasmvm - command: make build-libwasmvm - - persist_to_workspace: - root: ./internal/api - paths: - - libwasmvm.x86_64.so - - save_cache: - paths: - - ~/.cargo/registry - - libwasmvm/target/debug/.fingerprint - - libwasmvm/target/debug/build - - libwasmvm/target/debug/deps - - libwasmvm/target/release/.fingerprint - - libwasmvm/target/release/build - - libwasmvm/target/release/deps - key: cargocache-v3-build_shared_library-rust:1.81.0-{{ checksum "libwasmvm/Cargo.lock" }} - - # Test the Go project and run benchmarks - wasmvm_test: - docker: - - image: cimg/go:1.22.12 - environment: - GORACE: "halt_on_error=1" - BUILD_VERSION: $(echo ${CIRCLE_SHA1} | cut -c 1-10) - steps: - - checkout - - attach_workspace: - at: /tmp/builds - - run: - name: Copy .so build - command: cp /tmp/builds/libwasmvm.x86_64.so ./internal/api - # Debugging step to better understand what is linked here - - run: - name: Check .so files - command: | - ls -lA ./internal/api/libwasmvm.*.so - sha256sum ./internal/api/libwasmvm.*.so - ldd ./internal/api/libwasmvm.x86_64.so - - run: - name: Build Go project - command: make build-go - - run: - name: Go integration tests - command: make test - - run: - name: Go tests with cgo and race condition safety checks - command: make test-safety - - run: - name: Go benchmarks - command: make bench - - test_alpine_build: - machine: - image: ubuntu-2004:2022.10.1 - steps: - - checkout - - run: make test-alpine - - run: - name: Debug build results - command: ls -l ./internal/api - - deploy_to_git: - machine: - image: ubuntu-2004:2022.10.1 - resource_class: xlarge - steps: - - add_ssh_keys: - fingerprints: - # Custom read/write deployment key with private key stored on CircleCI - # (see https://app.circleci.com/settings/project/github/CosmWasm/wasmvm/ssh and https://github.com/CosmWasm/wasmvm/settings/keys) - - "31:de:e5:84:1b:12:81:94:aa:06:50:c0:cb:bd:79:f0" - - checkout - - run: - name: Build shared library for Linux - command: make release-build-linux - - run: - name: Build shared library for macOS - command: make release-build-macos - # Shared libraries for Windows (.dll) currently do not work (https://github.com/CosmWasm/wasmvm/issues/389) - # and .dll builds are not deterministic. - # Deactivating this step to avoid polluting the git hostory. - # - # - run: - # name: Build shared library for Windows - # command: make release-build-windows - - run: - name: Debug build results - command: ls -l ./internal/api - - run: - name: Configure git user - # This is not a GitHub user and no permissions can be configured other than "push access", which - # we can configure for Deploy keys at https://github.com/CosmWasm/wasmvm/settings/keys - command: | - git config user.email "wasmvm@circleci.confio.example.com" - git config user.name "Deployer" - - run: - name: Check-in and push new libraries - command: | - git status - git add ./internal/api - git commit --allow-empty -m '[skip ci] Built release libraries' - git push origin $CIRCLE_BRANCH - - build_static_lib: - machine: - image: ubuntu-2004:2022.10.1 - resource_class: xlarge - steps: - - run: - name: Check Go version # needed for ghr installation - command: go version - - run: - name: Install ghr - command: | - go install github.com/tcnksm/ghr@v0.16.0 - ghr --version - - checkout - - run: - name: Build static library for Alpine - command: make release-build-alpine - - run: - name: Build static library for MacOS - command: make release-build-macos-static - - run: - name: Debug build results - command: ls -l ./internal/api - # Deploy to GitHub releases on tag builds. At some point we might want to extract - # those steps into a separate job for better maintainability. - - when: - condition: << pipeline.git.tag >> - steps: - - run: - name: Collect artifacts - command: | - mkdir artifacts - - # Static (from build) - cp ./internal/api/libwasmvm_muslc.x86_64.a artifacts/ - cp ./internal/api/libwasmvm_muslc.aarch64.a artifacts/ - cp ./internal/api/libwasmvmstatic_darwin.a artifacts/ - - # Shared (from git) - cp ./internal/api/libwasmvm.aarch64.so artifacts/ - cp ./internal/api/libwasmvm.x86_64.so artifacts/ - cp ./internal/api/libwasmvm.dylib artifacts/ - - run: - name: Create checksums - working_directory: artifacts - command: sha256sum * > checksums.txt && cat checksums.txt - - run: - name: Publish artifacts on GitHub - command: | - TAG="$CIRCLE_TAG" - TITLE="$TAG" - BODY="Build artifacts generated at this tag." - # Check if tag is a version without suffix (e.g. -rc or -beta) and - # set prerelease flag accordingly - [[ "$TAG" =~ ^v[0-9]+.[0-9]+.[0-9]+$ ]] || PRERELEASE=-prerelease - ghr -t "$GITHUB_TOKEN" \ - -u "$CIRCLE_PROJECT_USERNAME" -r "$CIRCLE_PROJECT_REPONAME" \ - -c "$CIRCLE_SHA1" \ - -n "$TITLE" -b "$BODY" \ - -delete \ - $PRERELEASE \ - "$TAG" ./artifacts/ - -workflows: - version: 2 - build_and_test: - jobs: - - libwasmvm_sanity - # Temporarily disabled. This check is still running on main. - # - libwasmvm_sanity_windows - - libwasmvm_clippy: - matrix: - parameters: - # Run with MSRV and some modern stable Rust - rust-version: ["1.81.0", "1.82.0"] - - libwasmvm_audit - - format-go - - wasmvm_no_cgo - - nolink_libwasmvm - - tidy-go - - format-scripts - - lint-scripts - - build_shared_library: - filters: # required since other jobs with tag filters require this one - tags: - only: /.*/ - - wasmvm_test: - requires: - - build_shared_library - - build_static_lib: - requires: - - build_shared_library - filters: - # tags and branches are OR combined - tags: - only: /^v[0-9]+\.[0-9]+\.[0-9]+.*/ - branches: - only: - # long living branches - - main - - 0.14-dev - # This is long running, so only double-check on merge commits - # ensures that all code works on alpine linux - - test_alpine_build: - filters: - tags: - ignore: - - /.*/ - branches: - only: - # long living branches - - main - # Development - - GoIter-creation - # Run only on main, not on tags (auto-build on merge PR) - - deploy_to_git: - requires: - - libwasmvm_sanity - - format-go - - tidy-go - - format-scripts - - lint-scripts - - wasmvm_test - filters: - tags: - ignore: - - /.*/ - branches: - only: - - main - - /^release\/.*/ diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml new file mode 100644 index 000000000..828043c18 --- /dev/null +++ b/.github/workflows/go.yml @@ -0,0 +1,55 @@ +name: Go Build and Test + +on: + push: + branches: [ main ] + pull_request: + workflow_dispatch: + +jobs: + go: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version: '1.22.12' + cache: false + + - name: Install gofumpt + run: go install mvdan.cc/gofumpt@v0.4.0 + + - name: Install shfmt + run: GO111MODULE=on go install mvdan.cc/sh/v3/cmd/shfmt@v3.7.0 + + - name: Install shellcheck + run: sudo apt-get update && sudo apt-get install -y shellcheck + + - name: Check Go formatting with gofmt + run: | + [ "$(gofmt -l .)" = "" ] || (gofmt -d . && exit 1) + + - name: Check Go formatting with gofumpt + run: | + [ "$(gofumpt -l .)" = "" ] || (gofumpt -d . && exit 1) + + - name: Check go mod tidy + run: | + go mod tidy + git diff --exit-code + + - name: Run shfmt + run: shfmt --diff . + + - name: Run shellcheck + run: find . -name "*.sh" -exec shellcheck {} + + + - name: Build Go project + run: make build-go + + - name: Go integration tests + run: make test + + - name: Go safety tests + run: make test-safety diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 000000000..cbc9e6650 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,50 @@ +name: Rust Build and Test + +on: + push: + branches: [ main ] + pull_request: + workflow_dispatch: + +jobs: + rust: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: 1.82.0 + profile: minimal + components: rustfmt, clippy + override: true + + - name: Check Rust formatting + run: cargo fmt -- --check + working-directory: libwasmvm + + - name: Run clippy + run: cargo clippy --all-targets -- -D warnings + working-directory: libwasmvm + + - name: Run Rust tests + run: cargo test + working-directory: libwasmvm + + - name: Build docs + run: cargo doc --no-deps + working-directory: libwasmvm + + - name: Test docs + run: | + sed -i '/^crate-type = \["cdylib"\]/d' Cargo.toml + cargo test --doc + working-directory: libwasmvm + + - name: Install cargo-audit + run: cargo install --debug cargo-audit --version 0.21.0 --locked + + - name: Run cargo-audit + run: cargo audit + working-directory: libwasmvm From 52e74e6624bceb3d8cfb425b8cfd35c1401ce243 Mon Sep 17 00:00:00 2001 From: Jacob Gadikian Date: Tue, 20 May 2025 22:52:11 +0700 Subject: [PATCH 06/19] Update go.yml --- .github/workflows/go.yml | 25 +------------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 828043c18..8a7d89548 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -14,37 +14,14 @@ jobs: - uses: actions/setup-go@v5 with: - go-version: '1.22.12' + go-version: '1.24' cache: false - - name: Install gofumpt - run: go install mvdan.cc/gofumpt@v0.4.0 - - - name: Install shfmt - run: GO111MODULE=on go install mvdan.cc/sh/v3/cmd/shfmt@v3.7.0 - - - name: Install shellcheck - run: sudo apt-get update && sudo apt-get install -y shellcheck - - - name: Check Go formatting with gofmt - run: | - [ "$(gofmt -l .)" = "" ] || (gofmt -d . && exit 1) - - - name: Check Go formatting with gofumpt - run: | - [ "$(gofumpt -l .)" = "" ] || (gofumpt -d . && exit 1) - - name: Check go mod tidy run: | go mod tidy git diff --exit-code - - name: Run shfmt - run: shfmt --diff . - - - name: Run shellcheck - run: find . -name "*.sh" -exec shellcheck {} + - - name: Build Go project run: make build-go From ba335f107e95317270098103a7079d0a4a020571 Mon Sep 17 00:00:00 2001 From: Jacob Gadikian Date: Tue, 20 May 2025 22:53:21 +0700 Subject: [PATCH 07/19] Update rust.yml --- .github/workflows/rust.yml | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index cbc9e6650..5e89394e6 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -15,7 +15,7 @@ jobs: - name: Set up Rust uses: actions-rs/toolchain@v1 with: - toolchain: 1.82.0 + toolchain: stable profile: minimal components: rustfmt, clippy override: true @@ -41,10 +41,3 @@ jobs: sed -i '/^crate-type = \["cdylib"\]/d' Cargo.toml cargo test --doc working-directory: libwasmvm - - - name: Install cargo-audit - run: cargo install --debug cargo-audit --version 0.21.0 --locked - - - name: Run cargo-audit - run: cargo audit - working-directory: libwasmvm From ebc23bf165b3e8aef01fd184fc893e5e18f60d71 Mon Sep 17 00:00:00 2001 From: Jacob Gadikian Date: Tue, 20 May 2025 15:57:01 +0000 Subject: [PATCH 08/19] tidy --- go.mod | 10 +++++----- go.sum | 2 ++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 5a6ab9790..6abb48573 100644 --- a/go.mod +++ b/go.mod @@ -5,11 +5,11 @@ go 1.22.0 toolchain go1.23.8 require ( - github.com/google/btree v1.0.0 - github.com/shamaton/msgpack/v2 v2.2.0 - github.com/stretchr/testify v1.8.1 - github.com/tetratelabs/wazero v1.9.0 - golang.org/x/sys v0.16.0 + github.com/google/btree v1.0.0 + github.com/shamaton/msgpack/v2 v2.2.0 + github.com/stretchr/testify v1.8.1 + github.com/tetratelabs/wazero v1.9.0 + golang.org/x/sys v0.16.0 ) require ( diff --git a/go.sum b/go.sum index 0e767c24f..8c39224db 100644 --- a/go.sum +++ b/go.sum @@ -22,6 +22,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I= +github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM= golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 3e2d60aa637b38029ce9bc731e8f5013856b492d Mon Sep 17 00:00:00 2001 From: Jacob Gadikian Date: Tue, 20 May 2025 23:14:07 +0700 Subject: [PATCH 09/19] Add wazero build option --- ibc_test.go | 2 +- lib_libwasmvm.go | 2 +- lib_libwasmvm_test.go | 2 +- lib_libwasmvm_wazero.go | 190 ++++++++++++++++++++++++++++++++++++++++ version_cgo.go | 2 +- version_no_cgo.go | 2 +- version_wazero.go | 7 ++ 7 files changed, 202 insertions(+), 5 deletions(-) create mode 100644 lib_libwasmvm_wazero.go create mode 100644 version_wazero.go diff --git a/ibc_test.go b/ibc_test.go index 265116792..8ec957f2e 100644 --- a/ibc_test.go +++ b/ibc_test.go @@ -1,4 +1,4 @@ -//go:build cgo && !nolink_libwasmvm +//go:build cgo && !wazero package cosmwasm diff --git a/lib_libwasmvm.go b/lib_libwasmvm.go index d581e5310..9a3c197a5 100644 --- a/lib_libwasmvm.go +++ b/lib_libwasmvm.go @@ -1,4 +1,4 @@ -//go:build cgo && !nolink_libwasmvm +//go:build cgo && !wazero // This file contains the part of the API that is exposed when libwasmvm // is available (i.e. cgo is enabled and nolink_libwasmvm is not set). diff --git a/lib_libwasmvm_test.go b/lib_libwasmvm_test.go index a799ab7c9..918d9c563 100644 --- a/lib_libwasmvm_test.go +++ b/lib_libwasmvm_test.go @@ -1,4 +1,4 @@ -//go:build cgo && !nolink_libwasmvm +//go:build cgo && !wazero package cosmwasm diff --git a/lib_libwasmvm_wazero.go b/lib_libwasmvm_wazero.go new file mode 100644 index 000000000..77fade170 --- /dev/null +++ b/lib_libwasmvm_wazero.go @@ -0,0 +1,190 @@ +//go:build wazero + +package cosmwasm + +import ( + "context" + "fmt" + + "github.com/CosmWasm/wasmvm/v3/internal/wazeroimpl" + "github.com/CosmWasm/wasmvm/v3/types" +) + +// VM implements a very small subset of the cosmwasm VM using the wazero runtime. +type VM struct { + cache *wazeroimpl.Cache + printDebug bool +} + +// NewVM creates a new wazero based VM. +func NewVM(dataDir string, supportedCapabilities []string, memoryLimit uint32, printDebug bool, cacheSize uint32) (*VM, error) { + return NewVMWithConfig(types.VMConfig{ + Cache: types.CacheOptions{ + BaseDir: dataDir, + AvailableCapabilities: supportedCapabilities, + MemoryCacheSizeBytes: types.NewSizeMebi(cacheSize), + InstanceMemoryLimitBytes: types.NewSizeMebi(memoryLimit), + }, + }, printDebug) +} + +// NewVMWithConfig creates a new VM with a custom configuration. +func NewVMWithConfig(config types.VMConfig, printDebug bool) (*VM, error) { + cache, err := wazeroimpl.InitCache(config) + if err != nil { + return nil, err + } + return &VM{cache: cache, printDebug: printDebug}, nil +} + +// Cleanup releases resources used by this VM. +func (vm *VM) Cleanup() { + _ = vm.cache.Close(context.Background()) +} + +// StoreCode compiles the given wasm code and stores it under its checksum. +func (vm *VM) StoreCode(code WasmCode, gasLimit uint64) (Checksum, uint64, error) { + checksum, err := CreateChecksum(code) + if err != nil { + return nil, 0, err + } + if err := vm.cache.Compile(context.Background(), checksum, code); err != nil { + return nil, 0, err + } + return checksum, 0, nil +} + +// SimulateStoreCode behaves like StoreCode but does not persist anything. +func (vm *VM) SimulateStoreCode(code WasmCode, gasLimit uint64) (Checksum, uint64, error) { + checksum, err := CreateChecksum(code) + if err != nil { + return nil, 0, err + } + // Do not store the compiled module + if _, err := wazeroimpl.InitCache(types.VMConfig{}); err != nil { + return nil, 0, err + } + return checksum, 0, nil +} + +// StoreCodeUnchecked is currently not implemented in the wazero runtime. +func (vm *VM) StoreCodeUnchecked(code WasmCode) (Checksum, error) { + checksum, err := CreateChecksum(code) + if err != nil { + return nil, err + } + if err := vm.cache.Compile(context.Background(), checksum, code); err != nil { + return nil, err + } + return checksum, nil +} + +func (vm *VM) RemoveCode(checksum Checksum) error { + return fmt.Errorf("RemoveCode not supported in wazero VM") +} + +func (vm *VM) GetCode(checksum Checksum) (WasmCode, error) { + return nil, fmt.Errorf("GetCode not supported in wazero VM") +} + +func (vm *VM) Pin(checksum Checksum) error { + return nil +} + +func (vm *VM) Unpin(checksum Checksum) error { + return nil +} + +func (vm *VM) AnalyzeCode(checksum Checksum) (*types.AnalysisReport, error) { + return nil, fmt.Errorf("AnalyzeCode not supported in wazero VM") +} + +func (vm *VM) GetMetrics() (*types.Metrics, error) { + return nil, fmt.Errorf("GetMetrics not supported in wazero VM") +} + +func (vm *VM) GetPinnedMetrics() (*types.PinnedMetrics, error) { + return nil, fmt.Errorf("GetPinnedMetrics not supported in wazero VM") +} + +func (vm *VM) Instantiate(checksum Checksum, env types.Env, info types.MessageInfo, initMsg []byte, store KVStore, goapi GoAPI, querier Querier, gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction) (*types.ContractResult, uint64, error) { + if err := vm.cache.Instantiate(context.Background(), checksum, nil, nil, nil, store, &goapi, &querier, gasMeter); err != nil { + return nil, 0, err + } + return &types.ContractResult{}, 0, nil +} + +func (vm *VM) Execute(checksum Checksum, env types.Env, info types.MessageInfo, executeMsg []byte, store KVStore, goapi GoAPI, querier Querier, gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction) (*types.ContractResult, uint64, error) { + if err := vm.cache.Execute(context.Background(), checksum, nil, nil, nil, store, &goapi, &querier, gasMeter); err != nil { + return nil, 0, err + } + return &types.ContractResult{}, 0, nil +} + +func (vm *VM) Query(checksum Checksum, env types.Env, queryMsg []byte, store KVStore, goapi GoAPI, querier Querier, gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction) (*types.QueryResult, uint64, error) { + return nil, 0, fmt.Errorf("Query not supported in wazero VM") +} + +func (vm *VM) Migrate(checksum Checksum, env types.Env, migrateMsg []byte, store KVStore, goapi GoAPI, querier Querier, gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction) (*types.ContractResult, uint64, error) { + return nil, 0, fmt.Errorf("Migrate not supported in wazero VM") +} + +func (vm *VM) MigrateWithInfo(checksum Checksum, env types.Env, migrateMsg []byte, migrateInfo types.MigrateInfo, store KVStore, goapi GoAPI, querier Querier, gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction) (*types.ContractResult, uint64, error) { + return nil, 0, fmt.Errorf("MigrateWithInfo not supported in wazero VM") +} + +func (vm *VM) Sudo(checksum Checksum, env types.Env, sudoMsg []byte, store KVStore, goapi GoAPI, querier Querier, gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction) (*types.ContractResult, uint64, error) { + return nil, 0, fmt.Errorf("Sudo not supported in wazero VM") +} + +func (vm *VM) Reply(checksum Checksum, env types.Env, reply types.Reply, store KVStore, goapi GoAPI, querier Querier, gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction) (*types.ContractResult, uint64, error) { + return nil, 0, fmt.Errorf("Reply not supported in wazero VM") +} + +func (vm *VM) IBCChannelOpen(checksum Checksum, env types.Env, msg types.IBCChannelOpenMsg, store KVStore, goapi GoAPI, querier Querier, gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction) (*types.IBCChannelOpenResult, uint64, error) { + return nil, 0, fmt.Errorf("IBCChannelOpen not supported in wazero VM") +} + +func (vm *VM) IBCChannelConnect(checksum Checksum, env types.Env, msg types.IBCChannelConnectMsg, store KVStore, goapi GoAPI, querier Querier, gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction) (*types.IBCBasicResult, uint64, error) { + return nil, 0, fmt.Errorf("IBCChannelConnect not supported in wazero VM") +} + +func (vm *VM) IBCChannelClose(checksum Checksum, env types.Env, msg types.IBCChannelCloseMsg, store KVStore, goapi GoAPI, querier Querier, gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction) (*types.IBCBasicResult, uint64, error) { + return nil, 0, fmt.Errorf("IBCChannelClose not supported in wazero VM") +} + +func (vm *VM) IBCPacketReceive(checksum Checksum, env types.Env, msg types.IBCPacketReceiveMsg, store KVStore, goapi GoAPI, querier Querier, gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction) (*types.IBCReceiveResult, uint64, error) { + return nil, 0, fmt.Errorf("IBCPacketReceive not supported in wazero VM") +} + +func (vm *VM) IBCPacketAck(checksum Checksum, env types.Env, msg types.IBCPacketAckMsg, store KVStore, goapi GoAPI, querier Querier, gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction) (*types.IBCBasicResult, uint64, error) { + return nil, 0, fmt.Errorf("IBCPacketAck not supported in wazero VM") +} + +func (vm *VM) IBCPacketTimeout(checksum Checksum, env types.Env, msg types.IBCPacketTimeoutMsg, store KVStore, goapi GoAPI, querier Querier, gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction) (*types.IBCBasicResult, uint64, error) { + return nil, 0, fmt.Errorf("IBCPacketTimeout not supported in wazero VM") +} + +func (vm *VM) IBCSourceCallback(checksum Checksum, env types.Env, msg types.IBCSourceCallbackMsg, store KVStore, goapi GoAPI, querier Querier, gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction) (*types.IBCBasicResult, uint64, error) { + return nil, 0, fmt.Errorf("IBCSourceCallback not supported in wazero VM") +} + +func (vm *VM) IBCDestinationCallback(checksum Checksum, env types.Env, msg types.IBCDestinationCallbackMsg, store KVStore, goapi GoAPI, querier Querier, gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction) (*types.IBCBasicResult, uint64, error) { + return nil, 0, fmt.Errorf("IBCDestinationCallback not supported in wazero VM") +} + +func (vm *VM) IBC2PacketAck(checksum Checksum, env types.Env, msg types.IBC2AcknowledgeMsg, store KVStore, goapi GoAPI, querier Querier, gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction) (*types.IBCBasicResult, uint64, error) { + return nil, 0, fmt.Errorf("IBC2PacketAck not supported in wazero VM") +} + +func (vm *VM) IBC2PacketReceive(checksum Checksum, env types.Env, msg types.IBC2PacketReceiveMsg, store KVStore, goapi GoAPI, querier Querier, gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction) (*types.IBCReceiveResult, uint64, error) { + return nil, 0, fmt.Errorf("IBC2PacketReceive not supported in wazero VM") +} + +func (vm *VM) IBC2PacketTimeout(checksum Checksum, env types.Env, msg types.IBC2PacketTimeoutMsg, store KVStore, goapi GoAPI, querier Querier, gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction) (*types.IBCBasicResult, uint64, error) { + return nil, 0, fmt.Errorf("IBC2PacketTimeout not supported in wazero VM") +} + +func (vm *VM) IBC2PacketSend(checksum Checksum, env types.Env, msg types.IBC2PacketSendMsg, store KVStore, goapi GoAPI, querier Querier, gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction) (*types.IBCBasicResult, uint64, error) { + return nil, 0, fmt.Errorf("IBC2PacketSend not supported in wazero VM") +} diff --git a/version_cgo.go b/version_cgo.go index dc9779b8d..3a963c819 100644 --- a/version_cgo.go +++ b/version_cgo.go @@ -1,4 +1,4 @@ -//go:build cgo && !nolink_libwasmvm +//go:build cgo && !wazero package cosmwasm diff --git a/version_no_cgo.go b/version_no_cgo.go index cc7131fca..8c7e7e7df 100644 --- a/version_no_cgo.go +++ b/version_no_cgo.go @@ -1,4 +1,4 @@ -//go:build !cgo || nolink_libwasmvm +//go:build (!cgo && !wazero) || nolink_libwasmvm package cosmwasm diff --git a/version_wazero.go b/version_wazero.go new file mode 100644 index 000000000..33119a39c --- /dev/null +++ b/version_wazero.go @@ -0,0 +1,7 @@ +//go:build wazero + +package cosmwasm + +func libwasmvmVersionImpl() (string, error) { + return "wazero", nil +} From 45415816008f1b1f6b63bf8632f1cc601ab786d2 Mon Sep 17 00:00:00 2001 From: Jacob Gadikian Date: Wed, 21 May 2025 00:05:22 +0700 Subject: [PATCH 10/19] docs: document wazero build and update ci --- .github/workflows/go.yml | 7 +++++-- README.md | 26 +++++++++++++++++++------- docs/MIGRATING.md | 19 +++++++++++++++++++ 3 files changed, 43 insertions(+), 9 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 8a7d89548..6eeb8a52c 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -2,7 +2,7 @@ name: Go Build and Test on: push: - branches: [ main ] + branches: [main] pull_request: workflow_dispatch: @@ -14,7 +14,7 @@ jobs: - uses: actions/setup-go@v5 with: - go-version: '1.24' + go-version: "1.24" cache: false - name: Check go mod tidy @@ -28,5 +28,8 @@ jobs: - name: Go integration tests run: make test + - name: Go wazero tests + run: go test -tags wazero ./... + - name: Go safety tests run: make test-safety diff --git a/README.md b/README.md index b4fcdabd8..21f0b6c83 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,8 @@ applications, in particular from [x/wasm](https://github.com/CosmWasm/wasmd/tree/master/x/wasm). More information on what is CosmWasm and how to use it can be found here: -[CosmWasm Docs](https://docs.cosmwasm.com). To generate and show -the Rust part documentation you can run `make doc-rust`. +[CosmWasm Docs](https://docs.cosmwasm.com). To generate and show the Rust part +documentation you can run `make doc-rust`. ## Structure @@ -80,8 +80,9 @@ CGO_ENABLED=0 go build ./types This package contains the code binding the libwasmvm build to the Go code. All low level FFI handling code belongs there. This package can only be built using -cgo. Using the `internal/` convention makes this package fully private. -For an overview of the exported C functions and their Go wrappers see [docs/CGO_INTERFACE.md](docs/CGO_INTERFACE.md). +cgo. Using the `internal/` convention makes this package fully private. For an +overview of the exported C functions and their Go wrappers see +[docs/CGO_INTERFACE.md](docs/CGO_INTERFACE.md). #### Package github.com/CosmWasm/wasmvm @@ -103,6 +104,17 @@ linking disabled an additional build tag is available. go build -tags "nolink_libwasmvm" ``` +You can also build using the experimental +[wazero](https://github.com/tetratelabs/wazero) runtime which removes the need +for CGO: + +```sh +CGO_ENABLED=0 go build -tags wazero . +``` + +Once wazero has feature parity with the Rust implementation, the Rust dependency +will be removed. + ## Supported Platforms See [COMPILER_VERSIONS.md](docs/COMPILER_VERSIONS.md) for information on Go and @@ -153,9 +165,9 @@ which for example excludes all 32 bit systems. ## Development -There are two halves to this code - go and rust. The first step is to ensure that -there is a proper dll built for your platform. This should be `api/libwasmvm.X`, -where X is: +There are two halves to this code - go and rust. The first step is to ensure +that there is a proper dll built for your platform. This should be +`api/libwasmvm.X`, where X is: - `so` for Linux systems - `dylib` for MacOS diff --git a/docs/MIGRATING.md b/docs/MIGRATING.md index 7425f2327..d21177a13 100644 --- a/docs/MIGRATING.md +++ b/docs/MIGRATING.md @@ -72,3 +72,22 @@ where the old name was deprecated. | `VoteMsg.Vote` | `VoteMsg.Option` | Brings consistency with Cosmos SDK naming | [ft]: https://stackoverflow.com/a/60073310 + +## Building with or without CGO + +The default build links against the Rust based `libwasmvm` and therefore +requires CGO: + +```sh +go build ./... +``` + +If you want a pure Go build without the Rust dependency, disable CGO and enable +the `wazero` build tag: + +```sh +CGO_ENABLED=0 go build -tags wazero ./... +``` + +Once wazero provides all features of the Rust implementation, the Rust +dependency will be removed. From 50feb051ab2f00fbbd48cc0116ebbd6db67b0f9f Mon Sep 17 00:00:00 2001 From: Jacob Gadikian Date: Wed, 21 May 2025 00:21:10 +0700 Subject: [PATCH 11/19] Add wazero VM tests and directory check --- .reference-code/cosmwasm | 1 + .reference-code/wazero | 1 + ibc_test.go | 2 +- internal/wazeroimpl/runtime.go | 12 +++ lib_libwasmvm.go | 2 +- lib_libwasmvm_test.go | 2 +- lib_libwasmvm_wazero.go | 190 +++++++++++++++++++++++++++++++++ version_cgo.go | 2 +- version_no_cgo.go | 2 +- version_wazero.go | 7 ++ wazero_test.go | 41 +++++++ 11 files changed, 257 insertions(+), 5 deletions(-) create mode 100644 .reference-code/cosmwasm create mode 100644 .reference-code/wazero create mode 100644 lib_libwasmvm_wazero.go create mode 100644 version_wazero.go create mode 100644 wazero_test.go diff --git a/.reference-code/cosmwasm b/.reference-code/cosmwasm new file mode 100644 index 000000000..27d4be84f --- /dev/null +++ b/.reference-code/cosmwasm @@ -0,0 +1 @@ +Subproject commit 2a36ce4818ad67cd112833a973941862c497e018 diff --git a/.reference-code/wazero b/.reference-code/wazero new file mode 100644 index 000000000..77573f9db --- /dev/null +++ b/.reference-code/wazero @@ -0,0 +1 @@ +Subproject commit 0dea5d7ee1de12d2817d6ac8548a4d36aaf59aea diff --git a/ibc_test.go b/ibc_test.go index 265116792..8ec957f2e 100644 --- a/ibc_test.go +++ b/ibc_test.go @@ -1,4 +1,4 @@ -//go:build cgo && !nolink_libwasmvm +//go:build cgo && !wazero package cosmwasm diff --git a/internal/wazeroimpl/runtime.go b/internal/wazeroimpl/runtime.go index dca31ea86..b872246a9 100644 --- a/internal/wazeroimpl/runtime.go +++ b/internal/wazeroimpl/runtime.go @@ -4,6 +4,9 @@ import ( "context" "encoding/hex" "fmt" + "os" + "runtime" + "strings" "unsafe" "github.com/tetratelabs/wazero" @@ -20,6 +23,15 @@ type Cache struct { // InitCache creates a new wazero Runtime with memory limits similar to api.InitCache. func InitCache(config types.VMConfig) (*Cache, error) { + if base := config.Cache.BaseDir; base != "" { + if strings.Contains(base, ":") && runtime.GOOS != "windows" { + return nil, fmt.Errorf("could not create base directory") + } + if err := os.MkdirAll(base, 0o700); err != nil { + return nil, fmt.Errorf("could not create base directory: %w", err) + } + } + ctx := context.Background() limitBytes := *(*uint32)(unsafe.Pointer(&config.Cache.InstanceMemoryLimitBytes)) r := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfig().WithMemoryLimitPages(limitBytes/65536)) diff --git a/lib_libwasmvm.go b/lib_libwasmvm.go index d581e5310..9a3c197a5 100644 --- a/lib_libwasmvm.go +++ b/lib_libwasmvm.go @@ -1,4 +1,4 @@ -//go:build cgo && !nolink_libwasmvm +//go:build cgo && !wazero // This file contains the part of the API that is exposed when libwasmvm // is available (i.e. cgo is enabled and nolink_libwasmvm is not set). diff --git a/lib_libwasmvm_test.go b/lib_libwasmvm_test.go index a799ab7c9..918d9c563 100644 --- a/lib_libwasmvm_test.go +++ b/lib_libwasmvm_test.go @@ -1,4 +1,4 @@ -//go:build cgo && !nolink_libwasmvm +//go:build cgo && !wazero package cosmwasm diff --git a/lib_libwasmvm_wazero.go b/lib_libwasmvm_wazero.go new file mode 100644 index 000000000..77fade170 --- /dev/null +++ b/lib_libwasmvm_wazero.go @@ -0,0 +1,190 @@ +//go:build wazero + +package cosmwasm + +import ( + "context" + "fmt" + + "github.com/CosmWasm/wasmvm/v3/internal/wazeroimpl" + "github.com/CosmWasm/wasmvm/v3/types" +) + +// VM implements a very small subset of the cosmwasm VM using the wazero runtime. +type VM struct { + cache *wazeroimpl.Cache + printDebug bool +} + +// NewVM creates a new wazero based VM. +func NewVM(dataDir string, supportedCapabilities []string, memoryLimit uint32, printDebug bool, cacheSize uint32) (*VM, error) { + return NewVMWithConfig(types.VMConfig{ + Cache: types.CacheOptions{ + BaseDir: dataDir, + AvailableCapabilities: supportedCapabilities, + MemoryCacheSizeBytes: types.NewSizeMebi(cacheSize), + InstanceMemoryLimitBytes: types.NewSizeMebi(memoryLimit), + }, + }, printDebug) +} + +// NewVMWithConfig creates a new VM with a custom configuration. +func NewVMWithConfig(config types.VMConfig, printDebug bool) (*VM, error) { + cache, err := wazeroimpl.InitCache(config) + if err != nil { + return nil, err + } + return &VM{cache: cache, printDebug: printDebug}, nil +} + +// Cleanup releases resources used by this VM. +func (vm *VM) Cleanup() { + _ = vm.cache.Close(context.Background()) +} + +// StoreCode compiles the given wasm code and stores it under its checksum. +func (vm *VM) StoreCode(code WasmCode, gasLimit uint64) (Checksum, uint64, error) { + checksum, err := CreateChecksum(code) + if err != nil { + return nil, 0, err + } + if err := vm.cache.Compile(context.Background(), checksum, code); err != nil { + return nil, 0, err + } + return checksum, 0, nil +} + +// SimulateStoreCode behaves like StoreCode but does not persist anything. +func (vm *VM) SimulateStoreCode(code WasmCode, gasLimit uint64) (Checksum, uint64, error) { + checksum, err := CreateChecksum(code) + if err != nil { + return nil, 0, err + } + // Do not store the compiled module + if _, err := wazeroimpl.InitCache(types.VMConfig{}); err != nil { + return nil, 0, err + } + return checksum, 0, nil +} + +// StoreCodeUnchecked is currently not implemented in the wazero runtime. +func (vm *VM) StoreCodeUnchecked(code WasmCode) (Checksum, error) { + checksum, err := CreateChecksum(code) + if err != nil { + return nil, err + } + if err := vm.cache.Compile(context.Background(), checksum, code); err != nil { + return nil, err + } + return checksum, nil +} + +func (vm *VM) RemoveCode(checksum Checksum) error { + return fmt.Errorf("RemoveCode not supported in wazero VM") +} + +func (vm *VM) GetCode(checksum Checksum) (WasmCode, error) { + return nil, fmt.Errorf("GetCode not supported in wazero VM") +} + +func (vm *VM) Pin(checksum Checksum) error { + return nil +} + +func (vm *VM) Unpin(checksum Checksum) error { + return nil +} + +func (vm *VM) AnalyzeCode(checksum Checksum) (*types.AnalysisReport, error) { + return nil, fmt.Errorf("AnalyzeCode not supported in wazero VM") +} + +func (vm *VM) GetMetrics() (*types.Metrics, error) { + return nil, fmt.Errorf("GetMetrics not supported in wazero VM") +} + +func (vm *VM) GetPinnedMetrics() (*types.PinnedMetrics, error) { + return nil, fmt.Errorf("GetPinnedMetrics not supported in wazero VM") +} + +func (vm *VM) Instantiate(checksum Checksum, env types.Env, info types.MessageInfo, initMsg []byte, store KVStore, goapi GoAPI, querier Querier, gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction) (*types.ContractResult, uint64, error) { + if err := vm.cache.Instantiate(context.Background(), checksum, nil, nil, nil, store, &goapi, &querier, gasMeter); err != nil { + return nil, 0, err + } + return &types.ContractResult{}, 0, nil +} + +func (vm *VM) Execute(checksum Checksum, env types.Env, info types.MessageInfo, executeMsg []byte, store KVStore, goapi GoAPI, querier Querier, gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction) (*types.ContractResult, uint64, error) { + if err := vm.cache.Execute(context.Background(), checksum, nil, nil, nil, store, &goapi, &querier, gasMeter); err != nil { + return nil, 0, err + } + return &types.ContractResult{}, 0, nil +} + +func (vm *VM) Query(checksum Checksum, env types.Env, queryMsg []byte, store KVStore, goapi GoAPI, querier Querier, gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction) (*types.QueryResult, uint64, error) { + return nil, 0, fmt.Errorf("Query not supported in wazero VM") +} + +func (vm *VM) Migrate(checksum Checksum, env types.Env, migrateMsg []byte, store KVStore, goapi GoAPI, querier Querier, gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction) (*types.ContractResult, uint64, error) { + return nil, 0, fmt.Errorf("Migrate not supported in wazero VM") +} + +func (vm *VM) MigrateWithInfo(checksum Checksum, env types.Env, migrateMsg []byte, migrateInfo types.MigrateInfo, store KVStore, goapi GoAPI, querier Querier, gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction) (*types.ContractResult, uint64, error) { + return nil, 0, fmt.Errorf("MigrateWithInfo not supported in wazero VM") +} + +func (vm *VM) Sudo(checksum Checksum, env types.Env, sudoMsg []byte, store KVStore, goapi GoAPI, querier Querier, gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction) (*types.ContractResult, uint64, error) { + return nil, 0, fmt.Errorf("Sudo not supported in wazero VM") +} + +func (vm *VM) Reply(checksum Checksum, env types.Env, reply types.Reply, store KVStore, goapi GoAPI, querier Querier, gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction) (*types.ContractResult, uint64, error) { + return nil, 0, fmt.Errorf("Reply not supported in wazero VM") +} + +func (vm *VM) IBCChannelOpen(checksum Checksum, env types.Env, msg types.IBCChannelOpenMsg, store KVStore, goapi GoAPI, querier Querier, gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction) (*types.IBCChannelOpenResult, uint64, error) { + return nil, 0, fmt.Errorf("IBCChannelOpen not supported in wazero VM") +} + +func (vm *VM) IBCChannelConnect(checksum Checksum, env types.Env, msg types.IBCChannelConnectMsg, store KVStore, goapi GoAPI, querier Querier, gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction) (*types.IBCBasicResult, uint64, error) { + return nil, 0, fmt.Errorf("IBCChannelConnect not supported in wazero VM") +} + +func (vm *VM) IBCChannelClose(checksum Checksum, env types.Env, msg types.IBCChannelCloseMsg, store KVStore, goapi GoAPI, querier Querier, gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction) (*types.IBCBasicResult, uint64, error) { + return nil, 0, fmt.Errorf("IBCChannelClose not supported in wazero VM") +} + +func (vm *VM) IBCPacketReceive(checksum Checksum, env types.Env, msg types.IBCPacketReceiveMsg, store KVStore, goapi GoAPI, querier Querier, gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction) (*types.IBCReceiveResult, uint64, error) { + return nil, 0, fmt.Errorf("IBCPacketReceive not supported in wazero VM") +} + +func (vm *VM) IBCPacketAck(checksum Checksum, env types.Env, msg types.IBCPacketAckMsg, store KVStore, goapi GoAPI, querier Querier, gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction) (*types.IBCBasicResult, uint64, error) { + return nil, 0, fmt.Errorf("IBCPacketAck not supported in wazero VM") +} + +func (vm *VM) IBCPacketTimeout(checksum Checksum, env types.Env, msg types.IBCPacketTimeoutMsg, store KVStore, goapi GoAPI, querier Querier, gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction) (*types.IBCBasicResult, uint64, error) { + return nil, 0, fmt.Errorf("IBCPacketTimeout not supported in wazero VM") +} + +func (vm *VM) IBCSourceCallback(checksum Checksum, env types.Env, msg types.IBCSourceCallbackMsg, store KVStore, goapi GoAPI, querier Querier, gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction) (*types.IBCBasicResult, uint64, error) { + return nil, 0, fmt.Errorf("IBCSourceCallback not supported in wazero VM") +} + +func (vm *VM) IBCDestinationCallback(checksum Checksum, env types.Env, msg types.IBCDestinationCallbackMsg, store KVStore, goapi GoAPI, querier Querier, gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction) (*types.IBCBasicResult, uint64, error) { + return nil, 0, fmt.Errorf("IBCDestinationCallback not supported in wazero VM") +} + +func (vm *VM) IBC2PacketAck(checksum Checksum, env types.Env, msg types.IBC2AcknowledgeMsg, store KVStore, goapi GoAPI, querier Querier, gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction) (*types.IBCBasicResult, uint64, error) { + return nil, 0, fmt.Errorf("IBC2PacketAck not supported in wazero VM") +} + +func (vm *VM) IBC2PacketReceive(checksum Checksum, env types.Env, msg types.IBC2PacketReceiveMsg, store KVStore, goapi GoAPI, querier Querier, gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction) (*types.IBCReceiveResult, uint64, error) { + return nil, 0, fmt.Errorf("IBC2PacketReceive not supported in wazero VM") +} + +func (vm *VM) IBC2PacketTimeout(checksum Checksum, env types.Env, msg types.IBC2PacketTimeoutMsg, store KVStore, goapi GoAPI, querier Querier, gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction) (*types.IBCBasicResult, uint64, error) { + return nil, 0, fmt.Errorf("IBC2PacketTimeout not supported in wazero VM") +} + +func (vm *VM) IBC2PacketSend(checksum Checksum, env types.Env, msg types.IBC2PacketSendMsg, store KVStore, goapi GoAPI, querier Querier, gasMeter GasMeter, gasLimit uint64, deserCost types.UFraction) (*types.IBCBasicResult, uint64, error) { + return nil, 0, fmt.Errorf("IBC2PacketSend not supported in wazero VM") +} diff --git a/version_cgo.go b/version_cgo.go index dc9779b8d..3a963c819 100644 --- a/version_cgo.go +++ b/version_cgo.go @@ -1,4 +1,4 @@ -//go:build cgo && !nolink_libwasmvm +//go:build cgo && !wazero package cosmwasm diff --git a/version_no_cgo.go b/version_no_cgo.go index cc7131fca..8c7e7e7df 100644 --- a/version_no_cgo.go +++ b/version_no_cgo.go @@ -1,4 +1,4 @@ -//go:build !cgo || nolink_libwasmvm +//go:build (!cgo && !wazero) || nolink_libwasmvm package cosmwasm diff --git a/version_wazero.go b/version_wazero.go new file mode 100644 index 000000000..33119a39c --- /dev/null +++ b/version_wazero.go @@ -0,0 +1,7 @@ +//go:build wazero + +package cosmwasm + +func libwasmvmVersionImpl() (string, error) { + return "wazero", nil +} diff --git a/wazero_test.go b/wazero_test.go new file mode 100644 index 000000000..a2dbeb2b7 --- /dev/null +++ b/wazero_test.go @@ -0,0 +1,41 @@ +//go:build wazero + +package cosmwasm + +import ( + "os" + "testing" + + "github.com/CosmWasm/wasmvm/v3/internal/api" + "github.com/CosmWasm/wasmvm/v3/types" +) + +func TestWazeroInstantiateExecute(t *testing.T) { + vm, err := NewVM("", TESTING_CAPABILITIES, TESTING_MEMORY_LIMIT, TESTING_PRINT_DEBUG, TESTING_CACHE_SIZE) + if err != nil { + t.Fatal(err) + } + defer vm.Cleanup() + + wasm, err := os.ReadFile("testdata/hackatom.wasm") + if err != nil { + t.Fatal(err) + } + checksum, _, err := vm.StoreCode(wasm, TESTING_GAS_LIMIT) + if err != nil { + t.Fatalf("store error: %v", err) + } + + env := api.MockEnv() + info := api.MockInfo("creator", nil) + store := api.NewLookup(api.NewMockGasMeter(TESTING_GAS_LIMIT)) + querier := api.DefaultQuerier(api.MOCK_CONTRACT_ADDR, nil) + + res, _, err := vm.Instantiate(checksum, env, info, []byte(`{"verifier":"fred","beneficiary":"bob"}`), store, *api.NewMockAPI(), querier, api.NewMockGasMeter(TESTING_GAS_LIMIT), TESTING_GAS_LIMIT, types.UFraction{1, 1}) + t.Log("instantiate", res, err) + + env = api.MockEnv() + info = api.MockInfo("fred", nil) + execRes, _, err := vm.Execute(checksum, env, info, []byte(`{"release":{}}`), store, *api.NewMockAPI(), querier, api.NewMockGasMeter(TESTING_GAS_LIMIT), TESTING_GAS_LIMIT, types.UFraction{1, 1}) + t.Log("execute", execRes, err) +} From baf181ec13bd5712dd744f55bfd852bd641cd52b Mon Sep 17 00:00:00 2001 From: Jacob Gadikian Date: Wed, 21 May 2025 00:27:28 +0700 Subject: [PATCH 12/19] Delete .reference-code directory --- .reference-code/cosmwasm | 1 - .reference-code/wazero | 1 - 2 files changed, 2 deletions(-) delete mode 100644 .reference-code/cosmwasm delete mode 100644 .reference-code/wazero diff --git a/.reference-code/cosmwasm b/.reference-code/cosmwasm deleted file mode 100644 index 27d4be84f..000000000 --- a/.reference-code/cosmwasm +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2a36ce4818ad67cd112833a973941862c497e018 diff --git a/.reference-code/wazero b/.reference-code/wazero deleted file mode 100644 index 77573f9db..000000000 --- a/.reference-code/wazero +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0dea5d7ee1de12d2817d6ac8548a4d36aaf59aea From c5a4e023a5f7a19988c8ba8b87cd32c631cde9c4 Mon Sep 17 00:00:00 2001 From: Jacob Gadikian Date: Wed, 21 May 2025 00:34:01 +0700 Subject: [PATCH 13/19] Update .gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 0c71e4711..e426f0988 100644 --- a/.gitignore +++ b/.gitignore @@ -20,4 +20,4 @@ a.out # direnv Nix stuff .direnv/ -reference-code/ +.reference-code/ From ef826e44695fffc16591222efb48211a4debae32 Mon Sep 17 00:00:00 2001 From: Jacob Gadikian Date: Wed, 21 May 2025 05:06:16 +0700 Subject: [PATCH 14/19] store code --- internal/wazeroimpl/runtime.go | 117 ++++++++++++++++++++++++++++++--- lib_libwasmvm_wazero.go | 15 +++-- wazero_test.go | 10 +++ 3 files changed, 129 insertions(+), 13 deletions(-) diff --git a/internal/wazeroimpl/runtime.go b/internal/wazeroimpl/runtime.go index b872246a9..5ffc3f6e8 100644 --- a/internal/wazeroimpl/runtime.go +++ b/internal/wazeroimpl/runtime.go @@ -5,57 +5,156 @@ import ( "encoding/hex" "fmt" "os" + "path/filepath" "runtime" "strings" "unsafe" "github.com/tetratelabs/wazero" "github.com/tetratelabs/wazero/api" + "golang.org/x/sys/unix" "github.com/CosmWasm/wasmvm/v3/types" ) -// Cache manages a wazero runtime and compiled modules. +// Cache manages a wazero runtime, compiled modules, and on-disk code storage. type Cache struct { runtime wazero.Runtime modules map[string]wazero.CompiledModule + // raw stores the original Wasm bytes by checksum hex + raw map[string][]byte + // lockfile holds the exclusive lock on BaseDir + lockfile *os.File + // baseDir is the root directory for on-disk cache + baseDir string +} + +// RemoveCode removes stored Wasm and compiled module for the given checksum. +func (c *Cache) RemoveCode(checksum types.Checksum) error { + key := hex.EncodeToString(checksum) + if _, ok := c.raw[key]; !ok { + return fmt.Errorf("code '%s' not found", key) + } + // Remove on-disk Wasm file if persisted + if c.baseDir != "" { + filePath := filepath.Join(c.baseDir, "code", key+".wasm") + if err := os.Remove(filePath); err != nil && !os.IsNotExist(err) { + return fmt.Errorf("failed to remove wasm file: %w", err) + } + } + delete(c.raw, key) + delete(c.modules, key) + return nil +} + +// GetCode returns the original Wasm bytes for the given checksum. +func (c *Cache) GetCode(checksum types.Checksum) ([]byte, error) { + key := hex.EncodeToString(checksum) + data, ok := c.raw[key] + if !ok { + return nil, fmt.Errorf("code '%s' not found", key) + } + return append([]byte(nil), data...), nil } // InitCache creates a new wazero Runtime with memory limits similar to api.InitCache. func InitCache(config types.VMConfig) (*Cache, error) { - if base := config.Cache.BaseDir; base != "" { + // Prepare in-memory storage, lockfile handle, and base directory + raw := make(map[string][]byte) + var lf *os.File + base := config.Cache.BaseDir + if base != "" { + // Create base and code directories if strings.Contains(base, ":") && runtime.GOOS != "windows" { - return nil, fmt.Errorf("could not create base directory") + return nil, fmt.Errorf("invalid base directory: %s", base) } - if err := os.MkdirAll(base, 0o700); err != nil { + if err := os.MkdirAll(base, 0o755); err != nil { return nil, fmt.Errorf("could not create base directory: %w", err) } + codeDir := filepath.Join(base, "code") + if err := os.MkdirAll(codeDir, 0o755); err != nil { + return nil, fmt.Errorf("could not create code directory: %w", err) + } + // Acquire exclusive lock + lockPath := filepath.Join(base, "exclusive.lock") + var err error + lf, err = os.OpenFile(lockPath, os.O_WRONLY|os.O_CREATE, 0o666) + if err != nil { + return nil, fmt.Errorf("could not open exclusive.lock: %w", err) + } + _, err = lf.WriteString("exclusive lock for wazero VM\n") + if err != nil { + lf.Close() + return nil, fmt.Errorf("error writing to exclusive.lock: %w", err) + } + if err := unix.Flock(int(lf.Fd()), unix.LOCK_EX|unix.LOCK_NB); err != nil { + lf.Close() + return nil, fmt.Errorf("could not lock exclusive.lock; is another VM running? %w", err) + } + // Pre-load existing Wasm blobs + patterns := filepath.Join(codeDir, "*.wasm") + files, err := filepath.Glob(patterns) + if err != nil { + lf.Close() + return nil, fmt.Errorf("failed scanning code directory: %w", err) + } + for _, p := range files { + data, err := os.ReadFile(p) + if err != nil { + lf.Close() + return nil, fmt.Errorf("failed reading existing code %s: %w", p, err) + } + name := filepath.Base(p) + key := strings.TrimSuffix(name, ".wasm") + raw[key] = data + } } ctx := context.Background() limitBytes := *(*uint32)(unsafe.Pointer(&config.Cache.InstanceMemoryLimitBytes)) r := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfig().WithMemoryLimitPages(limitBytes/65536)) return &Cache{ - runtime: r, - modules: make(map[string]wazero.CompiledModule), + runtime: r, + modules: make(map[string]wazero.CompiledModule), + raw: raw, + lockfile: lf, + baseDir: base, }, nil } -// Close releases all resources of the runtime. +// Close releases the runtime and the directory lock. func (c *Cache) Close(ctx context.Context) error { if c.runtime != nil { - return c.runtime.Close(ctx) + c.runtime.Close(ctx) + } + if c.lockfile != nil { + c.lockfile.Close() } return nil } // Compile stores a compiled module under the given checksum. func (c *Cache) Compile(ctx context.Context, checksum types.Checksum, wasm []byte) error { + key := hex.EncodeToString(checksum) + // Persist Wasm blob to disk if enabled + if c.baseDir != "" { + codeDir := filepath.Join(c.baseDir, "code") + if err := os.MkdirAll(codeDir, 0o755); err != nil { + return fmt.Errorf("could not create code directory: %w", err) + } + filePath := filepath.Join(codeDir, key+".wasm") + if err := os.WriteFile(filePath, wasm, 0o644); err != nil { + return fmt.Errorf("failed to write wasm file: %w", err) + } + } + // Store raw Wasm bytes in memory + c.raw[key] = append([]byte(nil), wasm...) + // Compile module mod, err := c.runtime.CompileModule(ctx, wasm) if err != nil { return err } - c.modules[hex.EncodeToString(checksum)] = mod + c.modules[key] = mod return nil } diff --git a/lib_libwasmvm_wazero.go b/lib_libwasmvm_wazero.go index 77fade170..3c520dd05 100644 --- a/lib_libwasmvm_wazero.go +++ b/lib_libwasmvm_wazero.go @@ -55,13 +55,18 @@ func (vm *VM) StoreCode(code WasmCode, gasLimit uint64) (Checksum, uint64, error } // SimulateStoreCode behaves like StoreCode but does not persist anything. +// It compiles the module to ensure validity, then removes it. func (vm *VM) SimulateStoreCode(code WasmCode, gasLimit uint64) (Checksum, uint64, error) { checksum, err := CreateChecksum(code) if err != nil { return nil, 0, err } - // Do not store the compiled module - if _, err := wazeroimpl.InitCache(types.VMConfig{}); err != nil { + // Compile to cache + if err := vm.cache.Compile(context.Background(), checksum, code); err != nil { + return nil, 0, err + } + // Remove to avoid persisting + if err := vm.cache.RemoveCode(checksum); err != nil { return nil, 0, err } return checksum, 0, nil @@ -79,12 +84,14 @@ func (vm *VM) StoreCodeUnchecked(code WasmCode) (Checksum, error) { return checksum, nil } +// RemoveCode deletes stored Wasm and compiled module for the given checksum. func (vm *VM) RemoveCode(checksum Checksum) error { - return fmt.Errorf("RemoveCode not supported in wazero VM") + return vm.cache.RemoveCode(checksum) } +// GetCode returns the original Wasm bytes for the given checksum. func (vm *VM) GetCode(checksum Checksum) (WasmCode, error) { - return nil, fmt.Errorf("GetCode not supported in wazero VM") + return vm.cache.GetCode(checksum) } func (vm *VM) Pin(checksum Checksum) error { diff --git a/wazero_test.go b/wazero_test.go index a2dbeb2b7..d0646e5be 100644 --- a/wazero_test.go +++ b/wazero_test.go @@ -10,6 +10,16 @@ import ( "github.com/CosmWasm/wasmvm/v3/types" ) +// Testing constants for wazero VM +const ( + TESTING_PRINT_DEBUG = false + TESTING_GAS_LIMIT = uint64(500_000_000_000) // ~0.5ms + TESTING_MEMORY_LIMIT = 32 // MiB + TESTING_CACHE_SIZE = 100 // MiB +) + +var TESTING_CAPABILITIES = []string{"staking", "stargate", "iterator"} + func TestWazeroInstantiateExecute(t *testing.T) { vm, err := NewVM("", TESTING_CAPABILITIES, TESTING_MEMORY_LIMIT, TESTING_PRINT_DEBUG, TESTING_CACHE_SIZE) if err != nil { From bc458cc9c50305bb4b8be150d03d591edc42e70f Mon Sep 17 00:00:00 2001 From: Jacob Gadikian Date: Wed, 21 May 2025 05:18:52 +0700 Subject: [PATCH 15/19] update wazero --- internal/wazeroimpl/runtime.go | 74 ++++++++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 3 deletions(-) diff --git a/internal/wazeroimpl/runtime.go b/internal/wazeroimpl/runtime.go index 5ffc3f6e8..3ec6155a8 100644 --- a/internal/wazeroimpl/runtime.go +++ b/internal/wazeroimpl/runtime.go @@ -2,6 +2,7 @@ package wazeroimpl import ( "context" + "encoding/binary" "encoding/hex" "fmt" "os" @@ -206,10 +207,77 @@ func (c *Cache) registerHost(ctx context.Context, store types.KVStore, apiImpl * }), []api.ValueType{api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{}).Export("db_remove") // query_external - simplified: returns 0 length + // canonicalize_address: input human string -> canonical bytes builder.NewFunctionBuilder().WithGoModuleFunction(api.GoModuleFunc(func(ctx context.Context, m api.Module, stack []uint64) { - resPtr := uint32(stack[2]) - _ = m.Memory().WriteUint32Le(resPtr, 0) - }), []api.ValueType{api.ValueTypeI64, api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{}).Export("query_external") + inputPtr := uint32(stack[0]) + inputLen := uint32(stack[1]) + outPtr := uint32(stack[2]) + errPtr := uint32(stack[3]) + gasPtr := uint32(stack[4]) + mem := m.Memory() + input := mem.Read(inputPtr, inputLen) + // call GoAPI + canonical, usedGas, err := apiImpl.CanonicalizeAddress(string(input)) + // write gas + buf := make([]byte, 8) + binary.LittleEndian.PutUint64(buf, usedGas) + mem.Write(gasPtr, buf) + if err != nil { + mem.WriteUint32Le(errPtr, uint32(len(err.Error()))) + mem.Write(errPtr+4, []byte(err.Error())) + return + } + mem.WriteUint32Le(outPtr, uint32(len(canonical))) + mem.Write(outPtr+4, canonical) + }), []api.ValueType{ + api.ValueTypeI32, api.ValueTypeI32, + api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, + }, []api.ValueType{}).Export("canonicalize_address") + // humanize_address: input canonical bytes -> human string + builder.NewFunctionBuilder().WithGoModuleFunction(api.GoModuleFunc(func(ctx context.Context, m api.Module, stack []uint64) { + inputPtr := uint32(stack[0]) + inputLen := uint32(stack[1]) + outPtr := uint32(stack[2]) + errPtr := uint32(stack[3]) + gasPtr := uint32(stack[4]) + mem := m.Memory() + input := mem.Read(inputPtr, inputLen) + human, usedGas, err := apiImpl.HumanizeAddress(input) + buf := make([]byte, 8) + binary.LittleEndian.PutUint64(buf, usedGas) + mem.Write(gasPtr, buf) + if err != nil { + mem.WriteUint32Le(errPtr, uint32(len(err.Error()))) + mem.Write(errPtr+4, []byte(err.Error())) + return + } + mem.WriteUint32Le(outPtr, uint32(len(human))) + mem.Write(outPtr+4, []byte(human)) + }), []api.ValueType{ + api.ValueTypeI32, api.ValueTypeI32, + api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, + }, []api.ValueType{}).Export("humanize_address") + // validate_address: input human string -> error only + builder.NewFunctionBuilder().WithGoModuleFunction(api.GoModuleFunc(func(ctx context.Context, m api.Module, stack []uint64) { + inputPtr := uint32(stack[0]) + inputLen := uint32(stack[1]) + errPtr := uint32(stack[2]) + gasPtr := uint32(stack[3]) + mem := m.Memory() + input := string(mem.Read(inputPtr, inputLen)) + usedGas, err := apiImpl.ValidateAddress(input) + buf := make([]byte, 8) + binary.LittleEndian.PutUint64(buf, usedGas) + mem.Write(gasPtr, buf) + if err != nil { + msg := err.Error() + mem.WriteUint32Le(errPtr, uint32(len(msg))) + mem.Write(errPtr+4, []byte(msg)) + } + }), []api.ValueType{ + api.ValueTypeI32, api.ValueTypeI32, + api.ValueTypeI32, api.ValueTypeI32, + }, []api.ValueType{}).Export("validate_address") return builder.Instantiate(ctx) } From 90c99d3277716f05c8e0785c26de5c66af71237d Mon Sep 17 00:00:00 2001 From: Jacob Gadikian Date: Wed, 21 May 2025 05:23:44 +0700 Subject: [PATCH 16/19] lint --- internal/wazeroimpl/runtime.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/internal/wazeroimpl/runtime.go b/internal/wazeroimpl/runtime.go index 3ec6155a8..def812332 100644 --- a/internal/wazeroimpl/runtime.go +++ b/internal/wazeroimpl/runtime.go @@ -215,7 +215,7 @@ func (c *Cache) registerHost(ctx context.Context, store types.KVStore, apiImpl * errPtr := uint32(stack[3]) gasPtr := uint32(stack[4]) mem := m.Memory() - input := mem.Read(inputPtr, inputLen) + input, _ := mem.Read(inputPtr, inputLen) // call GoAPI canonical, usedGas, err := apiImpl.CanonicalizeAddress(string(input)) // write gas @@ -241,7 +241,7 @@ func (c *Cache) registerHost(ctx context.Context, store types.KVStore, apiImpl * errPtr := uint32(stack[3]) gasPtr := uint32(stack[4]) mem := m.Memory() - input := mem.Read(inputPtr, inputLen) + input, _ := mem.Read(inputPtr, inputLen) human, usedGas, err := apiImpl.HumanizeAddress(input) buf := make([]byte, 8) binary.LittleEndian.PutUint64(buf, usedGas) @@ -264,7 +264,8 @@ func (c *Cache) registerHost(ctx context.Context, store types.KVStore, apiImpl * errPtr := uint32(stack[2]) gasPtr := uint32(stack[3]) mem := m.Memory() - input := string(mem.Read(inputPtr, inputLen)) + tmp, _ := mem.Read(inputPtr, inputLen) + input := string(tmp) usedGas, err := apiImpl.ValidateAddress(input) buf := make([]byte, 8) binary.LittleEndian.PutUint64(buf, usedGas) From d87a2b82809764089fe191a2ac71bc7623d1ee2c Mon Sep 17 00:00:00 2001 From: Jacob Gadikian Date: Wed, 21 May 2025 05:27:39 +0700 Subject: [PATCH 17/19] run wazero tests by default --- Makefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 68fcbbe41..e0b16f01f 100644 --- a/Makefile +++ b/Makefile @@ -60,8 +60,11 @@ build-go: .PHONY: test test: - # Use package list mode to include all subdirectores. The -count=1 turns off caching. + # Run standard tests (CGO binding) and pure-Go wazero tests. + # The first invocation runs default CGO-enabled tests. RUST_BACKTRACE=1 go test -v -count=1 ./... + # The second invocation runs all tests under the 'wazero' build tag. + RUST_BACKTRACE=1 go test -v -count=1 -tags wazero ./... .PHONY: test-safety test-safety: From 3c1c49902b728f422a310833d394f94f2bb08376 Mon Sep 17 00:00:00 2001 From: Jacob Gadikian Date: Wed, 21 May 2025 05:33:35 +0700 Subject: [PATCH 18/19] re-add the circleci folder --- .circleci/config.yml | 515 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 515 insertions(+) create mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 000000000..61e2b4a8f --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,515 @@ +version: 2.1 + +orbs: + win: circleci/windows@5.0 + +jobs: + # All checks on the codebase that can run in parallel to build_shared_library + libwasmvm_sanity: + docker: + - image: cimg/rust:1.81.0 + steps: + - checkout + - run: + name: Show Rust version information + command: rustc --version; cargo --version; rustup --version + - run: + name: Add Rust components + command: rustup component add rustfmt + - restore_cache: + keys: + - cargocache-v3-libwasmvm_sanity-rust:1.81.0-{{ checksum "libwasmvm/Cargo.lock" }} + - cargocache-v3-libwasmvm_sanity-rust:1.81.0- + - run: + name: Ensure libwasmvm/bindings.h is up-to-date + working_directory: libwasmvm + command: | + cargo check + CHANGES_IN_REPO=$(git status --porcelain bindings.h) + if [[ -n "$CHANGES_IN_REPO" ]]; then + echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" + git status && git --no-pager diff + exit 1 + fi + - run: + # It is easy to update libwasmvm/bindings.h as part of the development but forget `make update-bindings` + name: Ensure internal/api/bindings.h is up to date + command: diff libwasmvm/bindings.h internal/api/bindings.h + - run: + name: Check Rust formatting + working_directory: libwasmvm + command: cargo fmt -- --check + - run: + name: Run unit tests + working_directory: libwasmvm + command: cargo test + - run: + name: Build docs + working_directory: libwasmvm + command: cargo doc --no-deps + - run: + name: Test docs + working_directory: libwasmvm + command: | + sed -i '/^crate-type = \["cdylib"\]/d' Cargo.toml + cargo test --doc + - save_cache: + paths: + - ~/.cargo/registry + - libwasmvm/target/debug/.fingerprint + - libwasmvm/target/debug/build + - libwasmvm/target/debug/deps + - libwasmvm/target/release/.fingerprint + - libwasmvm/target/release/build + - libwasmvm/target/release/deps + key: cargocache-v3-libwasmvm_sanity-rust:1.81.0-{{ checksum "libwasmvm/Cargo.lock" }} + + libwasmvm_clippy: + parameters: + rust-version: + type: string + docker: + - image: rust:<< parameters.rust-version >> + steps: + - checkout + - run: + name: Version information + command: rustc --version && cargo --version + - restore_cache: + keys: + - v3-libwasmvm_clippy-rust:<< parameters.rust-version >>-{{ checksum "libwasmvm/Cargo.lock" }} + - v3-libwasmvm_clippy-rust:<< parameters.rust-version >>- + - run: + name: Add clippy component + command: rustup component add clippy + - run: + name: Run clippy + working_directory: libwasmvm + command: cargo clippy --all-targets -- -D warnings + - save_cache: + paths: + - ~/.cargo/registry + - libwasmvm/target/debug/.fingerprint + - libwasmvm/target/debug/build + - libwasmvm/target/debug/deps + - libwasmvm/target/release/.fingerprint + - libwasmvm/target/release/build + - libwasmvm/target/release/deps + key: v3-libwasmvm_clippy-rust:<< parameters.rust-version >>-{{ checksum "libwasmvm/Cargo.lock" }} + + # This performs all the Rust debug builds on Windows. Similar to libwasmvm_sanity + # but avoids duplicating things that are not platform dependent. + libwasmvm_sanity_windows: + executor: + name: win/default + shell: bash.exe + steps: + - checkout + - run: + name: Reset git config set by CircleCI to make Cargo work + command: git config --global --unset url.ssh://git@github.com.insteadof + - run: + name: Install Rust + command: | + set -o errexit + curl -sS --output rustup-init.exe https://static.rust-lang.org/rustup/dist/x86_64-pc-windows-msvc/rustup-init.exe + ./rustup-init.exe --no-modify-path --profile minimal --default-toolchain 1.81.0 -y + echo 'export PATH="$PATH;$USERPROFILE/.cargo/bin"' >> "$BASH_ENV" + - run: + name: Show Rust version information + command: rustc --version; cargo --version; rustup --version + - restore_cache: + keys: + - cachev4-libwasmvm_sanity_windows-rust:1.81.0-{{ checksum "libwasmvm/Cargo.lock" }} + - cachev4-libwasmvm_sanity_windows-rust:1.81.0- + - run: + name: Run unit tests + working_directory: libwasmvm + command: cargo test + - save_cache: + paths: + # ".." is the easiest way to get $HOME here (pwd is $HOME\project) + - ../.cargo/registry + - libwasmvm/target/debug/.fingerprint + - libwasmvm/target/debug/build + - libwasmvm/target/debug/deps + key: cachev4-libwasmvm_sanity_windows-rust:1.81.0-{{ checksum "libwasmvm/Cargo.lock" }} + + libwasmvm_audit: + docker: + # The audit tool might use a more modern Rust version than the build jobs. See + # "Tooling Rust compiler" in docs/COMPILER_VERSIONS.md + - image: cimg/rust:1.81.0 + steps: + - checkout + - run: + name: Install OpenSSL + command: | + sudo apt update + sudo apt install libssl-dev + - run: + name: Show Rust version information + command: rustc --version; cargo --version; rustup --version + - restore_cache: + keys: + - v3-libwasmvm_audit-rust:1.81.0-{{ checksum "libwasmvm/Cargo.lock" }} + - v3-libwasmvm_audit-rust:1.81.0- + - run: + name: Install cargo-audit + command: cargo install --debug cargo-audit --version 0.21.0 --locked + - run: + name: Run cargo-audit + working_directory: libwasmvm + command: cargo audit + - save_cache: + paths: + - ~/.cargo/registry + key: v3-libwasmvm_audit-rust:1.81.0-{{ checksum "libwasmvm/Cargo.lock" }} + + format-go: + docker: + - image: cimg/go:1.22.12 + steps: + - run: + name: Install gofumpt + command: go install mvdan.cc/gofumpt@v0.4.0 + - checkout + - run: + name: Check Go formatting with gofmt + command: | + [ "$(gofmt -l .)" = "" ] || (gofmt -d . && exit 1) + - run: + name: Check Go formatting with gofumpt + command: | + [ "$(gofumpt -l .)" = "" ] || (gofumpt -d . && exit 1) + + # Build types and cosmwam package without cgo + wasmvm_no_cgo: + docker: + - image: cimg/go:1.22.12 + steps: + - checkout + - run: + name: Build package "types" without cgo + command: CGO_ENABLED=0 go build ./types + - run: + name: Build package "cosmwasm" without cgo + command: CGO_ENABLED=0 go build . + - run: + name: Test package "types" without cgo + command: CGO_ENABLED=0 go test ./types + - run: + name: Test package "cosmwasm" without cgo + command: CGO_ENABLED=0 go test . + + # Build types and cosmwasm with libwasmvm linking disabled + nolink_libwasmvm: + docker: + - image: cimg/go:1.22.12 + steps: + - checkout + - run: + name: Build package "types" with libwasmvm linking disabled + command: go build -tags "nolink_libwasmvm" ./types + - run: + name: Build package "cosmwasm" with libwasmvm linking disabled + command: go build -tags "nolink_libwasmvm" . + - run: + name: Test package "types" with libwasmvm linking disabled + command: go test -tags "nolink_libwasmvm" ./types + - run: + name: Test package "cosmwasm" with libwasmvm linking disabled + command: go test -tags "nolink_libwasmvm" . + + tidy-go: + docker: + - image: cimg/go:1.22.12 + steps: + - checkout + - run: + name: Check go mod tidy + # Use --check or --exit-code when available (Go 1.22?) + # https://github.com/golang/go/issues/27005 + command: | + go mod tidy + CHANGES_IN_REPO=$(git status --porcelain) + if [[ -n "$CHANGES_IN_REPO" ]]; then + echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" + git status && git --no-pager diff + exit 1 + fi + + format-scripts: + docker: + - image: cimg/go:1.22.12 + steps: + - run: + name: Install shfmt + command: GO111MODULE=on go install mvdan.cc/sh/v3/cmd/shfmt@v3.7.0 + - checkout + - run: + name: Run shfmt + command: shfmt --diff . + + lint-scripts: + docker: + - image: ubuntu:20.04 + steps: + - run: + name: Install packages + command: | + apt update + apt install -y git shellcheck + - checkout + - run: + name: Run shellcheck + command: find . -name "*.sh" -exec shellcheck {} + + + build_shared_library: + docker: + # libwasmvm versions built with 1.81 are broken, so we use 1.82 here + - image: cimg/rust:1.82.0 + steps: + - checkout + - run: + name: Show version information + command: rustc --version; cargo --version; rustup --version + - restore_cache: + keys: + - cargocache-v3-build_shared_library-rust:1.82.0-{{ checksum "libwasmvm/Cargo.lock" }} + - cargocache-v3-build_shared_library-rust:1.82.0- + - run: + name: Create release build of libwasmvm + command: make build-libwasmvm + - persist_to_workspace: + root: ./internal/api + paths: + - libwasmvm.x86_64.so + - save_cache: + paths: + - ~/.cargo/registry + - libwasmvm/target/debug/.fingerprint + - libwasmvm/target/debug/build + - libwasmvm/target/debug/deps + - libwasmvm/target/release/.fingerprint + - libwasmvm/target/release/build + - libwasmvm/target/release/deps + key: cargocache-v3-build_shared_library-rust:1.81.0-{{ checksum "libwasmvm/Cargo.lock" }} + + # Test the Go project and run benchmarks + wasmvm_test: + docker: + - image: cimg/go:1.22.12 + environment: + GORACE: "halt_on_error=1" + BUILD_VERSION: $(echo ${CIRCLE_SHA1} | cut -c 1-10) + steps: + - checkout + - attach_workspace: + at: /tmp/builds + - run: + name: Copy .so build + command: cp /tmp/builds/libwasmvm.x86_64.so ./internal/api + # Debugging step to better understand what is linked here + - run: + name: Check .so files + command: | + ls -lA ./internal/api/libwasmvm.*.so + sha256sum ./internal/api/libwasmvm.*.so + ldd ./internal/api/libwasmvm.x86_64.so + - run: + name: Build Go project + command: make build-go + - run: + name: Go integration tests + command: make test + - run: + name: Go tests with cgo and race condition safety checks + command: make test-safety + - run: + name: Go benchmarks + command: make bench + + test_alpine_build: + machine: + image: ubuntu-2004:2022.10.1 + steps: + - checkout + - run: make test-alpine + - run: + name: Debug build results + command: ls -l ./internal/api + + deploy_to_git: + machine: + image: ubuntu-2004:2022.10.1 + resource_class: xlarge + steps: + - add_ssh_keys: + fingerprints: + # Custom read/write deployment key with private key stored on CircleCI + # (see https://app.circleci.com/settings/project/github/CosmWasm/wasmvm/ssh and https://github.com/CosmWasm/wasmvm/settings/keys) + - "31:de:e5:84:1b:12:81:94:aa:06:50:c0:cb:bd:79:f0" + - checkout + - run: + name: Build shared library for Linux + command: make release-build-linux + - run: + name: Build shared library for macOS + command: make release-build-macos + # Shared libraries for Windows (.dll) currently do not work (https://github.com/CosmWasm/wasmvm/issues/389) + # and .dll builds are not deterministic. + # Deactivating this step to avoid polluting the git hostory. + # + # - run: + # name: Build shared library for Windows + # command: make release-build-windows + - run: + name: Debug build results + command: ls -l ./internal/api + - run: + name: Configure git user + # This is not a GitHub user and no permissions can be configured other than "push access", which + # we can configure for Deploy keys at https://github.com/CosmWasm/wasmvm/settings/keys + command: | + git config user.email "wasmvm@circleci.confio.example.com" + git config user.name "Deployer" + - run: + name: Check-in and push new libraries + command: | + git status + git add ./internal/api + git commit --allow-empty -m '[skip ci] Built release libraries' + git push origin $CIRCLE_BRANCH + + build_static_lib: + machine: + image: ubuntu-2004:2022.10.1 + resource_class: xlarge + steps: + - run: + name: Check Go version # needed for ghr installation + command: go version + - run: + name: Install ghr + command: | + go install github.com/tcnksm/ghr@v0.16.0 + ghr --version + - checkout + - run: + name: Build static library for Alpine + command: make release-build-alpine + - run: + name: Build static library for MacOS + command: make release-build-macos-static + - run: + name: Debug build results + command: ls -l ./internal/api + # Deploy to GitHub releases on tag builds. At some point we might want to extract + # those steps into a separate job for better maintainability. + - when: + condition: << pipeline.git.tag >> + steps: + - run: + name: Collect artifacts + command: | + mkdir artifacts + + # Static (from build) + cp ./internal/api/libwasmvm_muslc.x86_64.a artifacts/ + cp ./internal/api/libwasmvm_muslc.aarch64.a artifacts/ + cp ./internal/api/libwasmvmstatic_darwin.a artifacts/ + + # Shared (from git) + cp ./internal/api/libwasmvm.aarch64.so artifacts/ + cp ./internal/api/libwasmvm.x86_64.so artifacts/ + cp ./internal/api/libwasmvm.dylib artifacts/ + - run: + name: Create checksums + working_directory: artifacts + command: sha256sum * > checksums.txt && cat checksums.txt + - run: + name: Publish artifacts on GitHub + command: | + TAG="$CIRCLE_TAG" + TITLE="$TAG" + BODY="Build artifacts generated at this tag." + # Check if tag is a version without suffix (e.g. -rc or -beta) and + # set prerelease flag accordingly + [[ "$TAG" =~ ^v[0-9]+.[0-9]+.[0-9]+$ ]] || PRERELEASE=-prerelease + ghr -t "$GITHUB_TOKEN" \ + -u "$CIRCLE_PROJECT_USERNAME" -r "$CIRCLE_PROJECT_REPONAME" \ + -c "$CIRCLE_SHA1" \ + -n "$TITLE" -b "$BODY" \ + -delete \ + $PRERELEASE \ + "$TAG" ./artifacts/ + +workflows: + version: 2 + build_and_test: + jobs: + - libwasmvm_sanity + # Temporarily disabled. This check is still running on main. + # - libwasmvm_sanity_windows + - libwasmvm_clippy: + matrix: + parameters: + # Run with MSRV and some modern stable Rust + rust-version: ["1.81.0", "1.82.0"] + - libwasmvm_audit + - format-go + - wasmvm_no_cgo + - nolink_libwasmvm + - tidy-go + - format-scripts + - lint-scripts + - build_shared_library: + filters: # required since other jobs with tag filters require this one + tags: + only: /.*/ + - wasmvm_test: + requires: + - build_shared_library + - build_static_lib: + requires: + - build_shared_library + filters: + # tags and branches are OR combined + tags: + only: /^v[0-9]+\.[0-9]+\.[0-9]+.*/ + branches: + only: + # long living branches + - main + - 0.14-dev + # This is long running, so only double-check on merge commits + # ensures that all code works on alpine linux + - test_alpine_build: + filters: + tags: + ignore: + - /.*/ + branches: + only: + # long living branches + - main + # Development + - GoIter-creation + # Run only on main, not on tags (auto-build on merge PR) + - deploy_to_git: + requires: + - libwasmvm_sanity + - format-go + - tidy-go + - format-scripts + - lint-scripts + - wasmvm_test + filters: + tags: + ignore: + - /.*/ + branches: + only: + - main + - /^release\/.*/ From 7f2caa396439856100e6586c331f9ce6d07431c4 Mon Sep 17 00:00:00 2001 From: Jacob Gadikian Date: Wed, 21 May 2025 07:23:13 +0700 Subject: [PATCH 19/19] Update AGENTS.md --- AGENTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index ee0da6053..dc9b19aea 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,7 +2,7 @@ ## Formatting -- Run `gofmt -w` and `gofumpt -w` on any modified Go files. +- Run golangci-lint run ./... --fix on the whole repo whenever we change Go files. - Run `cargo fmt` on any modified Rust files. - Run `prettier -w` on any modified Markdown files.