@hisoka-io/anon-rpc-worker 0.0.0-stage → 0.3.0

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 ADDED
@@ -0,0 +1,86 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@hisoka-io/anon-rpc-worker`.
4
+
5
+ ## 0.3.0 (2026-10-06)
6
+
7
+ Every 0.2.0 config stays valid. Works against entries on nox 0.4.0-rc.6 and on nodes with claim protocol v2.
8
+
9
+ ### Added
10
+
11
+ - Config `hedgeAfterMs` (default 3,000, 0 turns it off): resendable reads send a second copy on another route at
12
+ about the observed p95 reply time; the first reply wins.
13
+ - The client runs with the low-latency settings: binary, retaining, long-polling claims where the entry supports
14
+ them; concurrent claims with data blocks first; immediate resend on a lost reply; two transport-failure resends;
15
+ same-entry resends for a single bridge; a standby entry kept connected (claims share the primary connection).
16
+ - Wallet calls first: the first registry check waits for the first call to settle (at most 15 s).
17
+ - Compressed replies: upstream requests carry `accept-encoding: gzip` where the runtime has `DecompressionStream`,
18
+ and the worker inflates gzip bodies itself, capped at `maxResponseBytes`. A 414 KB `eth_getLogs` reply crosses the
19
+ mixnet as about 41 KB (2 reply packets instead of 14), a full block of 490 KB as about 81 KB (3 instead of 16).
20
+ - Worker-local answers: `eth_chainId` and `net_version` per upstream URL once a second, independently routed
21
+ request agrees. Reads addressed by block hash (`eth_getBlockByHash`, `eth_getBlockReceipts`, `eth_getLogs`
22
+ with `blockHash`, EIP-1898 `{blockHash}` state reads, kept once two calls returned the same result) are behind
23
+ the `LocalAnswers` option `blockHashReads`, off in the worker until answers are checked against the hash. A hit answers at
24
+ once with the caller's JSON-RPC `id` and sends nothing. `Cache-Control: no-cache` or `no-store` skips it. An
25
+ empty log or receipt list is always fetched again, since an upstream behind the chain head answers `[]`.
26
+ - The client's send-path settings: chunked packet writes, a send-window warm-up after each entry connection
27
+ opens, and concurrent calls spread over the pinned and standby entries.
28
+
29
+ ## 0.2.0 (2026-10-06)
30
+
31
+ Built on `@hisoka-io/nox-client` 0.6.0 (`kps.discovery`). Every 0.1.0 config stays valid.
32
+
33
+ ### Added
34
+
35
+ - Discovery (S1): identity from the snapshot and NoxRegistry, location looked up at run time. The bundle pins a
36
+ `nox-anon-rpc-bootstrap/1` file next to the snapshot: default anchors nox-1, nox-2 and nox-8 on their Elastic IPs,
37
+ the registry implementation, two keyless Arbitrum Sepolia RPC providers of different organisations and the
38
+ discovery policy. An empty config boots on those anchors.
39
+ - Chain checks through the mixnet after ready: two exits to two providers at one finalized block, used on
40
+ byte-identical agreement only; location updates, removals, new members on probation (1 per route, 14 days).
41
+ - Config: `bridges` (only addresses ever dialled), `registryRpcUrls`, `chainQuorum`, `discovery`
42
+ (`"chain"` / `"snapshot"`), reserved `trust` and `checkpoint`.
43
+ - Learned-anchor cache `nox/v1/anchors` (public chain data, at most 32 entries, 30 days), validated whole and
44
+ confirmed through `/metadata.json` before use.
45
+ - Build: `--bootstrap`, bootstrap digest in the build record and provenance; the test-bed build writes a bed
46
+ bootstrap (local providers, anchors, short check interval).
47
+
48
+ - Release snapshot of NoxRegistry at Arbitrum Sepolia block 316207920 (10 members; nox-1, nox-2 and nox-8 publish
49
+ their KPS addresses), read and re-verified byte for byte through two RPC providers.
50
+ - `verify-reproducible.sh --native` (a third build with host tools next to the two container builds) and `--bwrap`
51
+ (host builds at the container paths for hosts without a container runtime).
52
+
53
+ ### Changed
54
+
55
+ - `gateways` no longer has to be a pinned member's published address; gateways are tried first in place of the
56
+ default anchors instead of restricting entries. After the gateways, the worker may dial the default anchors and
57
+ other published entries. Wallets that used `gateways` to keep traffic on chosen addresses switch to `bridges`,
58
+ which the worker treats as the only addresses it ever dials.
59
+ - A gateway or bridge must answer `/metadata.json` with the node it serves (nox-kps `node_address`), and that node
60
+ must be a member in the bundle's snapshot.
61
+
62
+ ### Fixed
63
+
64
+ - Reproducible WASM across checkout paths: `reproducible-rustc.sh` strips the cargo home, sysroot and workspace
65
+ prefixes only at the start of a source path. A checkout at a path such as `/src` used to rewrite the
66
+ `registry/src/` part of every dependency path, which changed each crate's `-C metadata` and the module bytes.
67
+
68
+ ## 0.1.0
69
+
70
+ First version of the Nox mixnet worker for the anon-rpc standard (SPEC 0.3.2), built on
71
+ `@hisoka-io/nox-client` 0.5.0 in KPS mode.
72
+
73
+ ### Added
74
+
75
+ - The worker: boots from the NoxRegistry snapshot pinned in the bundle, dials Nox entry nodes through
76
+ `anonRpcWorker.kps`, and serves `fetch` calls as Nox HTTP requests through an exit, with JSON-RPC aware reply
77
+ sizing and retries for reads, per-call deadlines and abort, `fetch`-style redirects and stable `signalFailed` and
78
+ per-call error codes. The config is optional and validated before ready.
79
+ - A reproducible single-file bundle: the nox-wasm module and the snapshot are embedded, the snapshot byte for byte;
80
+ the SDK's ambient `fetch` and `WebSocket` defaults are replaced by a stand-in that fails closed, and the build
81
+ checks the output for other network APIs. Provenance records the toolchain and every input digest.
82
+ - Snapshot tools: generation from the chain, offline and on-chain verification, and a release gate that requires
83
+ every member's KPS address and two RPC providers.
84
+ - Worker specifier contracts (the reference `WorkerSpecifier` and an `ImmutableWorkerSpecifier`) with a read-only
85
+ deploy planner and inspector.
86
+ - An end-to-end test bed: a local Nox mesh with `nox-kps` sidecars and the reference harness in headless Chromium.
package/LICENSE ADDED
@@ -0,0 +1,190 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to the Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by the Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding any notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ Copyright 2026 hisoka-io
179
+
180
+ Licensed under the Apache License, Version 2.0 (the "License");
181
+ you may not use this file except in compliance with the License.
182
+ You may obtain a copy of the License at
183
+
184
+ http://www.apache.org/licenses/LICENSE-2.0
185
+
186
+ Unless required by applicable law or agreed to in writing, software
187
+ distributed under the License is distributed on an "AS IS" BASIS,
188
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
189
+ See the License for the specific language governing permissions and
190
+ limitations under the License.
package/README.md CHANGED
@@ -1,3 +1,214 @@
1
- # Temporary Holding Version
1
+ # @hisoka-io/anon-rpc-worker
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The Nox mixnet worker for the [anon-rpc](https://github.com/ethereum/anon-rpc) standard: one classic-script
4
+ bundle that a wallet's anon-rpc harness loads and pins by its keccak-256 hash.
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
9
+ ready the worker reads the registry through the mixnet itself. Where the nodes are (IP, port, KPS certhash) is
10
+ looked up at run time, so operators change IPs whenever they like and new nodes join without a new bundle. A cold
11
+ boot needs no seed server and no public RPC; the bundle reaches the network only through the harness's KPS dialer.
12
+
13
+ What each party sees: the entry node sees the wallet's IP address and encrypted packets; the exit node sees the
14
+ HTTP request (RPC URL, headers it forwards, body) and never who sent it. End-to-end TLS inside the worker,
15
+ which also hides the request from the exit, is the next milestone.
16
+
17
+ ## Layout
18
+
19
+ | Path | Contents |
20
+ |---|---|
21
+ | `src/` | The worker: `worker.ts` (bundle entry), `core.ts` (boot, readiness, accept loop, deadlines), `config.ts`, `fetch-map.ts` (`fetch` to Nox `HttpRequest`), `jsonrpc.ts`, `errors.ts`, `log.ts`, `storage.ts`, `spec-types.ts` (anon-rpc SPEC 0.3.2 worker types) |
22
+ | `snapshot/` | The pinned registry snapshot, its keccak-256, JSON Schema and reviewed capability hints, and the discovery bootstrap (`nox-bootstrap.json`) |
23
+ | `scripts/` | Snapshot generator and verifier, reproducible WASM and bundle build, hashing, provenance |
24
+ | `specifier/` | Worker specifier contracts (Foundry) and a read-only deploy planner and inspector |
25
+ | `e2e/` | End-to-end test bed: a local Nox mesh with `nox-kps` sidecars, the reference harness in headless Chromium |
26
+
27
+ ## Configuration
28
+
29
+ `anonRpcWorker.config` is optional; `undefined` and `{}` boot on the anchors pinned in the bundle. Every field is
30
+ optional, and every config valid for 0.1.0 stays valid:
31
+
32
+ | Field | Type | Default | Range and meaning |
33
+ |---|---|---|---|
34
+ | `v` | number | 1 | must be 1 |
35
+ | `gateways` | string[] | the bundle's default anchors | 1-16 unique KPS addresses (`<ip>:<port>:<certhash>`, no DNS names), tried first in place of the default anchors. Any address is accepted: the node behind it must name an eligible member in its `/metadata.json` before the worker routes through it |
36
+ | `bridges` | string[] | none | 1-16 KPS addresses. When set, the worker dials **only** bridges and never a published Nox address (Tor bridge semantics). Each bridge's `/metadata.json` names the member it serves (nox-kps `node_address`), which must be in the bundle's snapshot. Excludes `gateways` |
37
+ | `registryRpcUrls` | string[] | the bundle's list | 2-8 `https:` URLs, replacing the bundle's registry RPC providers (for example a wallet's own Arbitrum node) |
38
+ | `chainQuorum` | integer | 2 (bundle policy) | 2-4 (exit, provider) pairs that must answer byte for byte the same; a check waits when fewer distinct exits or providers are available |
39
+ | `discovery` | `"chain"`, `"snapshot"` | `"chain"` | `"snapshot"`: no registry reads, the pinned snapshot is the only membership source (0.1.0 behaviour, no RPC provider involved) |
40
+ | `trust` | `"auto"` | `"auto"` | reserved for proof-backed discovery; any other value fails with `bad-config` |
41
+ | `checkpoint` | | | reserved; any value fails with `bad-config` |
42
+ | `logLevel` | `"debug"`, `"info"`, `"warn"`, `"error"` | `"info"` | |
43
+ | `attemptTimeoutMs` | integer | 12,000 | 3,000-60,000 |
44
+ | `hedgeAfterMs` | integer | 3,000 | 0-60,000. Resendable reads send a second copy on another route when no reply arrived after this long, raised to the observed p95 reply time and kept below `attemptTimeoutMs`; the first reply wins. 0 turns hedging off |
45
+ | `callDeadlineMs` | integer | 25,000 | at least `attemptTimeoutMs`, at most 120,000 |
46
+ | `maxConcurrentCalls` | integer | 16 | 1-64 |
47
+ | `maxRequestBytes` | integer | 1,048,576 | 1,024-4,194,304 |
48
+ | `maxResponseBytes` | integer | 8,388,608 | 65,536-67,108,864 |
49
+ | `surbFormat` | `"auto"`, `"v1"`, `"v2"` | `"auto"` | |
50
+ | `claimIntervalMs` | integer | 200 | 50-2,000 |
51
+ | `bootRetryMaxMs` | integer | 60,000 | 5,000-300,000 |
52
+ | `topologySources` | integer | 2 | 1-4 |
53
+ | `warmup` | boolean | false | one echo through a full route before ready |
54
+
55
+ An unknown key, a wrong type or an out-of-range value fails the boot with `bad-config`, naming the field.
56
+
57
+ Examples:
58
+
59
+ - default wallet: no config, or `{}`;
60
+ - censored user: `{ "bridges": ["203.0.113.9:15005:uEiB..."] }`;
61
+ - adopters listing `exampleConfig`:
62
+
63
+ ```json5
64
+ { gateways: ["100.56.0.72:15005:uEiBVDwIs40bsslDkM-BYb2AOHw3PHe70_bj5U_09r7vdIQ"] }
65
+ ```
66
+
67
+ ## Discovery (identity from chain, location at run time)
68
+
69
+ The bundle pins `snapshot/nox-bootstrap.json` (`nox-anon-rpc-bootstrap/1`) next to the snapshot, both covered by
70
+ the worker hash:
71
+
72
+ | Field | Value |
73
+ |---|---|
74
+ | `anchors` | nox-1 `100.56.0.72:15005`, nox-2 `3.232.137.146:15005`, nox-8 `18.215.18.61:15005` (Elastic IPs) with their KPS certhashes |
75
+ | `registry`, `registryImpl` | NoxRegistry `0xf7bf...6cb6` on Arbitrum Sepolia (421614), implementation `0x7285...e2a2` behind the EIP-1967 proxy |
76
+ | `registryRpcUrls` | `https://sepolia-rollup.arbitrum.io/rpc` (Offchain Labs) and `https://arbitrum-sepolia-testnet.api.pocket.network` (Pocket Network): keyless, and checked through the live exits for `finalized`, EIP-1898 block-hash reads at the finalized block and 20-call batches. Tenderly's and Tatum's public gateways rate-limit or charge for those batches, PublicNode prunes the state of the finalized block, dRPC's free tier caps batches at 3 calls; a wallet can add its own providers with `registryRpcUrls` |
77
+ | `policy` | quorum 2, state at most 3,600 s old, check every 600 s, at most 1 member on probation per route, probation 14 days, removals need 2 anchors, 2 members per layer |
78
+
79
+ Boot: the worker dials, in priority classes and shuffled within each, bridges only (when set); otherwise the
80
+ wallet's gateways or the bundle's anchors, then learned anchors, then the snapshot's KPS addresses, three at a time.
81
+ As soon as one anchor serves an acceptable topology it signals ready (boot time unchanged). Served topologies are
82
+ judged on identity (address, Sphinx key, role): a changed IP is a move, never a removal.
83
+
84
+ After ready, the SDK reads NoxRegistry at one finalized block through the mixnet: two different exits to two
85
+ different providers, every call pinned to the block hash. It uses the answer only when both pairs agree byte for
86
+ byte, the registered members close the set (`relayerCount()` and the XOR `topologyFingerprint()`), and the proxy
87
+ still points at the implementation the bundle knows. The answer updates locations and removes members (each route
88
+ layer keeps at least two snapshot members); a member outside the snapshot is on probation for 14 days after the
89
+ worker first saw it, and a route holds at most one such member. Checks repeat every 10 minutes, and early when a
90
+ served fingerprint differs or every entry failed. Log events: `discovery.verified`, `discovery.disagreement`,
91
+ `discovery.incomplete`, `discovery.rejected`, `discovery.probation`, `discovery.floor`, `discovery.failed`.
92
+
93
+ What each party learns: RPC providers see exit IPs and public registry reads, never the wallet's address. Until
94
+ TLS runs inside the worker, the exits terminate HTTPS, so the two-exit quorum is attested by the exits; probation
95
+ and the snapshot floor bound what a forged answer can change. If every chain path fails, the worker keeps working
96
+ on the snapshot floor.
97
+
98
+ Operators move by sending `updateUrl` / `updateMetadataUrl` from the node key (self-service, about 0.00002 ETH);
99
+ running workers pick the new address up within about 10 minutes.
100
+
101
+ ## Readiness and failure codes
102
+
103
+ The worker signals ready once the config is valid, the embedded WebAssembly is initialised, the pinned snapshot
104
+ verifies, a KPS entry has answered and a served topology was accepted. Transient faults (no entry reachable,
105
+ a topology fetch failing) are retried with back-off while `ready` stays pending.
106
+
107
+ `signalFailed` codes: `bad-config`, `unsupported-platform` (no KPS dialer, `crypto.getRandomValues` or
108
+ WebAssembly), `wasm-blocked` (the embedder's CSP must allow `'wasm-unsafe-eval'`), `snapshot-invalid`,
109
+ `snapshot-invalid` also covers an invalid discovery bootstrap. `snapshot-stale` (two or more nodes agree the registry no
110
+ longer lists any known member of a route layer with its identity, and a chain check does not resolve it: a newer
111
+ bundle is due) and `internal-error`. Members that are listed but reported offline, as while
112
+ nodes reconnect after a restart, keep the worker running: calls through them fail one by one (`timeout` or
113
+ `network-error`) until a topology refresh sees them online.
114
+
115
+ A rejected call carries a string `code`: `cancelled` (`AbortError`), `timeout`, `network-error`, `too-large`,
116
+ `unsupported`, `protocol-error` or `internal-error`. Redirects follow `fetch` rules inside the call deadline (at
117
+ most 5 hops). `eth_sendRawTransaction` is sent once and never resent; after a `timeout`, check the receipt by
118
+ transaction hash.
119
+
120
+ ## Storage
121
+
122
+ The worker stores public network state only, under `nox/v1/`: members recent topologies removed (reorders anchors)
123
+ and the learned-anchor cache (`nox/v1/anchors`): KPS addresses verified chain checks confirmed, with the block they
124
+ were read at, and when each member outside the snapshot was first seen. At most 32 anchors, each dropped after 30
125
+ days. A cache that fails any check (another registry, too many records, one malformed record) is ignored whole, and
126
+ a learned address is used only after the node's `/metadata.json` names the same member.
127
+
128
+ ## Release 0.2.0
129
+
130
+ | | |
131
+ |---|---|
132
+ | `workerHash` (keccak-256 of `dist/anon-rpc-worker.js`) | `0x0a58f9915f686950072a4786249d396ecbf2194a39ac835effe8ea1a7c76f324` |
133
+ | Size | 924,847 bytes; 268,945 bytes with `gzip -9 -n` |
134
+ | Specifier | Ethereum Sepolia `0x29b4a6A8Cc11769531854d87f9F33EC63Efe8fe6` (`ImmutableWorkerSpecifier`, resolvers in [specifier/README.md](./specifier/README.md#deployments)) |
135
+ | Snapshot | NoxRegistry at Arbitrum Sepolia block 316,207,920, keccak-256 `0xa351960e…94fbb2` |
136
+ | nox-wasm module | 188,708 bytes, sha256 `5171416972ea4499fd4b4ea8bcadf002f4c10f10ed42cafb0d5a426121da9c4f` |
137
+ | Built from | `nox-sdk` commit `ef019a716d7994820d7d63d6de3e60ae8f71e80d`, rustc 1.95.0, wasm-pack 0.13.1, wasm-bindgen 0.2.114, binaryen 117, esbuild 0.27.3 |
138
+
139
+ Two builds in fresh containers of the pinned images (at two different paths) and a host build give these exact
140
+ bytes (`scripts/verify-reproducible.sh --native`). Run it from the repository root as
141
+ `packages/anon-rpc-worker/scripts/verify-reproducible.sh` (or `pnpm verify:reproducible` inside the package); host builds (`--native`,
142
+ `--local`) use rustc 1.95.0, so set `RUSTUP_TOOLCHAIN=1.95.0` when the host default differs.
143
+
144
+ ## What the bundle contains
145
+
146
+ - `dist/anon-rpc-worker.js`: a single IIFE built by esbuild (target es2022, not minified, so it can be read
147
+ and audited). Every module is inlined: the worker source, the `@hisoka-io/nox-client` sources, the
148
+ `nox-wasm` WebAssembly module (base64), the pinned registry snapshot and the discovery bootstrap, each embedded
149
+ byte for byte as one string.
150
+ The bundle has no imports and never loads code or WebAssembly by URL. The SDK's ambient `fetch` and
151
+ `WebSocket` defaults are replaced at build time by a stand-in that fails closed, and the build checks the
152
+ output for any other network API.
153
+ - `dist/anon-rpc-worker.js.keccak256`: the Ethereum keccak-256 of those exact bytes, the value a worker
154
+ specifier pins as `workerHash()`.
155
+ - `dist/anon-rpc-worker.provenance.json`: source commit, toolchain versions, input digests (snapshot, bootstrap,
156
+ WASM, lockfile) and output digests.
157
+
158
+ ## Pinned registry snapshot
159
+
160
+ `snapshot/nox-snapshot.json` (format `nox-anon-rpc-snapshot/1`, schema in `snapshot/nox-snapshot.schema.json`)
161
+ records every NoxRegistry member on Arbitrum Sepolia at one block: address, Sphinx key, routing and ingress
162
+ URLs, `metadataUrl` (where each node publishes its KPS address as `kps:<ip>:<port>:<certhash>/metadata.json`),
163
+ stake, role, layer, status, and reviewed capability hints from `snapshot/capabilities.json`. Its topology
164
+ fingerprint is computed with the SDK's own `computeTopologyFingerprint` and equals the registry's on-chain
165
+ `topologyFingerprint()` at that block.
166
+
167
+ Nodes may serve fresher topology over KPS. The worker uses it for liveness and removals (only when two different
168
+ nodes agree, keeping at least two members per route layer) and for moved routing addresses two nodes agree on;
169
+ additions and confirmed locations come from the chain check.
170
+
171
+ The committed release snapshot is of block 316207920 (finalized, read and re-verified byte for byte through two RPC
172
+ providers of different organisations). It carries the KPS addresses nox-1, nox-2 and nox-8 publish, so the worker
173
+ finds its entries from the snapshot alone, and the default anchors and the chain check add any later moves.
174
+ The release gate names the seven mix and exit nodes, which serve the mixnet without a KPS listener, with
175
+ `--allow-missing-kps`.
176
+
177
+ ```bash
178
+ pnpm --filter @hisoka-io/nox-client build
179
+ node scripts/make-snapshot.mjs --rpc <arbitrum-sepolia-rpc> --rpc <second-rpc> [--block <n>]
180
+ node scripts/verify-snapshot.mjs --offline # schema, hashes, SDK-derived fields
181
+ node scripts/verify-snapshot.mjs --rpc <rpc> --rpc <rpc> --release # chain re-read + every member publishes a KPS address
182
+ ```
183
+
184
+ ## Building and checking the hash
185
+
186
+ ```bash
187
+ corepack enable && pnpm install --frozen-lockfile
188
+ bash packages/anon-rpc-worker/scripts/build-worker.sh --release # pinned toolchain, clean tree
189
+ node packages/anon-rpc-worker/scripts/hash.mjs packages/anon-rpc-worker/dist/anon-rpc-worker.js
190
+ ```
191
+
192
+ `scripts/toolchain.env` pins every tool that shapes the bytes: the Node and Rust container images by digest,
193
+ rustc, wasm-pack, wasm-bindgen and binaryen (with sha256 digests of their release archives); pnpm comes from the
194
+ repository's `packageManager` field and esbuild from the lockfile. `scripts/build-wasm.sh` follows the
195
+ tor-js recipe: path remapping, a `RUSTC_WRAPPER` that makes cargo's metadata host-independent, `--locked`, and
196
+ a pinned `wasm-opt` on `PATH`.
197
+
198
+ `scripts/verify-reproducible.sh` builds a commit twice from clean `git archive` exports in fresh containers
199
+ of the pinned images, at two different paths, and compares the WASM, bundle and provenance digests
200
+ (`--local` does the same with the host's tools). `--native` adds a third build with the host's tools to the two
201
+ container builds and requires all three to match; `--local --bwrap` runs the two host builds in bubblewrap mount
202
+ namespaces at the container paths, for hosts without a container runtime.
203
+
204
+ For a test bed, `node scripts/build.mjs --snapshot <file>` embeds another canonical snapshot (for example a
205
+ local mesh); the provenance records which snapshot a bundle carries.
206
+
207
+ ## Tests
208
+
209
+ ```bash
210
+ pnpm --filter @hisoka-io/anon-rpc-worker test # worker, SDK integration over an in-memory KPS network, build
211
+ pnpm --filter @hisoka-io/anon-rpc-specifier test # Foundry and harness-path tests (forge, anvil)
212
+ ```
213
+
214
+ The end-to-end bed has its own lockfile: see `e2e/README.md`.