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.
Files changed (97) hide show
  1. package/CHANGELOG.md +359 -1
  2. package/README.md +48 -27
  3. package/SECURITY.md +2 -2
  4. package/bin/blockyard.js +2 -1
  5. package/docs/API.md +17 -15
  6. package/docs/ARCHITECTURE.md +128 -10
  7. package/docs/CONFIGURATION.md +39 -30
  8. package/docs/DEFECTS.md +4 -1
  9. package/docs/GETTING-STARTED.md +18 -8
  10. package/docs/INSTALL.md +97 -37
  11. package/docs/MEASUREMENTS.md +147 -0
  12. package/docs/PLAN-SCORCHED-YARD.md +456 -0
  13. package/docs/PLAN-SKIES.md +142 -0
  14. package/docs/SECURITY-AUDIT-2026-09-16.md +647 -0
  15. package/docs/SECURITY.md +58 -22
  16. package/docs/TROUBLESHOOTING.md +44 -5
  17. package/docs/USER-GUIDE.md +505 -35
  18. package/package.json +4 -2
  19. package/public/404.html +1 -1
  20. package/public/css/app.css +393 -82
  21. package/public/donate-qr.png +0 -0
  22. package/public/index.html +353 -109
  23. package/public/js/agents.js +228 -51
  24. package/public/js/app.js +131 -16
  25. package/public/js/blockanoid.js +15 -7
  26. package/public/js/blockout.js +15 -7
  27. package/public/js/blockscene3d.js +230 -38
  28. package/public/js/charts.js +21 -21
  29. package/public/js/depthchart.js +31 -27
  30. package/public/js/details3d.js +1481 -73
  31. package/public/js/doom.js +31 -0
  32. package/public/js/dosaudio.js +48 -0
  33. package/public/js/dosgame.js +389 -0
  34. package/public/js/dosio.js +186 -0
  35. package/public/js/dospc.js +1353 -0
  36. package/public/js/dosworker.js +196 -0
  37. package/public/js/explorer.js +7 -1
  38. package/public/js/livingsky.js +494 -0
  39. package/public/js/login.js +8 -2
  40. package/public/js/markets.js +46 -8
  41. package/public/js/mining.js +314 -36
  42. package/public/js/panels.js +41 -28
  43. package/public/js/pricechart.js +14 -13
  44. package/public/js/quake.js +20 -0
  45. package/public/js/safenext.js +14 -0
  46. package/public/js/scorched.js +1051 -0
  47. package/public/js/scorchedai.js +227 -0
  48. package/public/js/scorchedair.js +286 -0
  49. package/public/js/scorchedfx.js +376 -0
  50. package/public/js/scorchedshop.js +105 -0
  51. package/public/js/scorchedwind.js +69 -0
  52. package/public/js/scorchedyard.js +1338 -0
  53. package/public/js/settings.js +368 -100
  54. package/public/js/soundcard.js +459 -0
  55. package/public/js/tetrust.js +15 -6
  56. package/public/js/tetsound.js +35 -5
  57. package/public/js/theme.js +235 -0
  58. package/public/js/wolf3d.js +22 -0
  59. package/public/js/x86.js +1978 -0
  60. package/scripts/check.js +46 -0
  61. package/scripts/donate-qr.py +12 -9
  62. package/scripts/dos-bench.js +56 -0
  63. package/scripts/index-build.js +9 -2
  64. package/scripts/pool-map.js +152 -36
  65. package/scripts/setup.js +142 -22
  66. package/scripts/shots.mjs +27 -0
  67. package/scripts/smoke.sh +7 -6
  68. package/scripts/tls.js +31 -0
  69. package/scripts/ui.js +4 -2
  70. package/server/auth/sessions.js +33 -13
  71. package/server/chain/blockfile.js +64 -5
  72. package/server/chain/index/build.js +451 -58
  73. package/server/chain/index/heights.js +29 -3
  74. package/server/chain/index/live.js +13 -7
  75. package/server/chain/index/rows.js +6 -1
  76. package/server/chain/index/store.js +28 -5
  77. package/server/chain/index/worker.js +23 -11
  78. package/server/collect/logparse.js +65 -18
  79. package/server/collect/markets.js +76 -7
  80. package/server/collect/mining.js +32 -0
  81. package/server/collect/monitor.js +54 -12
  82. package/server/collect/network.js +305 -0
  83. package/server/config.js +53 -22
  84. package/server/http/api.js +119 -18
  85. package/server/http/games.js +77 -0
  86. package/server/http/server.js +30 -5
  87. package/server/http/sse.js +53 -7
  88. package/server/main.js +66 -11
  89. package/server/rpc/allowlist.js +26 -0
  90. package/server/rpc/client.js +30 -2
  91. package/server/store/audit.js +6 -1
  92. package/server/store/history.js +19 -3
  93. package/server/store/ledger.js +15 -4
  94. package/server/tls/selfsigned.js +160 -0
  95. package/systemd/blockyard.service +41 -8
  96. package/docs/PRIVATE-LEADERBOARD.md +0 -230
  97. 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: open by default, accounts on request](#access-open-by-default-accounts-on-request)
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 has no
27
- wallet access.
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: open by default, accounts on request
30
+ ## Access: sign-in by default, open on request
30
31
 
31
- With the default `auth.enabled: false`, anyone who can reach the port reads the monitor
32
- with the fixed role `viewer`:
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
- Set `BLOCKYARD_AUTH=1` (or `"auth": { "enabled": true }`) to require sign-in. Roles:
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 unaffected: it can already reach the port, and this guards against what a
73
- *browser* can be made to do on someone's behalf.
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, key-material, spending, peer-control,
84
- chain-mutating and very heavy methods are refused by name — including `getnewaddress` and
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 built in: name a certificate and key and **every** listener serves HTTPS. A
128
- half-configured pair or an expired certificate stops the start-up; a certificate close to
129
- expiry starts with a warning, and the log prints its fingerprint.
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 is **on by default**, so the landing page reaches out 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 |
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
- Nothing is fetched when nobody is looking. Your machine's public address is visible to those
157
- exchanges when a request is made, as with any web request. To make **no** outbound connections
158
- at all, set `BLOCKYARD_MARKETS=0` (or `"markets": { "enabled": false }`): the Markets and Kiosk
159
- tabs then say that market data is off, and the explorer shows no dollar figures.
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.
@@ -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: there is no
167
- resume, so the build starts over from the first file.
168
- - **It started over** — stopping BlockYard stops the build, and the next start begins it again
169
- from scratch. Leave it running until the notification says it is done.
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 data is off"** — `BLOCKYARD_MARKETS=0` or `markets.enabled: false` is set.
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