diff --git a/crates/nexum-runtime/src/host/local_store_redb.rs b/crates/nexum-runtime/src/host/local_store_redb.rs index eec7c4ef..5d9b8a3f 100644 --- a/crates/nexum-runtime/src/host/local_store_redb.rs +++ b/crates/nexum-runtime/src/host/local_store_redb.rs @@ -1,10 +1,13 @@ //! `nexum:host/local-store` backend. //! -//! Single redb file under `EngineConfig.engine.state_dir`. Each module is -//! namespaced host-side by a fixed 32-byte prefix `keccak256(module_name)` -//! prepended to every key, so modules sharing a key string see disjoint -//! data and cannot forge a key into another's range. keccak256 matches ENS -//! node derivation (ADR-0003). +//! Single redb file under `EngineConfig.engine.state_dir`. The contract is +//! namespace isolation: each module sees a disjoint key range that no other +//! module can read, write, or forge its way into, even if two modules use +//! the same key string. The host implements this today with a fixed 32-byte +//! `keccak256(module_name)` prefix prepended to every key (see ADR-0003 for +//! why keccak256 - it matches ENS node derivation); that prefix scheme is an +//! implementation detail, free to change, not part of the specified +//! guarantee. #![allow(clippy::result_large_err)] diff --git a/docs/diagrams/diagrams.md b/docs/diagrams/diagrams.md index a9d6c21a..01d8d3e9 100644 --- a/docs/diagrams/diagrams.md +++ b/docs/diagrams/diagrams.md @@ -147,7 +147,7 @@ classDiagram | **Supervisor** | Orchestrates boot and event dispatch. Creates one `HostState` per module. On each incoming block or chain-log, calls `dispatch_block` / `dispatch_chain_log` to fan the event out to subscribed modules. | | **ProviderPool** | Holds one alloy `DynProvider` per chain. `wss://` chains get a pubsub provider that supports both subscriptions and requests. `https://` chains get HTTP-only (subscriptions unavailable, by design - ADR-0002). | | **OrderBookPool** | Holds one `OrderBookApi` client per known CoW chain (Mainnet, Gnosis, Sepolia, ArbitrumOne, Base). Instantiated via `OrderBookPool::default()` at boot (ADR-0005). | -| **LocalStore** | A single redb embedded database at `state_dir`. All modules write into the same file. Keys are prefixed host-side as `[32-byte module namespace][raw_key]` so two modules never collide, and the namespace is unspoofable (ADR-0003). The namespace is `keccak256(module_name)` for locally-loaded modules and `ens_namehash(name)` for ENS-discovered modules. | +| **LocalStore** | A single redb embedded database at `state_dir`. All modules write into the same file, but the contract guarantees namespace isolation - each module sees a disjoint, non-forgeable key range. The host implements this today by prefixing every key host-side (`keccak256(module_name)` for locally-loaded modules, `ens_namehash(name)` for ENS-discovered ones - see ADR-0003), an implementation detail rather than part of the specified guarantee. | | **HostState** | The per-module runtime context. `wasmtime::component::bindgen!` generates one trait per WIT interface (e.g. `shepherd::cow::cow_api::Host`); `HostState` implements each trait, and the linker registers all trait implementations once at boot. Fields: `wasi: WasiCtx` + `table: ResourceTable` (the constrained WASI surface), `limits: StoreLimits` (memory/table caps), `http_ctx: WasiHttpCtx` + `http_gate: HttpGate` (the wasi:http outgoing gate holding the module's `[capabilities.http].allow` list), `module_namespace` (log tagging), `ext` (extension backends), `chain` (provider pool seam), `store` (namespace-prefixed local-store handle). | | **EventLoop** | Runs `futures::stream::select_all` over a `Vec + Send>>>`. The loop never exits until SIGINT/SIGTERM. Each fired event is forwarded to `Supervisor` for fan-out. | | **TwapModule** | The TWAP watcher WASM component. On a `Log` event (ConditionalOrderCreated): persists the registration in `local-store`. On a `Block` event: iterates all watches and, for each, makes an `eth_call` via `chain.request`, decodes the result via `alloy_sol_types` (in-module), builds an `OrderCreation` via `cowprotocol` types (consumed via wasm32 feature), and submits via `cow-api.submit-order`. Orderbook errors flow through `OrderPostError::retry_hint`. All polling logic lives in the module, not the host (ADR-0006). | @@ -165,7 +165,7 @@ graph TD SC["shepherd:cow@0.2.0\n(CoW Protocol extensions)"] NH --> n1["chain ✅ implemented\nrequest(chain-id, method, params)\nrequest-batch(chain-id, requests)\n - \nsubscribe-blocks · subscribe-logs →\n engine-managed via module.toml subscriptions\nregister-address · unregister-address →\n 🕓 deferred to 0.3 (ADR-0008)"] - NH --> n2["local-store ✅ implemented\nget(key) · set(key, value)\ndelete(key) · list-keys(prefix)\nnamespacing: 32-byte hash prefix (ADR-0003)"] + NH --> n2["local-store ✅ implemented\nget(key) · set(key, value)\ndelete(key) · list-keys(prefix)\ncontract: per-module namespace isolation (ADR-0003)"] NH --> n3["identity · messaging · remote-store\n✅ stubs (Unsupported) - full impl in 0.3"] NH --> n4["logging ✅ implemented"] @@ -184,7 +184,7 @@ graph TD |---|---| | **nexum:host@0.2.0** | The base WIT package. Any module running in the engine - CoW-aware or not - imports from here. Defines shared types (`chain-id`, `chain-log`, `fault`) used by both packages. | | **chain** | Reads from the blockchain via JSON-RPC. `request` sends a single call; `request-batch` sends several in one round-trip. **Subscriptions are not callable WIT functions** - they are declared in `module.toml` and opened by the engine at boot. Dynamic `register-address` for factory patterns is deferred to 0.3 (ADR-0008). | -| **local-store** | Persistent key-value storage that survives restarts. Operations: `get(key)`, `set(key, value)`, `delete(key)`, `list-keys(prefix)`. The host prefixes every key with a 32-byte deterministic namespace (`keccak256(module_name)` locally, or `ens_namehash(name)` when ENS-loaded) so modules are fully isolated and the namespace cannot be spoofed (ADR-0003). | +| **local-store** | Persistent key-value storage that survives restarts. Operations: `get(key)`, `set(key, value)`, `delete(key)`, `list-keys(prefix)`. The contract is namespace isolation - each module's keys are disjoint from every other module's and cannot be spoofed. The host implements this today via a deterministic key prefix (`keccak256(module_name)` locally, or `ens_namehash(name)` when ENS-loaded - see ADR-0003), an implementation detail rather than the specified guarantee. | | **identity · messaging · remote-store** | Capabilities stubbed at 0.2 - they return `Unsupported`. `identity` will provide keystore-backed signing. `messaging` will send Waku messages. `remote-store` will read/write Swarm/IPFS. | | **logging** | Lightweight utility. `logging` emits to the engine's `tracing` subscriber (inherits `RUST_LOG` filters). Time and secure randomness are available ambiently via `wasi:clocks` and `wasi:random`. | | **(outbound HTTP)** | Not a `nexum:host` interface: a module that declares the `http` capability imports the standard `wasi:http/outgoing-handler`, and the host checks every outgoing request against the manifest's `[capabilities.http].allow` list before any connection is made. |