diff options
| author | Nsable-sh <nsable-sh@noreply.git.bitflash.network> | 2026-08-12 11:58:03 -0300 |
|---|---|---|
| committer | Nsable-sh <nsable-sh@noreply.git.bitflash.network> | 2026-08-12 11:58:03 -0300 |
| commit | b0ed2ce585d2a6fd7c6d8d4db7422734aedccb58 (patch) | |
| tree | 509f8424e4fed7eb00a1fa3294a26da70eb5e7ef | |
| parent | bc3de301d9ed0e5fb5e2034be2e066d77cf23804 (diff) | |
site: document public deployment contract
| -rw-r--r-- | README.md | 17 | ||||
| -rw-r--r-- | docs/mining-calculator.md | 22 | ||||
| -rw-r--r-- | docs/pool-directory.md | 18 | ||||
| -rw-r--r-- | docs/release-verification.md | 10 | ||||
| -rw-r--r-- | docs/site.md | 115 | ||||
| -rw-r--r-- | docs/tor.md | 29 | ||||
| -rwxr-xr-x | scripts/build-explorer.py | 128 | ||||
| -rw-r--r-- | tests/scripts/test_bitflash_tools.py | 7 |
8 files changed, 300 insertions, 46 deletions
| @@ -38,6 +38,8 @@ Worth being precise, because the difference matters if you are relying on it. | |||
| 38 | 38 | ||
| 39 | This is unlinkability between peers, not anonymity against a network observer. | 39 | This is unlinkability between peers, not anonymity against a network observer. |
| 40 | For Tor routing, start the node with `-tor`; see [Tor mode](docs/tor.md). | 40 | For Tor routing, start the node with `-tor`; see [Tor mode](docs/tor.md). |
| 41 | For 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 | |||
| 74 | See [release verification](docs/release-verification.md) for the full release | 76 | See [release verification](docs/release-verification.md) for the full release |
| 75 | audit flow and the maintainer signing step. | 77 | audit flow and the maintainer signing step. |
| 76 | 78 | ||
| 79 | Public mirrors should make the latest checksum files available both under the | ||
| 80 | versioned release directory and at the release root: | ||
| 81 | |||
| 82 | ```text | ||
| 83 | https://releases.bitflash.network/v1.2.18/SHA256SUMS | ||
| 84 | https://releases.bitflash.network/SHA256SUMS # alias to latest | ||
| 85 | ``` | ||
| 86 | |||
| 77 | Bitflash also ships a deterministic UTXO-set commitment tool: | 87 | Bitflash 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 | |||
| 113 | python3 scripts/build-explorer.py ~/.bitflash/blk0001.dat ~/.bitflash/blk0002.dat ./explorer-out | 123 | python3 scripts/build-explorer.py ~/.bitflash/blk0001.dat ~/.bitflash/blk0002.dat ./explorer-out |
| 114 | ``` | 124 | ``` |
| 115 | 125 | ||
| 126 | The generated explorer is static and self-contained: `index.html`, | ||
| 127 | `style.css`, `explorer.js`, `blocks.json`, `block/*.json`, and `logo.png`. It is | ||
| 128 | safe to host behind a strict policy such as `default-src 'none'; img-src 'self'; | ||
| 129 | style-src 'self'; script-src 'self'; connect-src 'self'; base-uri 'none'; | ||
| 130 | form-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 |
| 117 | releases — 1.2.1 fixed a bug that let anyone spend anyone's coins, and 1.2.2 | 133 | releases — 1.2.1 fixed a bug that let anyone spend anyone's coins, and 1.2.2 |
| 118 | added a per-block signature-operation cap. A node on an older build will accept | 134 | added 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. |
| 266 | The files are informational only: nodes still discover pools through Nostr and | 282 | The 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. |
| 284 | See [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 | |||
| 84 | website widget or status dashboard. Until a public price exists, a website | 84 | website widget or status dashboard. Until a public price exists, a website |
| 85 | calculator should keep price as a manual field and label fiat profit as | 85 | calculator should keep price as a manual field and label fiat profit as |
| 86 | speculative. | 86 | speculative. |
| 87 | |||
| 88 | ## Website Fields | ||
| 89 | |||
| 90 | A 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 | |||
| 99 | The 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 | |||
| 107 | Never present the fiat number as a market quote unless it comes from a real | ||
| 108 | public 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 | ||
| 29 | The website is a convenience layer, not a consensus or discovery dependency. | ||
| 30 | Nodes discover pools through Nostr descriptors and `.btf`; a missing website | ||
| 31 | must not stop mining or pool operation. | ||
| 32 | |||
| 29 | When a source is an HTTP(S) `pool_status.json`, the directory links to a | 33 | When 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, |
| 31 | block hash, coinbase txid, coinbase value, pool fee, total shares, per-address | 35 | block hash, coinbase txid, coinbase value, pool fee, total shares, per-address |
| @@ -41,3 +45,17 @@ Ranking order is intentionally simple: | |||
| 41 | 45 | ||
| 42 | An entry is considered online when the source says the pool is running and its | 46 | An entry is considered online when the source says the pool is running and its |
| 43 | status file is no more than 120 seconds old. | 47 | status file is no more than 120 seconds old. |
| 48 | |||
| 49 | ## Recommended Hostnames | ||
| 50 | |||
| 51 | Use: | ||
| 52 | |||
| 53 | ```text | ||
| 54 | pool.bitflash.network human-readable pool directory | ||
| 55 | api.bitflash.network/pools.json | ||
| 56 | status.bitflash.network relay/node health, not pool ranking | ||
| 57 | docs.bitflash.network operator setup guide | ||
| 58 | ``` | ||
| 59 | |||
| 60 | The GUI may use the API as a shortcut for humans, but it should keep the | ||
| 61 | existing 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`: | |||
| 34 | scripts/verify-release.sh latest | 34 | scripts/verify-release.sh latest |
| 35 | ``` | 35 | ``` |
| 36 | 36 | ||
| 37 | The public release host should expose latest-file aliases for convenience: | ||
| 38 | |||
| 39 | ```text | ||
| 40 | https://releases.bitflash.network/SHA256SUMS | ||
| 41 | https://releases.bitflash.network/SHA256SUMS.asc | ||
| 42 | ``` | ||
| 43 | |||
| 44 | Those aliases must point at the current versioned directory. They do not replace | ||
| 45 | signature verification; they only make the common manual path shorter. | ||
| 46 | |||
| 37 | For a specific release: | 47 | For 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 | |||
| 3 | The public Bitflash sites are generated from this repository. If a deployed | ||
| 4 | page and the source disagree, the deployed page is stale. | ||
| 5 | |||
| 6 | ## Canonical Navigation | ||
| 7 | |||
| 8 | Every 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 | |||
| 19 | The `code` link intentionally points at the repository, not the Forgejo | ||
| 20 | instance root. A visitor choosing "code" wants the source tree. | ||
| 21 | |||
| 22 | ## Documentation Pages To Publish | ||
| 23 | |||
| 24 | The 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 | |||
| 42 | Each release remains under its versioned directory: | ||
| 43 | |||
| 44 | ```text | ||
| 45 | https://releases.bitflash.network/v1.2.18/SHA256SUMS | ||
| 46 | https://releases.bitflash.network/v1.2.18/SHA256SUMS.asc | ||
| 47 | ``` | ||
| 48 | |||
| 49 | The release root should also alias the latest files: | ||
| 50 | |||
| 51 | ```text | ||
| 52 | https://releases.bitflash.network/SHA256SUMS | ||
| 53 | https://releases.bitflash.network/SHA256SUMS.asc | ||
| 54 | https://releases.bitflash.network/Bitflash-latest-windows.zip | ||
| 55 | https://releases.bitflash.network/Bitflash-latest-x86_64.AppImage | ||
| 56 | https://releases.bitflash.network/bitflash-node-latest-x86_64 | ||
| 57 | ``` | ||
| 58 | |||
| 59 | The aliases are convenience only. Verification must still require the pinned | ||
| 60 | release 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 | ||
| 67 | index.html | ||
| 68 | style.css | ||
| 69 | explorer.js | ||
| 70 | logo.png | ||
| 71 | blocks.json | ||
| 72 | block/<height>.json | ||
| 73 | ``` | ||
| 74 | |||
| 75 | Serve it with a strict content policy: | ||
| 76 | |||
| 77 | ```text | ||
| 78 | default-src 'none'; | ||
| 79 | img-src 'self'; | ||
| 80 | style-src 'self'; | ||
| 81 | script-src 'self'; | ||
| 82 | connect-src 'self'; | ||
| 83 | base-uri 'none'; | ||
| 84 | form-action 'none'; | ||
| 85 | frame-ancestors 'none'; | ||
| 86 | ``` | ||
| 87 | |||
| 88 | The viewer must never use `innerHTML` for chain data. A malicious transaction | ||
| 89 | must be rendered as text, not interpreted as markup. | ||
| 90 | |||
| 91 | ## Cache Policy | ||
| 92 | |||
| 93 | Recommended 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 | |||
| 105 | Recommended for all static pages: | ||
| 106 | |||
| 107 | ```text | ||
| 108 | Strict-Transport-Security: max-age=31536000; includeSubDomains | ||
| 109 | X-Content-Type-Options: nosniff | ||
| 110 | Referrer-Policy: no-referrer | ||
| 111 | Permissions-Policy: interest-cohort=(), geolocation=(), camera=(), microphone=() | ||
| 112 | ``` | ||
| 113 | |||
| 114 | Forgejo has its own application headers and should not blindly inherit the | ||
| 115 | static-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 | |||
| 25 | bitflash -tor=[::1]:9050 | 25 | bitflash -tor=[::1]:9050 |
| 26 | ``` | 26 | ``` |
| 27 | 27 | ||
| 28 | For a SOCKS5 proxy that is not Tor, use `-socks` directly: | ||
| 29 | |||
| 30 | ```bash | ||
| 31 | bitflash -socks=127.0.0.1:9050 | ||
| 32 | bitflash -socks=[::1]:9050 | ||
| 33 | ``` | ||
| 34 | |||
| 35 | If both `-tor` and `-socks` are present, Tor mode wins and the node prints a | ||
| 36 | warning. That keeps a command line with `-tor` from silently becoming a custom | ||
| 37 | proxy route. | ||
| 38 | |||
| 28 | When Tor mode is enabled: | 39 | When 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 | ||
| 48 | Tor 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 | ||
| 50 | relay still sees the TCP client that connected to it; with Tor mode that client | ||
| 51 | is the Tor exit or onion circuit endpoint instead of the user's direct IP. | ||
| 52 | |||
| 53 | ## What to test | ||
| 54 | |||
| 55 | Use the public status page only as a rough health check. To prove the local node | ||
| 56 | is using Tor/SOCKS, test from the node machine: | ||
| 57 | |||
| 58 | ```bash | ||
| 59 | bitflash -tor -debug | ||
| 60 | ``` | ||
| 61 | |||
| 62 | Then confirm in `debug.log` that Nostr and rendezvous dials report the proxy | ||
| 63 | path. A DNS leak test should show no local resolver lookup for relay hostnames: | ||
| 64 | the 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 | ||
| 39 | An operator can expose a Bitflash rendezvous relay as a Tor hidden service. The | 68 | An 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 @@ | |||
| 4 | import argparse | 4 | import argparse |
| 5 | import hashlib | 5 | import hashlib |
| 6 | import json | 6 | import json |
| 7 | import shutil | ||
| 7 | import sys | 8 | import sys |
| 8 | from pathlib import Path | 9 | from 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 | |||
| 212 | STYLE_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;} | ||
| 223 | html,body{margin:0;background:var(--bg);color:var(--fg);font:15px/1.55 var(--mono);-webkit-text-size-adjust:100%;} | ||
| 224 | a{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;} | ||
| 237 | h1{font-size:1rem;margin:0 0 .3rem;letter-spacing:.02em;} | ||
| 238 | h2{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;} | ||
| 241 | input{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;} | ||
| 242 | input:focus{outline:none;border-color:var(--accent);} | ||
| 243 | .scroll{overflow-x:auto;} | ||
| 244 | table{width:100%;border-collapse:collapse;margin-top:.8rem;font-size:.86rem;} | ||
| 245 | th,td{text-align:left;padding:.5rem .6rem;border-bottom:1px solid var(--line);font-variant-numeric:tabular-nums;} | ||
| 246 | th{font-size:.72rem;text-transform:uppercase;letter-spacing:.1em;color:var(--dim);font-weight:normal;} | ||
| 247 | tr.blk{cursor:pointer;} | ||
| 248 | tr.blk:hover td{background:var(--bg-2);} | ||
| 249 | code{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;} | ||
| 257 | button.link{background:none;border:none;color:var(--accent);cursor:pointer;font:inherit;padding:0;text-decoration:underline;} | ||
| 258 | """ | ||
| 259 | |||
| 260 | |||
| 261 | EXPLORER_JS = r""" | ||
| 219 | var SUM = [], SHOWN = 200; | 262 | var SUM = [], SHOWN = 200; |
| 220 | function btf(sat){ return (sat/1e8).toFixed(8).replace(/0+$/,'').replace(/\.$/,'')+" BTF"; } | 263 | function btf(sat){ return (sat/1e8).toFixed(8).replace(/0+$/,'').replace(/\.$/,'')+" BTF"; } |
| 221 | function ts(t){ return new Date(t*1000).toISOString().replace('T',' ').replace('.000Z',' UTC'); } | 264 | function 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 | } |
| 319 | document.getElementById('home').onclick=function(){ | 362 | document.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 | }; |
| 324 | document.getElementById('q').oninput=function(){ renderList(this.value); }; | 367 | document.getElementById('q').oninput=function(){ renderList(this.value); }; |
| 325 | fetch('blocks.json').then(function(r){return r.json();}).then(function(d){ | 368 | fetch('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 | ||