upleb.uk

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

summaryrefslogtreecommitdiff
path: root/scripts/verify-fair-launch.py
diff options
context:
space:
mode:
authorbitflash-maintainer <noreply@bitflash.invalid>2026-09-05 22:46:52 +0000
committerbitflash-maintainer <noreply@bitflash.invalid>2026-09-05 22:46:52 +0000
commitceae3d076b5ad11ee84729ae43f14e9312896646 (patch)
tree7e9132aa871fdc01cb95268d2f5ea3bb11853ddf /scripts/verify-fair-launch.py
parentbe63e83d79e7e00e6b8ca889a70e6a19886aa1d8 (diff)
parent66e0b9158d050c46b1ff2509faeee3f4573cac1c (diff)
Bring main onto the line this project actually ships
main had drifted to a history with no ancestor in common with the tree we build and deploy. It sat at be63e83 (2026-08-09) with 28 commits; the shipped line, deploy/soak-2026-08-21, had 383. The two shared nothing -- git merge-base returns no result at all -- so anyone cloning this repository landed on a default branch that was neither current nor related to the running node. The 28 commits on main were audited before this merge: every one is already present in the tree by content, including the two that are hardest to see because they were rewritten afterwards -- the payout reorg-safety work (PayoutBlockStillOnChain, since evolved to re-check on every retry rather than once at maturity) and the nostr relay certificate verification (SSL_set1_host, now with a Windows root-store path). Nothing on main is lost by this merge. Recorded as a merge rather than a force-push: main only ever moves forward, its old history stays reachable as the first parent, and this is revertible. The tree is taken wholesale from the merged branch, so main's content is now exactly what was built and tested. What this brings onto main: - compactblock: BIP152-style compact block relay ported from upstream 1.2.23, primitives and unit test only, with the RAND_bytes failure path closed and the reconstruction allocation bounded. - upstream/main 17741d9: isolated testnet parameters and genesis, optional 2 MB large pages for RandomX. Mainnet genesis verified byte-identical through upstream's BuildGenesisBlock() refactor. - deploy: every image built on bookworm so binaries run on Debian 12, and the container build now honours the RandomX and libsecp256k1 pins it had been silently ignoring since before 1.2.15. Gates at this tree: 15/15 selftests, compactblock's 17 checks, consensus constants unchanged against e4f898c apart from the pre-existing MAX_BLOCK_SIZE_GEN, mainnet genesis intact, money-log check ok.
Diffstat (limited to 'scripts/verify-fair-launch.py')
-rw-r--r--scripts/verify-fair-launch.py166
1 files changed, 166 insertions, 0 deletions
diff --git a/scripts/verify-fair-launch.py b/scripts/verify-fair-launch.py
new file mode 100644
index 0000000..c33c8be
--- /dev/null
+++ b/scripts/verify-fair-launch.py
@@ -0,0 +1,166 @@
1#!/usr/bin/env python3
2"""Produce a deterministic Bitflash fair-launch audit report."""
3
4import argparse
5import hashlib
6import sys
7from pathlib import Path
8
9import bitflash_chain as chain
10
11
12def build_report(blocks, source_mode):
13 genesis = blocks[0]
14 launch_outputs = genesis["transactions"][0]["vout"] if genesis["transactions"] else []
15 coinbase_outputs = []
16 total_mined = 0
17 non_coinbase_txs = 0
18
19 for height, block in enumerate(blocks):
20 if not block["transactions"]:
21 continue
22 cb = block["transactions"][0]
23 if chain.is_coinbase(cb):
24 amount = sum(out["value_satoshis"] for out in cb["vout"])
25 total_mined += amount
26 coinbase_outputs.append({
27 "height": height,
28 "block_hash": block["hash"],
29 "coinbase_txid": cb["txid"],
30 "value_satoshis": amount,
31 "value_btf": chain.format_money(amount),
32 "outputs": cb["vout"],
33 })
34 non_coinbase_txs += sum(1 for tx in block["transactions"] if not chain.is_coinbase(tx))
35
36 checks = {
37 "genesis_hash_matches_source": genesis["hash"] == chain.GENESIS_HASH,
38 "genesis_merkle_matches_source": genesis["merkle_root"] == chain.GENESIS_MERKLE,
39 "computed_merkle_matches_header": genesis["computed_merkle_root"] == genesis["merkle_root"],
40 "genesis_has_single_transaction": genesis["tx_count"] == 1,
41 "genesis_transaction_is_coinbase": bool(genesis["transactions"] and chain.is_coinbase(genesis["transactions"][0])),
42 "genesis_supply_is_50_btf": sum(out["value_satoshis"] for out in launch_outputs) == 50 * chain.COIN,
43 }
44
45 report = {
46 "schema": "bitflash-fair-launch-report-1",
47 "deterministic": True,
48 "source_mode": source_mode,
49 "chain": {
50 "name": "Bitflash",
51 "ticker": "BTF",
52 "message_start_hex": chain.MAGIC.hex(),
53 "coin": chain.COIN,
54 },
55 "source_constants": {
56 "genesis_hash": chain.GENESIS_HASH,
57 "genesis_merkle_root": chain.GENESIS_MERKLE,
58 "genesis_time": chain.GENESIS_TIME,
59 "genesis_time_utc": chain.utc(chain.GENESIS_TIME),
60 "genesis_bits": "0x%08x" % chain.GENESIS_BITS,
61 "genesis_nonce": chain.GENESIS_NONCE,
62 "genesis_message": chain.GENESIS_TEXT,
63 },
64 "scanned": {
65 "block_count": len(blocks),
66 "first_height": 0,
67 "last_height": len(blocks) - 1,
68 "non_coinbase_transactions": non_coinbase_txs,
69 "total_coinbase_satoshis": total_mined,
70 "total_coinbase_btf": chain.format_money(total_mined),
71 },
72 "genesis": {
73 "hash": genesis["hash"],
74 "merkle_root": genesis["merkle_root"],
75 "computed_merkle_root": genesis["computed_merkle_root"],
76 "time": genesis["time"],
77 "time_utc": genesis["time_utc"],
78 "bits": genesis["bits"],
79 "nonce": genesis["nonce"],
80 "tx_count": genesis["tx_count"],
81 "coinbase_txid": genesis["transactions"][0]["txid"] if genesis["transactions"] else "",
82 "outputs": launch_outputs,
83 },
84 "coinbase_outputs": coinbase_outputs,
85 "checks": checks,
86 "verdict": "FAIR_LAUNCH_BASELINE_VERIFIED" if all(checks.values()) else "CHECKS_FAILED",
87 "external_timestamping": {
88 "status": "not_embedded",
89 "recommended": "OpenTimestamps",
90 "instructions": [
91 "Save the canonical JSON report.",
92 "Run: ots stamp bitflash-fair-launch-report.json",
93 "Later verify with: ots verify bitflash-fair-launch-report.json.ots",
94 ],
95 },
96 }
97 report["attestation_payload_sha256"] = hashlib.sha256(chain.canonical_bytes(report)).hexdigest()
98 return report
99
100
101def print_text(report):
102 checks = report["checks"]
103 print("Bitflash fair-launch verifier")
104 print("schema: %s" % report["schema"])
105 print("source: %s" % report["source_mode"])
106 print("attestation payload sha256: %s" % report["attestation_payload_sha256"])
107 print()
108 print("Genesis")
109 print(" hash: %s" % report["genesis"]["hash"])
110 print(" merkle: %s" % report["genesis"]["merkle_root"])
111 print(" time: %s" % report["genesis"]["time_utc"])
112 print(" txs: %d" % report["genesis"]["tx_count"])
113 print(" supply: %s BTF" % chain.format_money(sum(o["value_satoshis"] for o in report["genesis"]["outputs"])))
114 print()
115 print("Scanned")
116 print(" blocks: %d" % report["scanned"]["block_count"])
117 print(" height: %d..%d" % (report["scanned"]["first_height"], report["scanned"]["last_height"]))
118 print(" minted: %s BTF" % report["scanned"]["total_coinbase_btf"])
119 print(" normal transactions in range: %d" % report["scanned"]["non_coinbase_transactions"])
120 print()
121 print("Checks")
122 for key in sorted(checks):
123 print(" %s %s" % ("OK " if checks[key] else "FAIL", key))
124 print()
125 print("Verdict: %s" % report["verdict"])
126 print()
127 print("OpenTimestamps")
128 print(" canonical JSON can be stamped later with: ots stamp <report.json>")
129
130
131def main(argv):
132 ap = argparse.ArgumentParser(description=__doc__)
133 ap.add_argument("--datadir", default=chain.default_datadir(), help="Bitflash data directory")
134 ap.add_argument("--max-blocks", type=int, default=100, help="number of blocks to scan from blk*.dat")
135 ap.add_argument("--json", action="store_true", help="print canonical JSON")
136 ap.add_argument("--out", help="write canonical JSON to this path")
137 ap.add_argument("--reconstruct-genesis", action="store_true",
138 help="do not read disk; reconstruct only the genesis block from source constants")
139 args = ap.parse_args(argv)
140
141 if args.reconstruct_genesis:
142 blocks = [chain.reconstructed_genesis()]
143 source_mode = "reconstructed-genesis"
144 else:
145 blocks = chain.read_blocks(args.datadir, args.max_blocks)
146 if not blocks:
147 raise SystemExit("no Bitflash blocks found in %s; use --reconstruct-genesis for a source-only check" % args.datadir)
148 source_mode = "local-block-files"
149
150 try:
151 report = build_report(blocks, source_mode)
152 except chain.ParseError as e:
153 raise SystemExit(str(e))
154 data = chain.canonical_bytes(report)
155
156 if args.out:
157 Path(args.out).write_bytes(data)
158 if args.json:
159 sys.stdout.buffer.write(data)
160 else:
161 print_text(report)
162 return 0 if report["verdict"] == "FAIR_LAUNCH_BASELINE_VERIFIED" else 1
163
164
165if __name__ == "__main__":
166 raise SystemExit(main(sys.argv[1:]))