@hisoka-io/anon-rpc-worker 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,40 @@
2
2
 
3
3
  All notable changes to `@hisoka-io/anon-rpc-worker`.
4
4
 
5
+ ## 0.4.1 (2026-10-25)
6
+
7
+ Built on `@hisoka-io/nox-client` 0.9.0 and `@hisoka-io/nox-wasm` 0.3.0. Every 0.4.0 config stays valid.
8
+
9
+ ### Changed
10
+
11
+ - TLS spares: defaults `tlsSpares` 2 (was 1) and `tlsSpareTtlMs` 12,000 (was 20,000). The upstream RPC hosts close
12
+ an idle TLS connection 15.0 s after the handshake, so a 20 s spare could be dead when used: one wasted exchange
13
+ and a `tls.retry` on a new session. Measured from India on 2026-10-15 at a 15 s call gap: 13 of 30 calls retried
14
+ with a 20 s TTL, none of 70 with 12 s.
15
+ - A spare is refreshed before its TTL while its host was called within the last minute: the replacement opens at
16
+ once 1.5-2 s before the TTL, and a host holds at most `tlsSpares` spares plus the one being replaced. Past that
17
+ window an expired spare is not replaced, as before. The one-minute window bounds how far apart the refresh chain
18
+ can link a wallet's calls to one host at low traffic (about 20 s in 0.4.0).
19
+ - A call that finds no spare while a spare's handshake for its host has run for half the handshake median waits for
20
+ that handshake instead of opening another session; if that handshake fails, the call opens its own.
21
+
22
+ - Embedded nox-wasm 0.3.0: nox-crypto at nox `dff8ebe`, which refuses a reply body shorter than 33 bytes with an
23
+ error instead of trapping the module.
24
+
25
+ ### Added
26
+
27
+ - Log event `tls.call` (info), one per tunnel hop: `path` (`spare`, `inline`, `waited`, `reused` or `retry`),
28
+ `openMs`, `spareAgeMs` (spares only), `exchangeMs`, `retries`, `ok`.
29
+
30
+ ## Stable specifier (2026-10-13)
31
+
32
+ Deployment record and tooling; the published package stays 0.4.0.
33
+
34
+ - Stable specifier `0x35133267A8F7EC35046a83DCA0f4Ca8A6e1b7A9A` deployed on Ethereum Sepolia on 2026-10-13,
35
+ pointing at 0.4.0: the reference `WorkerSpecifier` of ethereum/anon-rpc, which follows each worker release
36
+ through `setWorker`. The per-release immutable specifiers stay as pins (`specifier/README.md`).
37
+ - `specifier`: `pnpm set-worker`, a dry-run-first tool for the `setWorker` update.
38
+
5
39
  ## 0.4.0 (2026-10-10)
6
40
 
7
41
  Built on `@hisoka-io/nox-client` 0.8.0. Every 0.3.0 config stays valid; with the new defaults, https calls go
package/README.md CHANGED
@@ -3,9 +3,10 @@
3
3
  The Nox mixnet worker for the [anon-rpc](https://github.com/ethereum/anon-rpc) standard: one classic-script
4
4
  bundle that a wallet's anon-rpc harness loads and pins by its keccak-256 hash.
5
5
 
6
- The worker dials Nox entry nodes over KPS (`anonRpcWorker.kps`), sends each `fetch` call as a Sphinx packet
7
- through the mixnet, and an exit node performs the HTTP request; the reply returns over single-use reply blocks
8
- (SURBs). Who the nodes are comes from NoxRegistry: a snapshot pinned inside the bundle is the floor, and after
6
+ The worker dials Nox entry nodes over KPS (`anonRpcWorker.kps`) and sends each `fetch` call as Sphinx packets
7
+ through the mixnet. It runs TLS with the RPC provider itself, and an exit relays the encrypted records (the exit
8
+ performs the HTTP request with `tls: "off"`, or as the `"preferred"` fallback); replies return over single-use
9
+ reply blocks (SURBs). Who the nodes are comes from NoxRegistry: a snapshot pinned inside the bundle is the floor, and after
9
10
  ready the worker reads the registry through the mixnet itself. Where the nodes are (IP, port, KPS certhash) is
10
11
  looked up at run time, so operators change IPs whenever they like and new nodes join without a new bundle. A cold
11
12
  boot needs no seed server and no public RPC; the bundle reaches the network only through the harness's KPS dialer.
@@ -22,7 +23,7 @@ over one TCP connection, seeing the provider's host name, timing and sizes (see
22
23
  | `src/` | The worker: `worker.ts` (bundle entry), `core.ts` (boot, readiness, accept loop, deadlines), `config.ts`, `fetch-map.ts` (`fetch` to Nox `HttpRequest`, redirects, gzip), `tls/` (end-to-end TLS: `tunnel.ts` one exit tunnel, `channel.ts` TLS and HTTP/1.1 over it, `pool.ts` sessions, spares and exit choice, `transport.ts` the per-hop transport and error mapping, `module.ts` the TLS module bindings), `jsonrpc.ts`, `errors.ts`, `log.ts`, `storage.ts`, `spec-types.ts` (anon-rpc SPEC 0.3.2 worker types) |
23
24
  | `snapshot/` | The pinned registry snapshot, its keccak-256, JSON Schema and reviewed capability hints, and the discovery bootstrap (`nox-bootstrap.json`) |
24
25
  | `scripts/` | Snapshot generator and verifier, reproducible WASM and bundle build, hashing, provenance |
25
- | `specifier/` | Worker specifier contracts (Foundry) and a read-only deploy planner and inspector |
26
+ | `specifier/` | Worker specifier contracts (Foundry), a read-only deploy planner and inspector, and the `setWorker` update tool for the stable address |
26
27
  | `e2e/` | End-to-end test bed: a local Nox mesh with `nox-kps` sidecars, the reference harness in headless Chromium |
27
28
 
28
29
  ## Configuration
@@ -54,8 +55,8 @@ optional, and every config valid for 0.1.0 stays valid:
54
55
  | `warmup` | boolean | false | one echo through a full route before ready |
55
56
  | `tls` | `"required"`, `"preferred"`, `"off"` | `"required"` | end-to-end TLS, see [End-to-end TLS](#end-to-end-tls) |
56
57
  | `tlsSession` | `"per-call"`, `"keep-alive"` | `"per-call"` | one TLS session per call, or sessions shared by the calls to one host |
57
- | `tlsSpares` | integer | 1 | 0-4 handshaken spare sessions per recently used host (per-call mode) |
58
- | `tlsSpareTtlMs` | integer | 20,000 | 1,000-50,000; an unused spare is closed after this long |
58
+ | `tlsSpares` | integer | 2 | 0-4 handshaken spare sessions per recently used host (per-call mode; 1 in 0.4.0) |
59
+ | `tlsSpareTtlMs` | integer | 12,000 | 1,000-50,000; an unused spare is closed this long after its handshake (20,000 in 0.4.0) |
59
60
  | `tlsKeepAliveMs` | integer | 30,000 | 1,000-50,000; how long a keep-alive session serves calls after it opened |
60
61
  | `tlsMaxCallsPerSession` | integer | 100 | 1-1,000 calls per keep-alive session |
61
62
  | `tlsOpenTimeoutMs` | integer | 5,000 | 1,000-30,000; an exit that does not answer a tunnel open in this long is skipped for 10 minutes |
@@ -113,14 +114,25 @@ Session modes:
113
114
  - `"per-call"` (default): every call has its own TLS session and `Connection: close`; no resumption, no 0-RTT, a
114
115
  fresh key share each time. `tlsSpares` keeps handshaken, unused sessions for hosts the wallet called, opened in
115
116
  the background (only after the first wallet call settled) on a random tunnel exit after an exponential delay
116
- (mean 2 s), each serving one call. Each call travels in its own session; at low traffic the timing of a spare's
117
- open can still chain sequential calls of one wallet to one host, which weakens as traffic grows.
117
+ (mean 2 s), each serving one call. While a host was called in the last minute, a spare's replacement opens at
118
+ once 1.5-2 s before its TTL, so a spare is ready across idle gaps; after that an expired spare is not replaced. A
119
+ call that finds no spare while a spare's handshake is halfway done waits for it. Each call travels in its own
120
+ session; at low traffic the timing of a spare's open can still chain sequential calls of one wallet to one host,
121
+ which weakens as traffic grows. The refresh opens each replacement about 10-10.5 s after the spare it replaces,
122
+ so that chain can span calls up to 1 minute apart (about 20 s in 0.4.0); `tlsSpares: 0` turns spares and
123
+ refreshes off.
118
124
  - `"keep-alive"`: calls to one host share a session on one exit for `tlsKeepAliveMs`, up to 4 sessions per host.
119
125
  The exit links the calls of a session, and the provider sees them on one TLS connection.
120
126
 
121
127
  Latency: a call on a ready session (a spare, or a warm keep-alive session) costs what the exit `HttpRequest` path
122
128
  costs. The first call to a host, and calls beyond the ready spares, add one mixnet round trip for the handshake.
123
129
 
130
+ Measured on 2026-10-10 with the anon-rpc reference bench (ethereum/anon-rpc `impl/site/bench`; worker 0.4.0 with
131
+ its defaults, `eth_getBalance` against a public Ethereum RPC, 39 warm calls per vantage, 40 of 40 calls answered):
132
+ warm median 381 ms from the US (p90 795 ms) and 636 ms from India (p90 1,294 ms); cold boot, median of four,
133
+ 1.5 s from the US and 2.8 s from India. The testnet fleet runs a mix delay of 40 ms per hop, and the worker claims
134
+ replies with claim protocol v2.
135
+
124
136
  Certificates are checked against the Mozilla root store compiled into the bundle (webpki-roots; the version and its
125
137
  release date are in the provenance, refreshed with every worker release), with the host name and the device clock;
126
138
  a wrong clock fails with a message that names it. Each worker release ships its root store, so wallets keep their
@@ -137,7 +149,8 @@ a hybrid post-quantum key exchange (X25519MLKEM768).
137
149
  Errors: `permission-denied` (the exit refuses the destination), `network-error` (no tunnel exit, a certificate
138
150
  failure, the exit cannot reach the provider), `protocol-error` (TLS or HTTP checks failed, a truncated response),
139
151
  `too-large`, `timeout`. Log events: `boot.tls`, `tls.open` (debug: handshake time and CPU), `tls.fallback{reason}`,
140
- `tls.reject{code}`, `tls.exit.skipped`, `tls.retry`; none carries bytes or a host name.
152
+ `tls.reject{code}`, `tls.exit.skipped`, `tls.retry`, `tls.call{path, openMs, spareAgeMs, exchangeMs, retries, ok}`; none
153
+ carries bytes or a host name.
141
154
 
142
155
  ## Discovery (identity from chain, location at run time)
143
156
 
@@ -165,9 +178,9 @@ worker first saw it, and a route holds at most one such member. Checks repeat ev
165
178
  served fingerprint differs or every entry failed. Log events: `discovery.verified`, `discovery.disagreement`,
166
179
  `discovery.incomplete`, `discovery.rejected`, `discovery.probation`, `discovery.floor`, `discovery.failed`.
167
180
 
168
- What each party learns: RPC providers see exit IPs and public registry reads, never the wallet's address. Until
169
- TLS runs inside the worker, the exits terminate HTTPS, so the two-exit quorum is attested by the exits; probation
170
- and the snapshot floor bound what a forged answer can change. If every chain path fails, the worker keeps working
181
+ What each party learns: RPC providers see exit IPs and public registry reads, never the wallet's address. Registry
182
+ reads use the exit `HttpRequest` path, so the two-exit quorum is attested by the exits; registry reads through
183
+ tunnels are the next milestone. Probation and the snapshot floor bound what a forged answer can change. If every chain path fails, the worker keeps working
171
184
  on the snapshot floor.
172
185
 
173
186
  Operators move by sending `updateUrl` / `updateMetadataUrl` from the node key (self-service, about 0.00002 ETH);
@@ -200,25 +213,36 @@ were read at, and when each member outside the snapshot was first seen. At most
200
213
  days. A cache that fails any check (another registry, too many records, one malformed record) is ignored whole, and
201
214
  a learned address is used only after the node's `/metadata.json` names the same member.
202
215
 
203
- ## Release 0.3.0
216
+ ## Release 0.4.0
204
217
 
205
218
  | | |
206
219
  |---|---|
207
- | `workerHash` (keccak-256 of `dist/anon-rpc-worker.js`) | `0x24604525d220bcc7e39f2dbc22966a814600a63ada51baafad5dd0bcc1d28549` |
208
- | Size | 961,799 bytes; 279,515 bytes with `gzip -9 -n` |
209
- | sha256 | `0458b19258dffcfb9ce23d584096f3df06532080390d34ee71b201b6b78dbbac` |
210
- | Specifier | Ethereum Sepolia `0x29B51ca9Ad80E9c0B0D111C8748E6a7908b82eDB` (`ImmutableWorkerSpecifier`, resolvers in [specifier/README.md](./specifier/README.md#deployments)) |
211
- | Snapshot | NoxRegistry at Arbitrum Sepolia block 316,207,920, keccak-256 `0xa351960e…94fbb2` |
220
+ | `workerHash` (keccak-256 of `dist/anon-rpc-worker.js`) | `0xd8bef626ec8511d7a468bca5137880680ca2da20d2ea4d9c981a2a3d0c86ba1e` |
221
+ | Size | 1,820,668 bytes; 649,310 bytes with `gzip -9 -n` |
222
+ | sha256 | `4b7b69cd6f45c364cd87ff75f5f8cacdebea2cd852d5384c47215bb88d56f35a` |
223
+ | Specifier, stable address | Ethereum Sepolia `0x35133267A8F7EC35046a83DCA0f4Ca8A6e1b7A9A` (`WorkerSpecifier`, owner-updatable: it follows the current release; trust model in [specifier/README.md](./specifier/README.md#deployments)) |
224
+ | Specifier, 0.4.0 pin | Ethereum Sepolia `0xDf5Db854BA75B52a4bF1a250a93D8d25cB982b2d` (`ImmutableWorkerSpecifier`, resolvers in [specifier/README.md](./specifier/README.md#deployments)) |
225
+ | Snapshot | NoxRegistry at Arbitrum Sepolia block 316,207,920, keccak-256 `0x1090f071…014681` (the 0.3.0 registry state with the exits' `tunnel_v1` hints) |
212
226
  | nox-wasm module | 188,708 bytes, sha256 `5171416972ea4499fd4b4ea8bcadf002f4c10f10ed42cafb0d5a426121da9c4f` |
213
- | Built from | `nox-sdk` commit `6bddd851c5b1f4e5508f4718e2ea9b4db626b17f`, rustc 1.95.0, wasm-pack 0.13.1, wasm-bindgen 0.2.114, binaryen 117, esbuild 0.27.3 |
227
+ | nox-tls module | 593,738 bytes, sha256 `f74e8cfbed7be33ef1a9158b7b66e454b4ca0849bcb8956487fd13da47e4f4d2` |
228
+ | Root store | webpki-roots 1.0.9 (released 2026-07-18), 121 Mozilla root certificates |
229
+ | Built from | `nox-sdk` commit `fee8d69861a112c65f0cbb0b848a7726ec12f01a`, rustc 1.95.0, clang 19.1.7, wasm-pack 0.13.1, wasm-bindgen 0.2.114, binaryen 117, esbuild 0.27.3 |
214
230
 
215
231
  The npm package carries the bundle and provenance file of the first pinned container build.
216
232
 
217
- Release 0.2.0: `workerHash` `0x0a58f9915f686950072a4786249d396ecbf2194a39ac835effe8ea1a7c76f324` (924,847 bytes, commit
218
- `ef019a7`), specifier `0x29b4a6A8Cc11769531854d87f9F33EC63Efe8fe6`.
233
+ Each release publishes its own immutable specifier and then repoints the stable address with one `setWorker`
234
+ transaction from its owner ([specifier/README.md](./specifier/README.md#updating-the-stable-address-setworker)), so
235
+ wallets and listings that pin the stable address move to the new worker with nothing to change on their side.
236
+
237
+ Release 0.3.0: `workerHash` `0x24604525d220bcc7e39f2dbc22966a814600a63ada51baafad5dd0bcc1d28549` (961,799 bytes, commit
238
+ `6bddd85`), specifier `0x29B51ca9Ad80E9c0B0D111C8748E6a7908b82eDB`. Release 0.2.0: `workerHash`
239
+ `0x0a58f9915f686950072a4786249d396ecbf2194a39ac835effe8ea1a7c76f324` (924,847 bytes, commit `ef019a7`), specifier
240
+ `0x29b4a6A8Cc11769531854d87f9F33EC63Efe8fe6`.
219
241
 
220
242
  Two builds in fresh containers of the pinned images (at two different paths) give these exact bytes
221
- (`scripts/verify-reproducible.sh --ref 6bddd85`). Run it from the repository root as
243
+ (`scripts/verify-reproducible.sh --ref fee8d69`), on an x86_64 workstation and again on the x86_64 CI runner
244
+ (job "Worker bundle (two container builds)"). The aarch64 reproduction is the next check: the toolchain pins carry
245
+ aarch64 digests for it. Run the script from the repository root as
222
246
  `packages/anon-rpc-worker/scripts/verify-reproducible.sh` (or `pnpm verify:reproducible` inside the package); host builds (`--native`,
223
247
  `--local`) use rustc 1.95.0, so set `RUSTUP_TOOLCHAIN=1.95.0` when the host default differs.
224
248