upleb.uk

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

summaryrefslogtreecommitdiff
path: root/scripts/verify-utxo-set.py
diff options
context:
space:
mode:
authorbitflash-maintainer <noreply@bitflash.invalid>2026-09-06 01:53:03 +0000
committerbitflash-maintainer <noreply@bitflash.invalid>2026-09-06 01:53:03 +0000
commitef9f06d43fc6577c15df2256abbb25b20d4227b8 (patch)
tree7e9132aa871fdc01cb95268d2f5ea3bb11853ddf /scripts/verify-utxo-set.py
parentbe63e83d79e7e00e6b8ca889a70e6a19886aa1d8 (diff)
parentceae3d076b5ad11ee84729ae43f14e9312896646 (diff)
Merge branch 'chore/main-catchup-2026-08-26' into 'main'HEADmain
Bring main onto the line this project actually ships (incl. the 1.2.23 compactblock port) Closes #6, #7, #8, #11, #35, #34, #29, #28, #27, and #16 See merge request !16
Diffstat (limited to 'scripts/verify-utxo-set.py')
-rw-r--r--scripts/verify-utxo-set.py131
1 files changed, 131 insertions, 0 deletions
diff --git a/scripts/verify-utxo-set.py b/scripts/verify-utxo-set.py
new file mode 100644
index 0000000..b5bf3d5
--- /dev/null
+++ b/scripts/verify-utxo-set.py
@@ -0,0 +1,131 @@
1#!/usr/bin/env python3
2"""Build a deterministic Bitflash UTXO-set commitment from local blk*.dat."""
3
4import argparse
5import hashlib
6import sys
7from pathlib import Path
8
9import bitflash_chain as chain
10
11
12def build_report(raw_blocks, include_utxos=False, strict_duplicates=False):
13 blocks = chain.select_main_chain(raw_blocks)
14 state = chain.apply_blocks(blocks, strict_duplicates=strict_duplicates)
15 utxos = state["utxos"]
16 stats = state["stats"]
17 tip = blocks[-1]
18 root = chain.utxo_root(utxos)
19 checks = {
20 "genesis_hash_matches_source": blocks[0]["hash"] == chain.GENESIS_HASH,
21 "genesis_merkle_matches_source": blocks[0]["merkle_root"] == chain.GENESIS_MERKLE,
22 "all_scanned_merkle_roots_match": all(b["computed_merkle_root"] == b["merkle_root"] for b in blocks),
23 "no_missing_spends": len(stats["missing_spends"]) == 0,
24 }
25 warnings = {
26 "legacy_duplicate_output_overwrites": len(stats["duplicate_outputs"]),
27 }
28 report = {
29 "schema": "bitflash-utxo-set-commitment-1",
30 "deterministic": True,
31 "chain": {
32 "name": "Bitflash",
33 "ticker": "BTF",
34 "message_start_hex": chain.MAGIC.hex(),
35 "coin": chain.COIN,
36 },
37 "commitment": {
38 "algorithm": "sorted-outpoint-merkle-sha256",
39 "leaf_domain": "BTFUTXO1",
40 "node_domain": "BTFNODE1",
41 "root": root,
42 },
43 "scanned": {
44 "raw_block_count": len(raw_blocks),
45 "side_blocks_ignored": len(raw_blocks) - len(blocks),
46 "main_chain_block_count": len(blocks),
47 "first_height": 0,
48 "last_height": len(blocks) - 1,
49 "tip_hash": tip["hash"],
50 "tip_time": tip["time"],
51 "tip_time_utc": tip["time_utc"],
52 },
53 "stats": stats,
54 "checks": checks,
55 "warnings": warnings,
56 "verdict": "UTXO_COMMITMENT_VERIFIED" if all(checks.values()) else "CHECKS_FAILED",
57 }
58 if include_utxos:
59 report["utxos"] = {k: utxos[k] for k in sorted(utxos)}
60 return report
61
62
63def print_text(report):
64 data_hash = hashlib.sha256(chain.canonical_bytes(report)).hexdigest()
65 print("Bitflash UTXO-set commitment verifier")
66 print("schema: %s" % report["schema"])
67 print("canonical report sha256: %s" % data_hash)
68 print()
69 print("Commitment")
70 print(" root: %s" % report["commitment"]["root"])
71 print(" algo: %s" % report["commitment"]["algorithm"])
72 print()
73 print("Scanned")
74 print(" main chain blocks: %d" % report["scanned"]["main_chain_block_count"])
75 print(" raw disk blocks: %d" % report["scanned"]["raw_block_count"])
76 print(" side ignored: %d" % report["scanned"]["side_blocks_ignored"])
77 print(" height: %d..%d" % (report["scanned"]["first_height"], report["scanned"]["last_height"]))
78 print(" tip: %s" % report["scanned"]["tip_hash"])
79 print()
80 print("Supply")
81 print(" utxos: %d" % report["stats"]["utxo_count"])
82 print(" supply: %s BTF" % report["stats"]["total_unspent_btf"])
83 print(" mined: %s BTF" % report["stats"]["total_coinbase_btf"])
84 print(" delta: %s BTF" % report["stats"]["coinbase_minus_unspent_btf"])
85 print(" fees: %s BTF" % report["stats"]["total_fees_btf"])
86 if report["warnings"]["legacy_duplicate_output_overwrites"]:
87 print(" legacy duplicate overwrites: %d" % report["warnings"]["legacy_duplicate_output_overwrites"])
88 print()
89 print("Checks")
90 for key in sorted(report["checks"]):
91 print(" %s %s" % ("OK " if report["checks"][key] else "FAIL", key))
92 print()
93 print("Verdict: %s" % report["verdict"])
94 print()
95 print("OpenTimestamps")
96 print(" canonical JSON can be stamped later with: ots stamp <report.json>")
97
98
99def main(argv):
100 ap = argparse.ArgumentParser(description=__doc__)
101 ap.add_argument("--datadir", default=chain.default_datadir(), help="Bitflash data directory")
102 ap.add_argument("--max-blocks", type=int, default=0,
103 help="number of blocks to scan; 0 means all block files")
104 ap.add_argument("--include-utxos", action="store_true",
105 help="include the full sorted UTXO map in JSON output")
106 ap.add_argument("--strict-duplicates", action="store_true",
107 help="fail instead of using legacy last-write-wins duplicate output handling")
108 ap.add_argument("--json", action="store_true", help="print canonical JSON")
109 ap.add_argument("--out", help="write canonical JSON to this path")
110 args = ap.parse_args(argv)
111
112 blocks = chain.read_blocks(args.datadir, args.max_blocks)
113 if not blocks:
114 raise SystemExit("no Bitflash blocks found in %s" % args.datadir)
115 try:
116 report = build_report(blocks, include_utxos=args.include_utxos,
117 strict_duplicates=args.strict_duplicates)
118 except chain.ParseError as e:
119 raise SystemExit(str(e))
120 data = chain.canonical_bytes(report)
121 if args.out:
122 Path(args.out).write_bytes(data)
123 if args.json:
124 sys.stdout.buffer.write(data)
125 else:
126 print_text(report)
127 return 0 if report["verdict"] == "UTXO_COMMITMENT_VERIFIED" else 1
128
129
130if __name__ == "__main__":
131 raise SystemExit(main(sys.argv[1:]))