upleb.uk

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

summaryrefslogtreecommitdiff
path: root/tests/scripts/test_bitflash_tools.py
diff options
context:
space:
mode:
authorNsable-sh <nsable-sh@noreply.git.bitflash.network>2026-08-11 15:11:40 -0300
committerNsable-sh <nsable-sh@noreply.git.bitflash.network>2026-08-11 15:11:40 -0300
commit0270b35528388241a6e6b843503cea342c640c30 (patch)
treee7eaaa70a51a57b827fea0a971c1cb9a444b7fba /tests/scripts/test_bitflash_tools.py
parentcaa6a9668fc2b2ed1192f7ebdc0b78621f45898d (diff)
tests: add deterministic chain tool fixtures
Diffstat (limited to 'tests/scripts/test_bitflash_tools.py')
-rw-r--r--tests/scripts/test_bitflash_tools.py178
1 files changed, 177 insertions, 1 deletions
diff --git a/tests/scripts/test_bitflash_tools.py b/tests/scripts/test_bitflash_tools.py
index ff569e1..4b18d1e 100644
--- a/tests/scripts/test_bitflash_tools.py
+++ b/tests/scripts/test_bitflash_tools.py
@@ -12,6 +12,7 @@ from pathlib import Path
12 12
13ROOT = Path(__file__).resolve().parents[2] 13ROOT = Path(__file__).resolve().parents[2]
14SCRIPTS = ROOT / "scripts" 14SCRIPTS = ROOT / "scripts"
15FIXTURES = ROOT / "tests" / "fixtures" / "chain-tools"
15sys.path.insert(0, str(SCRIPTS)) 16sys.path.insert(0, str(SCRIPTS))
16 17
17import bitflash_chain as chain # noqa: E402 18import bitflash_chain as chain # noqa: E402
@@ -45,12 +46,130 @@ def write_block_file(path, blocks):
45 f.write(payload) 46 f.write(payload)
46 47
47 48
49def p2pkh_script(byte):
50 return "76a914%s88ac" % (bytes([byte]) * 20).hex()
51
52
53def make_tx(vin, vout):
54 tx = {
55 "version": 1,
56 "vin": vin,
57 "vout": vout,
58 "lock_time": 0,
59 }
60 tx["txid"] = chain.hash_hex(chain.ser_tx(tx))
61 return tx
62
63
64def make_coinbase(label, script_byte, value=50 * chain.COIN):
65 return make_tx(
66 [{
67 "prevout_hash": "0" * 64,
68 "prevout_n": 0xFFFFFFFF,
69 "script_sig_hex": label.encode("ascii").hex(),
70 "sequence": 0xFFFFFFFF,
71 }],
72 [{
73 "value_satoshis": value,
74 "value_btf": chain.format_money(value),
75 "script_pub_key_hex": p2pkh_script(script_byte),
76 }],
77 )
78
79
80def make_spend(prev_txid, prev_n, outputs, label):
81 return make_tx(
82 [{
83 "prevout_hash": prev_txid,
84 "prevout_n": prev_n,
85 "script_sig_hex": label.encode("ascii").hex(),
86 "sequence": 0xFFFFFFFF,
87 }],
88 [{
89 "value_satoshis": value,
90 "value_btf": chain.format_money(value),
91 "script_pub_key_hex": p2pkh_script(script_byte),
92 } for value, script_byte in outputs],
93 )
94
95
96def make_block(prev_hash, txs, ntime, nonce):
97 merkle = chain.merkle_root([tx["txid"] for tx in txs])
98 header = (
99 struct.pack("<i", 1)
100 + bytes.fromhex(prev_hash)[::-1]
101 + bytes.fromhex(merkle)[::-1]
102 + struct.pack("<III", ntime, chain.GENESIS_BITS, nonce)
103 )
104 block = {
105 "hash": chain.hash_hex(header),
106 "version": 1,
107 "previous_hash": prev_hash,
108 "merkle_root": merkle,
109 "computed_merkle_root": merkle,
110 "time": ntime,
111 "time_utc": chain.utc(ntime),
112 "bits": "0x%08x" % chain.GENESIS_BITS,
113 "nonce": nonce,
114 "tx_count": len(txs),
115 "transactions": txs,
116 "source_file": "synthetic",
117 "source_offset": 0,
118 "size": 80 + len(chain.ser_compact_size(len(txs))) + sum(len(chain.ser_tx(tx)) for tx in txs),
119 }
120 return block
121
122
123def fixture_chain():
124 genesis = chain.reconstructed_genesis()
125 spend1 = make_spend(
126 chain.GENESIS_MERKLE,
127 0,
128 [(30 * chain.COIN, 0x22), (19 * chain.COIN, 0x33)],
129 "spend genesis with 1 BTF fee",
130 )
131 block1 = make_block(
132 genesis["hash"],
133 [make_coinbase("fixture coinbase 1", 0x11), spend1],
134 chain.GENESIS_TIME + 120,
135 4001,
136 )
137 spend2 = make_spend(
138 spend1["txid"],
139 0,
140 [(12 * chain.COIN, 0x44), (17 * chain.COIN + 50_000_000, 0x55)],
141 "split spend with 0.5 BTF fee",
142 )
143 block2 = make_block(
144 block1["hash"],
145 [make_coinbase("fixture coinbase 2", 0x66), spend2],
146 chain.GENESIS_TIME + 240,
147 4002,
148 )
149 return [genesis, block1, block2], {
150 "genesis_outpoint": "%s:0" % chain.GENESIS_MERKLE,
151 "first_spend_txid": spend1["txid"],
152 "proof_outpoint": "%s:1" % spend2["txid"],
153 }
154
155
156def compare_golden(name, actual, update):
157 path = FIXTURES / name
158 if update:
159 path.parent.mkdir(parents=True, exist_ok=True)
160 path.write_bytes(actual)
161 return
162 expected = path.read_bytes()
163 if actual != expected:
164 raise AssertionError("golden mismatch: %s" % name)
165
166
48def assert_true(cond, msg): 167def assert_true(cond, msg):
49 if not cond: 168 if not cond:
50 raise AssertionError(msg) 169 raise AssertionError(msg)
51 170
52 171
53def main(): 172def run_genesis_smoke():
54 genesis = chain.reconstructed_genesis() 173 genesis = chain.reconstructed_genesis()
55 outpoint = "%s:0" % chain.GENESIS_MERKLE 174 outpoint = "%s:0" % chain.GENESIS_MERKLE
56 175
@@ -99,6 +218,63 @@ def main():
99 else: 218 else:
100 raise AssertionError("strict duplicate output mode did not fail") 219 raise AssertionError("strict duplicate output mode did not fail")
101 220
221
222def run_golden_fixture(update):
223 blocks, meta = fixture_chain()
224
225 with tempfile.TemporaryDirectory(prefix="bitflash-tool-golden-") as tmp:
226 tmp = Path(tmp)
227 datadir = tmp / "datadir"
228 datadir.mkdir()
229 blk1 = datadir / "blk0001.dat"
230 blk2 = datadir / "blk0002.dat"
231 write_block_file(blk1, blocks[:2])
232 write_block_file(blk2, blocks[2:])
233
234 fair = run([SCRIPTS / "verify-fair-launch.py", "--datadir", datadir,
235 "--max-blocks", "3", "--json"]).stdout.encode("ascii")
236 compare_golden("fair-launch-report.json", fair, update)
237 fair_report = json.loads(fair)
238 assert_true(fair_report["scanned"]["non_coinbase_transactions"] == 2,
239 "fair-launch fixture did not count normal transactions")
240
241 utxo = run([SCRIPTS / "verify-utxo-set.py", "--datadir", datadir,
242 "--include-utxos", "--strict-duplicates", "--json"]).stdout.encode("ascii")
243 compare_golden("utxo-report.json", utxo, update)
244 utxo_report = json.loads(utxo)
245 assert_true(utxo_report["stats"]["normal_transactions"] == 2,
246 "UTXO fixture did not apply normal transactions")
247 assert_true(utxo_report["stats"]["total_fees_btf"] == "1.50000000",
248 "UTXO fixture fee total changed")
249 assert_true(meta["genesis_outpoint"] not in utxo_report["utxos"],
250 "spent genesis output still appears in the UTXO map")
251
252 proof_path = tmp / "fixture-proof.json"
253 proof = run([SCRIPTS / "prove-utxo.py", meta["proof_outpoint"],
254 "--datadir", datadir, "--strict-duplicates",
255 "--out", proof_path, "--json"]).stdout.encode("ascii")
256 compare_golden("utxo-proof.json", proof, update)
257
258 proof_check = run([SCRIPTS / "verify-utxo-proof.py", proof_path,
259 "--root", utxo_report["commitment"]["root"], "--json"]).stdout.encode("ascii")
260 compare_golden("utxo-proof-verification.json", proof_check, update)
261 proof_report = json.loads(proof_check)
262 assert_true(proof_report["verdict"] == "UTXO_PROOF_VERIFIED",
263 "fixture UTXO proof did not verify")
264
265 explorer_dir = tmp / "explorer"
266 run([SCRIPTS / "build-explorer.py", blk1, blk2, explorer_dir])
267 compare_golden("explorer-blocks.json",
268 (explorer_dir / "blocks.json").read_bytes(), update)
269 detail = json.loads((explorer_dir / "block" / "1.json").read_text(encoding="ascii"))
270 assert_true(len(detail["txs"]) == 2,
271 "explorer fixture block did not include the normal transaction")
272
273
274def main():
275 update = "--update-goldens" in sys.argv[1:]
276 run_genesis_smoke()
277 run_golden_fixture(update)
102 print("Bitflash script tool regressions passed") 278 print("Bitflash script tool regressions passed")
103 return 0 279 return 0
104 280