blockyard 0.0.9 → 0.1.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.
Files changed (58) hide show
  1. package/CHANGELOG.md +251 -1
  2. package/README.md +42 -23
  3. package/bin/blockyard.js +2 -1
  4. package/docs/API.md +16 -14
  5. package/docs/ARCHITECTURE.md +92 -5
  6. package/docs/CONFIGURATION.md +33 -26
  7. package/docs/GETTING-STARTED.md +5 -2
  8. package/docs/INSTALL.md +90 -33
  9. package/docs/MEASUREMENTS.md +147 -0
  10. package/docs/SECURITY.md +32 -15
  11. package/docs/TROUBLESHOOTING.md +35 -1
  12. package/docs/USER-GUIDE.md +266 -26
  13. package/package.json +1 -1
  14. package/public/404.html +1 -1
  15. package/public/css/app.css +306 -82
  16. package/public/donate-qr.png +0 -0
  17. package/public/index.html +295 -103
  18. package/public/js/agents.js +228 -51
  19. package/public/js/app.js +82 -8
  20. package/public/js/blockscene3d.js +179 -27
  21. package/public/js/charts.js +21 -21
  22. package/public/js/depthchart.js +31 -27
  23. package/public/js/details3d.js +1456 -71
  24. package/public/js/doom.js +31 -0
  25. package/public/js/dosaudio.js +48 -0
  26. package/public/js/dosgame.js +389 -0
  27. package/public/js/dosio.js +186 -0
  28. package/public/js/dospc.js +1353 -0
  29. package/public/js/dosworker.js +196 -0
  30. package/public/js/login.js +5 -0
  31. package/public/js/markets.js +46 -8
  32. package/public/js/mining.js +310 -32
  33. package/public/js/panels.js +14 -10
  34. package/public/js/pricechart.js +14 -13
  35. package/public/js/quake.js +20 -0
  36. package/public/js/settings.js +103 -21
  37. package/public/js/soundcard.js +459 -0
  38. package/public/js/theme.js +235 -0
  39. package/public/js/wolf3d.js +22 -0
  40. package/public/js/x86.js +1978 -0
  41. package/scripts/donate-qr.py +12 -9
  42. package/scripts/dos-bench.js +56 -0
  43. package/scripts/setup.js +34 -12
  44. package/scripts/shots.mjs +6 -0
  45. package/scripts/smoke.sh +1 -1
  46. package/scripts/tls.js +31 -0
  47. package/server/chain/index/build.js +21 -4
  48. package/server/collect/monitor.js +30 -1
  49. package/server/collect/network.js +295 -0
  50. package/server/config.js +46 -22
  51. package/server/http/api.js +49 -5
  52. package/server/http/games.js +77 -0
  53. package/server/http/server.js +8 -0
  54. package/server/main.js +53 -8
  55. package/server/tls/selfsigned.js +160 -0
  56. package/systemd/blockyard.service +7 -5
  57. package/docs/PRIVATE-LEADERBOARD.md +0 -230
  58. package/docs/STATE-2026-09-09.md +0 -200
@@ -1252,3 +1252,150 @@ showing STALLED. Block intervals are close to exponential with a 10-minute mean,
1252
1252
  minutes or more has probability e^-4, about 1.8% -- once in fifty blocks, a few times a day. Stalled
1253
1253
  now means a connected peer reports a higher tip (`getpeerinfo` `synced_headers`); peers agreeing on
1254
1254
  the tip is a long gap and synced; no peer height at all waits two hours.
1255
+
1256
+ ## 32. The DOOM Diversion's emulated PC (2026-09-15)
1257
+
1258
+ Not the node: the i386 and the PC in `public/js/x86.js` and `dospc.js` running the shareware
1259
+ `DOOM.EXE` v1.9 (the Diversion). Taken on this box -- AMD Ryzen 9 9950X3D, Node v22.23.2, Chromium
1260
+ 152 (snap, headless). Reproduce the headless figures with `node scripts/dos-bench.js doom`.
1261
+
1262
+ **Speed of the interpreter**, 400 M instructions of DOOM's title and demos on a clock of 30 M
1263
+ instructions to the virtual second:
1264
+
1265
+ | build | instructions a second |
1266
+ |---|---|
1267
+ | first cut: unsigned values (`>>> 0`), flag operands in closure variables, a try/catch per instruction | 66-68 M |
1268
+ | every value an int32, lazy flags in an `Int32Array`, one try/catch around the loop, 32-bit fast paths | 95 M |
1269
+ | the same with the Sound Blaster and OPL3 attached, synthesising at 44.1 kHz per call of `tick` | 73 M |
1270
+ | the OPL's per-register work hoisted out of the sample loop | 82 M |
1271
+ | `tick` working in batches of at least 128 frames (it had been called every 2,000 instructions: a sample or two each) | **92 M** |
1272
+
1273
+ The try/catch alone was 14% of the profile, and the collector 1.5% from doubles boxed in closure
1274
+ variables; the rewrite was checked instruction by instruction against the first cut for 40 M
1275
+ instructions (identical except the start-up's environment read, which the first cut got wrong).
1276
+
1277
+ **In the browser**, the machine in a worker, sound on: **94-110 M instructions a second, 34-37
1278
+ frames a second** -- DOOM's own cap is 35. The game needs about a million instructions a frame of
1279
+ real work; the rest of each tic it spends in its own busy wait for the timer. The page's CSP forbids
1280
+ eval, so a JIT was never an option; a slower machine than this one has roughly a factor of two in
1281
+ hand before DOOM drops below 35.
1282
+
1283
+ **Start-up**: graphics mode after 8.0 M instructions without a sound card, 17.0 M with one (the
1284
+ DMX driver probes the DSP and the OPL); 20 pages flipped by 31 M.
1285
+
1286
+ **Correctness of the CPU, measured against the host's own**: a differential fuzzer ran random
1287
+ instructions natively (a C harness in 64-bit mode, 32-bit operands, register forms) and in `x86.js`,
1288
+ comparing all registers and every flag the instruction defines -- ALU rows, immediates, shifts and
1289
+ rotates with counts past the width, MUL/IMUL/DIV/IDIV including divide faults, BT/BTS/BTR/BTC,
1290
+ BSF/BSR, SHLD/SHRD, MOVZX/MOVSX, SETcc, BSWAP, XADD, CMPXCHG, SAHF/LAHF. Two seeds, 78,565
1291
+ instructions, 2,338 divide faults agreed, **zero mismatches**. The harness needs a C compiler, so it
1292
+ is not in `npm test`; `test/x86.test.js` keeps a case from each class it covered.
1293
+
1294
+ **The music is in tune**: over 10 s of the title music, 178 notes keyed on, 96 of them within
1295
+ 2.5 cents of equal temperament; the rest spread to +-50 cents, which is DMX's pitch bends.
1296
+
1297
+ ## 33. Quake on the same PC (2026-09-15)
1298
+
1299
+ `QUAKE.EXE` v1.06 (DJGPP, go32 stub) on the emulated PC, this box, Node v22.23.2. Reproduce with
1300
+ `node scripts/dos-bench.js quake`.
1301
+
1302
+ **Speed**: 77 M instructions a second headless with the Sound Blaster attached, 72-79 in a Chromium
1303
+ worker -- lower than DOOM's 90-105 because Quake's code is FPU-heavy (the x87 was 15% of the profile)
1304
+ and every memory operand adds a segment base (DJGPP's DS is at its memory block; `ea` was 13%).
1305
+ Moving the FPU stack to a Float64Array with typed-array operand conversion and one base addition
1306
+ when DS and SS share it took 74 to 76: the interpreter's dispatch is the rest.
1307
+
1308
+ **Frame rate**: `+timedemo demo1` on a clock of 74 M instructions to the virtual second -- the
1309
+ emulator's own speed -- reported **969 frames in 33.5 seconds, 28.9 fps**, about 2.6 M instructions a
1310
+ frame. In the browser, a new game on the start map drew 26 frames a second. Period hardware for
1311
+ comparison: a Pentium 90 ran the same demo at 320x200 at roughly that rate.
1312
+
1313
+ **Start-up**: graphics mode after 244 M instructions on a 30 M clock (Quake pages its 27 MB heap in
1314
+ and times its hardware), twenty screens drawn by 285 M; 2.8 s of wall time headless.
1315
+
1316
+ ## 34. The decoded-instruction cache, and Quake's view size (2026-09-15)
1317
+
1318
+ This box, Node v22.23.2, `node scripts/dos-bench.js doom 400` / `quake 1500` (M instructions a
1319
+ second, headless, Sound Blaster attached):
1320
+
1321
+ | build | DOOM | Quake |
1322
+ |---|---|---|
1323
+ | before (the interpreter, §32-33) | 90 | 77 |
1324
+ | a closure per decoded instruction | 68 | 69 |
1325
+ | int32 decodings in a typed array per page, one switch; a write drops the whole page | 83 | 90 |
1326
+ | ...a write clears only the instructions it overlaps (DOOM patches its span drawer each call) | 100 | 90 |
1327
+ | ...the FPU decoded too, and the loop keeps the current page between instructions | 107 | 104 |
1328
+ | ...no page check after a cached handler | **111** | **108** |
1329
+
1330
+ The page-drop row is the one to remember: 217,660 whole-page drops in 300 M instructions of DOOM,
1331
+ every one from `mov [eax],ebx` into the constants of its own span routine at 0x12bdaf.
1332
+
1333
+ **Quake's view size**, `+viewsize N +timedemo demo1` (969 frames), on a clock set to the emulator's
1334
+ speed:
1335
+
1336
+ | viewsize | before the cache (76 M clock) | after (105 M clock) |
1337
+ |---|---|---|
1338
+ | 100 | 29.7 fps | 40.9 fps |
1339
+ | 80 (the new default) | 32.5 | 44.7 |
1340
+ | 60 | 40.5 | -- |
1341
+
1342
+ In a Chromium worker: Quake 99-106 MIPS and 40-42 frames a second in a new game at `viewsize 80`
1343
+ (26 before), DOOM 133 MIPS at its 35 fps cap.
1344
+
1345
+ **Checked, not assumed**: both games lock-stepped against the uncached interpreter (DOOM 400 M, Quake
1346
+ 1.5 G instructions; registers and flags compared every 10,000; memory identical at the end), and the
1347
+ native fuzzer re-run through `run(1)` over 118k instructions with no mismatch.
1348
+
1349
+ ## 35. A second pass on the CPU: what paid and what did not (2026-09-15)
1350
+
1351
+ Asked for all five of: split the ALU routine, specialise the hot x87 forms, cheaper dispatch, dead
1352
+ flags, and a block copy of the frame into video memory. **Method, after the first readings misled:**
1353
+ each build against a copy of the previous one (`oracle4`), pinned to one core with `taskset`, best of
1354
+ three; unpinned runs on this shared box moved +-5% between identical runs. Quake throughput as M
1355
+ instructions a second over its timedemo, and as **frames a second of wall time** over 600 timedemo
1356
+ frames -- the second catches work a MIPS figure cannot, such as a `rep movsd` that is one instruction
1357
+ however many bytes it moves. DOOM on `-timedemo demo1` only: its normal MIPS depends on how much time
1358
+ lands in its cheap wait loop, which a change to the machine's slice size alone moved by 5%.
1359
+
1360
+ | build | Quake MIPS | Quake fps (wall) | DOOM timedemo MIPS |
1361
+ |---|---|---|---|
1362
+ | before (the cache, §34) | 101 | 37.7 | 103 |
1363
+ | ALU split per operation + hot x87 forms decoded to their own handlers | 133 | -- | -- |
1364
+ | + fused cmp/test+Jcc dispatch and "no flags" forms by flag liveness | 131 | -- | 110 |
1365
+ | the same without the look-ahead (fusion and no-flags off) | 133 | -- | 114.5 |
1366
+ | + the address formed inline in the loop instead of a call per handler | 141.5 | -- | 112 |
1367
+ | + aligned reads/writes inline in the hottest moves | 141 | 52.5 | 115 |
1368
+ | the same without the VGA block copy | 141 | 51 (noise) | -- |
1369
+
1370
+ **Kept**: the ALU split, the x87 handlers, the inline address and moves -- Quake's frames a second of
1371
+ wall time 37.7 -> 52.5 (+40%). **Removed**: the fused branches and the no-flags forms (nothing gained;
1372
+ the look-ahead re-ran on every re-decode of DOOM's self-patching drawer), and the block copy (within
1373
+ noise: the frame copy is 64,000 bytes against 140 M instructions a second).
1374
+
1375
+ The reason inlining mattered: `run()` is one function with 170-odd cases, V8's cumulative inlining
1376
+ budget runs out long before the helpers it calls, and every uninlined `eaOf` was a real call.
1377
+
1378
+ **In a Chromium worker** (new game, `viewsize 80`): Quake 115-118 MIPS and 39-49 frames a second on
1379
+ screen, once the worker looked for a finished frame every 50,000 instructions instead of once per
1380
+ 10 ms slice (two frame copies inside one slice had been showing as one). DOOM 139 MIPS.
1381
+
1382
+ Checked: DOOM 400 M and Quake 1.5 G instructions lock-stepped identical to `oracle4` (memory equal),
1383
+ and the native fuzzer through `run(1)`, 78k instructions, no mismatch.
1384
+
1385
+ ## 36. Wolfenstein 3D in real mode (2026-09-15)
1386
+
1387
+ `WOLF3D.EXE` v1.4 (LZEXE-packed, Borland C, real mode) on the emulated PC, this box, Node v22.23.2,
1388
+ pinned to one core. It unpacks itself and reaches the sign-on screen in 1.0 s of wall time (60 M
1389
+ instructions on a 20 M clock).
1390
+
1391
+ **Speed**: in a game, turning on the first map, **65 M instructions a second** headless (twice the same
1392
+ reading) and 68-69 in a Chromium worker. Real-mode code runs through the uncached `step()`, so this is
1393
+ the plain interpreter's speed; the game needs about **285 k instructions a frame**, so its 70 frames a
1394
+ second (the VGA's refresh, which it waits for) take 20 M a second -- a third of what is there, and the
1395
+ browser shows 70 frames a second.
1396
+
1397
+ **DOOM and Quake unchanged**: best of three against a copy of the previous build (`oracle5`), DOOM
1398
+ timedemo 93.3 vs 93.0 M a second, Quake 126.3 vs 127.7. Quake lock-stepped identical for 600 M
1399
+ instructions, memory equal. DOOM lock-steps identical to 175 M and then differs, by design: at 173 M it
1400
+ copies between VGA pages in write mode 1, which the previous build wrote as plain data, and reads the
1401
+ planes back.
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)
@@ -26,10 +26,19 @@ monitor leaking information about you to third parties (limited to the on-demand
26
26
  connections listed below, which you can turn off). The monitor does not hold keys and has no
27
27
  wallet access.
28
28
 
29
- ## Access: open by default, accounts on request
29
+ ## Access: sign-in by default, open on request
30
30
 
31
- With the default `auth.enabled: false`, anyone who can reach the port reads the monitor
32
- with the fixed role `viewer`:
31
+ **Out of the box the monitor listens on `127.0.0.1` only and requires sign-in.** The first
32
+ start creates an `admin` account and prints its password once (or takes it from
33
+ `BLOCKYARD_ADMIN_PASSWORD`). Reach it from another machine over an SSH tunnel
34
+ (`ssh -L 21000:127.0.0.1:21000 you@host`), or bind a LAN address with `BLOCKYARD_BIND` /
35
+ `server.hosts` once you have decided who may see it. These are the defaults since
36
+ 2026-09-15; 0.0.9 shipped bound to every interface with no sign-in, which the first outside
37
+ review rightly called out.
38
+
39
+ Open access is still available as a posture you choose: with `auth.enabled: false`
40
+ (`BLOCKYARD_AUTH=0`), anyone who can reach the port reads the monitor with the fixed role
41
+ `viewer`:
33
42
 
34
43
  | open to anyone who can reach the port | still closed |
35
44
  |---|---|
@@ -42,7 +51,7 @@ The `viewer` ceiling cannot be raised by configuration or by any credential whil
42
51
  are off. The start-up log states which addresses are readable and how to close them, so
43
52
  "anyone on the LAN can read your node" is never a surprise.
44
53
 
45
- Set `BLOCKYARD_AUTH=1` (or `"auth": { "enabled": true }`) to require sign-in. Roles:
54
+ With sign-in on (the default; `BLOCKYARD_AUTH=1` restores it after an override), roles:
46
55
 
47
56
  | role | may |
48
57
  |---|---|
@@ -124,9 +133,15 @@ Every call and every refusal is appended to the audit trail.
124
133
 
125
134
  ## Transport security
126
135
 
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.
136
+ - HTTPS is the default on **every** listener. With no certificate named, the server makes its
137
+ own self-signed one on first start (`server/tls/selfsigned.js`, under `<data>/tls/`, the key
138
+ mode 600, ECDSA P-256), naming the addresses it is reached on, and remakes it when it nears
139
+ expiry or stops naming a bound address; the log prints its fingerprint. A certificate of your
140
+ own replaces it (`BLOCKYARD_TLS_CERT`/`_KEY`); a half-configured pair or an expired
141
+ certificate stops the start-up. `BLOCKYARD_TLS=0` is plain HTTP, for a proxy in front.
142
+ - A self-signed certificate proves nothing about who you are talking to the first time; it
143
+ does encrypt the session and pins the fingerprint after that. Compare the fingerprint the
144
+ log prints with the one the browser shows before trusting it on a network you do not own.
130
145
  - Over HTTPS the session cookie is `Secure` and `Strict-Transport-Security` is sent with a
131
146
  two-day lifetime, without `includeSubDomains` or `preload` — a LAN address can be reissued,
132
147
  and HSTS cannot be withdrawn once a browser has it.
@@ -150,13 +165,15 @@ Everything in the monitor talks only to your node, **except**:
150
165
 
151
166
  | when | to | what is sent |
152
167
  |---|---|---|
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 |
168
+ | 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 |
169
+ | 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
170
 
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.
171
+ **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
172
+ Markets and Kiosk tabs say so and the explorer shows no dollar figures. The switch is a Display
173
+ setting shared by every screen, so anyone who can change settings on this monitor can turn it
174
+ on; `BLOCKYARD_MARKETS=0` (or `"markets": { "enabled": false }`) removes the feed from the
175
+ 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
176
+ when a request is made, as with any web request.
160
177
 
161
178
  The monitor sends no telemetry, checks for no updates, and phones home to no one.
162
179
 
@@ -190,6 +207,6 @@ and errors; they never contain passwords, session tokens or RPC credentials.
190
207
  - Turn accounts on if anyone who can reach the port should not see your node.
191
208
  - Use HTTPS, a reverse proxy, or an SSH tunnel on untrusted networks.
192
209
  - 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.
210
+ - 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
211
  - Keep `config/local.json` and `data/` readable only by the service account.
195
212
  - 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,6 +163,14 @@ 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 starts over; it is not resumable. 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
@@ -171,9 +183,25 @@ so far and an ETA; the address page repeats it. Things it says, and what they me
171
183
  restart (the server builds again), or run `node scripts/index-build.js --out <dir>` by hand.
172
184
  A pruned node, unreadable block files and a full disk are the usual causes.
173
185
 
186
+ ## "getrawmempool verbose dropped as stale" in Events, and the mempool panels look old
187
+
188
+ The monitor keeps one RPC request in flight and serves the live polls first; the full-pool poll is
189
+ the lowest priority, so when the node's RPC is slow it waits behind them and, past its freshness
190
+ budget, is dropped rather than shown as current. A streak of drops is one warning event when it
191
+ starts, a counter on **Node & RPC → data quality** while it lasts, and one event when the poll
192
+ answers again with the count and the span. The Mempool, Block space and Mining panels show their
193
+ last reading meanwhile and say how old it is.
194
+
195
+ The cause is the node, not the monitor: look for what else is asking it. On 2026-09-15 an Umbrel
196
+ node answered in seconds for thirteen hours while another BlockYard built its address index
197
+ against it over the LAN, and the drops stopped the moment that build finished. A remote index
198
+ build, a wallet rescan, `gettxoutsetinfo` from another tool, or an initial block download all
199
+ show the same way.
200
+
174
201
  ## Markets or Kiosk show no prices
175
202
 
176
- - **"market data is off"** — `BLOCKYARD_MARKETS=0` or `markets.enabled: false` is set.
203
+ - **"market polling is off"** — the default. Tick **Display settings → Markets & Price → Enable market polling**.
204
+ - **"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
205
  - **"asking the exchanges…" for a long time** — the server cannot reach the exchanges. Test
178
206
  from the server: `curl -sI https://api.exchange.coinbase.com/products/BTC-USD/ticker`. Check
179
207
  outbound firewall rules and DNS.
@@ -289,6 +317,12 @@ browser in kiosk mode pointed at `http://<host>:21000/#kiosk`.
289
317
  - **"too many attempts"** — the lockout lasts 10 minutes per username and per address.
290
318
  - **Sign-in does not stick** — over plain HTTP, make sure `BLOCKYARD_SECURE_COOKIE` is not set
291
319
  (a `Secure` cookie is never sent over HTTP). Behind a TLS proxy, set it.
320
+ - **The browser warns about the certificate** — expected once per address: the monitor's own
321
+ certificate is self-signed. Compare the fingerprint in the start-up log with the browser's,
322
+ then accept it. To be rid of the warning, name a certificate of your own (INSTALL §9) or put
323
+ a proxy with a real one in front (§10).
324
+ - **`https://` says the connection was reset, or `http://` shows nothing** — the port speaks
325
+ one or the other: HTTPS unless `BLOCKYARD_TLS=0`. Use the scheme the start-up log prints.
292
326
 
293
327
  ## Tests fail on a fresh clone
294
328