Skip to content

Commit 54178d0

Browse files
authored
Merge pull request #125 from HorizenOfficial/as/multi_app_impl_exec_mgr
As/multi app impl exec mgr
2 parents ac7e125 + 18ee3a5 commit 54178d0

20 files changed

Lines changed: 756 additions & 142 deletions

app/simple/integration_test.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ func TestSimpleAppIntegration(t *testing.T) {
7474
wasmBytes := buildAndLoadWasmModule(t)
7575

7676
// Create a new wasmtime runtime
77-
runtime := vela_wasm.NewWasmtimeRuntime(testLogger)
77+
runtime := vela_wasm.NewWasmtimeRuntime(testLogger, 0)
7878
defer runtime.Close()
7979

8080
ctx := context.Background()
@@ -212,7 +212,7 @@ func TestSimpleAppIntegration_NullPayload(t *testing.T) {
212212
wasmBytes := buildAndLoadWasmModule(t)
213213

214214
// Create a new wasmtime runtime
215-
runtime := vela_wasm.NewWasmtimeRuntime(testLogger)
215+
runtime := vela_wasm.NewWasmtimeRuntime(testLogger, 0)
216216
defer runtime.Close()
217217

218218
ctx := context.Background()
@@ -234,7 +234,7 @@ func TestSimpleAppIntegration_NegativeScenarios(t *testing.T) {
234234
wasmBytes := buildAndLoadWasmModule(t)
235235

236236
// Create a new wasmtime runtime
237-
runtime := vela_wasm.NewWasmtimeRuntime(testLogger)
237+
runtime := vela_wasm.NewWasmtimeRuntime(testLogger, 0)
238238
defer runtime.Close()
239239

240240
ctx := context.Background()
@@ -344,7 +344,7 @@ func TestSimpleAppIntegration_NilData(t *testing.T) {
344344
wasmBytes := buildAndLoadWasmModule(t)
345345

346346
// Create a new wasmtime runtime
347-
runtime := vela_wasm.NewWasmtimeRuntime(testLogger)
347+
runtime := vela_wasm.NewWasmtimeRuntime(testLogger, 0)
348348
defer runtime.Close()
349349

350350
ctx := context.Background()
@@ -376,7 +376,7 @@ func TestSimpleAppIntegration_NilData(t *testing.T) {
376376
}
377377

378378
func TestSimpleAppIntegration_InvalidWasm(t *testing.T) {
379-
runtime := vela_wasm.NewWasmtimeRuntime(testLogger)
379+
runtime := vela_wasm.NewWasmtimeRuntime(testLogger, 0)
380380
defer runtime.Close()
381381

382382
ctx := context.Background()
@@ -415,7 +415,7 @@ func TestSimpleAppIntegration_InvalidState(t *testing.T) {
415415
wasmBytes := buildAndLoadWasmModule(t)
416416

417417
// Create a new wasmtime runtime
418-
runtime := vela_wasm.NewWasmtimeRuntime(testLogger)
418+
runtime := vela_wasm.NewWasmtimeRuntime(testLogger, 0)
419419
defer runtime.Close()
420420

421421
ctx := context.Background()
@@ -440,7 +440,7 @@ func TestSimpleAppIntegration_MemoryStress(t *testing.T) {
440440
wasmBytes := buildAndLoadWasmModule(t)
441441

442442
// Create a new wasmtime runtime with limited memory to make leaks surface faster.
443-
runtime := vela_wasm.NewWasmtimeRuntime(testLogger)
443+
runtime := vela_wasm.NewWasmtimeRuntime(testLogger, 0)
444444
defer runtime.Close()
445445

446446
ctx := context.Background()
@@ -544,7 +544,7 @@ func requireMemoryClean(t *testing.T, runtime *vela_wasm.WasmtimeRuntime, wasmBy
544544
// round-trip that cannot be unit-tested in native 64-bit Go.
545545
func TestSimpleAppIntegration_MemoryCleanBetweenOps(t *testing.T) {
546546
wasmBytes := buildAndLoadWasmModule(t)
547-
runtime := vela_wasm.NewWasmtimeRuntime(testLogger)
547+
runtime := vela_wasm.NewWasmtimeRuntime(testLogger, 0)
548548
defer runtime.Close()
549549

550550
ctx := context.Background()
@@ -611,7 +611,7 @@ func TestSimpleAppIntegration_MemoryCleanBetweenOps(t *testing.T) {
611611
// (which still use SerializeAndWriteResult -> BytesToPtr) do not leak memory.
612612
func TestSimpleAppIntegration_ErrorPathMemory(t *testing.T) {
613613
wasmBytes := buildAndLoadWasmModule(t)
614-
runtime := vela_wasm.NewWasmtimeRuntime(testLogger)
614+
runtime := vela_wasm.NewWasmtimeRuntime(testLogger, 0)
615615
defer runtime.Close()
616616

617617
ctx := context.Background()
@@ -656,7 +656,7 @@ func TestSimpleAppIntegration_ErrorPathMemory(t *testing.T) {
656656
// result that stresses the allocate -> BytesToPtr -> extractResultBytes -> deallocate pipeline.
657657
func TestSimpleAppIntegration_LargeResultRoundTrip(t *testing.T) {
658658
wasmBytes := buildAndLoadWasmModule(t)
659-
runtime := vela_wasm.NewWasmtimeRuntime(testLogger)
659+
runtime := vela_wasm.NewWasmtimeRuntime(testLogger, 0)
660660
defer runtime.Close()
661661

662662
ctx := context.Background()

cmd/admincli/README.md

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ Commands:
3636
2) Get Log Level
3737
3) Set Log Level
3838
4) Key Attestation
39-
5) Quit
39+
5) Get WASM Cache Size
40+
6) Set WASM Cache Size
41+
7) Quit
4042
Select command>
4143
```
4244

@@ -84,7 +86,7 @@ contains the new level from each:
8486
}
8587
```
8688

87-
### 4) Key Attestation
89+
### 4) Key Attestation (executor only)
8890

8991
Requests an AWS Nitro Enclave attestation document from the Executor. The
9092
attestation document cryptographically binds the Executor's keyset (P521
@@ -96,6 +98,29 @@ structure returned as base64.
9698
This command only works when the Executor is running inside a Nitro Enclave
9799
(requires `/dev/nsm`). Outside of an enclave it returns an error.
98100

101+
### 5) Get WASM Cache Size (executor only)
102+
103+
Returns the current WASM module LRU cache limit from the Executor. A value of
104+
`0` means unlimited (all modules stay in memory). Always targets the executor.
105+
106+
```json
107+
{"maxCachedModules": 0}
108+
```
109+
110+
### 6) Set WASM Cache Size (executor only)
111+
112+
Changes the WASM module LRU cache limit at runtime. If the new limit is lower
113+
than the current number of cached modules, excess modules are evicted lazily
114+
when the next module is loaded (least recently used first). A value of `0`
115+
means unlimited. Always targets the executor.
116+
117+
```json
118+
{"maxCachedModules": 5}
119+
```
120+
121+
The initial value is set via the `EXECUTOR_MAX_CACHED_MODULES` environment
122+
variable (default `0`).
123+
99124
### Targets
100125

101126
| Value | Behaviour |
@@ -146,8 +171,9 @@ cross-checks every duplicated value:
146171
- **Valid log levels** — the local list is compared against
147172
`admin.SupportedLogLevels` (parsed from the canonical comma-separated string).
148173
- **JSON field names** — both local and upstream structs (`setLogLevelReq` vs
149-
`admin.SetLogLevelRequest`, etc.) are marshaled and the resulting JSON keys
150-
are compared to catch renamed or missing fields.
174+
`admin.SetLogLevelRequest`, `setWasmCacheSizeReq` vs
175+
`admin.SetWasmCacheSizeRequest`, etc.) are marshaled and the resulting JSON
176+
keys are compared to catch renamed or missing fields.
151177

152178
These tests run in milliseconds with no network or servers required:
153179

cmd/admincli/main.go

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@ import (
1717
// can be distributed as a standalone tool. Tests import pkg/admin to guard
1818
// against drift.
1919
const (
20-
adminResponseMessage = "response"
21-
adminErrorMessage = "error"
22-
keyAttestationRequest = "key_attestation"
23-
getVersionRequest = "get_version"
24-
setLogLevelRequest = "set_log_level"
25-
getLogLevelRequest = "get_log_level"
20+
adminResponseMessage = "response"
21+
adminErrorMessage = "error"
22+
keyAttestationRequest = "key_attestation"
23+
getVersionRequest = "get_version"
24+
setLogLevelRequest = "set_log_level"
25+
getLogLevelRequest = "get_log_level"
26+
setWasmCacheSizeRequest = "set_wasm_cache_size"
27+
getWasmCacheSizeRequest = "get_wasm_cache_size"
2628
)
2729

2830
var validLogLevels = []string{
@@ -46,6 +48,10 @@ type setLogLevelReq struct {
4648
Level string `json:"level"`
4749
}
4850

51+
type setWasmCacheSizeReq struct {
52+
MaxCachedModules int `json:"maxCachedModules"`
53+
}
54+
4955
var (
5056
useColors bool
5157
errBack = fmt.Errorf("back")
@@ -96,8 +102,12 @@ func main() {
96102
case "3":
97103
msg, err = buildSetLogLevel(scanner)
98104
case "4":
99-
msg = buildKeyAttestation()
100-
case "5", "q", "Q":
105+
msg, err = buildKeyAttestation()
106+
case "5":
107+
msg, err = buildGetWasmCacheSize()
108+
case "6":
109+
msg, err = buildSetWasmCacheSize(scanner)
110+
case "7", "q", "Q":
101111
fmt.Println("Bye.")
102112
return
103113
default:
@@ -123,7 +133,9 @@ func printMenu() {
123133
fmt.Println(" 2) Get Log Level")
124134
fmt.Println(" 3) Set Log Level")
125135
fmt.Println(" 4) Key Attestation")
126-
fmt.Println(" 5) Quit")
136+
fmt.Println(" 5) Get WASM Cache Size")
137+
fmt.Println(" 6) Set WASM Cache Size")
138+
fmt.Println(" 7) Quit")
127139
}
128140

129141
func prompt(scanner *bufio.Scanner, label string) string {
@@ -198,8 +210,28 @@ func buildSetLogLevel(scanner *bufio.Scanner) (*adminMessage, error) {
198210
return &adminMessage{Type: setLogLevelRequest, Target: target, Data: raw}, nil
199211
}
200212

201-
func buildKeyAttestation() *adminMessage {
202-
return &adminMessage{Type: keyAttestationRequest, Target: "executor"}
213+
func buildKeyAttestation() (*adminMessage, error) {
214+
return &adminMessage{Type: keyAttestationRequest, Target: "executor"}, nil
215+
}
216+
217+
func buildGetWasmCacheSize() (*adminMessage, error) {
218+
return &adminMessage{Type: getWasmCacheSizeRequest, Target: "executor"}, nil
219+
}
220+
221+
func buildSetWasmCacheSize(scanner *bufio.Scanner) (*adminMessage, error) {
222+
raw := prompt(scanner, "Max cached modules (0 = unlimited)")
223+
if strings.EqualFold(raw, "b") || strings.EqualFold(raw, "back") {
224+
return nil, errBack
225+
}
226+
n, err := strconv.Atoi(raw)
227+
if err != nil || n < 0 {
228+
return nil, fmt.Errorf("invalid value %q: must be a non-negative integer", raw)
229+
}
230+
data, err := json.Marshal(setWasmCacheSizeReq{MaxCachedModules: n})
231+
if err != nil {
232+
return nil, fmt.Errorf("marshal request: %w", err)
233+
}
234+
return &adminMessage{Type: setWasmCacheSizeRequest, Target: "executor", Data: json.RawMessage(data)}, nil
203235
}
204236

205237
// --- send / receive ---

cmd/admincli/main_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ func TestMessageTypeConstants(t *testing.T) {
2525
{"GetVersionRequestMessage", getVersionRequest, string(admin.GetVersionRequestMessage)},
2626
{"SetLogLevelRequestMessage", setLogLevelRequest, string(admin.SetLogLevelRequestMessage)},
2727
{"GetLogLevelRequestMessage", getLogLevelRequest, string(admin.GetLogLevelRequestMessage)},
28+
{"SetWasmCacheSizeRequestMessage", setWasmCacheSizeRequest, string(admin.SetWasmCacheSizeRequestMessage)},
29+
{"GetWasmCacheSizeRequestMessage", getWasmCacheSizeRequest, string(admin.GetWasmCacheSizeRequestMessage)},
2830
}
2931
for _, c := range checks {
3032
if c.local != c.upstream {
@@ -95,6 +97,23 @@ func TestAdminMessageJSONShape(t *testing.T) {
9597
}
9698
}
9799

100+
// TestSetWasmCacheSizeReqJSONShape verifies that the local setWasmCacheSizeReq
101+
// produces the same JSON field names as admin.SetWasmCacheSizeRequest.
102+
func TestSetWasmCacheSizeReqJSONShape(t *testing.T) {
103+
local, err := json.Marshal(setWasmCacheSizeReq{MaxCachedModules: 5})
104+
require.NoError(t, err)
105+
upstream, err := json.Marshal(admin.SetWasmCacheSizeRequest{MaxCachedModules: 5})
106+
require.NoError(t, err)
107+
108+
var localMap, upstreamMap map[string]any
109+
require.NoError(t, json.Unmarshal(local, &localMap))
110+
require.NoError(t, json.Unmarshal(upstream, &upstreamMap))
111+
112+
if !reflect.DeepEqual(localMap, upstreamMap) {
113+
t.Errorf("SetWasmCacheSizeReq JSON mismatch:\n admincli: %s\n pkg/admin: %s", local, upstream)
114+
}
115+
}
116+
98117
// TestErrorDataJSONShape verifies that the local errorData produces the same
99118
// JSON field names as communication.ErrorData.
100119
func TestErrorDataJSONShape(t *testing.T) {

cmd/executor/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ func main() {
6060
}()
6161

6262
// Create the WASM runtime
63-
runtime := wasm.NewWasmtimeRuntime(log)
63+
runtime := wasm.NewWasmtimeRuntime(log, config.MaxCachedModules)
6464

6565
// Create the appropriate server based on configuration
6666
var server communication.ExecutorServer

dockerfiles/.env.template

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ EXECUTOR_IP_HOST='10.10.40.10'
1010
EXECUTOR_PORT='4000'
1111
EXECUTOR_FUEL_PRICE_PER_UNIT='1'
1212
EXECUTOR_MIN_FEE_PER_REQUEST='10'
13+
EXECUTOR_MAX_CACHED_MODULES='0'
1314
EXECUTOR_KEYSET_RECOVERY_TYPE='1'
1415
EXECUTOR_KMS_KEY_ARN='arn:aws:kms:eu-west-1:215705706013:key/7be0593e-72e9-46f2-9a9f-9a85a0e719e4'
1516
EXECUTOR_KMS_REGION='eu-west-1'

dockerfiles/docker-compose.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ services:
5858
- EXECUTOR_KEYSET_RECOVERY_TYPE
5959
- EXECUTOR_FUEL_PRICE_PER_UNIT
6060
- EXECUTOR_MIN_FEE_PER_REQUEST
61+
- EXECUTOR_MAX_CACHED_MODULES
6162
- EXECUTOR_LOG_KIND
6263
- EXECUTOR_LOG_CONSOLE
6364
- EXECUTOR_LOG_CONSOLE_LEVEL

pkg/admin/admin_interface.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,12 @@ const (
7373

7474
// GetLogLevelRequestMessage represents a request to get the current log level
7575
GetLogLevelRequestMessage AdminMessageType = "get_log_level"
76+
77+
// SetWasmCacheSizeRequestMessage represents a request to change the WASM module cache size (executor only)
78+
SetWasmCacheSizeRequestMessage AdminMessageType = "set_wasm_cache_size"
79+
80+
// GetWasmCacheSizeRequestMessage represents a request to get the WASM module cache size (executor only)
81+
GetWasmCacheSizeRequestMessage AdminMessageType = "get_wasm_cache_size"
7682
)
7783

7884
// Admin command type constants for ForwardAdminCommand dispatching through the
@@ -82,7 +88,9 @@ const (
8288
AdminCmdKeyAttestation = "key_attestation"
8389
AdminCmdGetVersion = "get_version"
8490
AdminCmdSetLogLevel = "set_log_level"
85-
AdminCmdGetLogLevel = "get_log_level"
91+
AdminCmdGetLogLevel = "get_log_level"
92+
AdminCmdSetWasmCacheSize = "set_wasm_cache_size"
93+
AdminCmdGetWasmCacheSize = "get_wasm_cache_size"
8694
)
8795

8896
// SupportedLogLevels lists all valid log level strings accepted by SetLogLevel.
@@ -152,6 +160,21 @@ func HandleGetLogLevel(log logger.Logger, componentName string) (string, error)
152160
return log.GetLevel(), nil
153161
}
154162

163+
// SetWasmCacheSizeRequest is the payload for SetWasmCacheSizeRequestMessage.
164+
type SetWasmCacheSizeRequest struct {
165+
MaxCachedModules int `json:"maxCachedModules"`
166+
}
167+
168+
// SetWasmCacheSizeResponse is the response for SetWasmCacheSizeRequestMessage.
169+
type SetWasmCacheSizeResponse struct {
170+
MaxCachedModules int `json:"maxCachedModules"`
171+
}
172+
173+
// GetWasmCacheSizeResponse is the response for GetWasmCacheSizeRequestMessage.
174+
type GetWasmCacheSizeResponse struct {
175+
MaxCachedModules int `json:"maxCachedModules"`
176+
}
177+
155178
// AdminMessage represents an admin command message.
156179
// Target controls routing: "manager", "executor", "all", or "" (defaults to "all").
157180
type AdminMessage struct {

pkg/blockchain/mock_client.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ type MockClient struct {
4040
reports map[common.RequestIdType]*common.DeanonymizationReport
4141
updatePayloads map[common.RequestIdType]*common.UpdatePayload
4242
eventSubscribers []chan<- interface{}
43-
stateRoot [32]byte
43+
stateRoots map[common.ApplicationIdType][32]byte // per-app state roots (mirrors contract's applicationStateRoots)
4444
chainID *big.Int
4545
blockNumber uint64
4646
*testutil.MockFunctions
@@ -56,6 +56,7 @@ func NewMockClient() *MockClient {
5656
withdrawals: make(map[common.ApplicationIdType]*[]common.Withdrawal),
5757
reports: make(map[common.RequestIdType]*common.DeanonymizationReport),
5858
updatePayloads: make(map[common.RequestIdType]*common.UpdatePayload),
59+
stateRoots: make(map[common.ApplicationIdType][32]byte),
5960
MockFunctions: testutil.NewMockFunctions(),
6061
}
6162
}
@@ -189,12 +190,12 @@ func (c *MockClient) GetNextPendingRequest(ctx context.Context) (*common.Request
189190
if f, ok := c.GetMockedFunc("GetNextPendingRequest"); ok {
190191
return f.(func(context.Context) (*common.Request, [32]byte, error))(ctx)
191192
}
192-
var req *common.Request
193193
if c.pendingRequests.Len() > 0 {
194-
req = c.pendingRequests.Front().Value
194+
req := c.pendingRequests.Front().Value
195+
return req, c.stateRoots[req.ApplicationID], nil
195196
}
196197

197-
return req, c.stateRoot, nil
198+
return nil, [32]byte{}, nil // no pending request — return zero root (matches contract behavior)
198199

199200
}
200201

@@ -279,7 +280,7 @@ func (c *MockClient) SubmitStateUpdate(ctx context.Context, update *common.Updat
279280
EncryptedState: nil, // State is stored separately in the data layer
280281
}
281282

282-
c.stateRoot = update.NewStateRoot
283+
c.stateRoots[update.ApplicationID] = update.NewStateRoot
283284

284285
// Emit events
285286
c.emitEvents(update.Events)
@@ -430,7 +431,7 @@ func (c *MockClient) ClearAllData() {
430431
c.reports = make(map[common.RequestIdType]*common.DeanonymizationReport)
431432
c.failedRequests = orderedmap.NewOrderedMap[common.RequestIdType, *common.Request]()
432433
c.updatePayloads = make(map[common.RequestIdType]*common.UpdatePayload)
433-
c.stateRoot = [32]byte{}
434+
c.stateRoots = make(map[common.ApplicationIdType][32]byte)
434435
c.MockedFunctions = make(map[string]interface{})
435436
}
436437

0 commit comments

Comments
 (0)