@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 +86 -0
- package/LICENSE +190 -0
- package/README.md +213 -2
- package/dist/anon-rpc-worker.js +19304 -0
- package/dist/anon-rpc-worker.js.keccak256 +1 -0
- package/dist/anon-rpc-worker.provenance.json +48 -0
- package/package.json +46 -3
- package/snapshot/capabilities.json +39 -0
- package/snapshot/nox-bootstrap.json +24 -0
- package/snapshot/nox-snapshot.json +165 -0
- package/snapshot/nox-snapshot.json.keccak256 +1 -0
- package/snapshot/nox-snapshot.schema.json +42 -0
- package/src/config.ts +193 -0
- package/src/core.ts +665 -0
- package/src/embed-modules.d.ts +15 -0
- package/src/errors.ts +92 -0
- package/src/fetch-map.ts +567 -0
- package/src/jsonrpc.ts +166 -0
- package/src/local-answers.ts +238 -0
- package/src/log.ts +100 -0
- package/src/spec-types.ts +147 -0
- package/src/storage.ts +309 -0
- package/src/worker.ts +28 -0
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
|
-
#
|
|
1
|
+
# @hisoka-io/anon-rpc-worker
|
|
2
2
|
|
|
3
|
-
|
|
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`.
|