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
35 changes: 35 additions & 0 deletions cmd/bee/cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,22 @@ const (
optionNameMinimumGasTipCap = "minimum-gas-tip-cap"
optionNameGasLimitFallback = "gas-limit-fallback"
optionNameP2PWSSEnable = "p2p-wss-enable"
optionNameWasmExecuteEnable = "wasm-execute-enable"
optionNameWasmWorkers = "wasm-workers"
optionNameWasmExecTimeout = "wasm-exec-timeout"
optionNameWasmMaxModuleSize = "wasm-max-module-size"
optionNameWasmMemory = "wasm-memory"
optionNameWasmMaxMemory = "wasm-max-memory"
optionNameWasmHostCalls = "wasm-host-calls"
optionNameWasmMaxHostCalls = "wasm-max-host-calls"
optionNameWasmHostBytes = "wasm-host-bytes"
optionNameWasmMaxHostBytes = "wasm-max-host-bytes"
optionNameWasmExecDepth = "wasm-exec-depth"
optionNameWasmMaxExecDepth = "wasm-max-exec-depth"
optionNameWasmMaxResponseHeaders = "wasm-max-response-headers"
optionNameWasmMaxResponseHeaderBytes = "wasm-max-response-header-bytes"
optionNameWasmRequestHeaders = "wasm-request-headers"
optionNameWasmMaxEnvBytes = "wasm-max-env-bytes"
optionP2PWSSAddr = "p2p-wss-addr"
optionNATWSSAddr = "nat-wss-addr"
optionAutoTLSDomain = "autotls-domain"
Expand Down Expand Up @@ -336,6 +352,25 @@ func (c *command) setAllFlags(cmd *cobra.Command) {
cmd.Flags().Uint64(optionNameMinimumGasTipCap, 0, "minimum gas tip cap in wei for transactions, 0 means use suggested gas tip cap")
cmd.Flags().Uint64(optionNameGasLimitFallback, 500_000, "gas limit fallback when estimation fails for contract transactions")
cmd.Flags().Bool(optionNameP2PWSSEnable, false, "Enable Secure WebSocket P2P connections")
cmd.Flags().Bool(optionNameWasmExecuteEnable, false, "enable the experimental WASM execute endpoint")
cmd.Flags().Int(optionNameWasmWorkers, 0, "maximum number of concurrent WASM executions, 0 means min(number of CPUs, 8)")
cmd.Flags().Duration(optionNameWasmExecTimeout, 10*time.Second, "wall-clock watchdog timeout for a single WASM execution")
cmd.Flags().Uint64(optionNameWasmMaxModuleSize, 16*1024*1024, "maximum size in bytes of a WASM module that may be executed")
cmd.Flags().Uint64(optionNameWasmMemory, 32*1024*1024, "default linear memory limit in bytes for a single WASM execution")
cmd.Flags().Uint64(optionNameWasmMaxMemory, 256*1024*1024, "maximum linear memory limit in bytes a request may ask for")
cmd.Flags().Uint64(optionNameWasmHostCalls, 64, "default number of swarm host calls a single WASM execution may make")
cmd.Flags().Uint64(optionNameWasmMaxHostCalls, 1024, "maximum number of swarm host calls a request may ask for")
cmd.Flags().Uint64(optionNameWasmHostBytes, 32*1024*1024, "default total bytes swarm host calls of a single WASM execution may move")
cmd.Flags().Uint64(optionNameWasmMaxHostBytes, 256*1024*1024, "maximum total bytes moved by swarm host calls a request may ask for")
cmd.Flags().Uint64(optionNameWasmExecDepth, 4, "default maximum nesting depth of swarm_execute calls")
cmd.Flags().Uint64(optionNameWasmMaxExecDepth, 8, "maximum nesting depth of swarm_execute calls a request may ask for")
// No default/maximum pair for these: the request-header overrides exist so a
// caller can bound risk it is exposed to, and a caller is not exposed to the
// response header budget.
cmd.Flags().Uint64(optionNameWasmMaxResponseHeaders, 32, "maximum number of response headers a WASM module may set")
cmd.Flags().Uint64(optionNameWasmMaxResponseHeaderBytes, 8*1024, "maximum total size in bytes of the response headers a WASM module may set")
cmd.Flags().StringSlice(optionNameWasmRequestHeaders, nil, "request headers exposed to a WASM module as CGI HTTP_* variables; replaces the built-in list. Authorization, Proxy-Authorization and Cookie are never exposed")
cmd.Flags().Uint64(optionNameWasmMaxEnvBytes, 16*1024, "maximum total size in bytes of the request metadata exposed to a WASM module")
cmd.Flags().String(optionP2PWSSAddr, ":1635", "p2p wss address")
cmd.Flags().String(optionNATWSSAddr, "", "WSS NAT exposed address")
cmd.Flags().String(optionAutoTLSDomain, p2pforge.DefaultForgeDomain, "autotls domain")
Expand Down
16 changes: 16 additions & 0 deletions cmd/bee/cmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,22 @@ func buildBeeNode(ctx context.Context, c *command, cmd *cobra.Command, logger lo
WarmupTime: c.config.GetDuration(optionWarmUpTime),
WelcomeMessage: c.config.GetString(optionWelcomeMessage),
WhitelistedWithdrawalAddress: c.config.GetStringSlice(optionNameWhitelistedWithdrawalAddress),
WasmExecuteEnable: c.config.GetBool(optionNameWasmExecuteEnable),
WasmWorkers: c.config.GetInt(optionNameWasmWorkers),
WasmExecTimeout: c.config.GetDuration(optionNameWasmExecTimeout),
WasmMaxModuleSize: c.config.GetUint64(optionNameWasmMaxModuleSize),
WasmMemory: c.config.GetUint64(optionNameWasmMemory),
WasmMaxMemory: c.config.GetUint64(optionNameWasmMaxMemory),
WasmHostCalls: c.config.GetUint64(optionNameWasmHostCalls),
WasmMaxHostCalls: c.config.GetUint64(optionNameWasmMaxHostCalls),
WasmHostBytes: c.config.GetUint64(optionNameWasmHostBytes),
WasmMaxHostBytes: c.config.GetUint64(optionNameWasmMaxHostBytes),
WasmExecDepth: c.config.GetUint64(optionNameWasmExecDepth),
WasmMaxExecDepth: c.config.GetUint64(optionNameWasmMaxExecDepth),
WasmMaxResponseHeaders: c.config.GetUint64(optionNameWasmMaxResponseHeaders),
WasmMaxResponseHeaderBytes: c.config.GetUint64(optionNameWasmMaxResponseHeaderBytes),
WasmRequestHeaders: c.config.GetStringSlice(optionNameWasmRequestHeaders),
WasmMaxEnvBytes: c.config.GetUint64(optionNameWasmMaxEnvBytes),
})

return b, err
Expand Down
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ require (
golang.org/x/crypto v0.48.0
golang.org/x/net v0.50.0
golang.org/x/sync v0.19.0
golang.org/x/sys v0.41.0
golang.org/x/sys v0.44.0
golang.org/x/term v0.40.0
golang.org/x/time v0.12.0
gopkg.in/yaml.v2 v2.4.0
Expand All @@ -59,6 +59,8 @@ require (
resenje.org/web v0.4.3
)

require github.com/tetratelabs/wazero v1.12.0

require (
filippo.io/bigmod v0.1.1-0.20260103110540-f8a47775ebe5 // indirect
filippo.io/keygen v0.0.0-20260114151900-8e2790ea4c5b // indirect
Expand Down
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -956,6 +956,8 @@ github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cb
github.com/tdewolff/minify/v2 v2.7.3/go.mod h1:BkDSm8aMMT0ALGmpt7j3Ra7nLUgZL0qhyrAHXwxcy5w=
github.com/tdewolff/parse/v2 v2.4.2/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho=
github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/tetratelabs/wazero v1.12.0 h1:DuWcpNu/FzgEXgGBDp8J1Spc+CWOvvtvVyjKlaZopYU=
github.com/tetratelabs/wazero v1.12.0/go.mod h1:LvKtzl2RqO4gyF27BiXU+nKAjcV8f38U+kP/q2vgxh0=
github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI=
github.com/tklauser/go-sysconf v0.3.6/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI=
Expand Down Expand Up @@ -1260,8 +1262,8 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/telemetry v0.0.0-20260109210033-bd525da824e2 h1:O1cMQHRfwNpDfDJerqRoE2oD+AFlyid87D40L/OkkJo=
golang.org/x/telemetry v0.0.0-20260109210033-bd525da824e2/go.mod h1:b7fPSJ0pKZ3ccUh8gnTONJxhn3c/PS6tyzQvyqw4iA8=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
Expand Down
168 changes: 167 additions & 1 deletion openapi/Swarm.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
openapi: 3.0.3

info:
version: 8.1.0
version: 8.2.0
title: Bee API
description: "API endpoints for interacting with the Swarm network, supporting file operations, messaging, and node management"

Expand Down Expand Up @@ -211,6 +211,172 @@ paths:
default:
description: Default response

"/@/{address}":
parameters:
- in: path
name: address
schema:
$ref: "SwarmCommon.yaml#/components/schemas/SwarmReference"
required: true
description: Swarm address reference of the WASM module
- $ref: "SwarmCommon.yaml#/components/parameters/SwarmWasmMemoryLimit"
- $ref: "SwarmCommon.yaml#/components/parameters/SwarmWasmEntrypoint"
- $ref: "SwarmCommon.yaml#/components/parameters/SwarmWasmHostCallsLimit"
- $ref: "SwarmCommon.yaml#/components/parameters/SwarmWasmHostBytesLimit"
- $ref: "SwarmCommon.yaml#/components/parameters/SwarmWasmDepthLimit"
get: &executeOperation
summary: "Execute a WASM module stored in Swarm"
description: >
Downloads the WebAssembly module at the given address, runs it in a
sandbox with the request body as its input and returns what it wrote as
output. Disabled by default; the node operator enables it with
`--wasm-execute-enable`.


Every HTTP method is accepted, including ones not listed here, and the
module decides how to react to it. The one exception is `OPTIONS`, which
the node answers itself as a CORS preflight so it never reaches
untrusted code.


**Request metadata** reaches the module CGI-style, through the
environment: `REQUEST_METHOD`, `SCRIPT_NAME`, `PATH_INFO`,
`QUERY_STRING`, `REQUEST_URI`, `CONTENT_TYPE`, `CONTENT_LENGTH` and the
allowlisted request headers as `HTTP_*`. A trailing path is available at
`/@/{address}/{path}` and arrives as `PATH_INFO`, which is empty on the
bare form. The host environment is never inherited, and
`Authorization`, `Proxy-Authorization` and `Cookie` are never forwarded.
The environment is capped; overflow is a 431.


The representation of the result is negotiated with the `Accept` header:
`application/json` returns the full execution envelope,
`application/octet-stream` and `text/html` return the raw output. Any
other media type is rejected with 406. A wildcard `Accept` returns the
envelope unless the module set response metadata of its own, in which
case it returns the raw output with the module's content type — which is
what lets a browser load a module's stylesheets and images.


A verdict on the program itself (`trap`, `invalid-module`) is reported
as 400 with the verdict in the `swarm-wasm-status` header; a failure
local to this node is reported as 500.


**Node access.** Besides `wasi_snapshot_preview1`, a module may import a
host module named `swarm` to read and write Swarm data:
`swarm_bytes_get`, `swarm_bytes_put`, `swarm_chunk_get`,
`swarm_chunk_put` and `swarm_execute`. The full ABI, including the
result codes and the caller-provides-buffer convention, is documented in
`pkg/compute/README.md`. Importing a name that module does not define is
rejected before the module runs.


**Shaping the response.** The same host module offers
`swarm_response_status` and `swarm_response_header`, which let a module
set the HTTP status and headers rather than having them derived from the
verdict and the caller's `Accept`. These need no node access and are
available even when it is switched off. A module cannot set
`Swarm-Wasm-*`, `Access-Control-*`, `Set-Cookie`, the origin-wide
security headers or the hop-by-hop headers. Response metadata is
committed only on a clean run: a module that traps sets no headers, just
as it stores nothing.


Uploads are paid for by the postage batch the module passes to a put
call, which it can only have received as input; the node resolves it
exactly as `POST /chunks` does, so this path grants no authority the
HTTP API does not already grant. One execution gets one upload session
and therefore one batch. Uploads are **deferred**: when the response
returns, the data is in the local upload store but not yet acknowledged
by the network, and the session is committed only if the execution
succeeded — a module that traps leaves nothing behind.


**Experimental.** Output is not reproducible across nodes: a module
reaching the node observes state local to it, and this engine enforces
no deterministic gas budget. What a module may make the node do is
bounded by the host-call, host-byte and depth budgets instead.
The endpoint runs untrusted code in the node's own process and
should not be enabled on a public gateway.
tags:
- Execute
requestBody:
required: false
description: Input handed to the module
content:
application/octet-stream:
schema:
type: string
format: binary
responses:
"200":
description: The module ran and produced a result
headers:
"swarm-wasm-status":
$ref: "SwarmCommon.yaml#/components/headers/SwarmWasmStatus"
content:
application/octet-stream:
schema:
type: string
format: binary
text/html:
schema:
type: string
application/json:
schema:
$ref: "SwarmCommon.yaml#/components/schemas/WasmExecutionResponse"
"400":
$ref: "SwarmCommon.yaml#/components/responses/400"
"403":
$ref: "SwarmCommon.yaml#/components/responses/403"
"404":
$ref: "SwarmCommon.yaml#/components/responses/404"
"406":
$ref: "SwarmCommon.yaml#/components/responses/406"
"413":
$ref: "SwarmCommon.yaml#/components/responses/413"
"429":
$ref: "SwarmCommon.yaml#/components/responses/429"
"500":
$ref: "SwarmCommon.yaml#/components/responses/500"
default:
description: Default response
head: *executeOperation
post: *executeOperation
put: *executeOperation
patch: *executeOperation
delete: *executeOperation

"/@/{address}/{path}":
parameters:
- in: path
name: address
schema:
$ref: "SwarmCommon.yaml#/components/schemas/SwarmReference"
required: true
description: Swarm address reference of the WASM module
- in: path
name: path
schema:
type: string
required: true
allowEmptyValue: true
description: >
Path handed to the module as `PATH_INFO`, prefixed with `/`. May be
empty, which is how `/@/{address}/` differs from `/@/{address}`.
- $ref: "SwarmCommon.yaml#/components/parameters/SwarmWasmMemoryLimit"
- $ref: "SwarmCommon.yaml#/components/parameters/SwarmWasmEntrypoint"
- $ref: "SwarmCommon.yaml#/components/parameters/SwarmWasmHostCallsLimit"
- $ref: "SwarmCommon.yaml#/components/parameters/SwarmWasmHostBytesLimit"
- $ref: "SwarmCommon.yaml#/components/parameters/SwarmWasmDepthLimit"
get: *executeOperation
head: *executeOperation
post: *executeOperation
put: *executeOperation
patch: *executeOperation
delete: *executeOperation

"/bytes/{address}":
get:
summary: "Retrieve data by reference"
Expand Down
Loading