blockyard 0.0.1 → 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.
- package/CHANGELOG.md +929 -0
- package/LICENSE +202 -0
- package/NOTICE +4 -0
- package/README.md +191 -4
- package/SECURITY.md +38 -0
- package/bin/blockyard.js +41 -0
- package/config/pool-map.json +2620 -0
- package/docs/API.md +1577 -0
- package/docs/ARCHITECTURE.md +1394 -0
- package/docs/AUTO-UPDATE.md +269 -0
- package/docs/CONFIGURATION.md +847 -0
- package/docs/DEFECTS.md +813 -0
- package/docs/EFFECTS-AGENTS.md +448 -0
- package/docs/GETTING-STARTED.md +205 -0
- package/docs/INSTALL.md +547 -0
- package/docs/MEASUREMENTS.md +1401 -0
- package/docs/RULES.md +681 -0
- package/docs/SECURITY-AUDIT-2026-09-14.md +177 -0
- package/docs/SECURITY-AUDIT.md +258 -0
- package/docs/SECURITY.md +212 -0
- package/docs/TROUBLESHOOTING.md +332 -0
- package/docs/USER-GUIDE.md +1262 -0
- package/package.json +53 -5
- package/public/404.html +9 -0
- package/public/css/app.css +2009 -0
- package/public/donate-qr.png +0 -0
- package/public/index.html +1085 -0
- package/public/js/about.js +112 -0
- package/public/js/agents.js +1141 -0
- package/public/js/app.js +1386 -0
- package/public/js/arkanoid.js +806 -0
- package/public/js/blockanoid.js +347 -0
- package/public/js/blockout.js +347 -0
- package/public/js/blockpack.js +428 -0
- package/public/js/blockscene3d.js +2830 -0
- package/public/js/breakout.js +224 -0
- package/public/js/charts.js +635 -0
- package/public/js/depthchart.js +315 -0
- package/public/js/details3d.js +4342 -0
- 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 +405 -0
- package/public/js/feepalette.js +149 -0
- package/public/js/fmt.js +162 -0
- package/public/js/goggles.js +886 -0
- package/public/js/kiosk.js +41 -0
- package/public/js/login.js +88 -0
- package/public/js/markets.js +395 -0
- package/public/js/mining.js +1416 -0
- package/public/js/panels.js +970 -0
- package/public/js/pricechart.js +189 -0
- package/public/js/quake.js +20 -0
- package/public/js/settings.js +1096 -0
- package/public/js/soundcard.js +459 -0
- package/public/js/tetris.js +226 -0
- package/public/js/tetrust.js +356 -0
- package/public/js/tetsound.js +175 -0
- package/public/js/theme.js +235 -0
- package/public/js/wolf3d.js +22 -0
- package/public/js/x86.js +1978 -0
- package/public/login.html +33 -0
- package/scripts/blockfile-measure.js +156 -0
- package/scripts/browser-check.mjs +286 -0
- package/scripts/check.js +173 -0
- package/scripts/decode-check.js +81 -0
- package/scripts/doc-counts.js +109 -0
- package/scripts/donate-qr.py +23 -0
- package/scripts/dos-bench.js +56 -0
- package/scripts/fake-node.js +534 -0
- package/scripts/index-bench.js +216 -0
- package/scripts/index-benchmark.js +117 -0
- package/scripts/index-build.js +40 -0
- package/scripts/live-render-check.mjs +89 -0
- package/scripts/manage-users.js +132 -0
- package/scripts/motion-check.mjs +138 -0
- package/scripts/pool-map.js +157 -0
- package/scripts/setup.js +432 -0
- package/scripts/shots.mjs +278 -0
- package/scripts/smoke.sh +327 -0
- package/scripts/tls.js +31 -0
- package/scripts/ui.js +174 -0
- package/server/auth/sessions.js +221 -0
- package/server/auth/users.js +243 -0
- package/server/chain/blockfile.js +234 -0
- package/server/chain/index/build.js +210 -0
- package/server/chain/index/heights.js +36 -0
- package/server/chain/index/live.js +276 -0
- package/server/chain/index/rows.js +145 -0
- package/server/chain/index/store.js +154 -0
- package/server/chain/index/worker.js +109 -0
- package/server/chain/tx.js +310 -0
- package/server/collect/gbt.js +229 -0
- package/server/collect/logparse.js +765 -0
- package/server/collect/logtail.js +189 -0
- package/server/collect/markets.js +333 -0
- package/server/collect/mining.js +333 -0
- package/server/collect/monitor.js +2545 -0
- package/server/collect/network.js +295 -0
- package/server/collect/nextblock.js +275 -0
- package/server/collect/sync.js +386 -0
- package/server/config.js +644 -0
- package/server/http/api.js +1319 -0
- package/server/http/explorer.js +418 -0
- package/server/http/games.js +77 -0
- package/server/http/server.js +420 -0
- package/server/http/sse.js +176 -0
- package/server/http/static.js +212 -0
- package/server/main.js +673 -0
- package/server/netinfo.js +253 -0
- package/server/rpc/allowlist.js +130 -0
- package/server/rpc/client.js +414 -0
- package/server/store/audit.js +148 -0
- package/server/store/history.js +220 -0
- package/server/store/ledger.js +290 -0
- package/server/store/ring.js +173 -0
- package/server/tls/selfsigned.js +160 -0
- package/server/util/fmt.js +29 -0
- package/systemd/blockyard.service +102 -0
package/docs/SECURITY.md
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# Security and privacy
|
|
2
|
+
|
|
3
|
+
BlockYard watches a Bitcoin node and shows what it sees to the people you let in. This
|
|
4
|
+
page describes the access model, what protects it, and exactly what leaves your machine.
|
|
5
|
+
To report a vulnerability, see [SECURITY.md](../SECURITY.md) at the repository root.
|
|
6
|
+
|
|
7
|
+
- [Threat model in one paragraph](#threat-model-in-one-paragraph)
|
|
8
|
+
- [Access: sign-in by default, open on request](#access-sign-in-by-default-open-on-request)
|
|
9
|
+
- [Accounts, sessions and passwords](#accounts-sessions-and-passwords)
|
|
10
|
+
- [Talking to the node](#talking-to-the-node)
|
|
11
|
+
- [Node writes](#node-writes)
|
|
12
|
+
- [Network exposure](#network-exposure)
|
|
13
|
+
- [Transport security](#transport-security)
|
|
14
|
+
- [Browser security](#browser-security)
|
|
15
|
+
- [Outbound connections](#outbound-connections)
|
|
16
|
+
- [What is stored, and where](#what-is-stored-and-where)
|
|
17
|
+
- [Hardening checklist](#hardening-checklist)
|
|
18
|
+
|
|
19
|
+
## Threat model in one paragraph
|
|
20
|
+
|
|
21
|
+
The monitor is a **reader**. It holds your node's RPC credentials, so the main risks are:
|
|
22
|
+
someone using the monitor to make the node do something (prevented: writes are off, the RPC
|
|
23
|
+
console is read-only behind a default-deny allowlist), someone reading your node's state who
|
|
24
|
+
should not (controlled by where it listens, a CIDR gate, and optional accounts), and the
|
|
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.
|
|
28
|
+
|
|
29
|
+
## Access: sign-in by default, open on request
|
|
30
|
+
|
|
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`:
|
|
42
|
+
|
|
43
|
+
| open to anyone who can reach the port | still closed |
|
|
44
|
+
|---|---|
|
|
45
|
+
| every chart, the sync view, the event stream | user administration |
|
|
46
|
+
| peer, mempool, block and transaction detail, the explorer | the audit trail |
|
|
47
|
+
| the read-only RPC console (behind the allowlist) | password changes and sessions |
|
|
48
|
+
| the live Server-Sent Events stream | **every node write**, even if actions are enabled |
|
|
49
|
+
|
|
50
|
+
The `viewer` ceiling cannot be raised by configuration or by any credential while accounts
|
|
51
|
+
are off. The start-up log states which addresses are readable and how to close them, so
|
|
52
|
+
"anyone on the LAN can read your node" is never a surprise.
|
|
53
|
+
|
|
54
|
+
With sign-in on (the default; `BLOCKYARD_AUTH=1` restores it after an override), roles:
|
|
55
|
+
|
|
56
|
+
| role | may |
|
|
57
|
+
|---|---|
|
|
58
|
+
| `viewer` | read everything above |
|
|
59
|
+
| `operator` | also run node actions that have been explicitly enabled |
|
|
60
|
+
| `admin` | also manage users and read the audit trail |
|
|
61
|
+
|
|
62
|
+
The last enabled admin cannot be demoted, disabled or deleted.
|
|
63
|
+
|
|
64
|
+
## Accounts, sessions and passwords
|
|
65
|
+
|
|
66
|
+
- **Passwords** are hashed with scrypt (N=16384, r=8, p=1 by default), with a per-user salt;
|
|
67
|
+
a 12-character minimum and common breach-corpus shapes are refused. Passwords are never
|
|
68
|
+
logged, never returned by any API, and stored only as hashes. Each user's scrypt
|
|
69
|
+
parameters are stored with the hash; raising them in the configuration upgrades an
|
|
70
|
+
account on its next successful sign-in.
|
|
71
|
+
- **Sessions** use 32-byte random tokens, stored hashed, in an `HttpOnly`, `SameSite=Strict`
|
|
72
|
+
cookie (`Secure` over HTTPS). A session ends 8 hours after its last request, and 72 hours
|
|
73
|
+
after sign-in whatever happens.
|
|
74
|
+
Roles are re-read on every request, so a demotion takes effect immediately.
|
|
75
|
+
- **CSRF**: every state-changing request must carry an `X-CSRF-Token` header matching the
|
|
76
|
+
session; the cookie alone is never accepted as proof. With accounts off there is no session
|
|
77
|
+
to ride, so the token check has nothing to compare -- and until 2026-09-13 that meant those
|
|
78
|
+
routes had no cross-site protection at all. An audit proved it with a working exploit against
|
|
79
|
+
the node-connection test. Open mode now refuses any state-changing request whose `Origin` is
|
|
80
|
+
not this server, or whose `Sec-Fetch-Site` says cross-site. A client that sends neither header
|
|
81
|
+
(curl, a script) is unaffected: it can already reach the port, and this guards against what a
|
|
82
|
+
*browser* can be made to do on someone's behalf.
|
|
83
|
+
- **Brute force**: sign-in is locked after 8 failures in 5 minutes per username and per
|
|
84
|
+
address, for 10 minutes, with the same error and the same hashing time for unknown users
|
|
85
|
+
and wrong passwords. A separate throttle limits sign-in attempts per address, because each
|
|
86
|
+
attempt costs a deliberately expensive hash.
|
|
87
|
+
- **Rate limits** apply per client address.
|
|
88
|
+
|
|
89
|
+
## Talking to the node
|
|
90
|
+
|
|
91
|
+
- **Read-only allowlist, default deny.** The RPC console and every internal call go through
|
|
92
|
+
an allowlist: read-shaped methods are allowed; wallet, key-material, spending, peer-control,
|
|
93
|
+
chain-mutating and very heavy methods are refused by name — including `getnewaddress` and
|
|
94
|
+
`getrawchangeaddress`, which start with "get" but create keys. Unknown methods are refused.
|
|
95
|
+
- **One request at a time.** The node's RPC server is single-threaded, so the monitor runs a
|
|
96
|
+
single serialized request lane with a minimum spacing, batching, priorities and a stale-drop
|
|
97
|
+
rule. It cannot be used to flood your node. The one exception is the address index build,
|
|
98
|
+
which makes its few cheap calls (block hashes) over a second connection so that the pages are
|
|
99
|
+
not queued behind it — and which pauses itself whenever the first lane sees the node failing
|
|
100
|
+
or answering slowly.
|
|
101
|
+
- **The block files are read directly** (`<datadir>/blocks`), once, to build the address
|
|
102
|
+
index; the node is not asked for them. The index directory is written by the build and by the
|
|
103
|
+
follower, and nothing else in the node's data directory is ever written.
|
|
104
|
+
- **Credentials** are read from the cookie file on demand (it changes on every node restart)
|
|
105
|
+
or from the configured user and password. They are never sent to the browser.
|
|
106
|
+
|
|
107
|
+
## Node writes
|
|
108
|
+
|
|
109
|
+
Node actions (for example `savemempool`) are **off**. Enabling one requires all of:
|
|
110
|
+
|
|
111
|
+
1. `BLOCKYARD_ENABLE_ACTIONS=1`,
|
|
112
|
+
2. the action named in `BLOCKYARD_ACTIONS` (a comma-separated list),
|
|
113
|
+
3. accounts on and a role of at least `operator` — or the deliberate
|
|
114
|
+
`BLOCKYARD_ALLOW_WRITES_WITHOUT_AUTH=1`, which exists so that the combination can only ever
|
|
115
|
+
be chosen on purpose,
|
|
116
|
+
4. a typed confirmation that matches the action's name, per call.
|
|
117
|
+
|
|
118
|
+
Every call and every refusal is appended to the audit trail.
|
|
119
|
+
|
|
120
|
+
## Network exposure
|
|
121
|
+
|
|
122
|
+
- **Bind** only the addresses you mean to serve (`server.host`, a string or a list). See
|
|
123
|
+
[INSTALL.md](INSTALL.md#7-decide-who-can-reach-it) for the options and their consequences.
|
|
124
|
+
- A bind chooses a destination address, not an incoming interface; use a firewall if "LAN
|
|
125
|
+
only" must hold against containers or tunnels on the same machine.
|
|
126
|
+
- **CIDR gate**: `server.allowCidrs` refuses clients outside the listed IPv4/IPv6 networks.
|
|
127
|
+
Membership is computed on the address bytes, and a malformed entry stops the start-up
|
|
128
|
+
rather than silently admitting or refusing everyone.
|
|
129
|
+
- `/api/health` needs no sign-in, so an uptime probe works. It reveals the node inventory,
|
|
130
|
+
version, tip and health to anyone who can reach the port.
|
|
131
|
+
- Behind a reverse proxy, set `server.trustProxy` so rate limits and the CIDR gate see the
|
|
132
|
+
real client address — and only then.
|
|
133
|
+
|
|
134
|
+
## Transport security
|
|
135
|
+
|
|
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.
|
|
145
|
+
- Over HTTPS the session cookie is `Secure` and `Strict-Transport-Security` is sent with a
|
|
146
|
+
two-day lifetime, without `includeSubDomains` or `preload` — a LAN address can be reissued,
|
|
147
|
+
and HSTS cannot be withdrawn once a browser has it.
|
|
148
|
+
- Plain HTTP is acceptable for a trusted LAN or behind a tunnel; the start-up log says when
|
|
149
|
+
it is serving plain HTTP.
|
|
150
|
+
|
|
151
|
+
## Browser security
|
|
152
|
+
|
|
153
|
+
- **Content Security Policy**: `default-src 'self'`, `frame-ancestors 'none'`, scripts only
|
|
154
|
+
from the monitor itself plus a per-response nonce, and **no inline styles at all**
|
|
155
|
+
(`style-src 'self'` with no attribute exception). Every data-driven style is applied through
|
|
156
|
+
the CSSOM instead of `style="…"` attributes.
|
|
157
|
+
- Also sent: `X-Content-Type-Options: nosniff`, `X-Frame-Options: DENY`,
|
|
158
|
+
`Referrer-Policy: no-referrer`.
|
|
159
|
+
- The browser loads nothing from third parties: no CDN, no web fonts, no analytics. Market
|
|
160
|
+
data reaches the browser only through the monitor's own API.
|
|
161
|
+
|
|
162
|
+
## Outbound connections
|
|
163
|
+
|
|
164
|
+
Everything in the monitor talks only to your node, **except**:
|
|
165
|
+
|
|
166
|
+
| when | to | what is sent |
|
|
167
|
+
|---|---|---|
|
|
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 |
|
|
170
|
+
|
|
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.
|
|
177
|
+
|
|
178
|
+
The monitor sends no telemetry, checks for no updates, and phones home to no one.
|
|
179
|
+
|
|
180
|
+
## What is stored, and where
|
|
181
|
+
|
|
182
|
+
Everything the monitor writes lives in its data directory (`./data` by default,
|
|
183
|
+
`BLOCKYARD_DATA` to move it):
|
|
184
|
+
|
|
185
|
+
| file | contents | sensitivity |
|
|
186
|
+
|---|---|---|
|
|
187
|
+
| `users.json` | account names, roles, scrypt hashes and salts | **secret** (mode 0600) |
|
|
188
|
+
| `sessions.json` | hashed session tokens | **secret** |
|
|
189
|
+
| `audit.jsonl` (+ rotations) | who called what and when; rotated by size (8 MiB, 5 kept) | private |
|
|
190
|
+
| history snapshots | chart time series for the retention window (72 h by default) | private |
|
|
191
|
+
| `pool-aliases.json`, `pool-map.json` | optional mining-pool labels: `pool-aliases.json` is human-edited; `data/pool-map.json` is what `node scripts/pool-map.js` fetches, and it overrides the curated `config/pool-map.json` that ships with the code (mempool.space/mining-pools, MIT, 151 pools) | not secret |
|
|
192
|
+
| the address index (`addressIndex`, `data/index` by default) | the explorer's address index: ~124 GB of sorted rows built from the node's block files, plus the follower's `live.log` and `layers/` | public chain data, not secret |
|
|
193
|
+
|
|
194
|
+
`config/local.json` may hold an RPC password; keep it readable only by the service account.
|
|
195
|
+
Both it and `data/` are git-ignored. Display settings (the gear) are stored in
|
|
196
|
+
`config/blockyard.json`, so every browser sees the same board; they change how things are drawn,
|
|
197
|
+
never what is measured.
|
|
198
|
+
|
|
199
|
+
Logs go to standard output (the systemd journal). They record requests, node state changes
|
|
200
|
+
and errors; they never contain passwords, session tokens or RPC credentials.
|
|
201
|
+
|
|
202
|
+
## Hardening checklist
|
|
203
|
+
|
|
204
|
+
- Run as a dedicated, unprivileged account that can read only the node's cookie and its
|
|
205
|
+
`blocks/` directory (and log), and write only its own `data/` and the index directory.
|
|
206
|
+
- Bind the narrowest set of addresses that serves your users; add a firewall rule if needed.
|
|
207
|
+
- Turn accounts on if anyone who can reach the port should not see your node.
|
|
208
|
+
- Use HTTPS, a reverse proxy, or an SSH tunnel on untrusted networks.
|
|
209
|
+
- Leave node actions off unless you have a specific need, and then enable only that action.
|
|
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.
|
|
211
|
+
- Keep `config/local.json` and `data/` readable only by the service account.
|
|
212
|
+
- Keep Node.js current within the 22.x line or later.
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
# Troubleshooting
|
|
2
|
+
|
|
3
|
+
The monitor tries to explain itself: the start-up log, the header badges and the **Node &
|
|
4
|
+
RPC** page usually name the problem. This page collects the cases people actually hit.
|
|
5
|
+
|
|
6
|
+
- [The service does not start](#the-service-does-not-start)
|
|
7
|
+
- [A node shows offline](#a-node-shows-offline)
|
|
8
|
+
- [The page does not load from another machine](#the-page-does-not-load-from-another-machine)
|
|
9
|
+
- [The header says "stale" or "reconnecting"](#the-header-says-stale-or-reconnecting)
|
|
10
|
+
- [The header says STALLED](#the-header-says-stalled)
|
|
11
|
+
- [The node's RPC is slow](#the-nodes-rpc-is-slow)
|
|
12
|
+
- [The block-space board is empty, or "no block template yet"](#the-block-space-board-is-empty-or-no-block-template-yet)
|
|
13
|
+
- [The address index build is paused, or slow](#the-address-index-build-is-paused-or-slow)
|
|
14
|
+
- [Markets or Kiosk show no prices](#markets-or-kiosk-show-no-prices)
|
|
15
|
+
- [An exchange shows an error](#an-exchange-shows-an-error)
|
|
16
|
+
- [Explorer: a transaction id is not found](#explorer-a-transaction-id-is-not-found)
|
|
17
|
+
- [Explorer: address pages read "not indexed"](#explorer-address-pages-read-not-indexed)
|
|
18
|
+
- [Explorer: the address page says the index is behind, or has stopped following](#explorer-the-address-page-says-the-index-is-behind-or-has-stopped-following)
|
|
19
|
+
- [Explorer: "spent by" links are missing](#explorer-spent-by-links-are-missing)
|
|
20
|
+
- [Explorer: no dollar figures](#explorer-no-dollar-figures)
|
|
21
|
+
- [The 3D views are slow or blank](#the-3d-views-are-slow-or-blank)
|
|
22
|
+
- [Full screen does not work on the Kiosk](#full-screen-does-not-work-on-the-kiosk)
|
|
23
|
+
- [Sign-in problems](#sign-in-problems)
|
|
24
|
+
- [Tests fail on a fresh clone](#tests-fail-on-a-fresh-clone)
|
|
25
|
+
|
|
26
|
+
## The service does not start
|
|
27
|
+
|
|
28
|
+
Read the journal first: `journalctl -u blockyard -n 100 --no-pager`.
|
|
29
|
+
|
|
30
|
+
| message or symptom | cause and fix |
|
|
31
|
+
|---|---|
|
|
32
|
+
| a syntax error at start-up | the runtime is older than Node 22. Check `node -v` **as the service account**, and give `ExecStart` an absolute path to a Node 22 binary. |
|
|
33
|
+
| `EADDRINUSE` | something else holds the port. Change `server.port` / `BLOCKYARD_PORT`, or stop the other process (`ss -ltnp | grep 21000`). |
|
|
34
|
+
| none of the configured addresses exist | the bind names addresses this machine does not have (for example after a DHCP change). The log prints the addresses it does have; fix `server.host`. |
|
|
35
|
+
| `Invalid configuration` followed by a list | each line names the setting and why — for example a certificate without a key, an expired certificate, a malformed CIDR, or node writes enabled without accounts. |
|
|
36
|
+
| `config: cannot parse …/local.json` | the JSON has a syntax error (a trailing comma is the usual one). |
|
|
37
|
+
|
|
38
|
+
## A node shows offline
|
|
39
|
+
|
|
40
|
+
The Node & RPC page shows the last error. Common causes:
|
|
41
|
+
|
|
42
|
+
- **`ECONNREFUSED`** — nothing is listening at `rpcUrl`. Check the node is running and which
|
|
43
|
+
RPC port it uses (`rpcport` in its configuration); the node may also bind RPC a little while
|
|
44
|
+
after its service reports "started".
|
|
45
|
+
- **`401` / authentication failed** — the cookie could not be read or is out of date. The
|
|
46
|
+
cookie is rewritten on every node restart and removed when the node stops. Make sure the
|
|
47
|
+
service account can read `<datadir>/<chain>/.cookie` (usually by being in the node's group),
|
|
48
|
+
or configure `rpcUser` / `rpcPassword`.
|
|
49
|
+
- **"datadir does not exist"** at start-up — the node entry points at a directory that is not
|
|
50
|
+
there; the node is skipped rather than shown as permanently offline. Fix `datadir` or set
|
|
51
|
+
`cookieFile` directly.
|
|
52
|
+
- **Timeouts** right after the node starts — some node builds block RPC for a while during
|
|
53
|
+
start-up. The monitor keeps retrying; wait a minute.
|
|
54
|
+
|
|
55
|
+
## The page does not load from another machine
|
|
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.
|
|
61
|
+
1. From the other machine, check how it reaches the server: `ip route get <address>`.
|
|
62
|
+
2. Make sure that address is one the monitor binds — see the start-up log. A LAN-only bind is
|
|
63
|
+
not reachable over a VPN unless the VPN address is also listed in `server.host`.
|
|
64
|
+
3. Check firewalls on the server (`ufw status`, `nft list ruleset`) for the port.
|
|
65
|
+
4. If `server.allowCidrs` is set, the client's network must be in it.
|
|
66
|
+
5. Testing from the server itself proves little: with a single LAN address bound, `127.0.0.1`
|
|
67
|
+
does not answer, and a firewall usually allows loopback.
|
|
68
|
+
|
|
69
|
+
## The header says "stale" or "reconnecting"
|
|
70
|
+
|
|
71
|
+
- **reconnecting** — the live stream (Server-Sent Events) dropped. Behind a reverse proxy the
|
|
72
|
+
usual cause is response buffering or a short read timeout on `/api/stream`; see the proxy
|
|
73
|
+
example in [INSTALL.md](INSTALL.md#10-behind-a-reverse-proxy-optional).
|
|
74
|
+
- **stale** — no fresh data has arrived for a while. Charts keep the last picture with a
|
|
75
|
+
"stale" label rather than going blank; the Node & RPC page shows which poll is failing.
|
|
76
|
+
|
|
77
|
+
## The header says STALLED
|
|
78
|
+
|
|
79
|
+
A long gap between blocks is not a stall: the network finds no block for 40 minutes about once
|
|
80
|
+
in fifty, and two healthy nodes at the same height once sat through 42 minutes of it with the
|
|
81
|
+
header red. Since 2026-09-14 the sync state is **stalled** only when the node's **connected
|
|
82
|
+
peers report a higher tip than the node holds** (`getpeerinfo` `synced_headers`) — it is behind
|
|
83
|
+
the network, not waiting for it. When the peers agree on the node's tip, a long gap is shown as
|
|
84
|
+
*synced* with a caveat naming the gap. When no peer height is known at all (no peers, or none
|
|
85
|
+
that report one), the node is called stalled only after **two hours** without a block, because a
|
|
86
|
+
long gap and a node cut off from its peers cannot be told apart sooner. The sync panel's caveats
|
|
87
|
+
say which of the three cases it is.
|
|
88
|
+
|
|
89
|
+
If it really is stalled: check the node's peer count and its own log; a node behind a firewall
|
|
90
|
+
that has lost its connections looks exactly like this.
|
|
91
|
+
|
|
92
|
+
## The node's RPC is slow
|
|
93
|
+
|
|
94
|
+
The Node & RPC page shows RPC latency and the queue. When the node is slow the monitor
|
|
95
|
+
stretches its polling automatically and skips heavy reads, so a busy node (for example during
|
|
96
|
+
initial sync) shows fewer updates rather than being slowed further. Slow means an average above
|
|
97
|
+
`rpc.slowLatencyMs` (5 s by default): the `rpc-slow` flag appears on the Overview, and a running
|
|
98
|
+
address index build pauses until the node recovers (see
|
|
99
|
+
[below](#the-address-index-build-is-paused-or-slow)).
|
|
100
|
+
|
|
101
|
+
To time the node **alone**, with no monitor in the way: `npm run check` runs the install-time
|
|
102
|
+
checks against every configured node and prints how long each call took — `getblockchaininfo`,
|
|
103
|
+
`getblock <tip> 3` and a verbose `getrawmempool` among them. A `getblock 3` over 5 s or a mempool
|
|
104
|
+
read over 10 s is marked slow there, with what it will mean for the pages.
|
|
105
|
+
|
|
106
|
+
The monitor runs **one** request at a time, always. `rpc.maxInFlight` exists in the config and
|
|
107
|
+
is reported by `/api/config`, but the lane is serialised by construction and does not read it --
|
|
108
|
+
measured 2026-09-13 at 1, 4 and 8: four 200 ms jobs took ~807 ms with peak concurrency 1 in every
|
|
109
|
+
case. Treat it as documentation of intent, not a tuning knob. (The address index build has a
|
|
110
|
+
second connection of its own for its few cheap calls; it is paced by the first lane's telemetry
|
|
111
|
+
and adds nothing while the node is slow.)
|
|
112
|
+
|
|
113
|
+
What costs time on a mainnet node is the expensive reads, and how much depends on how the node is
|
|
114
|
+
configured. The sharpest example used to be the block template -- since 2026-09-13 the monitor
|
|
115
|
+
assembles that from the mempool and never calls `getblocktemplate`, so the numbers below are kept
|
|
116
|
+
as the clearest illustration of what node tuning is worth, not as a call this software still
|
|
117
|
+
makes. Measured on a Core 31.1.0 node on 2026-09-13, **before** its RPC settings were
|
|
118
|
+
tuned: `getblockchaininfo` 95-110 ms, `getmempoolinfo` ~100 ms, but `getblocktemplate`
|
|
119
|
+
**4.0-4.5 s**, five times in a row with no warming -- while the same call on a local Core node
|
|
120
|
+
answered in **51 ms**. Two concurrent templates contended rather than overlapped there (6.9 s and
|
|
121
|
+
8.8 s against 4.5 s alone), so more concurrency would not have helped that call.
|
|
122
|
+
|
|
123
|
+
**After** applying `dbcache=4096` plus the RPC settings in [INSTALL](INSTALL.md), the same node
|
|
124
|
+
answered `getblocktemplate` in **488-565 ms** and the monitor's lane stopped timing out entirely,
|
|
125
|
+
with average latency sampling between ~180 ms and a few seconds. If your node shows
|
|
126
|
+
`rpc-slow`, check those settings before concluding the node is simply slow -- and note that a
|
|
127
|
+
rebuilding index (`coinstatsindex` takes hours from genesis) competes for the same disk and will
|
|
128
|
+
keep latency up until it finishes.
|
|
129
|
+
|
|
130
|
+
## The block-space board is empty, or "no block template yet"
|
|
131
|
+
|
|
132
|
+
The board and the block being built are **assembled from the node's verbose mempool**
|
|
133
|
+
(`getrawmempool true`), which the monitor reads every 20 s; the monitor never calls
|
|
134
|
+
`getblocktemplate`. The board is empty when that read has not succeeded yet:
|
|
135
|
+
|
|
136
|
+
- **Right after start-up** — the first verbose read comes a few seconds in; the board lands a
|
|
137
|
+
little after the Overview fills.
|
|
138
|
+
- **The node's RPC is slow** — a verbose mempool read on a large mempool is the monitor's
|
|
139
|
+
heaviest regular call, and on a slow node it is the one that takes tens of seconds. While the
|
|
140
|
+
lane's average latency is over the slow threshold the heavy tiers are skipped
|
|
141
|
+
(`heavy-tiers-skipped` on the Overview), and a read that is superseded before it finishes is
|
|
142
|
+
**dropped as stale** rather than queued (`getrawmempool verbose failed` in the events feed) — so
|
|
143
|
+
the board stays as it was, or empty. `npm run check` times that same read against the node with
|
|
144
|
+
nothing else in the way; if it is slow there too, the node is the bottleneck: see
|
|
145
|
+
[above](#the-nodes-rpc-is-slow) and the `dbcache` / `rpcservertimeout` lines in
|
|
146
|
+
[INSTALL](INSTALL.md#bitcoinconf-settings-worth-having). An address index build on the same disk
|
|
147
|
+
pauses itself while this lasts.
|
|
148
|
+
- **Initial block download** — the card says so; there is no chain to build a block on yet.
|
|
149
|
+
- **`BLOCKYARD_MINING_TEMPLATE=0`** — the block being built is disabled by configuration.
|
|
150
|
+
|
|
151
|
+
## The address index build is paused, or slow
|
|
152
|
+
|
|
153
|
+
The Overview's "What this panel cannot tell you" box shows the build's phase, files done, rows
|
|
154
|
+
so far and an ETA; the address page repeats it. Things it says, and what they mean:
|
|
155
|
+
|
|
156
|
+
- **paused while the node's RPC is slow** — by design. The build's workers read the block files
|
|
157
|
+
the node is also reading, so before each file the build checks the monitor's RPC telemetry:
|
|
158
|
+
while the node is failing or answering slower on average than `rpc.slowLatencyMs` (5 s by
|
|
159
|
+
default) it **holds**, rechecking every 10 s; while merely slow it eases off between files. The
|
|
160
|
+
log says `address index build: paused while the node's RPC is answering in N s` and `resumed`.
|
|
161
|
+
A build that is paused most of the time means the node cannot keep up with the monitor and the
|
|
162
|
+
build together on that disk: fewer workers, or a node tuned as in
|
|
163
|
+
[INSTALL](INSTALL.md#bitcoinconf-settings-worth-having), or let it run overnight.
|
|
164
|
+
- **The ETA is wrong at first** — it is computed from the files done so far in the current phase
|
|
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.
|
|
174
|
+
- **Hours, not minutes** — expect **a few hours**: 29 min 45 s is 16 workers on NVMe, and four
|
|
175
|
+
workers (the installer's default) are roughly four times slower; **spinning disks** are slower still whatever the
|
|
176
|
+
number, and there one worker is the fast setting, because parallel readers only seek against
|
|
177
|
+
each other and against the node. Set `addressIndexWorkers` on the node entry in
|
|
178
|
+
`config/local.json` (the installer writes the number you gave it) and restart: there is no
|
|
179
|
+
resume, so the build starts over from the first file.
|
|
180
|
+
- **It started over** — stopping BlockYard stops the build, and the next start begins it again
|
|
181
|
+
from scratch. Leave it running until the notification says it is done.
|
|
182
|
+
- **the address index build failed** — the reason is in the events feed and the log; fix it and
|
|
183
|
+
restart (the server builds again), or run `node scripts/index-build.js --out <dir>` by hand.
|
|
184
|
+
A pruned node, unreadable block files and a full disk are the usual causes.
|
|
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
|
+
|
|
201
|
+
## Markets or Kiosk show no prices
|
|
202
|
+
|
|
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.
|
|
205
|
+
- **"asking the exchanges…" for a long time** — the server cannot reach the exchanges. Test
|
|
206
|
+
from the server: `curl -sI https://api.exchange.coinbase.com/products/BTC-USD/ticker`. Check
|
|
207
|
+
outbound firewall rules and DNS.
|
|
208
|
+
- Market data is only fetched while the Markets, Kiosk or Overview tab is open; the first prices take a
|
|
209
|
+
few seconds, the order-book depth up to half a minute.
|
|
210
|
+
|
|
211
|
+
## An exchange shows an error
|
|
212
|
+
|
|
213
|
+
The exchange table shows each exchange's last error next to its row.
|
|
214
|
+
|
|
215
|
+
| error | meaning |
|
|
216
|
+
|---|---|
|
|
217
|
+
| `HTTP 451` | the exchange refuses requests from your region. |
|
|
218
|
+
| `HTTP 429` | the exchange is rate-limiting this address; the monitor retries on its next cycle. |
|
|
219
|
+
| `fetch failed` | a network error reaching that exchange (DNS, firewall, or no route over IPv6 while IPv4 works). The monitor already allows slow connection set-up; persistent failures are usually a firewall. |
|
|
220
|
+
|
|
221
|
+
A failing exchange is left out of the median and the spread; the others keep working.
|
|
222
|
+
|
|
223
|
+
## Explorer: a transaction id is not found
|
|
224
|
+
|
|
225
|
+
The explorer asks the node for `getrawtransaction <txid> 2` with no block hash. A node without a
|
|
226
|
+
transaction index can only answer that for transactions still in its **mempool**, so a confirmed
|
|
227
|
+
transaction looks missing even though the node is healthy and fully synced. Block pages are
|
|
228
|
+
unaffected — they pass the block hash, so the node can find the transaction without an index.
|
|
229
|
+
|
|
230
|
+
Set `txindex=1` in `bitcoin.conf` and restart the node. Adding it to a node that has been running
|
|
231
|
+
without one triggers a one-off reindex; `getindexinfo` reports progress and says `"synced": true`
|
|
232
|
+
when it is done:
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
bitcoin-cli getindexinfo
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Explorer: address pages read "not indexed"
|
|
239
|
+
|
|
240
|
+
**Bitcoin Core has no address index at any setting, and this is not something you have
|
|
241
|
+
misconfigured.** `getaddressbalance` and `getaddresstxids` are insight-style extensions that only
|
|
242
|
+
forks carry; stock Core answers `Method not found` (measured 2026-09-13 on two Core nodes). There is no node option to enable.
|
|
243
|
+
|
|
244
|
+
BlockYard builds its own index from the node's block files instead — about 30 minutes on 16
|
|
245
|
+
workers and 124 GB of disk for the whole chain — and the server keeps it current as blocks
|
|
246
|
+
arrive. It builds it **by itself, in the background**, when it starts with an `addressIndex`
|
|
247
|
+
directory that holds no index (the installer's default); while that runs the address page says
|
|
248
|
+
*the address index is being built* with the progress, and fills in when it is done. So *not
|
|
249
|
+
indexed* with no build in progress means one of:
|
|
250
|
+
|
|
251
|
+
- no `addressIndex` directory on the node entry in `config/local.json` — `npm run setup` writes
|
|
252
|
+
one (`data/index` by default), or add it by hand and restart;
|
|
253
|
+
- `addressIndexBuild: "manual"` on the node entry (the installer's **(l)ater** answer) — run
|
|
254
|
+
`node scripts/index-build.js --out <dir>` and restart, or remove the key and restart;
|
|
255
|
+
- the build failed — the reason is in the events feed and the log (see
|
|
256
|
+
[above](#the-address-index-build-is-paused-or-slow)).
|
|
257
|
+
|
|
258
|
+
[Building the address index](INSTALL.md#building-the-address-index) has the details.
|
|
259
|
+
|
|
260
|
+
Until then the address page confirms the address and its type (`validateaddress` needs no
|
|
261
|
+
index) and marks balance, totals and history as **not indexed**. It does not report a
|
|
262
|
+
transaction count of `0` — nothing counted — and it does not print the node's error where a
|
|
263
|
+
figure belongs.
|
|
264
|
+
|
|
265
|
+
## Explorer: the address page says the index is behind, or has stopped following
|
|
266
|
+
|
|
267
|
+
**Behind** by a block or two is normal: the follower polls every 30 s and fetches each new block
|
|
268
|
+
with `getblock <hash> 3`; it was measured reaching a new block 16 s after the node. An index
|
|
269
|
+
built a while ago catches up 50 blocks a poll. Behind by many blocks and not closing the gap means
|
|
270
|
+
the follower is failing: usually because `live.log` and `layers/` live **inside the index
|
|
271
|
+
directory**, which must be writable by the service user. A follower that cannot open its index
|
|
272
|
+
is reported in the server log at boot (`address index <dir>: ...`); one whose poll fails keeps
|
|
273
|
+
retrying every 30 s. `npm run check` reports how far behind the index is and whether the
|
|
274
|
+
directory is writable.
|
|
275
|
+
|
|
276
|
+
**Stopped following** means a reorganisation deeper than the blocks the follower still holds in
|
|
277
|
+
its tail (100), which cannot be repaired in place. Stop the server and either delete the index
|
|
278
|
+
directory (the server builds it again on the next start) or run the same `index-build.js`
|
|
279
|
+
command into the same directory — the build empties it first, the old log and layers included —
|
|
280
|
+
and start the server again.
|
|
281
|
+
|
|
282
|
+
## Explorer: "spent by" links are missing
|
|
283
|
+
|
|
284
|
+
"Spent by" links come from `gettxspendingprevout`, which Bitcoin Core (24.0 and later) answers
|
|
285
|
+
from its **mempool** only: an output spent by a transaction that is still unconfirmed gets a link,
|
|
286
|
+
an output spent in a block does not, and there is no node index to turn on for that. Looking up
|
|
287
|
+
an arbitrary historical transaction by id is a different matter — that needs `txindex` (see
|
|
288
|
+
[above](#explorer-a-transaction-id-is-not-found)).
|
|
289
|
+
|
|
290
|
+
## Explorer: no dollar figures
|
|
291
|
+
|
|
292
|
+
Dollar amounts appear only when a spot price is available within a moment of loading the page:
|
|
293
|
+
from a fresh Markets feed, or from one quick ticker request. With market data off, or with the
|
|
294
|
+
exchanges unreachable, pages show BTC figures only — never a guessed price.
|
|
295
|
+
|
|
296
|
+
## The 3D views are slow or blank
|
|
297
|
+
|
|
298
|
+
- **Slow** — the dense viewer mode (**Detailed**) draws thousands of tiles; on an old
|
|
299
|
+
or GPU-less machine switch the Block space viewer to **Simple** in its control bar.
|
|
300
|
+
The Markets board keeps animating while visible (for its star field); switching to another
|
|
301
|
+
tab stops it.
|
|
302
|
+
- **No motion** — the browser or operating system has "reduce motion" on; the views then draw
|
|
303
|
+
without animation, by design.
|
|
304
|
+
- **Blank** — reload the page. If it stays blank, open the browser console and look for errors;
|
|
305
|
+
a stale cached front-end after an update is the usual cause, and the header shows a notice
|
|
306
|
+
when the page is older than the server.
|
|
307
|
+
|
|
308
|
+
## Full screen does not work on the Kiosk
|
|
309
|
+
|
|
310
|
+
Browsers allow full screen only after a click on the page, and some embedded or kiosk-mode
|
|
311
|
+
browsers refuse it entirely. Use the browser's own full-screen key (F11) instead, or start the
|
|
312
|
+
browser in kiosk mode pointed at `http://<host>:21000/#kiosk`.
|
|
313
|
+
|
|
314
|
+
## Sign-in problems
|
|
315
|
+
|
|
316
|
+
- **Lost the admin password** — `node scripts/manage-users.js passwd admin` on the server.
|
|
317
|
+
- **"too many attempts"** — the lockout lasts 10 minutes per username and per address.
|
|
318
|
+
- **Sign-in does not stick** — over plain HTTP, make sure `BLOCKYARD_SECURE_COOKIE` is not set
|
|
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.
|
|
326
|
+
|
|
327
|
+
## Tests fail on a fresh clone
|
|
328
|
+
|
|
329
|
+
- Use Node 22 or later (`node -v`).
|
|
330
|
+
- The suite is hermetic — it does not read `config/local.json` and needs no node — but a few
|
|
331
|
+
tests boot servers on local ports; make sure nothing else holds them.
|
|
332
|
+
- Timing-sensitive tests can fail on a heavily loaded machine; run the suite again.
|