Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion NOTICES
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ PERMISSIVE DEPENDENCIES - Go (Direct)
================================================================================

wasmtime-go
Version: v1.0.0
Version: v42.0.0
License: Apache-2.0 WITH LLVM-exception
Source: https://github.com/bytecodealliance/wasmtime-go
Notice: Copyright The Bytecode Alliance contributors.
Expand Down
4 changes: 2 additions & 2 deletions app/simple/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ clean:
$(OUT): $(SRC)
@mkdir -p $(@D)
@echo "Building development WASM module..."
tinygo build -o $@ -target=wasi .
tinygo build -o $@ -target=wasip1 .
@ls -lh $@ | awk '{print "Binary size: " $$5}'

$(PROD_OUT): $(SRC)
@mkdir -p $(@D)
@echo "Building production WASM module..."
tinygo build -o $@ -opt=s -no-debug -target=wasi .
tinygo build -o $@ -opt=s -no-debug -target=wasip1 .
@ls -lh $@ | awk '{print "Binary size: " $$5}'

wat: $(WAT)
Expand Down
4 changes: 2 additions & 2 deletions app/trigger/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ clean:
$(OUT): $(SRC)
@mkdir -p $(@D)
@echo "Building development WASM module..."
tinygo build -o $@ -target=wasi .
tinygo build -o $@ -target=wasip1 .
@ls -lh $@ | awk '{print "Binary size: " $$5}'

$(PROD_OUT): $(SRC)
@mkdir -p $(@D)
@echo "Building production WASM module..."
tinygo build -o $@ -opt=s -no-debug -target=wasi .
tinygo build -o $@ -opt=s -no-debug -target=wasip1 .
@ls -lh $@ | awk '{print "Binary size: " $$5}'

wat: $(WAT)
Expand Down
3 changes: 3 additions & 0 deletions cmd/executor/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ func main() {

// Create the WASM runtime
runtime := wasm.NewWasmtimeRuntime(log, config.MaxCachedModules)
if config.MaxGuestMemoryBytes > 0 {
runtime.SetMaxGuestMemoryBytes(config.MaxGuestMemoryBytes)
}

// Create the appropriate server based on configuration
var server communication.ExecutorServer
Expand Down
2 changes: 2 additions & 0 deletions dockerfiles/.env.template
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ EXECUTOR_PORT='4000'
EXECUTOR_FUEL_PRICE_PER_UNIT='1'
EXECUTOR_MIN_FEE_PER_REQUEST='10'
EXECUTOR_MAX_CACHED_MODULES='0'
#max linear memory per WASM guest in bytes; 0 = default (2 GiB, also the maximum). Worst-case RAM is roughly EXECUTOR_MAX_CACHED_MODULES * this value.
EXECUTOR_MAX_GUEST_MEMORY_BYTES='0'
EXECUTOR_KEYSET_RECOVERY_TYPE='1'
EXECUTOR_KMS_KEY_ARN='arn:aws:kms:eu-west-1:215705706013:key/7be0593e-72e9-46f2-9a9f-9a85a0e719e4'
EXECUTOR_KMS_REGION='eu-west-1'
Expand Down
1 change: 1 addition & 0 deletions dockerfiles/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ services:
- EXECUTOR_FUEL_PRICE_PER_UNIT
- EXECUTOR_MIN_FEE_PER_REQUEST
- EXECUTOR_MAX_CACHED_MODULES
- EXECUTOR_MAX_GUEST_MEMORY_BYTES
- EXECUTOR_LOG_KIND
- EXECUTOR_LOG_CONSOLE
- EXECUTOR_LOG_CONSOLE_LEVEL
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ require (
github.com/aws/aws-sdk-go-v2 v1.41.1
github.com/aws/aws-sdk-go-v2/config v1.29.14
github.com/aws/aws-sdk-go-v2/service/kms v1.49.5
github.com/bytecodealliance/wasmtime-go v1.0.0
github.com/elliotchance/orderedmap/v3 v3.1.0
github.com/ethereum/go-ethereum v1.17.2
github.com/fxamacker/cbor/v2 v2.2.0
Expand Down Expand Up @@ -58,6 +57,7 @@ require (
github.com/VictoriaMetrics/fastcache v1.13.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bits-and-blooms/bitset v1.20.0 // indirect
github.com/bytecodealliance/wasmtime-go/v42 v42.0.0
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cockroachdb/errors v1.11.3 // indirect
github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bits-and-blooms/bitset v1.20.0 h1:2F+rfL86jE2d/bmw7OhqUg2Sj/1rURkBn3MdfoPyRVU=
github.com/bits-and-blooms/bitset v1.20.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/bytecodealliance/wasmtime-go v1.0.0 h1:9u9gqaUiaJeN5IoD1L7egD8atOnTGyJcNp8BhkL9cUU=
github.com/bytecodealliance/wasmtime-go v1.0.0/go.mod h1:jjlqQbWUfVSbehpErw3UoWFndBXRRMvfikYH6KsCwOg=
github.com/bytecodealliance/wasmtime-go/v42 v42.0.0 h1:pJMf9xmpx67KdfVuSv9rZQcwnIISO9EsAGeFTdGsnk4=
github.com/bytecodealliance/wasmtime-go/v42 v42.0.0/go.mod h1:uhQcMe9gNDkAeA1MEo0KDcI/QppEtP3zeFiZXaf1LSI=
github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk=
github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
Expand Down
5 changes: 4 additions & 1 deletion pkg/common/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ func GetConfigVarInt64(name string, defaultValue int64, fileProperties *properti
if confVar == "" {
return defaultValue
} else {
var parsed, err = strconv.ParseInt(confVar, 10, 32)
// Parse the full int64 range: values above 2^31 must reach the caller
// (e.g. so config validation can reject an out-of-range value) instead
// of silently falling back to the default.
var parsed, err = strconv.ParseInt(confVar, 10, 64)
if err != nil {
fmt.Printf("Failed to convert %v for error %v, using default value\n", name, err)
return defaultValue
Expand Down
44 changes: 44 additions & 0 deletions pkg/common/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package common

import (
"testing"

"github.com/magiconair/properties"
"github.com/stretchr/testify/require"
)

func TestGetConfigVarInt64(t *testing.T) {
props := properties.NewProperties()
require.NoError(t, props.SetValue("FROM_FILE", 42))
require.NoError(t, props.SetValue("ABOVE_INT32", int64(3221225472))) // 3 GiB, > math.MaxInt32
require.NoError(t, props.SetValue("NEGATIVE", -7))
props.MustSet("GARBAGE", "not-a-number")

t.Run("missing var returns default", func(t *testing.T) {
require.Equal(t, int64(5), GetConfigVarInt64("MISSING", 5, props))
})

t.Run("file value is parsed", func(t *testing.T) {
require.Equal(t, int64(42), GetConfigVarInt64("FROM_FILE", 5, props))
})

t.Run("env overrides file", func(t *testing.T) {
t.Setenv("FROM_FILE", "43")
require.Equal(t, int64(43), GetConfigVarInt64("FROM_FILE", 5, props))
})

t.Run("values above int32 range are parsed, not defaulted", func(t *testing.T) {
// Regression: ParseInt used bitSize 32, silently turning any value
// above 2^31-1 (e.g. EXECUTOR_MAX_GUEST_MEMORY_BYTES=3GiB) into the
// default so validation never saw it.
require.Equal(t, int64(3221225472), GetConfigVarInt64("ABOVE_INT32", 0, props))
})

t.Run("negative values are parsed", func(t *testing.T) {
require.Equal(t, int64(-7), GetConfigVarInt64("NEGATIVE", 5, props))
})

t.Run("unparseable value returns default", func(t *testing.T) {
require.Equal(t, int64(5), GetConfigVarInt64("GARBAGE", 5, props))
})
}
21 changes: 21 additions & 0 deletions pkg/executor/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,13 @@ type Config struct {
// MaxCachedModules is the maximum number of WASM modules to keep in the LRU cache.
// 0 means unlimited.
MaxCachedModules int

// MaxGuestMemoryBytes caps the linear memory a single WASM guest can grow to.
// 0 means the 2 GiB default, which is also the maximum allowed value (the
// host ABI exchanges guest pointers as signed 32-bit offsets). Note that the
// worst-case enclave RAM usage is roughly MaxCachedModules * MaxGuestMemoryBytes,
// so size the two together against the enclave memory budget.
MaxGuestMemoryBytes int64
}

const confFileName = "executor.conf"
Expand Down Expand Up @@ -132,6 +139,7 @@ func LoadConfig() (*Config, error) {
LogNetworkLevel: common.GetConfigVar("EXECUTOR_LOG_NETWORK_LEVEL", "info", fileProperties),
CommunicationParams: communicationParams,
MaxCachedModules: int(common.GetConfigVarInt64("EXECUTOR_MAX_CACHED_MODULES", 0, fileProperties)),
MaxGuestMemoryBytes: common.GetConfigVarInt64("EXECUTOR_MAX_GUEST_MEMORY_BYTES", 0, fileProperties),
}, nil
}

Expand Down Expand Up @@ -180,6 +188,19 @@ func (c *Config) Validate() error {
c.CommunicationParams.RequestTimeoutSec))
}

// --- Guest memory cap ---
// The WASM host ABI exchanges guest pointers as signed 32-bit offsets, so a
// guest may never grow past 2 GiB (see pkg/wasm maxGuestMemoryCeilingBytes —
// duplicated here to avoid linking libwasmtime into every pkg/executor
// consumer). 0 selects the 2 GiB default; anything else must be in range
// rather than silently clamped, so a misconfigured operator finds out at startup.
const maxGuestMemoryCeilingBytes = 2 << 30
if c.MaxGuestMemoryBytes < 0 || c.MaxGuestMemoryBytes > maxGuestMemoryCeilingBytes {
errs = append(errs, fmt.Sprintf(
"EXECUTOR_MAX_GUEST_MEMORY_BYTES must be between 0 (default: 2 GiB) and %d (2 GiB), got %d",
int64(maxGuestMemoryCeilingBytes), c.MaxGuestMemoryBytes))
}

// --- KMS configuration ---
errs = append(errs, c.validateKMSConfig()...)

Expand Down
67 changes: 67 additions & 0 deletions pkg/executor/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package executor

import (
"math/big"
"os"
"testing"

"github.com/HorizenOfficial/vela/pkg/common"
Expand Down Expand Up @@ -168,3 +169,69 @@ func TestValidate_MultipleErrors(t *testing.T) {
assert.Contains(t, err.Error(), "EXECUTOR_FUEL_PRICE_PER_UNIT")
assert.Contains(t, err.Error(), "EXECUTOR_COMMUNICATION_PARAMS_REQUEST_TIMEOUT_SEC")
}

func TestValidate_MaxGuestMemoryBytes_InRange_NoError(t *testing.T) {
cfg := validExecutorConfig()
cfg.MaxGuestMemoryBytes = 512 * 1024 * 1024
require.NoError(t, cfg.Validate())
}

func TestValidate_MaxGuestMemoryBytes_Negative(t *testing.T) {
cfg := validExecutorConfig()
cfg.MaxGuestMemoryBytes = -1

err := cfg.Validate()
require.Error(t, err)
assert.Contains(t, err.Error(), "EXECUTOR_MAX_GUEST_MEMORY_BYTES")
}

func TestValidate_MaxGuestMemoryBytes_AboveCeiling(t *testing.T) {
cfg := validExecutorConfig()
cfg.MaxGuestMemoryBytes = (2 << 30) + 1

err := cfg.Validate()
require.Error(t, err)
assert.Contains(t, err.Error(), "EXECUTOR_MAX_GUEST_MEMORY_BYTES")
}

// loadConfigFromFile writes an executor.conf with the given content in a fresh
// temp working directory and runs LoadConfig against it, exercising the real
// file-parsing path (GetConfigVarInt64 etc.) rather than setting struct fields
// directly.
func loadConfigFromFile(t *testing.T, conf string) *Config {
t.Helper()
t.Chdir(t.TempDir())
require.NoError(t, os.WriteFile(confFileName, []byte(conf), 0600))
cfg, err := LoadConfig()
require.NoError(t, err)
return cfg
}

func TestLoadConfig_MaxGuestMemoryBytes_AboveCeilingInConfFile_Rejected(t *testing.T) {
// Clear any ambient env overrides: empty env values fall through to the file.
t.Setenv("EXECUTOR_MAX_GUEST_MEMORY_BYTES", "")
t.Setenv("EXECUTOR_KEYSET_RECOVERY_TYPE", "")

// 3 GiB — above the 2 GiB ceiling. The value must survive parsing intact
// (regression: a 32-bit ParseInt silently replaced it with the default,
// so Validate never saw it) and then be rejected by Validate.
cfg := loadConfigFromFile(t,
"EXECUTOR_KEYSET_RECOVERY_TYPE=0\nEXECUTOR_MAX_GUEST_MEMORY_BYTES=3221225472\n")
require.Equal(t, int64(3221225472), cfg.MaxGuestMemoryBytes)

err := cfg.Validate()
require.Error(t, err)
assert.Contains(t, err.Error(), "EXECUTOR_MAX_GUEST_MEMORY_BYTES")
}

func TestLoadConfig_MaxGuestMemoryBytes_CeilingInConfFile_Accepted(t *testing.T) {
t.Setenv("EXECUTOR_MAX_GUEST_MEMORY_BYTES", "")
t.Setenv("EXECUTOR_KEYSET_RECOVERY_TYPE", "")

// Exactly 2 GiB (2147483648) is the maximum legal value; it is above
// math.MaxInt32, so it also only works with a full 64-bit parse.
cfg := loadConfigFromFile(t,
"EXECUTOR_KEYSET_RECOVERY_TYPE=0\nEXECUTOR_MAX_GUEST_MEMORY_BYTES=2147483648\n")
require.Equal(t, int64(2147483648), cfg.MaxGuestMemoryBytes)
require.NoError(t, cfg.Validate())
}
2 changes: 1 addition & 1 deletion pkg/testutil/fullstack/cgo_ldflags.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package fullstack

// Problem:
// wasmtime-go (v1.0.0) ships a pre-built libwasmtime.a that statically includes
// wasmtime-go ships a pre-built libwasmtime.a that statically includes
// zstd C sources (used internally by wasmtime's compiled-module cache). Meanwhile,
// go-ethereum transitively imports github.com/DataDog/zstd (v1.4.5), which compiles
// the same zstd C library via cgo. When both end up in the same binary — as they do
Expand Down
Loading
Loading