local-dev · diff

git:20261028.680d51d to git:20261103.f9fae8f

31 added, 469 removed. Audit B to A.

---
name: local-dev
description: Set up and manage local Freenet development environments and interact with a running node. Use when the user wants to test contract changes locally, debug UI issues, run a local node, query connections/diagnostics, inspect the dashboard, use the WebSocket API, or iterate on a Freenet application without deploying to the live network.
license: LGPL-3.0
---
# Freenet Local Development & Node Interaction
Guidance for running local Freenet nodes, publishing contracts, querying node state, and debugging dApps during development.
## Prerequisites
```bash
which freenet fdev
rustup target add wasm32-unknown-unknown
```
## Architecture
### Ports & Services
| Service | Default Port | Flag | Purpose |
|---------|-------------|------|---------|
- | Network (P2P) | 31337 | `--network-port` | Peer-to-peer connections |
+ | Network (P2P, UDP) | a free port chosen at first start | `--network-port` | Peer-to-peer connections |
| WebSocket API | 7509 | `--ws-api-port` | Client API (UI, CLI tools, fdev) |
### HTTP Endpoints
| Endpoint | Purpose |
|----------|---------|
- | `GET /` | Home dashboard (auto-refreshes every 5s) |
+ | `GET /` | Home dashboard |
| `GET /peer/{address}` | Peer detail page |
| `GET /v1/contract/web/{key}` | Contract web interface |
- | `WS /v1/contract/command?encodingProtocol=native` | WebSocket API v1 |
- | `WS /v2/contract/command?encodingProtocol=native` | WebSocket API v2 |
-
- ### Dashboard
-
- The home dashboard at `http://localhost:7509/` shows:
- - Connection status, peer count, own ring location
- - Peer table: address, ring location, type (Peer/Gateway), bytes sent/received, connected duration
- - External address (NAT traversal result), NAT statistics
- - Contract counts (hosted, subscribed, managed)
- - Operation stats (GET/PUT/UPDATE/SUBSCRIBE success/failure counts)
-
- Scraping peer data:
- ```bash
- # Get own location
- curl -s http://localhost:7509/ | grep -o 'own-loc[^<]*<[^>]*>[^<]*'
+ | `WS /v1/contract/command?encodingProtocol=native` | WebSocket API |
- # Get peer rows (address, location, type, sent, recv, uptime)
- curl -s http://localhost:7509/ | grep -o 'peer-row[^}]*'
- ```
+ The dashboard at `http://localhost:7509/` shows connection status, peers, hosted and subscribed contracts, and operation counts. For the same data in a script, use `fdev query` and `fdev diagnostics` rather than scraping the HTML.
### Node Data Locations
- | Platform | Default Data Path |
- |----------|-------------------|
- | macOS | `~/Library/Application Support/The-Freenet-Project-Inc.Freenet/` |
- | Linux | `~/.local/share/freenet/` |
-
- Contents: `contracts/` (WASM), `delegates/`, `secrets/`, `db/`, `config.toml`
-
- ### Log Directory Convention
-
- Choose an appropriate log directory for your OS:
- - **macOS**: `~/Library/Logs/freenet-test-node`
- - **Linux**: `~/.local/share/freenet-test-node/logs`
-
- The examples below use `$LOG_DIR` as a placeholder. Set it once:
- ```bash
- # macOS:
- LOG_DIR=~/Library/Logs/freenet-test-node
- # Linux:
- LOG_DIR=~/.local/share/freenet-test-node/logs
+ | Platform | Data directory | Config directory (`config.toml`, `gateways.toml`) |
+ |----------|----------------|-----------------|
+ | macOS | `~/Library/Application Support/The-Freenet-Project-Inc.Freenet/` | same as data |
+ | Linux | `~/.local/share/freenet/` | `~/.config/freenet/` |
- mkdir -p "$LOG_DIR"
- ```
+ The data directory holds `contracts/`, `delegates/`, `secrets/` and `db/`. Logs are written to `--log-dir` as `freenet.YYYY-MM-DD-HH.log` (WARN and above also go to `freenet.error.*.log`).
## Running Local Nodes
### Single node (simplest)
Your existing node on port 7509 works. Publish test contracts to it directly.
### Isolated test node (won't affect your running node)
- **IMPORTANT:** Gateway nodes require `--public-network-address`. Always use
- `--log-dir` to isolate logs from your main node.
-
```bash
+ mkdir -p ~/freenet-test-node/config # --config-dir must already exist
freenet network \
+ --is-gateway --skip-load-from-network \
--network-port 31338 \
+ --public-network-address 127.0.0.1 --public-network-port 31338 \
--ws-api-port 7510 \
- --ws-api-address 0.0.0.0 \
- --is-gateway \
- --skip-load-from-network \
+ --config-dir ~/freenet-test-node/config \
--data-dir ~/freenet-test-node/data \
- --public-network-address 127.0.0.1 \
- --log-dir "$LOG_DIR" \
- --log-level debug
- ```
-
- Persistent data lives in `--data-dir` and logs in `--log-dir`, but the
- **gateway bootstrap list is NOT isolated** by `--data-dir` — see
- [Isolation pitfalls](#isolation-pitfalls) below before assuming the node
- is offline-only. Likewise, `fdev` defaults to port 7509 and will silently
- target whichever node owns that port (often the system service, not your
- test node).
-
- **WARNING:** Do NOT use `--id` for local dev. It creates ephemeral temp directories
- that get wiped on restart, destroying delegate secrets (signing keys, app data).
- Use `--data-dir` for persistent isolation instead.
-
- ### Isolation pitfalls
-
- #### `--data-dir` does NOT isolate the gateway bootstrap list
-
- `freenet` reads `gateways.toml` from the global config directory regardless
- of `--data-dir`:
-
- - **macOS:** `~/Library/Application Support/The-Freenet-Project-Inc.Freenet/gateways.toml`
- - **Linux:** `~/.config/freenet/gateways.toml`
-
- On a machine with an existing Freenet install, a "local" test node will
- dial real public gateways (e.g. `nova.locut.us`, `vega.locut.us`) and
- attempt NAT traversal to live peers — silently joining the public network.
-
- To fully isolate, override `HOME` so the node sees an empty gateway list:
-
- ```bash
- # macOS
- mkdir -p ~/iso-home/Library/Application\ Support/The-Freenet-Project-Inc.Freenet
- printf 'gateways = []\n' > ~/iso-home/Library/Application\ Support/The-Freenet-Project-Inc.Freenet/gateways.toml
-
- # Linux
- mkdir -p ~/iso-home/.config/freenet
- printf 'gateways = []\n' > ~/iso-home/.config/freenet/gateways.toml
-
- # Then launch with HOME overridden. For an isolated *gateway* node
- # (--is-gateway, no --gateway flags), expect 0 bootstrap gateways:
- HOME=~/iso-home freenet network --is-gateway --skip-load-from-network ...
-
- # For an isolated *peer* node, pass your local gateway(s) explicitly:
- HOME=~/iso-home freenet network --gateway "127.0.0.1:31337,$GATEWAY_PUBKEY" ...
- ```
-
- Note: an empty `gateways.toml` will fail with `missing field 'gateways'`.
- The file must contain `gateways = []`.
-
- **Verification:** grep the node log for the initial-join line and confirm
- the gateway count matches what you passed:
-
- ```bash
- grep "Starting initial join procedure" "$LOG_DIR"/freenet.*.log
- # Expect: "...with N gateways" where N == number of --gateway flags
- # For an isolated gateway node (no --gateway flags), N must be 0.
- # If N is higher than expected, isolation is broken.
- ```
-
- Upstream tracking: [freenet/freenet-core#3980](https://github.com/freenet/freenet-core/issues/3980).
-
- #### `fdev` defaults to port 7509
-
- `fdev` targets `ws://127.0.0.1:7509` unless `--port` is passed. On a dev
- machine running a system Freenet service (which owns 7509), `fdev publish ...`
- without `--port` silently goes to that node, not your isolated test node.
-
- ```bash
- # WRONG: silently targets whichever node owns 7509 (often the system service)
- fdev publish --code ... contract ...
-
- # RIGHT: always pass --port when targeting a non-default test node
- fdev --port 7510 publish --code ... contract ...
- ```
-
- Symptom of a misdirected publish: `"Signature verification failed: signature error"`
- on a fresh publish to the test node, because the system node has stale
- contract state from a previous run signed by a different key. If you see
- this on a "fresh" test, check which node `fdev` actually hit.
-
- When a publish runs through `cargo-make` (e.g. a `publish-*` task) rather
- than `fdev` directly, there is no `--port` flag to pass — the task hardcodes
- the default port internally. Override it with the `WS_API_PORT` environment
- variable instead:
-
- ```bash
- # Targets the isolated test node on 7510 instead of the default 7509
- WS_API_PORT=7510 cargo make publish-myapp
- ```
-
- The failure mode for a misdirected `cargo-make` publish is the unhelpful
- `put failed after 4 attempts`, which gives no hint that the port was wrong —
- so set `WS_API_PORT` whenever the target node isn't on 7509.
-
- #### `--data-dir` does NOT isolate `config.toml` either — use `--config-dir` per node
-
- Two `freenet` processes on the same host that pass the same (or default)
- config directory share `config.toml` AND `secrets/transport-keypair.pem`.
- Symptoms: second node fails to bind its UDP port, or both nodes use
- identical peer IDs and the network refuses the duplicate connection.
-
- For a deterministic multi-node harness on one host (gateway + peer + …),
- pass `--config-dir` explicitly to each node, NOT just `--data-dir`:
-
- ```bash
- freenet network --config-dir /tmp/iso-net/gw/config --data-dir /tmp/iso-net/gw/data ...
- freenet network --config-dir /tmp/iso-net/peer/config --data-dir /tmp/iso-net/peer/data ...
- ```
-
- **CI gotcha:** on Linux runners that set `XDG_CONFIG_HOME` (e.g. ubicloud,
- sometimes GitHub Actions images), `dirs::config_dir()` returns
- `$XDG_CONFIG_HOME` regardless of `HOME` — so the `HOME=~/iso-home …`
- trick from the previous section is bypassed. `--config-dir` is the only
- flag that wins against `XDG_CONFIG_HOME`. Use it any time the harness
- must run identically on dev laptops and CI.
-
- A working reference harness lives at
- `scripts/run-isolated-nodes.sh` in the freenet/mail repo — covers up /
- down / wipe / status, full state wipe between test runs (avoids day-1
- AFT cap carryover in repeated E2E runs), and `FREENET_E2E_KEEP=1` to
- leave nodes up for post-mortem.
-
- ### Two-node local network
-
- ```bash
- # Terminal 1: Gateway
- freenet network \
- --network-port 31337 \
- --ws-api-port 7509 \
- --is-gateway \
- --skip-load-from-network \
- --data-dir ~/freenet-local-gw/data \
- --public-network-address 127.0.0.1 \
- --log-dir ~/freenet-local-gw/logs \
- --log-level debug
-
- # Terminal 2: Peer (get gateway pubkey first)
- GATEWAY_KEY=$(cat ~/.config/Freenet/secrets/local-gw/transport.pub 2>/dev/null || echo "CHECK_PUBKEY")
- freenet network \
- --network-port 31338 \
- --ws-api-port 7510 \
- --gateway "127.0.0.1:31337,${GATEWAY_KEY}" \
- --skip-load-from-network \
- --data-dir ~/freenet-local-peer/data \
- --log-dir ~/freenet-local-peer/logs \
- --log-level debug
+ --log-dir ~/freenet-test-node/logs
```
- ### Mobile testing (phone on same WiFi)
+ A gateway needs both `--public-network-address` and `--public-network-port`; without them it exits at startup. The API binds to loopback by default. Only add `--ws-api-address 0.0.0.0` for phone testing on a network you trust.
- ```bash
- # Bind WebSocket API to all interfaces (--ws-api-address 0.0.0.0)
- freenet network \
- --ws-api-address 0.0.0.0 \
- --ws-api-port 7510 \
- --network-port 31338 \
- --is-gateway \
- --skip-load-from-network \
- --data-dir ~/freenet-mobile-test/data \
- --public-network-address 127.0.0.1 \
- --log-dir ~/freenet-mobile-test/logs \
- --log-level debug
+ Give every test node its own `--config-dir` and `--data-dir`. `fdev` defaults to port 7509 and will silently target whichever node owns that port, often your system service, so pass `fdev --port 7510 ...` for a test node. Details, a gateway plus peer on one host, and phone testing: `references/node-isolation-and-debugging.md`.
- # Phone opens: http://{YOUR_LAN_IP}:7510/v1/contract/web/{CONTRACT_ID}/
- # Find LAN IP:
- # macOS: ifconfig en0 | grep "inet "
- # Linux: ip addr show wlan0
- ```
+ **WARNING:** Do NOT use `--id` for local dev. It creates ephemeral temp directories that get wiped on restart, destroying delegate secrets (signing keys, app data). A debug build does the same without `--id` when you pass no config or data dir: it uses and wipes a temp dir. Use `--data-dir` for persistent isolation instead.
- ### Multi-instance deployment (10 peers + gateway)
+ ### Delegates need network mode
- ```bash
- # Uses deploy-local-gateway.sh from freenet-core
- cd /path/to/freenet-core
- scripts/deploy-local-gateway.sh --all-instances
- ```
+ `freenet local` does not service a delegate's contract requests (GET, PUT, UPDATE, SUBSCRIBE), `RequestUserInput`, lifecycle events or wake-ups, and nothing reports an error. Only the delegate's reads through `DelegateCtx::get_contract_state` work. Start any node that runs a delegate with `freenet network`. Detail and source references: `dapp-builder` skill, `references/delegate-patterns.md`, "`freenet local` Does Not Run the Delegate Contract Loop".
## Publishing Contracts Locally
### Using fdev
- **IMPORTANT:** fdev argument order matters. `--code` and `--parameters` go
- before the `contract` subcommand. `--port` goes before `execute`.
+ **IMPORTANT:** fdev argument order matters. `--code` and `--parameters` go before the `contract` subcommand. `--port` goes before `execute`.
```bash
# Publish a contract with webapp
fdev --port 7510 execute put \
--code target/wasm32-unknown-unknown/release/my_contract.wasm \
--parameters params.bin \
contract \
--webapp-archive target/webapp/webapp.tar.xz \
--webapp-metadata target/webapp/webapp.metadata
# Get contract ID without publishing
fdev get-contract-id \
--code target/wasm32-unknown-unknown/release/my_contract.wasm \
--parameters params.bin
```
- ### Targeting a specific node
-
- Override the WebSocket port for fdev:
- ```bash
- fdev --port 7510 execute put --code ... contract ...
- ```
+ `fdev` also reads `WS_API_PORT`, so `WS_API_PORT=7510 fdev ...` targets the test node. Do not export it in the shell that starts the node, because the node reads the same variable for its own port.
### Querying Node State
```bash
- # List connected peers and subscriptions
- fdev query
-
- # Get detailed node diagnostics
- fdev diagnostics
-
- # Get diagnostics for specific contracts
+ fdev query # connected peers and subscriptions
+ fdev diagnostics # full node diagnostics
fdev diagnostics --contract <base58_contract_id>
```
## WebSocket API
- ### Connection
-
```
ws://127.0.0.1:7509/v1/contract/command?encodingProtocol=native
```
- - **Encoding:** `native` (bincode) or `flatbuffers`
- - **Auth:** Send `ClientRequest::Authenticate { token }` after connecting
- - **Tokens:** Generated per-connection, base58-encoded 32 bytes. Invalidated on node restart (error prefix: `AUTH_TOKEN_INVALID`).
-
- ### Request Types
-
- ```rust
- pub enum ClientRequest {
- ContractOp(ContractRequest), // GET, PUT, UPDATE, Subscribe
- DelegateOp(DelegateRequest), // Delegate operations
- Authenticate { token }, // Auth token
- NodeQueries(NodeQuery), // Queries (see below)
- Disconnect { cause }, // Close with reason
- Close, // Graceful close
- }
- ```
-
- ### NodeQuery Variants
-
- | Query | Response | Data |
- |-------|----------|------|
- | `ConnectedPeers` | `ConnectedPeers { peers }` | `Vec<(peer_id, socket_addr)>` |
- | `SubscriptionInfo` | `NetworkDebug(info)` | Subscriptions + connected peers |
- | `NodeDiagnostics { config }` | `NodeDiagnostics(response)` | Configurable (see below) |
- | `ProximityCacheInfo` | `ProximityCache(info)` | Proximity cache state for update propagation |
-
- ### NodeDiagnostics Config
-
- ```rust
- NodeDiagnosticsConfig {
- include_node_info: bool, // Peer ID, location, uptime
- include_network_info: bool, // Active connections, peer list
- include_subscriptions: bool, // Active subscriptions
- contract_keys: Vec<ContractKey>, // Specific contracts (empty = all)
- include_system_metrics: bool, // Connection count, hosting contracts
- include_detailed_peer_info: bool, // Full peer details
- include_subscriber_peer_ids: bool, // Peer IDs of subscribers per contract
- }
- ```
-
- **Wire-format change (stdlib v0.7.0):** `NodeDiagnosticsResponse.contract_states` is now `HashMap<String, ContractState>` where the key is the Base58-encoded `ContractKey::Display` form (the `instance` field, not the full struct). Previously it was `HashMap<ContractKey, ContractState>` but that serialization broke JSON because the key was a struct. To map back to a typed `ContractKey`, decode the Base58 string and reconstruct. This is a bidirectional bincode break — match node and tooling versions.
-
- ```rust
- for (key_str, state) in diag.contract_states.iter() {
- // key_str is the Base58 ContractKey instance id
- }
- ```
-
- ## Config File Reference
-
- ```toml
- mode = "network" # "network" or "local"
- network-address = "0.0.0.0"
- network-port = 54761 # UDP port for peer traffic
- ws-api-address = "0.0.0.0"
- ws-api-port = 7509 # HTTP + WebSocket port
- min-number-of-connections = 25
- max-number-of-connections = 100
- transient-budget = 2048 # Max concurrent transient connections (gateway)
- transient-ttl-secs = 30 # TTL for unpromoted transient connections
- token-ttl-seconds = 86400 # Auth token lifetime
- token-cleanup-interval-seconds = 300
- log_level = "info"
- is_gateway = false
- ```
-
- ## Ring Distance
+ - **Encoding:** `native` (bincode) or `flatbuffers` (the default when the parameter is omitted).
+ - **Auth:** the node issues a token when it serves a contract's shell page, in memory, so it is invalid after a node restart (the node closes the WebSocket with code 4401). A webapp inside that page gets it from the shell. A standalone client may connect with no token, as `fdev` does.
- Each peer has a ring location in [0.0, 1.0). Distance between two locations:
+ Request and response types are in `freenet_stdlib::client_api` (`ClientRequest`, `NodeQuery`, `NodeDiagnosticsConfig`).
- ```
- distance = min(|a - b|, 1.0 - |a - b|)
- ```
+ ## Config File
- Max distance is 0.5. Use the dashboard peer table to get locations and compute distances.
+ `config.toml` (in the config directory) uses the long CLI flag names without the dashes, for example `ws-api-port`, `network-port`, `min-number-of-connections`. `freenet network --help` lists them with defaults.
## Debugging
- ### Check node status
-
- ```bash
- # Node status page
- curl -s http://127.0.0.1:7510/
-
- # Active WebSocket connections
- lsof -i :7510 -P | grep ESTABLISHED # macOS
- ss -tnp | grep 7510 # Linux
- ```
-
- ### Check node logs
-
- ```bash
- # Follow logs (use your --log-dir path)
- tail -f "$LOG_DIR"/freenet.$(date +%Y-%m-%d-%H).log
-
- # Filter for contract/delegate events
- tail -f "$LOG_DIR"/freenet.*.log | grep -i "delegate\|contract\|websocket\|error\|sign"
- ```
-
- Each node instance should use `--log-dir` pointing to a unique directory
- so logs don't interleave.
-
- ### Debugging node logs
-
- Key patterns to search for:
-
- ```bash
- # Follow all delegate + contract activity
- grep -i "delegate\|sign\|update\|put\|subscribe" "$LOG_DIR"/freenet.*.log | tail -50
-
- # Track a specific operation by transaction ID
- grep "01KK70QEAR" "$LOG_DIR"/freenet.*.log
-
- # WebSocket lifecycle
- grep -i "websocket\|connection\|disconnect\|client" "$LOG_DIR"/freenet.*.log | tail -20
- ```
-
- ### Debugging with Playwright (automated browser testing)
-
- Use the Playwright MCP tools to test the full UI flow without manual interaction:
-
- ```
- 1. browser_navigate → open the contract URL
- 2. browser_snapshot → see the DOM state
- 3. browser_click / browser_fill_form → interact with the UI
- 4. browser_console_messages → check for WASM panics or JS errors
- ```
-
- Especially useful for reproducing mobile issues on desktop, where console
- output is visible. If a flow works in Playwright but not on mobile, the
- issue is likely WebSocket suspension or browser caching.
-
- ### Mobile-specific debugging
-
- **Browser caching:** Mobile browsers aggressively cache WASM bundles. After
- republishing, use a cache-busting URL parameter:
- ```
- http://{IP}:7510/v1/contract/web/{CONTRACT_ID}/?_v={timestamp}
- ```
-
- Or clear browser cache / force close and reopen. Firefox mobile is
- particularly aggressive about caching.
-
- **WebSocket suspension:** Mobile browsers suspend WebSocket connections when:
- - Screen locks
- - Tab goes to background
- - Browser switches to another app
- - Heavy WASM computation starves the event loop
-
- Your app should handle reconnection when the tab becomes visible again.
- Consider implementing a `visibilitychange` listener that re-establishes
- the WebSocket connection.
-
- ### Common issues
-
- | Symptom | Cause | Fix |
- |---------|-------|-----|
- | "Gateway nodes must specify a public network address" | Missing `--public-network-address` | Add `--public-network-address 127.0.0.1` |
- | Signing key lost after node restart | Used `--id` (ephemeral temp dir) | Use `--data-dir` for persistent data |
- | "Auth token not found" | Stale cached page | Hard refresh or clear browser cache |
- | "delegate not found in store" | Legacy delegate migration | Expected on fresh node, non-blocking |
- | "Connection reset by peer" | Browser killed WebSocket | Check if page is in background tab |
- | "peer connection dropped" on put | Publishing to live node failed | Use isolated test node (`--skip-load-from-network`) |
- | Contract not found | Not published to this node | Publish with `fdev --port {PORT}` (see [Isolation pitfalls](#isolation-pitfalls)) |
- | "Signature verification failed" on a fresh publish | `fdev` defaulted to port 7509 and hit the system node | Pass `fdev --port {TEST_PORT}` explicitly |
- | Test node joins public network despite `--data-dir` | `gateways.toml` is read from global config, not `--data-dir` | Override `HOME` to a sandbox dir with `gateways = []` (see [Isolation pitfalls](#isolation-pitfalls)) |
- | Blank page (cached old WASM) | Mobile browser caches aggressively | Clear cache, force close browser, or use `?_v=timestamp` |
- | `sed -i` fails on macOS | BSD sed requires backup extension | Use build tools directly instead of sed |
- | `cargo make` targets Linux | Cross-compilation for web-container-tool | Build natively: `cargo build --release -p web-container-tool` |
- | A delegate's contract GET/PUT/UPDATE/SUBSCRIBE silently does nothing, or its lifecycle events / wake-ups never arrive | Node started with `freenet local` | Start with `freenet network` as every recipe here does. See [Delegates need network mode](#delegates-need-network-mode) |
-
- ### Delegates need network mode
-
- Every node recipe in this skill uses `freenet network`, and delegates are one of
- the reasons to keep it that way. `freenet local` is the mode whose own help text
- calls it useful for development, so it is the natural thing to reach for, and it
- does not service a delegate's contract requests at all.
-
- The loop that handles `GetContractRequest`, `PutContractRequest`,
- `UpdateContractRequest` and `SubscribeContractRequest` from a delegate is
- `handle_delegate_with_contract_requests` (`crates/core/src/contract.rs`). Every
- one of its call sites (client-driven, resumed after a park,
- contract-notification-driven, and lifecycle/wake-up runs) is reached only from
- `contract_handling`, and `contract_handling` is spawned from one production site,
- `crates/core/src/node/p2p_impl.rs`, the network node. `run_local_node`
- (`crates/core/src/node.rs`) handles `ClientRequest::DelegateOp` by calling
- `executor.delegate_request(...)` straight through, which runs the delegate's
- `process()` and hands back its outbound messages without acting on any of them.
- Nothing reports an error, so the delegate looks healthy and simply has no
- effect.
-
- Delegate notification is dead in local mode too, for the same reason:
- `send_delegate_contract_notifications` returns immediately when
- `delegate_notification_tx` is `None` (`executor_impl.rs`), and that field is set
- only by `RuntimePool`, which local mode does not build. `RequestUserInput` is
- lost the same way, which is freenet-core#5273. So are a manifest delegate's
- lifecycle events and wake-ups (freenet-core#5730, #5747): they are scheduled on
- `contract_handling`, from capability state only `RuntimePool` builds, so they
- never fire under `freenet local`.
-
- `DelegateCtx::get_contract_state` is the exception: it is a wasmtime import
- resolved inside the delegate's own execution rather than a message the node
- services afterwards, so a delegate's *reads* of state this node already holds do
- work under `freenet local`. There is no synchronous write or subscribe to go
- with it — freenet-core#5638 withdrew `put_contract_state`,
- `update_contract_state` and `subscribe_contract` from the node, and
- freenet-stdlib 0.11.0 removed the `DelegateCtx` wrappers, so on 0.11.0 calling
- one is a compile error rather than a delegate that instantiates and dies.
-
- The practical upshot for local development is unchanged and now has no
- exception worth chasing: a delegate that writes or subscribes needs `freenet
- network`.
-
- Verified against freenet-core `main` at `b11d06c64` (one commit past v0.2.151) on 2026-10-28. The
- `dapp-builder` skill's `references/delegate-patterns.md` has the full picture,
- including what a delegate's contract access does and does not reach on a real
- network node.
-
- ## Other Infrastructure
-
- ### Docker containers (freenet-core)
-
- ```bash
- # Gateway container
- cd /path/to/freenet-core/docker/freenet-gateway
- docker-compose up
-
- # Node container
- cd /path/to/freenet-core/docker/freenet-node
- docker-compose up
- ```
+ Release builds log at INFO and above whatever `--log-level` says; debug output needs a binary built from source. Give each node its own `--log-dir` so logs do not interleave. Log patterns, Playwright and phone debugging, and a table of common errors: `references/node-isolation-and-debugging.md`.
- ### fdev simulation testing
+ ## Simulation testing
```bash
# Single-process test network (no real networking)
fdev test --gateways 2 --nodes 10 --events 100 --seed 0xDEADBEEF single-process
# With fault injection
fdev test --message-loss 0.1 --latency-min 50 --latency-max 200 single-process
```
- ## Related Skills
-
- - **dapp-builder**: Design and architect new Freenet dApps
- - **telemetry-monitor**: Analyze network telemetry from the central collector
- - **release**: Publish production releases
+ Flags go before `single-process`.