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/API.md
CHANGED
|
@@ -89,7 +89,7 @@ Other statuses: `204` for `OPTIONS` on any path (`Allow: GET,POST,DELETE,HEAD`).
|
|
|
89
89
|
|
|
90
90
|
Each route in the table has one of three auth levels:
|
|
91
91
|
|
|
92
|
-
| Level | Accounts off (
|
|
92
|
+
| Level | Accounts off (`BLOCKYARD_AUTH=0`) | Accounts on (the default) |
|
|
93
93
|
|---|---|---|
|
|
94
94
|
| `none` | Open. | Open. No session needed. |
|
|
95
95
|
| `any` | Served as the built-in anonymous user (`role: "viewer"`). | Any valid, non-disabled session. Without one: `401`. |
|
|
@@ -97,14 +97,14 @@ Each route in the table has one of three auth levels:
|
|
|
97
97
|
|
|
98
98
|
The roles are `viewer` < `operator` < `admin`. No route in the table requires `viewer` or `operator` by itself. Those two roles matter in two places only: each node action names a minimum role (section 14), and `POST /api/password` needs `admin` to change *another* user's password.
|
|
99
99
|
|
|
100
|
-
**Open mode (
|
|
100
|
+
**Open mode (`auth.enabled=false`, a choice; the default is accounts on).** No sign-in. Anyone who can reach the port can read everything (state, charts, events, peers, mempool, explorer, markets, the read-only RPC console) as `viewer`. The ceiling is fixed and nothing can raise it. As a result:
|
|
101
101
|
|
|
102
102
|
- user administration, `/api/audit` and password changes are closed;
|
|
103
103
|
- node writes (`/api/action`) are refused unless `BLOCKYARD_ALLOW_WRITES_WITHOUT_AUTH=1` was set deliberately;
|
|
104
104
|
- `/api/login` answers `403 accounts_disabled`;
|
|
105
105
|
- `GET /login` redirects (`302`) to `/`.
|
|
106
106
|
|
|
107
|
-
**Accounts mode (
|
|
107
|
+
**Accounts mode (the default).** Sign-in, roles, sessions, CSRF protection and a per-user audit trail. On first start with no users, an `admin` account is created. Its password comes from `BLOCKYARD_ADMIN_PASSWORD`, or is generated and printed once to the server log.
|
|
108
108
|
|
|
109
109
|
### Session cookie
|
|
110
110
|
|
|
@@ -224,8 +224,8 @@ Auth `none`, not rate limited. Built for uptime probes. `ok` is `true` when at l
|
|
|
224
224
|
{
|
|
225
225
|
"ok": true,
|
|
226
226
|
"degraded": [],
|
|
227
|
-
"version": "0.0
|
|
228
|
-
"build": "0.0
|
|
227
|
+
"version": "0.1.0",
|
|
228
|
+
"build": "0.1.0-fd620adc52",
|
|
229
229
|
"scheme": "http",
|
|
230
230
|
"tls": false,
|
|
231
231
|
"uptimeSec": 1519,
|
|
@@ -242,7 +242,7 @@ Auth `none`, not rate limited. Built for uptime probes. `ok` is `true` when at l
|
|
|
242
242
|
What the About page shows: the monitor's version and live build, and the **shape** of the machine it runs on.
|
|
243
243
|
|
|
244
244
|
```json
|
|
245
|
-
{ "version": "0.0
|
|
245
|
+
{ "version": "0.1.0", "build": "0.1.0-a6ecedff3c", "platform": "linux", "release": "7.0.0-31-generic",
|
|
246
246
|
"arch": "x64", "cpus": 32, "cpuModel": "AMD Ryzen 9 9950X3D 16-Core Processor",
|
|
247
247
|
"totalMemGb": 132.3, "node": "v22.23.2", "uptimeSec": 2355 }
|
|
248
248
|
```
|
|
@@ -261,9 +261,9 @@ Auth `none`. Answers "is the code in my tab the code on disk?" The build id is `
|
|
|
261
261
|
|
|
262
262
|
```json
|
|
263
263
|
{
|
|
264
|
-
"version": "0.0
|
|
265
|
-
"build": "0.0
|
|
266
|
-
"bootBuild": "0.0
|
|
264
|
+
"version": "0.1.0",
|
|
265
|
+
"build": "0.1.0-5223f98d84",
|
|
266
|
+
"bootBuild": "0.1.0-fd620adc52",
|
|
267
267
|
"matchesClient": null,
|
|
268
268
|
"scheme": "http",
|
|
269
269
|
"tls": false,
|
|
@@ -295,6 +295,7 @@ Top-level keys:
|
|
|
295
295
|
```
|
|
296
296
|
id label color online chain ibd tip sync progress warnings difficulty hashrateEstEh hashrateNote
|
|
297
297
|
avgBlockGapSec sizeOnDisk pruned chainwork uptimeSec network mempool peers net attribution blocks
|
|
298
|
+
network (the Mining tab's network row: rewards over 144 blocks, adjustment, halving, adjustments[], hashrate { networkHashPs, series[] }, pools { blocks, luckPct, pools[], filled, todo }; collect/network.js)
|
|
298
299
|
fees mining utxo chaintxstats indexes tips deployments rpcInfo log health series app user seq
|
|
299
300
|
```
|
|
300
301
|
|
|
@@ -331,7 +332,7 @@ Trimmed example:
|
|
|
331
332
|
"health": { "rpc": { "online": true, "...": "..." }, "cadence": { "fast": { "configuredMs": 4000,
|
|
332
333
|
"effectiveMs": 4000, "stretched": false, "lastRunMs": 120 } }, "quality": [] },
|
|
333
334
|
"series": { "mempool": { "hour": [ { "t": 1789154820000, "v": 18780 } ], "...": "..." } },
|
|
334
|
-
"app": { "version": "0.0
|
|
335
|
+
"app": { "version": "0.1.0", "build": "0.1.0-fd620adc52", "scheme": "http", "uptimeSec": 1536,
|
|
335
336
|
"sseClients": 5, "self": { "rssMb": 175.6, "heapMb": 42.8, "cpuPct": 2.71 }, "serverTime": 1789155688322 },
|
|
336
337
|
"user": { "username": "anonymous", "role": "viewer", "id": "anonymous", "disabled": false, "lastLoginAt": null },
|
|
337
338
|
"seq": 406
|
|
@@ -1029,8 +1030,9 @@ To poll incrementally, keep `maxSeq` and pass it back as `since`. The filters ar
|
|
|
1029
1030
|
|
|
1030
1031
|
Exchange prices from the public REST APIs of Coinbase, Kraken, Bitstamp, Bitfinex and OKX. **This is the only outbound connection BlockYard makes that is not to the node.** It runs server-side, because the page's CSP allows `connect-src 'self'` only.
|
|
1031
1032
|
|
|
1032
|
-
- **
|
|
1033
|
-
- `BLOCKYARD_MARKETS=0` (or `markets.enabled=false`)
|
|
1033
|
+
- **Off by default.** Polling runs only while the Display setting **Markets & Price → Enable market polling** is on (`markets.polling` in the shared settings file, read per request). With it off, both endpoints answer `{ "ok": true, "enabled": false, "polling": false, "note": "market polling is off ..." }`, the call parks the feed, and the monitor makes no outbound connection but to the node.
|
|
1034
|
+
- `BLOCKYARD_MARKETS=0` (or `markets.enabled=false`) removes the feed altogether; both endpoints then answer `{ "ok": true, "enabled": false, "note": "market data is off on this server ..." }` whatever the setting.
|
|
1035
|
+
- **On demand.** With polling on, nothing is fetched until someone calls `/api/markets` or `/api/markets/depth`. Each call "touches" the feed. The dashboard's Overview makes that call by default (Display settings → Markets & Price → Price line on Overview), so a monitor with anyone on its landing page is polling. While touched, it polls tickers every 15 s, hourly candles every 5 min and order books every 30 s. It stops 10 minutes after the last touch.
|
|
1034
1036
|
- An exchange that fails keeps its last data and reports `error`. It is never dropped or zero-filled.
|
|
1035
1037
|
|
|
1036
1038
|
### `GET /api/markets`
|
|
@@ -1153,7 +1155,7 @@ The default is **deny**. The rules are applied in this order, and the first matc
|
|
|
1153
1155
|
Consequences worth knowing:
|
|
1154
1156
|
|
|
1155
1157
|
- The list is fixed in code, and no setting widens it.
|
|
1156
|
-
-
|
|
1158
|
+
- Wallet RPCs are refused by name, reads included (`getbalance`, `listunspent`, `listdescriptors`, `gethdkeys`, …): some of them return private keys, and the monitor has no use for a wallet.
|
|
1157
1159
|
- `createrawtransaction` is allowed: it only builds an unsigned transaction and changes nothing.
|
|
1158
1160
|
- `/api/config` → `allowlist` publishes a summary: the prefixes, the size of the deny list and the default decision.
|
|
1159
1161
|
|
|
@@ -1222,7 +1224,7 @@ If the node refuses, the answer is `200` with `{ "ok": false, "action", "method"
|
|
|
1222
1224
|
|
|
1223
1225
|
## 15. Accounts, sessions, users and audit
|
|
1224
1226
|
|
|
1225
|
-
These routes are meaningful only with
|
|
1227
|
+
These routes are meaningful only with accounts on (the default). In open mode they answer as noted.
|
|
1226
1228
|
|
|
1227
1229
|
The user object returned by these routes (`publicUser`):
|
|
1228
1230
|
|
|
@@ -1378,7 +1380,7 @@ The monitor's own health: process, RPC client, log tail, stream clients and the
|
|
|
1378
1380
|
```json
|
|
1379
1381
|
{
|
|
1380
1382
|
"self": { "t": 1789155700000, "rssMb": 175.6, "heapMb": 42.8, "sseClients": 5, "usersActive": 0,
|
|
1381
|
-
"cpuPct": 2.71, "eventRate": 0, "build": "0.0
|
|
1383
|
+
"cpuPct": 2.71, "eventRate": 0, "build": "0.1.0-fd620adc52" },
|
|
1382
1384
|
"nodes": [
|
|
1383
1385
|
{ "id": "main",
|
|
1384
1386
|
"rpc": { "nodeId": "main", "url": "http://127.0.0.1:8332", "cookieSource": "...", "online": true,
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -192,8 +192,9 @@ it directly; running the file as a script calls it and prints the banner.
|
|
|
192
192
|
missing is skipped with a logged reason rather than kept as a permanently
|
|
193
193
|
offline panel. `wireMonitor()` connects each monitor's events to the SSE hub
|
|
194
194
|
and coalesces pushes to one snapshot per second.
|
|
195
|
-
8. `MarketFeed` is created if `markets.enabled` is set. It stays idle until the
|
|
196
|
-
|
|
195
|
+
8. `MarketFeed` is created if `markets.enabled` is set. It stays idle until the Markets
|
|
196
|
+
API is requested with the **Enable market polling** setting on (off by default; read from
|
|
197
|
+
the shared settings file per request, `marketsPollingOn` in `http/api.js`).
|
|
197
198
|
9. One HTTP(S) server is created per bound address, all sharing the same `app`.
|
|
198
199
|
An address missing at boot is skipped with a warning. Boot is fatal only when
|
|
199
200
|
none of the configured addresses exist.
|
|
@@ -211,8 +212,8 @@ it directly; running the file as a script calls it and prints the banner.
|
|
|
211
212
|
`address-index-building` quality flag, `index` events at start, finish and
|
|
212
213
|
failure, and the follower started on completion (section 2.8).
|
|
213
214
|
`app.shutdown()` stops timers, closes streams, stops monitors, saves history
|
|
214
|
-
and sessions, and closes the listeners. A build in flight
|
|
215
|
-
the next start
|
|
215
|
+
and sessions, and closes the listeners. A build in flight stops with the
|
|
216
|
+
process and resumes on the next start from its journal (section 2.8).
|
|
216
217
|
|
|
217
218
|
`boot({ log })` and `loadConfig({ ifaces, now })` are **seams**: tests inject a
|
|
218
219
|
logger, a fake interface list, or a fake clock instead of intercepting
|
|
@@ -346,7 +347,9 @@ Bitstamp, Bitfinex and OKX.
|
|
|
346
347
|
with its error, never zero-filled. Tickers older than three intervals are marked
|
|
347
348
|
`stale`.
|
|
348
349
|
|
|
349
|
-
|
|
350
|
+
Polling is off by default — the **Enable market polling** Display setting turns it on, and a
|
|
351
|
+
request with it off parks the feed. `BLOCKYARD_MARKETS=0` (or `markets.enabled: false`)
|
|
352
|
+
removes the feed altogether.
|
|
350
353
|
|
|
351
354
|
### 2.4 The RPC lane (`server/rpc/client.js`)
|
|
352
355
|
|
|
@@ -523,11 +526,12 @@ flowchart LR
|
|
|
523
526
|
|
|
524
527
|
### 2.7 Auth (`server/auth/*`)
|
|
525
528
|
|
|
526
|
-
- **
|
|
529
|
+
- **Sign-in by default** (since 2026-09-15; it shipped open in 0.0.9), bound to
|
|
530
|
+
`127.0.0.1`. With `auth.enabled: false` (`BLOCKYARD_AUTH=0`), every request is
|
|
527
531
|
served as a frozen anonymous user with role `viewer`. That ceiling is hardcoded,
|
|
528
532
|
not configurable. Admin routes return 403 in both modes, CSRF is not needed
|
|
529
533
|
because there is no cookie to ride, and rate limits apply per client address.
|
|
530
|
-
- **Accounts
|
|
534
|
+
- **Accounts** (the default; `BLOCKYARD_AUTH=1` restores them after an override):
|
|
531
535
|
- scrypt password hashes (upgraded to current parameters on login)
|
|
532
536
|
- session tokens that are stored hashed, with idle and absolute TTLs
|
|
533
537
|
- double-submit CSRF (the token in a header or body is compared with the
|
|
@@ -573,7 +577,24 @@ files. The numbers are in `docs/MEASUREMENTS.md` §28-30 and the history in
|
|
|
573
577
|
bucket files; a check that every height is indexed exactly once, or the build
|
|
574
578
|
stops rather than publish a hole; each bucket sorted into `seg-XX.rows` plus a
|
|
575
579
|
sparse `seg-XX.idx` (one key per 4,096 rows); and a manifest written last, so an
|
|
576
|
-
index without one is unfinished.
|
|
580
|
+
index without one is unfinished. **An interrupted build resumes** from
|
|
581
|
+
`build-journal.json`, written beside the output with a temporary name, fsync and
|
|
582
|
+
rename, and carrying a SHA-256 of its own body. The scan resumes per block file:
|
|
583
|
+
a checkpoint (at most once a minute, and when the scan ends or fails in an orderly
|
|
584
|
+
way) fsyncs the buckets written since the last one and records the files
|
|
585
|
+
finished, each bucket's length and running CRC-32, the heights seen and the
|
|
586
|
+
counters; a resume cuts every bucket back to its journaled length, so rows from a
|
|
587
|
+
half-appended file are gone, and scans that file again. The sort resumes per
|
|
588
|
+
bucket: the worker checks the input against the journaled length and CRC, writes
|
|
589
|
+
the segment with fsync and rename, the journal records it, and only then is the
|
|
590
|
+
input removed. The journal names the journal version, index format, row and
|
|
591
|
+
block sizes, chain, tip height and hash and the `--files` selection; a resume
|
|
592
|
+
works to the journal's tip, not the node's newer one, so the result is byte for
|
|
593
|
+
byte the index an uninterrupted build would have written. A journal that is torn,
|
|
594
|
+
of another format or selection, whose tip hash the node's active chain no longer
|
|
595
|
+
has, or whose files on disk disagree with it is discarded with the reason logged,
|
|
596
|
+
and the build starts over. The manifest is still last; the journal is removed
|
|
597
|
+
after it. Measured on the whole chain: 29 min 45 s on 16
|
|
577
598
|
workers, 5.89 billion rows, 123.7 GB (§30). The build reads ~880 GB and writes
|
|
578
599
|
~120 GB, so `--out` should be a different device from the block files.
|
|
579
600
|
- **Lookups** (`chain/index/store.js`, `IndexStore`). The sparse keys of the 256
|
|
@@ -603,8 +624,8 @@ files. The numbers are in `docs/MEASUREMENTS.md` §28-30 and the history in
|
|
|
603
624
|
the phase's own rate, `paused while the node's RPC is slow`), refreshed at most
|
|
604
625
|
every 5 s; `index` events mark start, finish and failure; on finish the follower
|
|
605
626
|
starts and address pages go live with no restart; failure leaves
|
|
606
|
-
`address-index-build-failed` naming the command to run by hand.
|
|
607
|
-
|
|
627
|
+
`address-index-build-failed` naming the command to run by hand. A build stopped with the server resumes next time; the flag's ETA is
|
|
628
|
+
computed from what this run has done (`from` in the progress), not from zero.
|
|
608
629
|
- **Following the chain** (`chain/index/live.js`, `LiveIndex`). The base is
|
|
609
630
|
immutable and covers the chain to the block it was built at. The follower polls
|
|
610
631
|
the node's tip, rolls the tail back to the fork if a block it holds is no longer
|
|
@@ -659,6 +680,7 @@ loaded from the same origin. There is no build step and no framework.
|
|
|
659
680
|
| `settings.js` | display settings: `DEFAULTS`, the `PANEL` rows of the settings dialog, `normalise()`, and the option builders (`spaceOptions`, `enabledEffects`, ...) the boards read; stored on the server (`/api/settings`) with a `localStorage` copy |
|
|
660
681
|
| `about.js` | the About page (version, system and node info) |
|
|
661
682
|
| `tetris.js` / `tetrust.js`, `breakout.js` / `blockout.js`, `arkanoid.js` / `blockanoid.js`, `tetsound.js` | the Diversions: pure game rules in the first file of each pair, the tab drawn on the 3D engine in the second, and Tetrust's sound |
|
|
683
|
+
| `x86.js`, `dospc.js`, `soundcard.js`, `dosworker.js`, `dosaudio.js`, `dosio.js`, `dosgame.js`, `doom.js`, `quake.js` | the DOS Diversions: an i386 interpreter, the PC around it (DOS/4GW for DOOM, the go32 stub and CWSDPMI for Quake), a Sound Blaster Pro 2 with an OPL3, the worker the machine runs in, the AudioWorklet it plays through, the pure keyboard/config/text-mode helpers, the shared tab, and each game's own few lines (section 3.4) |
|
|
662
684
|
| `fmt.js` | formatters: decimal units (as the node prints them), `–` for anything absent |
|
|
663
685
|
| `login.js` | the login page (a separate file because of the CSP) |
|
|
664
686
|
|
|
@@ -790,6 +812,102 @@ What this means for front-end code:
|
|
|
790
812
|
|
|
791
813
|
---
|
|
792
814
|
|
|
815
|
+
### 3.4 The DOS Diversions: Wolfenstein 3D, DOOM and Quake
|
|
816
|
+
|
|
817
|
+
The shareware `WOLF3D.EXE` v1.4, `DOOM.EXE` v1.9 and `QUAKE.EXE` v1.06 run unmodified on a PC emulated in
|
|
818
|
+
the browser.
|
|
819
|
+
Nothing is ported and no dependency is used; every layer is this repository's own:
|
|
820
|
+
|
|
821
|
+
```
|
|
822
|
+
wolf3d.js / doom.js / quake.js -> dosgame.js (page) --scancodes, mouse, run/pause--> dosworker.js (Worker)
|
|
823
|
+
^ |
|
|
824
|
+
| frames (320x200 indices + palette), text-mode cells, stats | createPC() dospc.js
|
|
825
|
+
+----------------------------------------------------------------------+ createCpu() x86.js
|
|
826
|
+
dosaudio.js (AudioWorklet) <--stereo PCM over a MessagePort-----------------+ soundcard.js (SB Pro 2 + OPL3)
|
|
827
|
+
```
|
|
828
|
+
|
|
829
|
+
- **`x86.js`** is a user-mode i386 with an x87, interpreted. No paging or rings: a DOS extender's
|
|
830
|
+
program runs in protected mode, and segment registers carry a base and a size. EIP is
|
|
831
|
+
kept linear and converted at the edges (a pushed return address, a loaded jump target) against
|
|
832
|
+
the code segment's base, ESP is an offset in SS: DOS/4GW's segments are all at 0, DJGPP's at the
|
|
833
|
+
program's memory block. A code segment whose descriptor is 16-bit decodes 16-bit (DJGPP's
|
|
834
|
+
start-up and exit run small 16-bit helpers in DOS memory). **Real mode** is a switch
|
|
835
|
+
(`cpu.realMode`) for Wolfenstein 3D: a segment's base is its value times sixteen, code is 16-bit,
|
|
836
|
+
the stack pointer is SP and wraps inside its segment, the string instructions count with SI, DI
|
|
837
|
+
and CX, and an interrupt pushes a 16-bit frame and goes through the vector table at 0:0. Real-mode
|
|
838
|
+
code runs through `step()` rather than the decoded cache: it needs about a third of the speed it
|
|
839
|
+
gets. The page's CSP forbids eval, so there
|
|
840
|
+
is no JIT; speed comes from keeping every value an int32 (a `>>> 0` above 2^31 is a double and,
|
|
841
|
+
in a closure variable, an allocation), lazy flags recorded in an `Int32Array`, one try/catch
|
|
842
|
+
around the loop rather than each instruction, and 32-bit fast paths for the instructions
|
|
843
|
+
compilers emit most. The FPU keeps its stack in a Float64Array and converts operands through
|
|
844
|
+
typed-array views. **Decoded instructions are cached**: each is decoded once into three int32s
|
|
845
|
+
(a handler number with its registers packed beside it, a displacement, an immediate) in an
|
|
846
|
+
Int32Array per 4 KB page, and `run()` dispatches on the handler number with one switch; the forms
|
|
847
|
+
it does not specialise run through `step()`. Not closures -- a closure per instruction made every
|
|
848
|
+
call megamorphic and ran slower than the interpreter. Writes into a page holding decoded code
|
|
849
|
+
clear only the instructions they overlap (DOOM and Quake both patch constants into their own span
|
|
850
|
+
drawers on every call), and the machine calls `cpu.invalidate()` after it writes memory directly.
|
|
851
|
+
The ALU operations have a handler each (their flag bookkeeping inline, not one shared routine
|
|
852
|
+
branching on the operation), so do the x87 forms Quake runs most, and the loop forms a memory
|
|
853
|
+
operand's address inline before the switch: the switch is too big for V8 to inline helpers into
|
|
854
|
+
every case, and those calls were most of what was left. About 141 million instructions a second on
|
|
855
|
+
Quake headless on one core and 115-118 in a Chromium worker; Quake's timedemo 52.5 fps of wall time
|
|
856
|
+
(MEASUREMENTS §32-35).
|
|
857
|
+
- **`dospc.js`** is the machine, and it plays whichever DOS extender the program was bound to.
|
|
858
|
+
`boot()` tells them apart by the file: an EXE with neither an LE nor a COFF image inside is a
|
|
859
|
+
plain DOS program, and `bootMZ` loads it as DOS did -- a PSP, the image after it with its segment
|
|
860
|
+
relocations applied, the rest of conventional memory its block, the vector table filled with
|
|
861
|
+
pointers at stubs the machine answers (a program that installs its own handler gets it called),
|
|
862
|
+
and DOS's allocator behind INT 21h 48h/49h/4Ah, which Wolfenstein 3D uses to shrink itself and
|
|
863
|
+
claim the rest. `loadLE` finds DOOM's LE executable inside the DOS/4GW
|
|
864
|
+
stub, loads it at +1 MB and applies its fixups; `parseCoff`/`bootCoff` find Quake's COFF image
|
|
865
|
+
behind the go32 stub and do what that stub leaves behind -- a memory block with the sections in
|
|
866
|
+
it, selectors based at it, a transfer buffer with the PSP right below it (DJGPP's libc finds the
|
|
867
|
+
PSP by subtracting 100h), and the "stubinfo" crt0 reads through FS. The extenders themselves never
|
|
868
|
+
run; this file answers INT 21h (files from an in-memory, case-insensitive directory tree, written
|
|
869
|
+
files handed to the host on close, and a real system file table, because DJGPP's `fstat` walks
|
|
870
|
+
it), INT 31h DPMI (descriptors with base and size, memory blocks, protected-mode vectors,
|
|
871
|
+
simulated real-mode interrupts), INT 10h/16h/33h, and the hardware a DOS game programs directly:
|
|
872
|
+
the 8259s, the 8254 (with the BIOS tick count kept in step with it, which DJGPP's `uclock` reads),
|
|
873
|
+
the keyboard controller, and a VGA with planar memory, unchained mode, write mode 1's latched copy
|
|
874
|
+
(Wolfenstein 3D and DOOM copy between pages with it) and CRTC page flipping (how DOOM and
|
|
875
|
+
Wolfenstein 3D draw) as well as the linear window (how Quake does). The clock is injected (`now()`): wall
|
|
876
|
+
time in the worker, instruction count in tests, so a headless boot is the same run every time.
|
|
877
|
+
- **`soundcard.js`** is a Sound Blaster Pro 2 at 220h/IRQ 7/DMA 1 — the DSP's command set and the
|
|
878
|
+
8237 DMA controller it pulls samples through — and an OPL3 modelled as operators with
|
|
879
|
+
documented envelope rates. `tick(t)` produces output for the machine time that passed and raises
|
|
880
|
+
the end-of-block interrupt from inside the same loop.
|
|
881
|
+
- **`dosworker.js`** takes a game's name, fetches its files (`dosio.js` `GAMES`), and runs the
|
|
882
|
+
machine in ~10 ms slices, yielding between them so input arrives. It sends a frame when the CRTC
|
|
883
|
+
start address or the palette changed, or once the linear window has been written and a slice has
|
|
884
|
+
passed without more writes (never half of Quake's copy). The frame buffer bounces between the
|
|
885
|
+
worker and the page so no frame allocates. Savegames and configs are kept in IndexedDB, one
|
|
886
|
+
database a game.
|
|
887
|
+
- **`dosgame.js`** is a game's tab: it draws, captures input, pauses when the tab is not on screen
|
|
888
|
+
(the worker's clock stops, so nothing moves), and uses a ScriptProcessor when `audioWorklet` is
|
|
889
|
+
unavailable — a plain-HTTP LAN address is not a secure context. `doom.js` and `quake.js` give it
|
|
890
|
+
names, key lists and switches, and so does `wolf3d.js`.
|
|
891
|
+
|
|
892
|
+
The game files are served from `games/<game>_dos/` by `server/http/games.js`
|
|
893
|
+
(`/games/<game>/<path>`: a game it names, at most one directory and 8.3 names of `.EXE`, `.WAD`,
|
|
894
|
+
`.PAK`, `.CFG` and `.WL1`, behind the session when accounts are on) rather than from `public/`, whose every
|
|
895
|
+
file feeds the build id.
|
|
896
|
+
|
|
897
|
+
**Provenance** (2026-09-16, audit I6). The files are the three shareware releases, kept byte for byte
|
|
898
|
+
(`.gitattributes` marks `games/**` binary so git never rewrites them), and are served to the browser,
|
|
899
|
+
never executed on the server:
|
|
900
|
+
|
|
901
|
+
| Directory | Release | Its own terms, as shipped |
|
|
902
|
+
|---|---|---|
|
|
903
|
+
| `games/wolf3d_dos/` | Wolfenstein 3D v1.4 shareware (Apogee Software, 1992) | none beyond `file_id.diz`: the release's shareware licence text is not bundled |
|
|
904
|
+
| `games/doom_dos/` | DOOM v1.9 shareware (id Software; `DOOM1.WAD`) | `README.TXT`, `ORDER.FRM`, the DOOM FAQ v6.666 |
|
|
905
|
+
| `games/quake_dos/` | Quake v1.06 shareware (id Software, 1 October 1996) | `LICINFO.TXT`, `SLICNSE.TXT`, `READV106.TXT` |
|
|
906
|
+
|
|
907
|
+
`games/SHA256SUMS` lists the SHA-256 of every tracked file under `games/` (`cd games && sha256sum -c
|
|
908
|
+
SHA256SUMS`), and `test/audit-2026-09-16-low-scripts.test.js` recomputes them, so a changed game file
|
|
909
|
+
is a failing test rather than an unnoticed one.
|
|
910
|
+
|
|
793
911
|
## 4. The 3D engine
|
|
794
912
|
|
|
795
913
|
The 3D viewer turns a set of transactions, or any caller-supplied tiles, into
|
package/docs/CONFIGURATION.md
CHANGED
|
@@ -95,18 +95,20 @@ Durations are in milliseconds unless the name says otherwise (`retentionHours`).
|
|
|
95
95
|
|
|
96
96
|
| key | default | meaning |
|
|
97
97
|
|---|---|---|
|
|
98
|
-
| `server.host` | `"
|
|
98
|
+
| `server.host` | `"127.0.0.1"` | Address to listen on — this machine only, by default. Kept for compatibility; `server.hosts` wins when both are set. |
|
|
99
99
|
| `server.hosts` | *(unset; falls back to `host`)* | Addresses to listen on: an array (`["192.0.2.10", "2001:db8::10"]`), or a single string with commas (`"192.0.2.10,198.51.100.7"`). One HTTP server is started per address, and all of them share sessions, rate limits and monitors. Entries must be address literals: IPv4, IPv6, `0.0.0.0`, `::`, or `localhost`. Hostnames are refused. See [Binding](#binding-to-specific-addresses). |
|
|
100
100
|
| `server.port` | `21000` | TCP port. It is the same port on every address. It must be an integer from 1 to 65535. |
|
|
101
101
|
| `server.allowCidrs` | `[]` | Client allowlist. Empty means every client that can reach the port is admitted. Otherwise only addresses inside one of the networks connect, and everyone else gets HTTP 403 (the reason goes to the server log). Entries are CIDRs or bare addresses (a bare address means `/32` or `/128`), IPv4 or IPv6, compared bit by bit. An entry that cannot be parsed stops the boot. |
|
|
102
102
|
| `server.trustProxy` | `false` | When `true`, the client address is the **first** entry of the `X-Forwarded-For` header instead of the socket's peer address. That address feeds the CIDR allowlist, the rate limits and the audit log. Turn it on only when a reverse proxy you control is the sole way in and it sets that header. Otherwise any client can pick its own address and walk past `allowCidrs`. |
|
|
103
|
-
| `server.tls.
|
|
103
|
+
| `server.tls.enabled` | `true` | HTTPS on every listener. With no `cert`/`key` named, the server makes its own self-signed certificate under `<data>/tls/` on first start (see INSTALL §9). `false` serves plain HTTP, for a reverse proxy that terminates TLS. |
|
|
104
|
+
| `server.tls.cert` | `null` | Path to a PEM certificate of your own, used instead of the made one. Set it together with `key`. |
|
|
104
105
|
| `server.tls.key` | `null` | Path to the PEM private key for `cert`. This file is secret. |
|
|
105
106
|
| `server.tls.hstsMs` | `172800000` (2 days) | `max-age` of the `Strict-Transport-Security` header. It is sent on TLS responses only. `0` turns it off. `includeSubDomains` and `preload` are never sent. |
|
|
106
107
|
|
|
107
108
|
When TLS is on, `auth.secureCookie` is forced to `true`. At startup the server logs
|
|
108
109
|
the certificate's SHA-256 fingerprint and whether it is self-signed. It also warns
|
|
109
|
-
when the certificate expires within 14 days
|
|
110
|
+
when the certificate expires within 14 days — and remakes its own, if it made it, at that
|
|
111
|
+
point or when the certificate no longer names a bound address.
|
|
110
112
|
|
|
111
113
|
### nodes
|
|
112
114
|
|
|
@@ -127,7 +129,7 @@ node gets its own charts and event stream.
|
|
|
127
129
|
| `logFile` | none | The node's log file, tailed only when `log.enabled` is `true`. With the log source off (the default) this field is ignored, and a line in the startup log says so. |
|
|
128
130
|
| `logStaleMs` | *(uses `log.staleMs`)* | Per-node override for how long the log may go without new bytes before the monitor reports it as silent. Use a smaller value for a node that is known to log often. |
|
|
129
131
|
| `addressIndex` | none | Directory of the address index, built from this node's own block files (`<datadir>/blocks`). With it, the explorer's address page shows history, balance, unspent outputs and per-transaction amounts; without it, Core cannot answer those and the page says so. If the directory holds no finished index (no `manifest.json`) when the server starts, **the server builds one in the background** (see the next two keys). One index serves every node on the same chain. The server keeps it current as blocks arrive (a follower per directory writes `live.log` and `layers/` inside it, so the directory must be writable by the service); the page says if it is behind or has stopped following. See `docs/MEASUREMENTS.md` §30. |
|
|
130
|
-
| `addressIndexBuild` | *(unset)* | `"manual"` keeps the server from building a missing address index on start. Otherwise a missing index is built inside the server on worker threads while every page keeps serving: the progress is the `address-index-building` quality flag on the Overview (phase, done of total, rows, time left, and whether it is paused), an event of kind `index` marks the start, the finish and a failure (the browser shows each as a notification), the follower starts the moment the build finishes so address pages go live with no restart, and a failure raises `address-index-build-failed` with the command to run by hand. The build is paced by the node's own RPC: it holds while the node is failing, its breaker is open or its average latency is above `rpc.slowLatencyMs`, and eases off above 40% of it. A build interrupted by a stop
|
|
132
|
+
| `addressIndexBuild` | *(unset)* | `"manual"` keeps the server from building a missing address index on start. Otherwise a missing index is built inside the server on worker threads while every page keeps serving: the progress is the `address-index-building` quality flag on the Overview (phase, done of total, rows, time left, and whether it is paused), an event of kind `index` marks the start, the finish and a failure (the browser shows each as a notification), the follower starts the moment the build finishes so address pages go live with no restart, and a failure raises `address-index-build-failed` with the command to run by hand. The build is paced by the node's own RPC: it holds while the node is failing, its breaker is open or its average latency is above `rpc.slowLatencyMs`, and eases off above 40% of it. A build interrupted by a stop resumes on the next start: a journal in the index directory records the block files already scanned and the buckets already sorted, so only the work it cannot prove finished is done again (at most about a minute of scanning), and the log says where it resumed. A journal for another tip that the node no longer has, another index format, or one that is damaged is thrown away with the reason in the log, and the build starts over. `npm run setup --build-later` writes this key. |
|
|
131
133
|
| `addressIndexWorkers` | half of a dedicated build's count, at most 4 | Worker threads for the background build. A dedicated build (`scripts/index-build.js`) uses `cpus − 4`, one per ~2.5 GB of memory, at most 16; the server takes half of that, at most 4, because the node shares the disk. **Use 1 on a spinning disk**: parallel readers seek against each other and against the node. The installer writes the number you give it. |
|
|
132
134
|
| `optional` | `false` | Marks a node whose absence is expected, such as a test or benchmark node. Its failures are logged at a lower severity, a missing datadir is reported at `info` instead of `warn`, and it does not count as a required node in `/api/health`. |
|
|
133
135
|
| `color` | `"#f7931a"` | Accent colour for this node in the UI. |
|
|
@@ -250,7 +252,7 @@ sessions, CSRF protection and a per-user audit trail.
|
|
|
250
252
|
|
|
251
253
|
| key | default | meaning |
|
|
252
254
|
|---|---|---|
|
|
253
|
-
| `auth.enabled` | `
|
|
255
|
+
| `auth.enabled` | `true` | Accounts. `false` is open mode: anyone who can reach the port reads the monitor as a viewer. |
|
|
254
256
|
| `auth.dataDir` | same as `store.dir` | Where `users.json` and `sessions.json` live. |
|
|
255
257
|
| `auth.sessionTtlMs` | `259200000` (72 h) | Absolute session lifetime, counted from sign-in. |
|
|
256
258
|
| `auth.idleTtlMs` | `28800000` (8 h) | A session unused for this long expires. Until 2026-09-13 the two defaults were the other way round, so the idle check could never fire and a session was 8 h whatever you did. |
|
|
@@ -262,6 +264,7 @@ sessions, CSRF protection and a per-user audit trail.
|
|
|
262
264
|
| `auth.loginMaxAttempts` | `8` | Intended: failed logins per username before lockout. See [Known quirks](#known-quirks); the effective value is 8 whatever you set. |
|
|
263
265
|
| `auth.loginWindowMs` | `300000` (5 min) | Intended: window in which failed attempts are counted. See [Known quirks](#known-quirks); the effective value is 5 minutes. |
|
|
264
266
|
| `auth.lockoutMs` | `600000` (10 min) | How long a username stays locked after too many failures. |
|
|
267
|
+
| `auth.openNodeConfigFromNetwork` | `false` | With accounts off, the node connection form (test and save) answers only a caller on this machine's loopback address, and never behind `server.trustProxy`. A browser check cannot stop a script, and a script that can save the connection decides where the node's cookie is sent after the next restart. `true` lets any client that reaches the port use the form. With accounts on, the form needs an admin either way. |
|
|
265
268
|
| `auth.cookieName` | `"blockyard_sid"` | Name of the session cookie. |
|
|
266
269
|
| `auth.secureCookie` | `false` | Mark the session cookie `Secure`. Forced to `true` when `server.tls` is on. Set it yourself only when a TLS-terminating reverse proxy sits in front, because browsers never send a `Secure` cookie over plain HTTP. |
|
|
267
270
|
|
|
@@ -340,17 +343,18 @@ Available actions:
|
|
|
340
343
|
The Markets tab fetches public BTC/USD prices, hourly candles and order books from
|
|
341
344
|
five exchanges over HTTPS: Coinbase, Kraken, Bitstamp, Bitfinex and OKX (OKX quotes
|
|
342
345
|
BTC/USDT). The fetches run on the server, not in the browser. This is BlockYard's
|
|
343
|
-
only outbound connection other than the node
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
346
|
+
only outbound connection other than the node, and **polling is off by default**: a fresh
|
|
347
|
+
install makes no request to anyone but the node until someone ticks **Display settings → Markets & Price → Enable market polling** in the browser (a Display
|
|
348
|
+
setting, stored in `config/blockyard.json` and shared by every screen; no restart).
|
|
349
|
+
Exchanges then see this server's IP address and a User-Agent, nothing about the node. Polling starts when a browser asks for market data
|
|
350
|
+
-- the Markets and Kiosk tabs, and Overview's price line (Display settings → Markets &
|
|
351
|
+
Price → Price line on Overview) -- and stops `idleAfterMs` after the last request, so an
|
|
352
|
+
unwatched monitor makes no exchange traffic. With that line switched off, only Markets
|
|
353
|
+
and Kiosk start it. The exchange list is fixed in code.
|
|
350
354
|
|
|
351
355
|
| key | default | meaning |
|
|
352
356
|
|---|---|---|
|
|
353
|
-
| `markets.enabled` | `true` | `false` removes the feed entirely
|
|
357
|
+
| `markets.enabled` | `true` | `false` removes the feed entirely: no request is ever made, Markets and Kiosk say so, and the **Enable market polling** checkbox cannot turn it on. |
|
|
354
358
|
| `markets.tickerMs` | `15000` | Ticker (last, bid, ask, 24 h volume) refresh. |
|
|
355
359
|
| `markets.candleMs` | `300000` | Hourly candle refresh. |
|
|
356
360
|
| `markets.bookMs` | `30000` | Order book refresh, for the depth chart. |
|
|
@@ -378,12 +382,13 @@ Environment variables override `config/local.json`.
|
|
|
378
382
|
| variable | sets | type | default | meaning |
|
|
379
383
|
|---|---|---|---|---|
|
|
380
384
|
| `BLOCKYARD_CONFIG` | *(which file is read)* | path or `none` | `<repo>/config/local.json` | Configuration file to read. `none`/`off`/`no`/`-` reads no file. |
|
|
381
|
-
| `BLOCKYARD_BIND` | `server.host` | list | `
|
|
382
|
-
| `BLOCKYARD_HOST` | `server.host` | list | `
|
|
385
|
+
| `BLOCKYARD_BIND` | `server.host` | list | `127.0.0.1` | Listen address(es), for example `0.0.0.0` or `192.0.2.10,2001:db8::10`. If both are set, this wins over `BLOCKYARD_HOST`. Ignored when the file sets `server.hosts` (see [Known quirks](#known-quirks)). |
|
|
386
|
+
| `BLOCKYARD_HOST` | `server.host` | list | `127.0.0.1` | Same as `BLOCKYARD_BIND`. |
|
|
383
387
|
| `BLOCKYARD_PORT` | `server.port` | number | `21000` | Listen port. |
|
|
384
388
|
| `BLOCKYARD_ALLOW_CIDRS` | `server.allowCidrs` | list | *(empty: everyone)* | Client allowlist, for example `192.0.2.0/24,2001:db8::/32`. |
|
|
385
389
|
| `BLOCKYARD_TRUST_PROXY` | `server.trustProxy` | boolean | `false` | Take the client address from `X-Forwarded-For`. |
|
|
386
|
-
| `
|
|
390
|
+
| `BLOCKYARD_TLS` | `server.tls.enabled` | boolean | `true` | `0` serves plain HTTP (behind a TLS-terminating proxy). |
|
|
391
|
+
| `BLOCKYARD_TLS_CERT` | `server.tls.cert` | path | unset | PEM certificate of your own, instead of the made one. Set it together with `BLOCKYARD_TLS_KEY`. |
|
|
387
392
|
| `BLOCKYARD_TLS_KEY` | `server.tls.key` | path | unset | PEM private key. Set it together with `BLOCKYARD_TLS_CERT`. |
|
|
388
393
|
| `BLOCKYARD_NODE_URL` | `nodes[0].rpcUrl` | URL | `http://127.0.0.1:8332` | RPC endpoint of the first node. |
|
|
389
394
|
| `BLOCKYARD_DATADIR` | `nodes[0].datadir` | path | `/home/bitcoin/.bitcoin` | Data directory of the first node. It also **clears** `nodes[0].cookieFile`, so the cookie is looked up under the new datadir. |
|
|
@@ -396,7 +401,8 @@ Environment variables override `config/local.json`.
|
|
|
396
401
|
| `BLOCKYARD_RPC_STALE_DROP` | `rpc.staleDropMs` | number | `12000` | Drop poll answers older than this. |
|
|
397
402
|
| `BLOCKYARD_DATA` | `store.dir` | path | `<repo>/data` | Data directory. Also the default `auth.dataDir`. |
|
|
398
403
|
| `BLOCKYARD_RETENTION_HOURS` | `store.retentionHours` | number | `72` | Chart history retention. |
|
|
399
|
-
| `BLOCKYARD_AUTH` | `auth.enabled` | boolean | `
|
|
404
|
+
| `BLOCKYARD_AUTH` | `auth.enabled` | boolean | `true` | Accounts; `0` is open mode. |
|
|
405
|
+
| `BLOCKYARD_OPEN_NODE_CONFIG_FROM_NETWORK` | `auth.openNodeConfigFromNetwork` | boolean | `false` | In open mode, let any client use the node connection form, not only this machine. |
|
|
400
406
|
| `BLOCKYARD_SECURE_COOKIE` | `auth.secureCookie` | boolean | `false` | `Secure` session cookie. Use it behind a TLS terminator; it is automatic with built-in TLS. |
|
|
401
407
|
| `BLOCKYARD_ADMIN_PASSWORD` | *(none)* | string | *(generated)* | Password for the `admin` account created on first boot when accounts are on and no users exist. It is ignored once any user exists. It is a secret, so do not leave it in a unit file after first boot. |
|
|
402
408
|
| `BLOCKYARD_ENABLE_ACTIONS` | `actions.enabled` | boolean | `false` | Master switch for node writes. |
|
|
@@ -404,7 +410,7 @@ Environment variables override `config/local.json`.
|
|
|
404
410
|
| `BLOCKYARD_ALLOW_WRITES_WITHOUT_AUTH` | `actions.allowWritesWithoutAuth` | boolean | `false` | Permit actions while accounts are off. |
|
|
405
411
|
| `BLOCKYARD_LOG_SOURCE` | `log.enabled` | boolean | `false` | `1` tails node log files; `0` (or unset) runs on RPC alone. |
|
|
406
412
|
| `BLOCKYARD_LOG_LEVEL` | `log.level` | string | `info` | `debug`, `info`, `warn` or `error`. |
|
|
407
|
-
| `BLOCKYARD_MARKETS` | `markets.enabled` | boolean | `true` | `0`
|
|
413
|
+
| `BLOCKYARD_MARKETS` | `markets.enabled` | boolean | `true` | `0` removes the Markets feed; the polling checkbox then cannot turn it on. |
|
|
408
414
|
| `BLOCKYARD_MINING` | *(none)* | `0` or anything | on | `0` turns off miner attribution, which decodes each block's coinbase to show the pool tag. Block sizes, fees and weights are unaffected. Only the literal `0` disables it. |
|
|
409
415
|
| `BLOCKYARD_MINING_BACKFILL` | *(none)* | number | `36` | How many recent blocks are attributed to miners at startup. |
|
|
410
416
|
| `BLOCKYARD_MINING_TEMPLATE` | *(none)* | `0` or anything | on | `0` turns off the "block being built" card. Since 2026-09-13 the template is **assembled from the mempool this monitor already reads**, so leaving it on costs your node no RPC call at all — it costs this process ~50-70 ms of CPU per assembly. Before that it was a `getblocktemplate` worth over a second of the node's single RPC thread, which is why the switch exists. Only the literal `0` disables it. |
|
|
@@ -577,10 +583,10 @@ instead:
|
|
|
577
583
|
BLOCKYARD_ADMIN_PASSWORD='choose-a-long-passphrase' npm start
|
|
578
584
|
```
|
|
579
585
|
|
|
580
|
-
Then create personal accounts with `npm run user -- create <name> <role>`.
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
an SSH tunnel.
|
|
586
|
+
Then create personal accounts with `npm run user -- create <name> <role>`. HTTPS is on by
|
|
587
|
+
default with the monitor's own certificate; if you turn it off (`BLOCKYARD_TLS=0`) on a bind
|
|
588
|
+
that crosses the LAN, the startup log warns that the session cookie crosses the network in
|
|
589
|
+
clear text. Pair that with a TLS-terminating proxy, or with a loopback bind and an SSH tunnel.
|
|
584
590
|
|
|
585
591
|
Accounts plus two node writes that operators may run:
|
|
586
592
|
|
|
@@ -596,7 +602,8 @@ Accounts plus two node writes that operators may run:
|
|
|
596
602
|
|
|
597
603
|
### TLS
|
|
598
604
|
|
|
599
|
-
|
|
605
|
+
HTTPS is on by default, with a certificate the server makes for itself. A certificate of your
|
|
606
|
+
own, on every listener:
|
|
600
607
|
|
|
601
608
|
```json
|
|
602
609
|
{
|
|
@@ -632,9 +639,9 @@ matters here too: it makes the proxy the only way in.
|
|
|
632
639
|
|
|
633
640
|
### Binding to specific addresses
|
|
634
641
|
|
|
635
|
-
The default `0.0.0.0` listens on every IPv4
|
|
636
|
-
tunnels and container bridges. To serve exactly
|
|
637
|
-
address, and admit only clients from those networks:
|
|
642
|
+
The default `127.0.0.1` answers this machine only. `0.0.0.0` listens on every IPv4
|
|
643
|
+
interface the machine has, including VPN tunnels and container bridges. To serve exactly
|
|
644
|
+
one LAN address and one VPN address, and admit only clients from those networks:
|
|
638
645
|
|
|
639
646
|
```json
|
|
640
647
|
{
|
|
@@ -661,9 +668,11 @@ Things to know about specific binds:
|
|
|
661
668
|
- For this machine only, use `"hosts": ["127.0.0.1"]` and reach it with
|
|
662
669
|
`ssh -L 21000:127.0.0.1:21000 user@monitor-host`.
|
|
663
670
|
|
|
664
|
-
### Markets off
|
|
671
|
+
### Markets off, for good
|
|
665
672
|
|
|
666
|
-
|
|
673
|
+
Out of the box there are no outbound connections except to the node, because market polling is
|
|
674
|
+
a checkbox that ships unticked (**Display settings → Markets & Price → Enable market polling**). To be certain a machine never reaches out whatever anyone
|
|
675
|
+
ticks, remove the feed from the server:
|
|
667
676
|
|
|
668
677
|
```json
|
|
669
678
|
{
|
|
@@ -713,7 +722,7 @@ sudo systemctl edit blockyard
|
|
|
713
722
|
|
|
714
723
|
```ini
|
|
715
724
|
[Service]
|
|
716
|
-
Environment=
|
|
725
|
+
Environment=BLOCKYARD_BIND=192.0.2.10
|
|
717
726
|
Environment=BLOCKYARD_DATA=/var/lib/blockyard
|
|
718
727
|
```
|
|
719
728
|
|
|
@@ -738,7 +747,7 @@ accounts and chart history, and never commit or publish it.
|
|
|
738
747
|
| `audit.1.jsonl` … `audit.N.jsonl` | the server | sensitive | Rotated audit files, newest first. There are at most `store.auditKeep` of them, and rotation happens at `store.auditMaxBytes`. |
|
|
739
748
|
| `history.json` | the server | no, but reveals node details | Snapshot of chart series, events and recent blocks, restored on startup and pruned to `store.retentionHours`. Can be tens of MB. |
|
|
740
749
|
| `pool-aliases.json` | **you**, optionally | no | Hand-written display names for mining pools: a JSON object from pool key to label, for example `{"examplepool": "Example Pool"}`. The pool key is the lowercased coinbase tag the Mining page shows, or `unknown:<hex>` for blocks without a readable tag. Absent by default, in which case the coinbase text is shown as written. |
|
|
741
|
-
| `pool-map.json` | `node scripts/pool-map.js` | no | Coinbase-tag-to-pool-name map built from the public mempool/mining-pools data set (MIT), with source URL and content hash. **A copy ships in `config/pool-map.json`** (151 pools), so pools are labelled from the first start; one here, written by the script (which needs network access), **overrides** the shipped copy. The server reads `BLOCKYARD_POOL_MAP` if set, else `<store.dir>/pool-map.json` if it exists, else the shipped file. The script always writes `<repo>/data/pool-map.json`, so move it, or set `BLOCKYARD_POOL_MAP`, if you use a different `store.dir`. `--file <pools-v2.json>` builds it offline. |
|
|
750
|
+
| `pool-map.json` | `node scripts/pool-map.js` | no | Coinbase-tag-to-pool-name map built from the public mempool/mining-pools data set (MIT), with source URL and content hash. **A copy ships in `config/pool-map.json`** (151 pools), so pools are labelled from the first start; one here, written by the script (which needs network access), **overrides** the shipped copy. The server reads `BLOCKYARD_POOL_MAP` if set, else `<store.dir>/pool-map.json` if it exists, else the shipped file. The script always writes `<repo>/data/pool-map.json`, so move it, or set `BLOCKYARD_POOL_MAP`, if you use a different `store.dir`. `--file <pools-v2.json>` builds it offline. Before replacing an existing file it prints the pools added, removed and changed and writes only with `--yes` or a "y" at the prompt; `--expect-sha256 <hex>` refuses a download that is not exactly those bytes; redirects are followed to HTTPS only. |
|
|
742
751
|
| `fake-node.log` | development mode only | no | Log of the simulated node when `BLOCKYARD_FAKE_NODE=1`. |
|
|
743
752
|
| `*.tmp` | the server | as the target file | Short-lived files from atomic writes (write, fsync, rename). A leftover one after a crash is safe to delete. |
|
|
744
753
|
|
package/docs/DEFECTS.md
CHANGED
|
@@ -744,7 +744,10 @@ Kept as checked rather than deleted, so nobody re-derives them.
|
|
|
744
744
|
budget has no room for a fourth figure and rules 4/9 forbid merging them. They now
|
|
745
745
|
appear as three separately-labelled rows behind `detail`, where "not printed by this
|
|
746
746
|
build" is a possible answer and the absence is visible.
|
|
747
|
-
- [
|
|
747
|
+
- [x] **An interrupted index build starts over.** Noted 2026-09-14. Fixed 2026-09-16: the build
|
|
748
|
+
keeps `build-journal.json` beside its output and resumes per scanned block file and per sorted
|
|
749
|
+
bucket, cutting the buckets back to what the journal proves (`server/chain/index/build.js`, THE
|
|
750
|
+
BUILD JOURNAL). What was noted: `buildIndex` empties its output
|
|
748
751
|
directory before it begins (`server/chain/index/build.js`), and the server treats an index as
|
|
749
752
|
built only when `manifest.json` exists (`server/main.js`), so a build stopped by Ctrl-C, a crash
|
|
750
753
|
or a reboot keeps nothing of its scan: the installer says so when it is stopped, and the server's
|
package/docs/GETTING-STARTED.md
CHANGED
|
@@ -137,8 +137,8 @@ node scripts/index-build.js --out data/index --workers 4
|
|
|
137
137
|
```
|
|
138
138
|
|
|
139
139
|
Last question: **start BlockYard now, in this terminal?** — yes runs it right there (Ctrl-C
|
|
140
|
-
stops it, and stops a background build with it;
|
|
141
|
-
|
|
140
|
+
stops it, and stops a background build with it; the next start resumes the build where it
|
|
141
|
+
stopped); `--start` does the same without asking.
|
|
142
142
|
|
|
143
143
|
Scripted, with no questions (a fresh machine, a Makefile):
|
|
144
144
|
|
|
@@ -147,10 +147,17 @@ node scripts/setup.js --yes --rpc-url http://127.0.0.1:8332 \
|
|
|
147
147
|
--datadir "$HOME/Library/Application Support/Bitcoin" --index-dir "$PWD/data/index" --workers 4
|
|
148
148
|
```
|
|
149
149
|
|
|
150
|
-
Every question has a flag: `--datadir`, `--rpc-url`, `--label`, `--rpc-user` /
|
|
151
|
-
`--host`, `--port`, `--index-dir`, `--workers`. `--build-here` builds in the
|
|
152
|
-
`--build-later` leaves it to you; `--start` boots the monitor at the end; `--force`
|
|
153
|
-
existing `config/local.json` (with a backup).
|
|
150
|
+
Every question has a flag: `--datadir`, `--rpc-url`, `--label`, `--rpc-user` /
|
|
151
|
+
`--rpc-password-file`, `--host`, `--port`, `--index-dir`, `--workers`. `--build-here` builds in the
|
|
152
|
+
terminal and `--build-later` leaves it to you; `--start` boots the monitor at the end; `--force`
|
|
153
|
+
replaces an existing `config/local.json` (with a backup, also mode `0600`, and git-ignored).
|
|
154
|
+
|
|
155
|
+
The RPC password, when the node needs one: `--rpc-password-file PATH` reads the first line of a
|
|
156
|
+
file, and `--rpc-password -` reads it from stdin (with `--yes`, e.g.
|
|
157
|
+
`pass show node/rpc | node scripts/setup.js --yes --rpc-user monitor --rpc-password - ...`).
|
|
158
|
+
`--rpc-password P` still works but prints a warning: a password on the command line is visible to
|
|
159
|
+
every user of the machine in `ps` and stays in your shell history. Typed at the interactive prompt,
|
|
160
|
+
it is not echoed.
|
|
154
161
|
|
|
155
162
|
## 5. Run it
|
|
156
163
|
|
|
@@ -158,12 +165,15 @@ existing `config/local.json` (with a backup).
|
|
|
158
165
|
npm start
|
|
159
166
|
```
|
|
160
167
|
|
|
161
|
-
The log says `BlockYard 0.
|
|
168
|
+
The log says `BlockYard 0.1.1 listening on https://127.0.0.1:21000` (its own self-signed
|
|
169
|
+
certificate, made on this first start; the browser warns once and remembers it), then `created the
|
|
170
|
+
first admin account (admin)` with a generated password **shown once** — copy it, or set
|
|
171
|
+
`BLOCKYARD_ADMIN_PASSWORD` before the first start to choose it — then `address index: building
|
|
162
172
|
/Users/you/blockyard/data/index from main's block files with 4 workers -- the Overview shows the
|
|
163
173
|
progress` (and `address index build: paused while the node's RPC is answering in … s` /
|
|
164
174
|
`resumed` if the node struggles) and, when that is done, `address index built: … rows to block N
|
|
165
175
|
in … min -- address pages are live` followed by `address index /Users/you/blockyard/data/index:
|
|
166
|
-
following main from block N`. Open <
|
|
176
|
+
following main from block N`. Open <https://127.0.0.1:21000> and sign in as `admin`. The
|
|
167
177
|
Overview fills in within about thirty seconds; Block space lands a little after. Open Explorer,
|
|
168
178
|
click the latest block, then any output address: while the index is building the page says so
|
|
169
179
|
with the progress; once it is built, its balance, history and unspent outputs appear.
|