Simplify WasmInstruction I/O: enforce batched read/write
Last updated: Jun 02, 2025
Proof of Concept
Proposal
Wasm executables currently allow queries and instructions to be mixed in any order.
In practice, even fairly complex flows—like multisig—don’t need queries and instructions to alternate. A simpler model is to require all queries to run in a single batch at the start, with instructions batched at the end.
This change would reduce the number of FFI crossings—improving performance (see #4756)—and, by simplifying the architecture, enable lower transaction fees.
The diagram below illustrates the instruction lifecycle (applicable to both Wasm and built-in instructions; not transactions—see #5147).
-
This proposal only makes sense when combined with #5357.
The executor will be reduced to a pure permission validator—or, more succinctly, an authorizer—that accepts read/write requests paired with permission tokens and returns verdicts to the host.
Instruction Lifecycle
stateDiagram-v2
[*] --> Host
Init --> XInit
XToRead --> ToRead
ToRead --> AToRead
AReading --> Reading
HasRead --> XHasRead
XToWrite --> ToWrite
ToWrite --> AToWrite
AWriting --> Writing
state Host {
state instruction_variant <<choice>>
[*] --> instruction_variant
instruction_variant --> Init: Wasm
instruction_variant --> ToWrite: Builtin
ToRead --> ToRead: batch_read_requests
Reading --> HasRead: read
ToWrite --> ToWrite: batch_write_requests
Writing --> HasWritten: write
HasWritten --> [*]
}
state WasmInstruction {
XInit --> XToRead: generate_read_request
XHasRead --> XToWrite: generate_write_request
}
state Authorizer {
AToRead --> AReading: seek_read_approval
AToWrite --> AWriting: seek_write_approval
}
Previous description
Currently, users can put queries and instructions in arbitrary order within Wasm executables:
sequenceDiagram
autonumber
participant X as Executable
participant H as Host
participant A as Executor
%% Note over X,H: note
%% Note over H,A: note
%% Note over A,X: note
H->>X: Context
X->>H: query req
H->>A: query req
A->>A: query validation
A->>H: query req
H->>H: query exec
H->>X: query res
X->>H: query req
H->>A: query req
A->>A: query validation
A->>H: query req
H->>H: query exec
H->>X: query res
X->>H: instruction req
H->>A: instruction req
A->>A: instruction validation
A->>H: instruction req
H->>H: instruction exec
H->>X: instruction res
X->>H: query req
H->>A: query req
A->>A: query validation
A->>H: query req
H->>H: query exec
H->>X: query res
X->>H: instruction req
H->>A: instruction req
A->>A: instruction validation
A->>H: instruction req
H->>H: instruction exec
H->>X: instruction res
X->>H: instruction req
H->>A: instruction req
A->>A: instruction validation
A->>H: instruction req
H->>H: instruction exec
H->>X: instruction res
H->>H: transaction commit
However, even relatively complex logic like multisig does not require alternating queries and instructions. A possible simplification is enforcing queries to be executed at the beginning and instructions at the end:
sequenceDiagram
autonumber
participant X as Executable
participant H as Host
participant A as Authorizer
%% Note over X,H: note
%% Note over H,A: note
%% Note over A,X: note
X->>H: Event declaration
H->>X: optional Context
X->>H: read req
H->>H: read exec
H->>A: read reS
A->>A: read validation
A->>H: Verdict
H->>X: read res
X->>H: write req
H->>A: expected Event
A->>A: write validation
A->>H: Verdict
H->>H: write exec
H->>H: compare declared & actual events
H->>H: transaction commit
Key Considerations
- Event declaration as a static method is required to prevent event loops: #5362
- Read requests must be executed before validation to enable query result inspection: #5338
- A transaction should only be committed if the actual event is a subset of the declared event.