@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 +34 -0
- package/README.md +46 -22
- package/dist/anon-rpc-worker.js +226 -39
- package/dist/anon-rpc-worker.js.keccak256 +1 -1
- package/dist/anon-rpc-worker.provenance.json +7 -7
- package/package.json +3 -3
- package/src/config.ts +6 -2
- package/src/tls/pool.ts +258 -35
- package/src/tls/transport.ts +24 -2
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`)
|
|
7
|
-
through the mixnet, and an exit
|
|
8
|
-
|
|
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)
|
|
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 |
|
|
58
|
-
| `tlsSpareTtlMs` | integer |
|
|
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.
|
|
117
|
-
|
|
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
|
|
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.
|
|
169
|
-
|
|
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.
|
|
216
|
+
## Release 0.4.0
|
|
204
217
|
|
|
205
218
|
| | |
|
|
206
219
|
|---|---|
|
|
207
|
-
| `workerHash` (keccak-256 of `dist/anon-rpc-worker.js`) | `
|
|
208
|
-
| Size |
|
|
209
|
-
| sha256 | `
|
|
210
|
-
| Specifier | Ethereum Sepolia `
|
|
211
|
-
|
|
|
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
|
-
|
|
|
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
|
-
|
|
218
|
-
|
|
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
|
|
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
|
|