blockyard 0.0.9 → 0.1.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 +359 -1
- package/README.md +48 -27
- package/SECURITY.md +2 -2
- package/bin/blockyard.js +2 -1
- package/docs/API.md +17 -15
- package/docs/ARCHITECTURE.md +128 -10
- package/docs/CONFIGURATION.md +39 -30
- package/docs/DEFECTS.md +4 -1
- package/docs/GETTING-STARTED.md +18 -8
- package/docs/INSTALL.md +97 -37
- package/docs/MEASUREMENTS.md +147 -0
- package/docs/PLAN-SCORCHED-YARD.md +456 -0
- package/docs/PLAN-SKIES.md +142 -0
- package/docs/SECURITY-AUDIT-2026-09-16.md +647 -0
- package/docs/SECURITY.md +58 -22
- package/docs/TROUBLESHOOTING.md +44 -5
- package/docs/USER-GUIDE.md +505 -35
- package/package.json +4 -2
- package/public/404.html +1 -1
- package/public/css/app.css +393 -82
- package/public/donate-qr.png +0 -0
- package/public/index.html +353 -109
- package/public/js/agents.js +228 -51
- package/public/js/app.js +131 -16
- package/public/js/blockanoid.js +15 -7
- package/public/js/blockout.js +15 -7
- package/public/js/blockscene3d.js +230 -38
- package/public/js/charts.js +21 -21
- package/public/js/depthchart.js +31 -27
- package/public/js/details3d.js +1481 -73
- package/public/js/doom.js +31 -0
- package/public/js/dosaudio.js +48 -0
- package/public/js/dosgame.js +389 -0
- package/public/js/dosio.js +186 -0
- package/public/js/dospc.js +1353 -0
- package/public/js/dosworker.js +196 -0
- package/public/js/explorer.js +7 -1
- package/public/js/livingsky.js +494 -0
- package/public/js/login.js +8 -2
- package/public/js/markets.js +46 -8
- package/public/js/mining.js +314 -36
- package/public/js/panels.js +41 -28
- package/public/js/pricechart.js +14 -13
- package/public/js/quake.js +20 -0
- package/public/js/safenext.js +14 -0
- package/public/js/scorched.js +1051 -0
- package/public/js/scorchedai.js +227 -0
- package/public/js/scorchedair.js +286 -0
- package/public/js/scorchedfx.js +376 -0
- package/public/js/scorchedshop.js +105 -0
- package/public/js/scorchedwind.js +69 -0
- package/public/js/scorchedyard.js +1338 -0
- package/public/js/settings.js +368 -100
- package/public/js/soundcard.js +459 -0
- package/public/js/tetrust.js +15 -6
- package/public/js/tetsound.js +35 -5
- package/public/js/theme.js +235 -0
- package/public/js/wolf3d.js +22 -0
- package/public/js/x86.js +1978 -0
- package/scripts/check.js +46 -0
- package/scripts/donate-qr.py +12 -9
- package/scripts/dos-bench.js +56 -0
- package/scripts/index-build.js +9 -2
- package/scripts/pool-map.js +152 -36
- package/scripts/setup.js +142 -22
- package/scripts/shots.mjs +27 -0
- package/scripts/smoke.sh +7 -6
- package/scripts/tls.js +31 -0
- package/scripts/ui.js +4 -2
- package/server/auth/sessions.js +33 -13
- package/server/chain/blockfile.js +64 -5
- package/server/chain/index/build.js +451 -58
- package/server/chain/index/heights.js +29 -3
- package/server/chain/index/live.js +13 -7
- package/server/chain/index/rows.js +6 -1
- package/server/chain/index/store.js +28 -5
- package/server/chain/index/worker.js +23 -11
- package/server/collect/logparse.js +65 -18
- package/server/collect/markets.js +76 -7
- package/server/collect/mining.js +32 -0
- package/server/collect/monitor.js +54 -12
- package/server/collect/network.js +305 -0
- package/server/config.js +53 -22
- package/server/http/api.js +119 -18
- package/server/http/games.js +77 -0
- package/server/http/server.js +30 -5
- package/server/http/sse.js +53 -7
- package/server/main.js +66 -11
- package/server/rpc/allowlist.js +26 -0
- package/server/rpc/client.js +30 -2
- package/server/store/audit.js +6 -1
- package/server/store/history.js +19 -3
- package/server/store/ledger.js +15 -4
- package/server/tls/selfsigned.js +160 -0
- package/systemd/blockyard.service +41 -8
- package/docs/PRIVATE-LEADERBOARD.md +0 -230
- package/docs/STATE-2026-09-09.md +0 -200
package/docs/SECURITY.md
CHANGED
|
@@ -5,7 +5,7 @@ page describes the access model, what protects it, and exactly what leaves your
|
|
|
5
5
|
To report a vulnerability, see [SECURITY.md](../SECURITY.md) at the repository root.
|
|
6
6
|
|
|
7
7
|
- [Threat model in one paragraph](#threat-model-in-one-paragraph)
|
|
8
|
-
- [Access:
|
|
8
|
+
- [Access: sign-in by default, open on request](#access-sign-in-by-default-open-on-request)
|
|
9
9
|
- [Accounts, sessions and passwords](#accounts-sessions-and-passwords)
|
|
10
10
|
- [Talking to the node](#talking-to-the-node)
|
|
11
11
|
- [Node writes](#node-writes)
|
|
@@ -23,13 +23,23 @@ someone using the monitor to make the node do something (prevented: writes are o
|
|
|
23
23
|
console is read-only behind a default-deny allowlist), someone reading your node's state who
|
|
24
24
|
should not (controlled by where it listens, a CIDR gate, and optional accounts), and the
|
|
25
25
|
monitor leaking information about you to third parties (limited to the on-demand market data
|
|
26
|
-
connections listed below, which you can turn off). The monitor does not hold keys and
|
|
27
|
-
wallet
|
|
26
|
+
connections listed below, which you can turn off). The monitor does not hold keys, and every
|
|
27
|
+
wallet RPC is refused by name (since 2026-09-16; before that, wallet reads such as
|
|
28
|
+
`listdescriptors` passed the allowlist's `list` prefix, see the audit of that date).
|
|
28
29
|
|
|
29
|
-
## Access:
|
|
30
|
+
## Access: sign-in by default, open on request
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
**Out of the box the monitor listens on `127.0.0.1` only and requires sign-in.** The first
|
|
33
|
+
start creates an `admin` account and prints its password once (or takes it from
|
|
34
|
+
`BLOCKYARD_ADMIN_PASSWORD`). Reach it from another machine over an SSH tunnel
|
|
35
|
+
(`ssh -L 21000:127.0.0.1:21000 you@host`), or bind a LAN address with `BLOCKYARD_BIND` /
|
|
36
|
+
`server.hosts` once you have decided who may see it. These are the defaults since
|
|
37
|
+
2026-09-15; 0.0.9 shipped bound to every interface with no sign-in, which the first outside
|
|
38
|
+
review rightly called out.
|
|
39
|
+
|
|
40
|
+
Open access is still available as a posture you choose: with `auth.enabled: false`
|
|
41
|
+
(`BLOCKYARD_AUTH=0`), anyone who can reach the port reads the monitor with the fixed role
|
|
42
|
+
`viewer`:
|
|
33
43
|
|
|
34
44
|
| open to anyone who can reach the port | still closed |
|
|
35
45
|
|---|---|
|
|
@@ -37,12 +47,13 @@ with the fixed role `viewer`:
|
|
|
37
47
|
| peer, mempool, block and transaction detail, the explorer | the audit trail |
|
|
38
48
|
| the read-only RPC console (behind the allowlist) | password changes and sessions |
|
|
39
49
|
| the live Server-Sent Events stream | **every node write**, even if actions are enabled |
|
|
50
|
+
| Display settings (they are normalised before use) | the node connection form, except from this machine's loopback address (`auth.openNodeConfigFromNetwork` widens it) |
|
|
40
51
|
|
|
41
52
|
The `viewer` ceiling cannot be raised by configuration or by any credential while accounts
|
|
42
53
|
are off. The start-up log states which addresses are readable and how to close them, so
|
|
43
54
|
"anyone on the LAN can read your node" is never a surprise.
|
|
44
55
|
|
|
45
|
-
|
|
56
|
+
With sign-in on (the default; `BLOCKYARD_AUTH=1` restores it after an override), roles:
|
|
46
57
|
|
|
47
58
|
| role | may |
|
|
48
59
|
|---|---|
|
|
@@ -69,19 +80,30 @@ The last enabled admin cannot be demoted, disabled or deleted.
|
|
|
69
80
|
routes had no cross-site protection at all. An audit proved it with a working exploit against
|
|
70
81
|
the node-connection test. Open mode now refuses any state-changing request whose `Origin` is
|
|
71
82
|
not this server, or whose `Sec-Fetch-Site` says cross-site. A client that sends neither header
|
|
72
|
-
(curl, a script) is
|
|
73
|
-
|
|
83
|
+
(curl, a script) is not stopped by that check, which only guards against what a *browser* can be
|
|
84
|
+
made to do. So the one form where a script could do real harm, the node connection (a saved
|
|
85
|
+
address decides where the node's cookie goes after a restart, and its test makes the server
|
|
86
|
+
connect somewhere), answers only a loopback caller while accounts are off (audit 2026-09-16).
|
|
87
|
+
A save that moves the node to a different host drops the old endpoint's `rpcUser`,
|
|
88
|
+
`rpcPassword` and `cookieFile`, and a test of a foreign endpoint reports the kind of failure,
|
|
89
|
+
never what the endpoint answered.
|
|
74
90
|
- **Brute force**: sign-in is locked after 8 failures in 5 minutes per username and per
|
|
75
91
|
address, for 10 minutes, with the same error and the same hashing time for unknown users
|
|
76
92
|
and wrong passwords. A separate throttle limits sign-in attempts per address, because each
|
|
77
93
|
attempt costs a deliberately expensive hash.
|
|
78
94
|
- **Rate limits** apply per client address.
|
|
95
|
+
- **Slow and stalled clients** cannot hold memory or connections: a request, body included, must
|
|
96
|
+
arrive within 30 seconds; an event stream whose reader stops reading is sent nothing more until
|
|
97
|
+
it drains, and is dropped when 4 MB is buffered or it stays blocked for a minute; one address
|
|
98
|
+
(or account) holds at most 16 streams. Free-form audit fields are clamped to 1,024 characters,
|
|
99
|
+
so a caller cannot rotate real events out of the trail.
|
|
79
100
|
|
|
80
101
|
## Talking to the node
|
|
81
102
|
|
|
82
103
|
- **Read-only allowlist, default deny.** The RPC console and every internal call go through
|
|
83
|
-
an allowlist: read-shaped methods are allowed; wallet
|
|
84
|
-
chain-mutating and very heavy methods are refused
|
|
104
|
+
an allowlist: read-shaped methods are allowed; every wallet method (reads included, since some
|
|
105
|
+
return private keys), spending, peer-control, chain-mutating and very heavy methods are refused
|
|
106
|
+
by name — including `getnewaddress` and
|
|
85
107
|
`getrawchangeaddress`, which start with "get" but create keys. Unknown methods are refused.
|
|
86
108
|
- **One request at a time.** The node's RPC server is single-threaded, so the monitor runs a
|
|
87
109
|
single serialized request lane with a minimum spacing, batching, priorities and a stale-drop
|
|
@@ -124,9 +146,15 @@ Every call and every refusal is appended to the audit trail.
|
|
|
124
146
|
|
|
125
147
|
## Transport security
|
|
126
148
|
|
|
127
|
-
- HTTPS is
|
|
128
|
-
|
|
129
|
-
|
|
149
|
+
- HTTPS is the default on **every** listener. With no certificate named, the server makes its
|
|
150
|
+
own self-signed one on first start (`server/tls/selfsigned.js`, under `<data>/tls/`, the key
|
|
151
|
+
mode 600, ECDSA P-256), naming the addresses it is reached on, and remakes it when it nears
|
|
152
|
+
expiry or stops naming a bound address; the log prints its fingerprint. A certificate of your
|
|
153
|
+
own replaces it (`BLOCKYARD_TLS_CERT`/`_KEY`); a half-configured pair or an expired
|
|
154
|
+
certificate stops the start-up. `BLOCKYARD_TLS=0` is plain HTTP, for a proxy in front.
|
|
155
|
+
- A self-signed certificate proves nothing about who you are talking to the first time; it
|
|
156
|
+
does encrypt the session and pins the fingerprint after that. Compare the fingerprint the
|
|
157
|
+
log prints with the one the browser shows before trusting it on a network you do not own.
|
|
130
158
|
- Over HTTPS the session cookie is `Secure` and `Strict-Transport-Security` is sent with a
|
|
131
159
|
two-day lifetime, without `includeSubDomains` or `preload` — a LAN address can be reissued,
|
|
132
160
|
and HSTS cannot be withdrawn once a browser has it.
|
|
@@ -150,13 +178,15 @@ Everything in the monitor talks only to your node, **except**:
|
|
|
150
178
|
|
|
151
179
|
| when | to | what is sent |
|
|
152
180
|
|---|---|---|
|
|
153
|
-
| while someone has the **Markets**, **Kiosk** or **Overview** tab open — Overview's price line
|
|
154
|
-
| when someone opens an **explorer** page and no fresh market price is at hand | two of the exchanges above, at most once a minute | a public ticker request |
|
|
181
|
+
| only with **Enable market polling** ticked in Display settings (**off by default**), while someone has the **Markets**, **Kiosk** or **Overview** tab open — Overview's price line reads the same feed unless you switch it off — and for 10 minutes after the last request | `api.exchange.coinbase.com`, `api.kraken.com`, `www.bitstamp.net`, `api-pub.bitfinex.com`, `www.okx.com` (HTTPS) | public ticker, hourly candle and order-book requests with a `User-Agent` naming the software — nothing about your node |
|
|
182
|
+
| with polling on, when someone opens an **explorer** page or the **Mining** tab and no fresh market price is at hand | two of the exchanges above, at most once a minute | a public ticker request |
|
|
155
183
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
184
|
+
**Out of the box none of this happens**: polling is off until someone ticks **Display settings → Markets & Price → Enable market polling**, and until then the
|
|
185
|
+
Markets and Kiosk tabs say so and the explorer shows no dollar figures. The switch is a Display
|
|
186
|
+
setting shared by every screen, so anyone who can change settings on this monitor can turn it
|
|
187
|
+
on; `BLOCKYARD_MARKETS=0` (or `"markets": { "enabled": false }`) removes the feed from the
|
|
188
|
+
server so that no checkbox can. With polling on, nothing is fetched when nobody is looking. Your machine's public address is visible to those exchanges
|
|
189
|
+
when a request is made, as with any web request.
|
|
160
190
|
|
|
161
191
|
The monitor sends no telemetry, checks for no updates, and phones home to no one.
|
|
162
192
|
|
|
@@ -185,11 +215,17 @@ and errors; they never contain passwords, session tokens or RPC credentials.
|
|
|
185
215
|
## Hardening checklist
|
|
186
216
|
|
|
187
217
|
- Run as a dedicated, unprivileged account that can read only the node's cookie and its
|
|
188
|
-
`blocks/` directory (and log), and write only its own `data/` and the index directory.
|
|
218
|
+
`blocks/` directory (and log), and write only its own `data/` and the index directory. The
|
|
219
|
+
shipped systemd unit enforces the write half (`ProtectSystem=strict` with `ReadWritePaths=` for
|
|
220
|
+
`data/`, `config/` and the index) and drops capabilities, system calls and address families the
|
|
221
|
+
monitor does not use; add your index directory to it.
|
|
222
|
+
- Point `addressIndex` (or `--out`) at a new or empty directory. The build refuses a symlink, the
|
|
223
|
+
filesystem root, a home or working directory, the node's blocks directory, and any directory
|
|
224
|
+
holding files an index does not write; it only ever removes its own files.
|
|
189
225
|
- Bind the narrowest set of addresses that serves your users; add a firewall rule if needed.
|
|
190
226
|
- Turn accounts on if anyone who can reach the port should not see your node.
|
|
191
227
|
- Use HTTPS, a reverse proxy, or an SSH tunnel on untrusted networks.
|
|
192
228
|
- Leave node actions off unless you have a specific need, and then enable only that action.
|
|
193
|
-
- Set `BLOCKYARD_MARKETS=0` on machines that must not make outbound connections.
|
|
229
|
+
- Set `BLOCKYARD_MARKETS=0` on machines that must not make outbound connections: the polling checkbox (off by default) then cannot turn the feed on.
|
|
194
230
|
- Keep `config/local.json` and `data/` readable only by the service account.
|
|
195
231
|
- Keep Node.js current within the 22.x line or later.
|
package/docs/TROUBLESHOOTING.md
CHANGED
|
@@ -54,6 +54,10 @@ The Node & RPC page shows the last error. Common causes:
|
|
|
54
54
|
|
|
55
55
|
## The page does not load from another machine
|
|
56
56
|
|
|
57
|
+
0. Out of the box the monitor binds `127.0.0.1` and answers this machine only. Either reach
|
|
58
|
+
it over an SSH tunnel (`ssh -L 21000:127.0.0.1:21000 you@host`, then `https://localhost:21000`)
|
|
59
|
+
or bind a LAN address: `BLOCKYARD_BIND=192.0.2.10` (or `0.0.0.0`), or `server.hosts` in
|
|
60
|
+
`config/local.json`, and restart.
|
|
57
61
|
1. From the other machine, check how it reaches the server: `ip route get <address>`.
|
|
58
62
|
2. Make sure that address is one the monitor binds — see the start-up log. A LAN-only bind is
|
|
59
63
|
not reachable over a VPN unless the VPN address is also listed in `server.host`.
|
|
@@ -159,21 +163,50 @@ so far and an ETA; the address page repeats it. Things it says, and what they me
|
|
|
159
163
|
[INSTALL](INSTALL.md#bitcoinconf-settings-worth-having), or let it run overnight.
|
|
160
164
|
- **The ETA is wrong at first** — it is computed from the files done so far in the current phase
|
|
161
165
|
and settles after the first few; files are not all the same size.
|
|
166
|
+
- **It stopped one short — "scan 5,720 of 5,721, about 1 s left" for an hour** — a worker
|
|
167
|
+
died, most likely killed for memory (four workers is about 10 GB beside the node). Since
|
|
168
|
+
2026-09-15 that fails the build at once with `an index worker exited with code N while on
|
|
169
|
+
{"type":"scan","file":...}` and the flag turns to *build failed*; before that fix the build
|
|
170
|
+
hung there for good. Either way: restart BlockYard with fewer workers (`addressIndexWorkers` on
|
|
171
|
+
the node entry in `config/local.json`). The build resumes with the files it had finished. The flag
|
|
172
|
+
also says **no progress for N min** whenever nothing has moved for two minutes, so a stall is
|
|
173
|
+
visible as one rather than as a stale ETA.
|
|
162
174
|
- **Hours, not minutes** — expect **a few hours**: 29 min 45 s is 16 workers on NVMe, and four
|
|
163
175
|
workers (the installer's default) are roughly four times slower; **spinning disks** are slower still whatever the
|
|
164
176
|
number, and there one worker is the fast setting, because parallel readers only seek against
|
|
165
177
|
each other and against the node. Set `addressIndexWorkers` on the node entry in
|
|
166
|
-
`config/local.json` (the installer writes the number you gave it) and restart:
|
|
167
|
-
|
|
168
|
-
- **It started over** —
|
|
169
|
-
|
|
178
|
+
`config/local.json` (the installer writes the number you gave it) and restart: the build resumes with the files already scanned, and only the rest are read with the
|
|
179
|
+
new number of workers.
|
|
180
|
+
- **It started over** — a stopped build normally resumes: the log says `address index build:
|
|
181
|
+
resuming the interrupted build in <dir> at block N` with how much was already done. When it
|
|
182
|
+
says `discarding the interrupted build … and starting over` instead, the reason follows: the
|
|
183
|
+
node's chain reorganised below the block the build was working to, the node is behind it (a
|
|
184
|
+
reindex, or another node), the index format changed with an update, or the journal or a bucket
|
|
185
|
+
file was damaged. Unfinished work that cannot be proven whole is never used, so the build begins
|
|
186
|
+
again from the first file.
|
|
170
187
|
- **the address index build failed** — the reason is in the events feed and the log; fix it and
|
|
171
188
|
restart (the server builds again), or run `node scripts/index-build.js --out <dir>` by hand.
|
|
172
189
|
A pruned node, unreadable block files and a full disk are the usual causes.
|
|
173
190
|
|
|
191
|
+
## "getrawmempool verbose dropped as stale" in Events, and the mempool panels look old
|
|
192
|
+
|
|
193
|
+
The monitor keeps one RPC request in flight and serves the live polls first; the full-pool poll is
|
|
194
|
+
the lowest priority, so when the node's RPC is slow it waits behind them and, past its freshness
|
|
195
|
+
budget, is dropped rather than shown as current. A streak of drops is one warning event when it
|
|
196
|
+
starts, a counter on **Node & RPC → data quality** while it lasts, and one event when the poll
|
|
197
|
+
answers again with the count and the span. The Mempool, Block space and Mining panels show their
|
|
198
|
+
last reading meanwhile and say how old it is.
|
|
199
|
+
|
|
200
|
+
The cause is the node, not the monitor: look for what else is asking it. On 2026-09-15 an Umbrel
|
|
201
|
+
node answered in seconds for thirteen hours while another BlockYard built its address index
|
|
202
|
+
against it over the LAN, and the drops stopped the moment that build finished. A remote index
|
|
203
|
+
build, a wallet rescan, `gettxoutsetinfo` from another tool, or an initial block download all
|
|
204
|
+
show the same way.
|
|
205
|
+
|
|
174
206
|
## Markets or Kiosk show no prices
|
|
175
207
|
|
|
176
|
-
- **"market
|
|
208
|
+
- **"market polling is off"** — the default. Tick **Display settings → Markets & Price → Enable market polling**.
|
|
209
|
+
- **"market data is off on this server"** — `BLOCKYARD_MARKETS=0` or `markets.enabled: false` is set on the server; the checkbox cannot override it.
|
|
177
210
|
- **"asking the exchanges…" for a long time** — the server cannot reach the exchanges. Test
|
|
178
211
|
from the server: `curl -sI https://api.exchange.coinbase.com/products/BTC-USD/ticker`. Check
|
|
179
212
|
outbound firewall rules and DNS.
|
|
@@ -289,6 +322,12 @@ browser in kiosk mode pointed at `http://<host>:21000/#kiosk`.
|
|
|
289
322
|
- **"too many attempts"** — the lockout lasts 10 minutes per username and per address.
|
|
290
323
|
- **Sign-in does not stick** — over plain HTTP, make sure `BLOCKYARD_SECURE_COOKIE` is not set
|
|
291
324
|
(a `Secure` cookie is never sent over HTTP). Behind a TLS proxy, set it.
|
|
325
|
+
- **The browser warns about the certificate** — expected once per address: the monitor's own
|
|
326
|
+
certificate is self-signed. Compare the fingerprint in the start-up log with the browser's,
|
|
327
|
+
then accept it. To be rid of the warning, name a certificate of your own (INSTALL §9) or put
|
|
328
|
+
a proxy with a real one in front (§10).
|
|
329
|
+
- **`https://` says the connection was reset, or `http://` shows nothing** — the port speaks
|
|
330
|
+
one or the other: HTTPS unless `BLOCKYARD_TLS=0`. Use the scheme the start-up log prints.
|
|
292
331
|
|
|
293
332
|
## Tests fail on a fresh clone
|
|
294
333
|
|