Skip to content

feat(routing-policy): native-Go preset engine so the TinyGo wasm-visor can run presets (no wazero) - #4040

Merged
0pcom merged 1 commit into
skycoin:developfrom
0pcom:feat/wasm-visor-preset-native
Aug 20, 2026
Merged

feat(routing-policy): native-Go preset engine so the TinyGo wasm-visor can run presets (no wazero)#4040
0pcom merged 1 commit into
skycoin:developfrom
0pcom:feat/wasm-visor-preset-native

Conversation

@0pcom

@0pcom 0pcom commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

Problem

The built-in routing-policy presets only ran on the native visor. preset:<name> loads the embedded pkg/router/policy/wasm/presets/bundle.wasm and evaluates it via wazero. A wasm-visor is itself TinyGo wasm and cannot host wazero (wasm-in-wasm), so it never evaluated presets — cmd/wasm-visor built its router with a nil DialHook, and pkg/router/policy (+ .../wasm) don't compile under TinyGo (starlark / wazero).

Approach

Extract the preset decide/tick logic into one pure-Go package, pkg/router/policy/preset (stdlib-only: no unsafe, no JSON ABI, no wazero, no starlark) as the single source of truth, and compile it into both paths:

  • docs/examples/routing-policies/wasm/bundle/main.go is now a thin wasm ABI shim over that package — it keeps only the JSON wire types and the alloc/free/decide_route/on_tick linear-memory glue, delegating every decision to preset. bundle.wasm is rebuilt from it (551798 → 551439 bytes). The native visor keeps running this bundle via wazero, unchanged.
  • pkg/router/policy/presethook adapts the preset package to the router.DialHook / RouteSelectingHook / RotationHook interfaces without wazero — TinyGo-safe (imports only pkg/router + preset). cmd/wasm-visor wires it as the router's DialHook, selected by the ?routing_policy=<name> query param. Unset ⇒ nil hook ⇒ unchanged (no-policy) behavior; an unknown name is rejected (no silent fallback).

pkg/router/policy / hook.go are untouched — the native selection path (pkg/visor/policy_loader.go → wazero) is unchanged.

Byte-identical guarantee (gate-1, extended to the native path)

A new parity_test.go drives the same inputs through the wazero bundle and the native preset package and asserts identical results:

  • decide: field-for-field for every preset (17 representative cases).
  • on_tick: step-for-step over a multi-tick leg-snapshot sequence for each stateful controller (rotating-bw, latency-adaptive, elastic-mux, probe-and-prune, adaptive) — proving the EWMA smoothing, AIMD peak/idle counters, and probe state machine evolve identically across the two compilations.

The existing wazero end-to-end presets_test.go and the bundle's native main_test.go still pass unchanged.

Verified

  • go build . (repo root)
  • go test ./pkg/router/policy/... (incl. wazero end-to-end + new parity + new unit tests)
  • GOOS=js GOARCH=wasm go build ./cmd/wasm-visor
  • Full TinyGo-fork build of ./cmd/wasm-visor (the committed embed lane) — the preset engine compiles into the browser visor blob.

Follow-up (documented gap)

The wasm-visor now invokes the preset engine on every dial via the DialHook. The conditional presets geo-avoid / transport-diverse read per-hop geo / transport-kind metadata, supplied on native by a policy.Provider; on the wasm-visor this comes from a presethook.Provider, which is nil today (NopProvider), so those two defer until a wasm-side metadata provider is wired. trust-tiered (needs only hop PKs) and all the shape/tick presets work with no provider.

…r can run presets (no wazero)

The built-in routing-policy presets (rotating-bw, latency-adaptive, elastic-mux,
probe-and-prune, adaptive, app-mux, geo-avoid, transport-diverse, trust-tiered,
time-of-day) could only run on the NATIVE visor: "preset:<name>" loads the
embedded bundle.wasm and evaluates it via wazero. A wasm-visor is itself TinyGo
wasm and cannot host wazero (wasm-in-wasm), so it never evaluated presets — its
router was built with a nil DialHook.

Extract the preset decide/tick logic into ONE pure-Go package,
pkg/router/policy/preset (stdlib-only, no unsafe/JSON-ABI/wazero/starlark), as
the single source of truth, and compile it into BOTH paths:

- docs/examples/routing-policies/wasm/bundle/main.go becomes a thin wasm ABI
  shim over the preset package (JSON wire types + alloc/free/decide_route/on_tick
  glue only); bundle.wasm rebuilt from it. The native visor still runs this
  bundle via wazero, byte-identically.
- pkg/router/policy/presethook adapts the preset package to the router.DialHook /
  RouteSelectingHook / RotationHook interfaces WITHOUT wazero, TinyGo-safe
  (imports only pkg/router + preset). cmd/wasm-visor wires it as the router's
  DialHook, selected by the ?routing_policy=<name> query param (unset ⇒ nil hook
  ⇒ unchanged no-policy behavior).

Byte-identical guarantee (gate-1) extended to the native path: a new
parity_test drives the SAME inputs through the wazero bundle and the native
preset package and asserts identical decisions — field-for-field on decide for
every preset, and step-for-step over a multi-tick on_tick sequence for the
stateful controllers (EWMA smoothing, AIMD peak/idle, probe state machine). The
existing wazero end-to-end presets_test and the bundle's native main_test still
pass unchanged.

Verified: go build . (root), go test ./pkg/router/policy/..., GOOS=js GOARCH=wasm
build of ./cmd/wasm-visor, and a full TinyGo-fork build of ./cmd/wasm-visor (the
committed embed lane) all pass.

Router-integration note: the conditional presets (geo-avoid, transport-diverse)
read per-hop geo/transport-kind metadata; on the wasm-visor these come from a
presethook.Provider, which is nil today (NopProvider), so those two defer until
a wasm-side metadata provider is wired — a follow-up. trust-tiered and all the
shape/tick presets work with no provider.
@0pcom
0pcom merged commit f3f96de into skycoin:develop Aug 20, 2026
12 of 16 checks passed
@0pcom
0pcom deleted the feat/wasm-visor-preset-native branch August 24, 2026 23:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant