upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md17
-rw-r--r--docs/mining-calculator.md22
-rw-r--r--docs/pool-directory.md18
-rw-r--r--docs/release-verification.md10
-rw-r--r--docs/site.md115
-rw-r--r--docs/tor.md29
-rwxr-xr-xscripts/build-explorer.py128
-rw-r--r--tests/scripts/test_bitflash_tools.py7
8 files changed, 300 insertions, 46 deletions
diff --git a/README.md b/README.md
index ef4f39b..e671302 100644
--- a/README.md
+++ b/README.md
@@ -38,6 +38,8 @@ Worth being precise, because the difference matters if you are relying on it.
38 38
39This is unlinkability between peers, not anonymity against a network observer. 39This is unlinkability between peers, not anonymity against a network observer.
40For Tor routing, start the node with `-tor`; see [Tor mode](docs/tor.md). 40For Tor routing, start the node with `-tor`; see [Tor mode](docs/tor.md).
41For a plain SOCKS5 proxy without Tor-specific defaults, use
42`-socks=HOST:PORT`.
41 43
42--- 44---
43 45
@@ -74,6 +76,14 @@ scripts/verify-release.sh latest
74See [release verification](docs/release-verification.md) for the full release 76See [release verification](docs/release-verification.md) for the full release
75audit flow and the maintainer signing step. 77audit flow and the maintainer signing step.
76 78
79Public mirrors should make the latest checksum files available both under the
80versioned release directory and at the release root:
81
82```text
83https://releases.bitflash.network/v1.2.18/SHA256SUMS
84https://releases.bitflash.network/SHA256SUMS # alias to latest
85```
86
77Bitflash also ships a deterministic UTXO-set commitment tool: 87Bitflash also ships a deterministic UTXO-set commitment tool:
78 88
79```bash 89```bash
@@ -113,6 +123,12 @@ python3 scripts/build-explorer.py --datadir ~/.bitflash ./explorer-out
113python3 scripts/build-explorer.py ~/.bitflash/blk0001.dat ~/.bitflash/blk0002.dat ./explorer-out 123python3 scripts/build-explorer.py ~/.bitflash/blk0001.dat ~/.bitflash/blk0002.dat ./explorer-out
114``` 124```
115 125
126The generated explorer is static and self-contained: `index.html`,
127`style.css`, `explorer.js`, `blocks.json`, `block/*.json`, and `logo.png`. It is
128safe to host behind a strict policy such as `default-src 'none'; img-src 'self';
129style-src 'self'; script-src 'self'; connect-src 'self'; base-uri 'none';
130form-action 'none'`.
131
116**Keep your node current.** Consensus rules have changed since the first 132**Keep your node current.** Consensus rules have changed since the first
117releases — 1.2.1 fixed a bug that let anyone spend anyone's coins, and 1.2.2 133releases — 1.2.1 fixed a bug that let anyone spend anyone's coins, and 1.2.2
118added a per-block signature-operation cap. A node on an older build will accept 134added a per-block signature-operation cap. A node on an older build will accept
@@ -265,6 +281,7 @@ default both are created in the data directory; use `-poolstatusfile=PATH` and
265`-poolroundsfile=PATH` to write them somewhere a dashboard or web sync can read. 281`-poolroundsfile=PATH` to write them somewhere a dashboard or web sync can read.
266The files are informational only: nodes still discover pools through Nostr and 282The files are informational only: nodes still discover pools through Nostr and
267`.btf`, not through any web domain. 283`.btf`, not through any web domain.
284See [public pool directory](docs/pool-directory.md) for the website/API shape.
268 285
269**Participant** — mine to someone else's pool. Enter or select the pool's `.btf` address and enable Start Mining. 286**Participant** — mine to someone else's pool. Enter or select the pool's `.btf` address and enable Start Mining.
270 287
diff --git a/docs/mining-calculator.md b/docs/mining-calculator.md
index 352eea1..e6cb61b 100644
--- a/docs/mining-calculator.md
+++ b/docs/mining-calculator.md
@@ -84,3 +84,25 @@ The script is intentionally standalone so the same math can later back a
84website widget or status dashboard. Until a public price exists, a website 84website widget or status dashboard. Until a public price exists, a website
85calculator should keep price as a manual field and label fiat profit as 85calculator should keep price as a manual field and label fiat profit as
86speculative. 86speculative.
87
88## Website Fields
89
90A public calculator should ask for:
91
92- miner hashrate and unit;
93- power draw in watts;
94- electricity price per kWh;
95- pool fee percent;
96- optional BTF price;
97- either current Bitflash difficulty or current network hashrate.
98
99The output should separate:
100
101- estimated BTF per day;
102- estimated gross fiat revenue, if a manual price was provided;
103- electricity cost;
104- net profit/loss;
105- the difficulty/hashrate assumptions used.
106
107Never present the fiat number as a market quote unless it comes from a real
108public market. Before that exists, label it as a manual scenario.
diff --git a/docs/pool-directory.md b/docs/pool-directory.md
index 76269b9..ff0640a 100644
--- a/docs/pool-directory.md
+++ b/docs/pool-directory.md
@@ -26,6 +26,10 @@ The generated output contains:
26- `index.html`: human-readable ranking page. 26- `index.html`: human-readable ranking page.
27- `pools.json`: machine-readable API for `api.bitflash.network/pools.json`. 27- `pools.json`: machine-readable API for `api.bitflash.network/pools.json`.
28 28
29The website is a convenience layer, not a consensus or discovery dependency.
30Nodes discover pools through Nostr descriptors and `.btf`; a missing website
31must not stop mining or pool operation.
32
29When a source is an HTTP(S) `pool_status.json`, the directory links to a 33When a source is an HTTP(S) `pool_status.json`, the directory links to a
30`pool_rounds.json` proof file next to it. That file records the block height, 34`pool_rounds.json` proof file next to it. That file records the block height,
31block hash, coinbase txid, coinbase value, pool fee, total shares, per-address 35block hash, coinbase txid, coinbase value, pool fee, total shares, per-address
@@ -41,3 +45,17 @@ Ranking order is intentionally simple:
41 45
42An entry is considered online when the source says the pool is running and its 46An entry is considered online when the source says the pool is running and its
43status file is no more than 120 seconds old. 47status file is no more than 120 seconds old.
48
49## Recommended Hostnames
50
51Use:
52
53```text
54pool.bitflash.network human-readable pool directory
55api.bitflash.network/pools.json
56status.bitflash.network relay/node health, not pool ranking
57docs.bitflash.network operator setup guide
58```
59
60The GUI may use the API as a shortcut for humans, but it should keep the
61existing decentralized pool discovery path as the source of truth.
diff --git a/docs/release-verification.md b/docs/release-verification.md
index 32de74f..408bab9 100644
--- a/docs/release-verification.md
+++ b/docs/release-verification.md
@@ -34,6 +34,16 @@ From a shell with `curl` or `wget`, `sha256sum` and `gpg`:
34scripts/verify-release.sh latest 34scripts/verify-release.sh latest
35``` 35```
36 36
37The public release host should expose latest-file aliases for convenience:
38
39```text
40https://releases.bitflash.network/SHA256SUMS
41https://releases.bitflash.network/SHA256SUMS.asc
42```
43
44Those aliases must point at the current versioned directory. They do not replace
45signature verification; they only make the common manual path shorter.
46
37For a specific release: 47For a specific release:
38 48
39```bash 49```bash
diff --git a/docs/site.md b/docs/site.md
new file mode 100644
index 0000000..522da10
--- /dev/null
+++ b/docs/site.md
@@ -0,0 +1,115 @@
1# Public Site Contract
2
3The public Bitflash sites are generated from this repository. If a deployed
4page and the source disagree, the deployed page is stale.
5
6## Canonical Navigation
7
8Every public page should use the same top navigation:
9
10| label | target |
11|---|---|
12| home | `https://bitflash.network/` |
13| code | `https://git.bitflash.network/bitflash/bitflash` |
14| docs | `https://docs.bitflash.network/` |
15| downloads | `https://releases.bitflash.network/` |
16| explorer | `https://explorer.bitflash.network/` |
17| status | `https://status.bitflash.network/` |
18
19The `code` link intentionally points at the repository, not the Forgejo
20instance root. A visitor choosing "code" wants the source tree.
21
22## Documentation Pages To Publish
23
24The docs index should expose these source files:
25
26| page | source | why it matters |
27|---|---|---|
28| Manual | `README.md` | run, mine, recover, operate |
29| Key derivation and address format | `docs/derivation.md` | independent wallet recovery |
30| Release verification | `docs/release-verification.md` | pinned signing-key flow |
31| Dependencies | `docs/dependencies.md` | pinned secp256k1/RandomX supply chain |
32| Rendezvous and .btf addressing | `docs/rendezvous.md` | network design and caveats |
33| Tor mode | `docs/tor.md` | `-tor`, `-socks`, onion relay operation |
34| Fair launch verifier | `docs/fair-launch.md` | no-premine launch audit |
35| UTXO set commitment | `docs/utxo-commitment.md` | deterministic supply/root audit |
36| UTXO inclusion proofs | `docs/utxo-proofs.md` | proof for one unspent output |
37| Mining calculator | `docs/mining-calculator.md` | local WhatToMine-style estimates |
38| Public pool directory | `docs/pool-directory.md` | `pool_status.json`, `pools.json`, ranking |
39
40## Release Mirror Aliases
41
42Each release remains under its versioned directory:
43
44```text
45https://releases.bitflash.network/v1.2.18/SHA256SUMS
46https://releases.bitflash.network/v1.2.18/SHA256SUMS.asc
47```
48
49The release root should also alias the latest files:
50
51```text
52https://releases.bitflash.network/SHA256SUMS
53https://releases.bitflash.network/SHA256SUMS.asc
54https://releases.bitflash.network/Bitflash-latest-windows.zip
55https://releases.bitflash.network/Bitflash-latest-x86_64.AppImage
56https://releases.bitflash.network/bitflash-node-latest-x86_64
57```
58
59The aliases are convenience only. Verification must still require the pinned
60release key documented in `docs/release-verification.md`.
61
62## Static Explorer
63
64`scripts/build-explorer.py` writes a static explorer with no backend:
65
66```text
67index.html
68style.css
69explorer.js
70logo.png
71blocks.json
72block/<height>.json
73```
74
75Serve it with a strict content policy:
76
77```text
78default-src 'none';
79img-src 'self';
80style-src 'self';
81script-src 'self';
82connect-src 'self';
83base-uri 'none';
84form-action 'none';
85frame-ancestors 'none';
86```
87
88The viewer must never use `innerHTML` for chain data. A malicious transaction
89must be rendered as text, not interpreted as markup.
90
91## Cache Policy
92
93Recommended defaults:
94
95| path | cache |
96|---|---|
97| HTML pages | `public, max-age=0, must-revalidate` |
98| `status.json` | `no-store` |
99| `blocks.json`, `block/*.json` | `public, max-age=0, must-revalidate` |
100| release assets | `public, max-age=14400, must-revalidate` |
101| `SHA256SUMS`, signatures, keys | `public, max-age=0, must-revalidate` |
102
103## Security Headers
104
105Recommended for all static pages:
106
107```text
108Strict-Transport-Security: max-age=31536000; includeSubDomains
109X-Content-Type-Options: nosniff
110Referrer-Policy: no-referrer
111Permissions-Policy: interest-cohort=(), geolocation=(), camera=(), microphone=()
112```
113
114Forgejo has its own application headers and should not blindly inherit the
115static-site CSP.
diff --git a/docs/tor.md b/docs/tor.md
index 087c822..69b29f1 100644
--- a/docs/tor.md
+++ b/docs/tor.md
@@ -25,6 +25,17 @@ bitflash -tor=127.0.0.1:9150
25bitflash -tor=[::1]:9050 25bitflash -tor=[::1]:9050
26``` 26```
27 27
28For a SOCKS5 proxy that is not Tor, use `-socks` directly:
29
30```bash
31bitflash -socks=127.0.0.1:9050
32bitflash -socks=[::1]:9050
33```
34
35If both `-tor` and `-socks` are present, Tor mode wins and the node prints a
36warning. That keeps a command line with `-tor` from silently becoming a custom
37proxy route.
38
28When Tor mode is enabled: 39When Tor mode is enabled:
29 40
30- outbound Nostr relay dials use Tor; 41- outbound Nostr relay dials use Tor;
@@ -34,6 +45,24 @@ When Tor mode is enabled:
34- direct `.onion` dials are refused unless a SOCKS5/Tor proxy is enabled; 45- direct `.onion` dials are refused unless a SOCKS5/Tor proxy is enabled;
35- plain HTTP external-IP probes are skipped. 46- plain HTTP external-IP probes are skipped.
36 47
48Tor mode affects outbound discovery and relay dials. It does not encrypt
49`wallet.dat`, hide mining rewards on-chain, or change the `.btf` identity. A
50relay still sees the TCP client that connected to it; with Tor mode that client
51is the Tor exit or onion circuit endpoint instead of the user's direct IP.
52
53## What to test
54
55Use the public status page only as a rough health check. To prove the local node
56is using Tor/SOCKS, test from the node machine:
57
58```bash
59bitflash -tor -debug
60```
61
62Then confirm in `debug.log` that Nostr and rendezvous dials report the proxy
63path. A DNS leak test should show no local resolver lookup for relay hostnames:
64the SOCKS5 request uses the domain-name form and asks the proxy to resolve it.
65
37## Onion rendezvous relay 66## Onion rendezvous relay
38 67
39An operator can expose a Bitflash rendezvous relay as a Tor hidden service. The 68An operator can expose a Bitflash rendezvous relay as a Tor hidden service. The
diff --git a/scripts/build-explorer.py b/scripts/build-explorer.py
index 97a7f4e..dbb8e17 100755
--- a/scripts/build-explorer.py
+++ b/scripts/build-explorer.py
@@ -4,6 +4,7 @@
4import argparse 4import argparse
5import hashlib 5import hashlib
6import json 6import json
7import shutil
7import sys 8import sys
8from pathlib import Path 9from pathlib import Path
9 10
@@ -148,6 +149,11 @@ def build_explorer(raw_blocks, out_dir):
148 encoding="ascii", 149 encoding="ascii",
149 ) 150 )
150 (out / "index.html").write_text(INDEX_HTML, encoding="utf-8") 151 (out / "index.html").write_text(INDEX_HTML, encoding="utf-8")
152 (out / "style.css").write_text(STYLE_CSS, encoding="utf-8")
153 (out / "explorer.js").write_text(EXPLORER_JS, encoding="utf-8")
154 logo = Path(__file__).resolve().parents[1] / "docs" / "logo.png"
155 if logo.exists():
156 shutil.copyfile(logo, out / "logo.png")
151 return len(blocks), len(blocks) - 1 157 return len(blocks), len(blocks) - 1
152 158
153 159
@@ -167,43 +173,26 @@ INDEX_HTML = r"""<!doctype html>
167<meta charset="utf-8"> 173<meta charset="utf-8">
168<meta name="viewport" content="width=device-width, initial-scale=1"> 174<meta name="viewport" content="width=device-width, initial-scale=1">
169<title>Bitflash Block Explorer</title> 175<title>Bitflash Block Explorer</title>
170<style> 176<meta name="description" content="Read-only Bitflash block explorer generated from local blk*.dat files.">
171 :root { color-scheme: light dark; --bg:#fff; --fg:#111; --mut:#666; --line:#e2e2e2; --card:#fafafa; --acc:#2b6cb0; } 177<link rel="icon" href="logo.png" type="image/png">
172 @media (prefers-color-scheme: dark){ :root{ --bg:#0e0f11; --fg:#e6e6e6; --mut:#9aa0a6; --line:#26282c; --card:#16181b; --acc:#63b3ed; } } 178<link rel="stylesheet" href="style.css">
173 * { box-sizing: border-box; } 179<script src="explorer.js" defer></script>
174 body { font: 15px/1.5 system-ui, sans-serif; margin:0; background:var(--bg); color:var(--fg); }
175 header { padding:20px 24px; border-bottom:1px solid var(--line); display:flex; gap:16px; align-items:baseline; flex-wrap:wrap; }
176 h1 { font-size:18px; margin:0; }
177 h2 { font-size:16px; }
178 .mut { color:var(--mut); }
179 .wrap { padding:16px 24px; max-width:1100px; margin:0 auto; }
180 input { width:100%; max-width:420px; padding:9px 12px; border:1px solid var(--line); border-radius:8px; background:var(--card); color:var(--fg); font-size:14px; }
181 table { width:100%; border-collapse:collapse; margin-top:12px; }
182 th,td { text-align:left; padding:9px 10px; border-bottom:1px solid var(--line); font-variant-numeric:tabular-nums; }
183 th { font-size:12px; text-transform:uppercase; letter-spacing:.05em; color:var(--mut); }
184 tr.blk { cursor:pointer; }
185 tr.blk:hover td { background:var(--card); }
186 code { font-size:12px; word-break:break-all; }
187 .scroll { overflow-x:auto; }
188 .card { border:1px solid var(--line); border-radius:10px; background:var(--card); padding:14px 16px; margin-top:12px; }
189 .kv { display:grid; grid-template-columns:150px 1fr; gap:4px 12px; font-size:13px; }
190 .kv div:nth-child(odd){ color:var(--mut); }
191 .tx { border-top:1px solid var(--line); padding:10px 0; }
192 .io { display:grid; grid-template-columns:1fr 1fr; gap:16px; }
193 @media (max-width:640px){ .io{ grid-template-columns:1fr; } .kv{ grid-template-columns:110px 1fr; } }
194 .pill { display:inline-block; font-size:11px; padding:1px 7px; border-radius:99px; border:1px solid var(--line); color:var(--mut); }
195 button.link { background:none; border:none; color:var(--acc); cursor:pointer; font:inherit; padding:0; }
196 .note { color:var(--mut); font-size:13px; max-width:820px; }
197</style>
198</head> 180</head>
199<body> 181<body>
200<header> 182<header class="topbar">
201 <h1>Bitflash Block Explorer</h1> 183 <a class="brand" href="https://bitflash.network/"><img src="logo.png" width="22" height="22" alt="">Bitflash</a>
202 <span class="mut" id="tip"></span> 184 <nav aria-label="Bitflash sites">
203 <span style="flex:1"></span> 185 <a href="https://bitflash.network/">home</a>
204 <button class="link" id="home" style="display:none">back to blocks</button> 186 <a href="https://git.bitflash.network/bitflash/bitflash">code</a>
187 <a href="https://docs.bitflash.network/">docs</a>
188 <a href="https://releases.bitflash.network/">downloads</a>
189 <a href="https://explorer.bitflash.network/" aria-current="page">explorer</a>
190 <a href="https://status.bitflash.network/">status</a>
191 </nav>
192 <span class="meta" id="tip"></span>
205</header> 193</header>
206<div class="wrap"> 194<div class="wrap">
195 <h1>Block explorer <button class="link" id="home" hidden>back to blocks</button></h1>
207 <p class="note">Read-only view of the main chain: blocks, their transactions, and the coinbase payout. There is no mempool and no per-address history here.</p> 196 <p class="note">Read-only view of the main chain: blocks, their transactions, and the coinbase payout. There is no mempool and no per-address history here.</p>
208 <div id="listview"> 197 <div id="listview">
209 <input id="q" placeholder="Search by height or block hash"> 198 <input id="q" placeholder="Search by height or block hash">
@@ -213,9 +202,63 @@ INDEX_HTML = r"""<!doctype html>
213 </table></div> 202 </table></div>
214 <p class="mut" id="more"></p> 203 <p class="mut" id="more"></p>
215 </div> 204 </div>
216 <div id="detail" style="display:none"></div> 205 <div id="detail" hidden></div>
217</div> 206</div>
218<script> 207</body>
208</html>
209"""
210
211
212STYLE_CSS = r""":root{
213 color-scheme: light dark;
214 --bg:#fff; --bg-2:#f6f6f6; --fg:#111; --dim:#6b6b6b; --line:#d7d7d7; --line-hard:#111;
215 --accent:#0b7a34; --accent-2:#12a047; --bar:3rem;
216 --mono: ui-monospace,"Cascadia Mono","Segoe UI Mono",Consolas,"DejaVu Sans Mono","Liberation Mono","Courier New",monospace;
217}
218@media (prefers-color-scheme: dark){:root{
219 --bg:#000; --bg-2:#0b0b0b; --fg:#dcdcdc; --dim:#8a8a8a; --line:#242424; --line-hard:#4a4a4a;
220 --accent:#2ee06a; --accent-2:#6bff9f;
221}}
222*{box-sizing:border-box;}
223html,body{margin:0;background:var(--bg);color:var(--fg);font:15px/1.55 var(--mono);-webkit-text-size-adjust:100%;}
224a{color:var(--accent);}
225.topbar{display:flex;align-items:center;gap:1rem;flex-wrap:wrap;min-height:var(--bar);
226 padding:.5rem clamp(.9rem,2.5vw,3rem);border-bottom:1px solid var(--line);position:sticky;top:0;background:var(--bg);z-index:5;}
227.brand{display:flex;align-items:center;gap:.6rem;text-decoration:none;color:var(--fg);font-weight:bold;}
228.brand img{width:22px;height:22px;display:block;}
229.topbar nav{display:flex;flex-wrap:wrap;gap:.15rem 1.4rem;font-size:.86rem;}
230.topbar nav a{text-decoration:none;color:var(--dim);}
231.topbar nav a::before{content:"/";color:var(--line-hard);margin-right:.3rem;}
232.topbar nav a:hover{color:var(--fg);text-decoration:underline;}
233.topbar nav a[aria-current="page"]{color:var(--accent);font-weight:bold;}
234.topbar nav a[aria-current="page"]::before{color:var(--accent);}
235.topbar .meta{margin-left:auto;font-size:.76rem;color:var(--dim);letter-spacing:.06em;}
236.wrap{padding:1.4rem clamp(.9rem,2.5vw,3rem) 4rem;}
237h1{font-size:1rem;margin:0 0 .3rem;letter-spacing:.02em;}
238h2{font-size:.95rem;margin:1.2rem 0 .4rem;}
239.mut{color:var(--dim);}
240.note{color:var(--dim);font-size:.82rem;max-width:60rem;margin:.2rem 0 1rem;}
241input{width:100%;max-width:28rem;padding:.5rem .7rem;border:1px solid var(--line);background:var(--bg-2);color:var(--fg);font:inherit;font-size:.86rem;}
242input:focus{outline:none;border-color:var(--accent);}
243.scroll{overflow-x:auto;}
244table{width:100%;border-collapse:collapse;margin-top:.8rem;font-size:.86rem;}
245th,td{text-align:left;padding:.5rem .6rem;border-bottom:1px solid var(--line);font-variant-numeric:tabular-nums;}
246th{font-size:.72rem;text-transform:uppercase;letter-spacing:.1em;color:var(--dim);font-weight:normal;}
247tr.blk{cursor:pointer;}
248tr.blk:hover td{background:var(--bg-2);}
249code{font-size:.8rem;word-break:break-all;color:var(--fg);}
250.card{border:1px solid var(--line);background:var(--bg-2);padding:.9rem 1rem;margin-top:.8rem;}
251.kv{display:grid;grid-template-columns:11rem 1fr;gap:.25rem .8rem;font-size:.82rem;}
252.kv div:nth-child(odd){color:var(--dim);}
253.tx{border-top:1px solid var(--line);padding:.7rem 0;}
254.io{display:grid;grid-template-columns:1fr 1fr;gap:1rem;}
255@media (max-width:640px){.io{grid-template-columns:1fr;}.kv{grid-template-columns:7rem 1fr;}.topbar .meta{margin-left:0;width:100%;}}
256.pill{display:inline-block;font-size:.68rem;padding:.05rem .5rem;border:1px solid var(--line);color:var(--dim);text-transform:uppercase;letter-spacing:.05em;}
257button.link{background:none;border:none;color:var(--accent);cursor:pointer;font:inherit;padding:0;text-decoration:underline;}
258"""
259
260
261EXPLORER_JS = r"""
219var SUM = [], SHOWN = 200; 262var SUM = [], SHOWN = 200;
220function btf(sat){ return (sat/1e8).toFixed(8).replace(/0+$/,'').replace(/\.$/,'')+" BTF"; } 263function btf(sat){ return (sat/1e8).toFixed(8).replace(/0+$/,'').replace(/\.$/,'')+" BTF"; }
221function ts(t){ return new Date(t*1000).toISOString().replace('T',' ').replace('.000Z',' UTC'); } 264function ts(t){ return new Date(t*1000).toISOString().replace('T',' ').replace('.000Z',' UTC'); }
@@ -310,25 +353,22 @@ function openBlock(h){
310 })); 353 }));
311 append(tx, head, io); append(detail, tx); 354 append(tx, head, io); append(detail, tx);
312 }); 355 });
313 document.getElementById('listview').style.display='none'; 356 document.getElementById('listview').hidden = true;
314 detail.style.display='block'; 357 detail.hidden = false;
315 document.getElementById('home').style.display='inline'; 358 document.getElementById('home').hidden = false;
316 window.scrollTo(0,0); 359 window.scrollTo(0,0);
317 }); 360 });
318} 361}
319document.getElementById('home').onclick=function(){ 362document.getElementById('home').onclick=function(){
320 document.getElementById('detail').style.display='none'; 363 document.getElementById('detail').hidden = true;
321 document.getElementById('listview').style.display='block'; 364 document.getElementById('listview').hidden = false;
322 this.style.display='none'; 365 this.hidden = true;
323}; 366};
324document.getElementById('q').oninput=function(){ renderList(this.value); }; 367document.getElementById('q').oninput=function(){ renderList(this.value); };
325fetch('blocks.json').then(function(r){return r.json();}).then(function(d){ 368fetch('blocks.json').then(function(r){return r.json();}).then(function(d){
326 SUM=d.blocks; document.getElementById('tip').textContent='tip height '+d.tipHeight+' - '+d.count+' blocks'; 369 SUM=d.blocks; document.getElementById('tip').textContent='tip height '+d.tipHeight+' - '+d.count+' blocks';
327 renderList(''); 370 renderList('');
328}); 371});
329</script>
330</body>
331</html>
332""" 372"""
333 373
334 374
diff --git a/tests/scripts/test_bitflash_tools.py b/tests/scripts/test_bitflash_tools.py
index 4b18d1e..3b4c846 100644
--- a/tests/scripts/test_bitflash_tools.py
+++ b/tests/scripts/test_bitflash_tools.py
@@ -159,8 +159,11 @@ def compare_golden(name, actual, update):
159 path.parent.mkdir(parents=True, exist_ok=True) 159 path.parent.mkdir(parents=True, exist_ok=True)
160 path.write_bytes(actual) 160 path.write_bytes(actual)
161 return 161 return
162 expected = path.read_bytes() 162 # The tools promise canonical LF-terminated JSON. On Windows checkouts a
163 if actual != expected: 163 # fixture may still appear as CRLF in the working tree; normalize only line
164 # endings so content, field order and compact serialization still drift-test.
165 expected = path.read_bytes().replace(b"\r\n", b"\n")
166 if actual.replace(b"\r\n", b"\n") != expected:
164 raise AssertionError("golden mismatch: %s" % name) 167 raise AssertionError("golden mismatch: %s" % name)
165 168
166 169