diff options
Diffstat (limited to 'scripts')
| -rw-r--r-- | scripts/bitflash_chain.py | 69 | ||||
| -rwxr-xr-x | scripts/build-explorer.py | 5 | ||||
| -rw-r--r-- | scripts/prove-utxo.py | 7 | ||||
| -rw-r--r-- | scripts/verify-utxo-proof.py | 10 |
4 files changed, 44 insertions, 47 deletions
diff --git a/scripts/bitflash_chain.py b/scripts/bitflash_chain.py index f3688f1..4a3231c 100644 --- a/scripts/bitflash_chain.py +++ b/scripts/bitflash_chain.py | |||
| @@ -4,6 +4,7 @@ | |||
| 4 | import datetime as _dt | 4 | import datetime as _dt |
| 5 | import hashlib | 5 | import hashlib |
| 6 | import json | 6 | import json |
| 7 | import mmap | ||
| 7 | import os | 8 | import os |
| 8 | import struct | 9 | import struct |
| 9 | from pathlib import Path | 10 | from pathlib import Path |
| @@ -11,6 +12,9 @@ from pathlib import Path | |||
| 11 | 12 | ||
| 12 | MAGIC = bytes([0xBF, 0x20, 0x5C, 0xFD]) | 13 | MAGIC = bytes([0xBF, 0x20, 0x5C, 0xFD]) |
| 13 | MAX_BLOCK_PAYLOAD = 32 * 1024 * 1024 | 14 | MAX_BLOCK_PAYLOAD = 32 * 1024 * 1024 |
| 15 | UTXO_LEAF_DOMAIN = "BTFUTXO1" | ||
| 16 | UTXO_NODE_DOMAIN_TAG = "BTFNODE1" | ||
| 17 | UTXO_NODE_DOMAIN = (UTXO_NODE_DOMAIN_TAG + "|").encode("ascii") | ||
| 14 | COIN = 100_000_000 | 18 | COIN = 100_000_000 |
| 15 | GENESIS_HASH = "5bd7cb255d814e48cebcdfb72da4dc87b34bd774227f8ceb546c5640f4bdc169" | 19 | GENESIS_HASH = "5bd7cb255d814e48cebcdfb72da4dc87b34bd774227f8ceb546c5640f4bdc169" |
| 16 | GENESIS_MERKLE = "1a45b4482532abb29b10e234d3f13132230525a339ecea91658ffa675a5b1325" | 20 | GENESIS_MERKLE = "1a45b4482532abb29b10e234d3f13132230525a339ecea91658ffa675a5b1325" |
| @@ -181,40 +185,33 @@ def iter_block_files(datadir): | |||
| 181 | def iter_blocks_from_file(path): | 185 | def iter_blocks_from_file(path): |
| 182 | path = Path(path) | 186 | path = Path(path) |
| 183 | with path.open("rb") as f: | 187 | with path.open("rb") as f: |
| 184 | window = f.read(4) | 188 | f.seek(0, os.SEEK_END) |
| 185 | offset = 0 | 189 | file_size = f.tell() |
| 186 | while len(window) == 4: | 190 | if file_size == 0: |
| 187 | if window != MAGIC: | 191 | return |
| 188 | nxt = f.read(1) | 192 | f.seek(0) |
| 189 | if not nxt: | 193 | mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) |
| 194 | try: | ||
| 195 | pos = 0 | ||
| 196 | while True: | ||
| 197 | offset = mm.find(MAGIC, pos) | ||
| 198 | if offset < 0 or offset + 8 > file_size: | ||
| 190 | break | 199 | break |
| 191 | window = window[1:] + nxt | 200 | size = struct.unpack("<I", mm[offset + 4:offset + 8])[0] |
| 192 | offset += 1 | 201 | payload_offset = offset + 8 |
| 193 | continue | 202 | end = payload_offset + size |
| 194 | 203 | if size == 0 or size > MAX_BLOCK_PAYLOAD or end > file_size: | |
| 195 | size_bytes = f.read(4) | 204 | pos = offset + 1 |
| 196 | if len(size_bytes) != 4: | 205 | continue |
| 197 | break | 206 | payload = mm[payload_offset:end] |
| 198 | size = struct.unpack("<I", size_bytes)[0] | 207 | try: |
| 199 | if size == 0 or size > MAX_BLOCK_PAYLOAD: | 208 | yield parse_block(payload, path.name, payload_offset) |
| 200 | offset += 1 | 209 | except ParseError: |
| 201 | f.seek(offset) | 210 | pos = offset + 1 |
| 202 | window = f.read(4) | 211 | continue |
| 203 | continue | 212 | pos = end |
| 204 | payload_offset = offset + 8 | 213 | finally: |
| 205 | payload = f.read(size) | 214 | mm.close() |
| 206 | if len(payload) != size: | ||
| 207 | break | ||
| 208 | try: | ||
| 209 | yield parse_block(payload, path.name, payload_offset) | ||
| 210 | except ParseError: | ||
| 211 | offset += 1 | ||
| 212 | f.seek(offset) | ||
| 213 | window = f.read(4) | ||
| 214 | continue | ||
| 215 | |||
| 216 | offset = f.tell() | ||
| 217 | window = f.read(4) | ||
| 218 | 215 | ||
| 219 | 216 | ||
| 220 | def read_blocks_from_files(paths, max_blocks=0): | 217 | def read_blocks_from_files(paths, max_blocks=0): |
| @@ -265,7 +262,7 @@ def is_coinbase(tx): | |||
| 265 | 262 | ||
| 266 | def utxo_leaf_hash(outpoint, utxo): | 263 | def utxo_leaf_hash(outpoint, utxo): |
| 267 | payload = "|".join([ | 264 | payload = "|".join([ |
| 268 | "BTFUTXO1", | 265 | UTXO_LEAF_DOMAIN, |
| 269 | outpoint, | 266 | outpoint, |
| 270 | str(utxo["value_satoshis"]), | 267 | str(utxo["value_satoshis"]), |
| 271 | utxo["script_pub_key_hex"], | 268 | utxo["script_pub_key_hex"], |
| @@ -285,7 +282,7 @@ def utxo_root(utxos): | |||
| 285 | for i in range(0, len(layer), 2): | 282 | for i in range(0, len(layer), 2): |
| 286 | left = layer[i] | 283 | left = layer[i] |
| 287 | right = layer[i + 1] if i + 1 < len(layer) else left | 284 | right = layer[i + 1] if i + 1 < len(layer) else left |
| 288 | nxt.append(sha256(b"BTFNODE1|" + left + right)) | 285 | nxt.append(sha256(UTXO_NODE_DOMAIN + left + right)) |
| 289 | layer = nxt | 286 | layer = nxt |
| 290 | return layer[0].hex() | 287 | return layer[0].hex() |
| 291 | 288 | ||
| @@ -327,9 +324,9 @@ def apply_blocks(blocks, strict_duplicates=False): | |||
| 327 | for n, out in enumerate(tx["vout"]): | 324 | for n, out in enumerate(tx["vout"]): |
| 328 | key = "%s:%d" % (tx["txid"], n) | 325 | key = "%s:%d" % (tx["txid"], n) |
| 329 | if key in utxos: | 326 | if key in utxos: |
| 330 | duplicate_outputs.append({"height": height, "outpoint": key}) | ||
| 331 | if strict_duplicates: | 327 | if strict_duplicates: |
| 332 | raise ParseError("duplicate output at height %d: %s" % (height, key)) | 328 | raise ParseError("duplicate output at height %d: %s" % (height, key)) |
| 329 | duplicate_outputs.append({"height": height, "outpoint": key}) | ||
| 333 | utxos[key] = { | 330 | utxos[key] = { |
| 334 | "txid": tx["txid"], | 331 | "txid": tx["txid"], |
| 335 | "vout": n, | 332 | "vout": n, |
diff --git a/scripts/build-explorer.py b/scripts/build-explorer.py index 0d86b3c..97a7f4e 100755 --- a/scripts/build-explorer.py +++ b/scripts/build-explorer.py | |||
| @@ -35,7 +35,10 @@ def hash160(b): | |||
| 35 | 35 | ||
| 36 | 36 | ||
| 37 | def spk_info(script_hex): | 37 | def spk_info(script_hex): |
| 38 | spk = bytes.fromhex(script_hex) | 38 | try: |
| 39 | spk = bytes.fromhex(script_hex) | ||
| 40 | except ValueError: | ||
| 41 | return {"type": "malformed", "hex": script_hex[:80]} | ||
| 39 | if ( | 42 | if ( |
| 40 | len(spk) == 25 and spk[0] == 0x76 and spk[1] == 0xA9 | 43 | len(spk) == 25 and spk[0] == 0x76 and spk[1] == 0xA9 |
| 41 | and spk[2] == 0x14 and spk[23] == 0x88 and spk[24] == 0xAC | 44 | and spk[2] == 0x14 and spk[23] == 0x88 and spk[24] == 0xAC |
diff --git a/scripts/prove-utxo.py b/scripts/prove-utxo.py index 9f2704b..105e677 100644 --- a/scripts/prove-utxo.py +++ b/scripts/prove-utxo.py | |||
| @@ -14,7 +14,6 @@ from pathlib import Path | |||
| 14 | import bitflash_chain as chain | 14 | import bitflash_chain as chain |
| 15 | 15 | ||
| 16 | SCHEMA = "bitflash-utxo-inclusion-proof-1" | 16 | SCHEMA = "bitflash-utxo-inclusion-proof-1" |
| 17 | NODE_DOMAIN = b"BTFNODE1|" | ||
| 18 | 17 | ||
| 19 | 18 | ||
| 20 | def canonical_bytes(obj): | 19 | def canonical_bytes(obj): |
| @@ -61,7 +60,7 @@ def build_merkle_proof(keys, leaf_hashes, index): | |||
| 61 | for i in range(0, len(layer), 2): | 60 | for i in range(0, len(layer), 2): |
| 62 | left = layer[i] | 61 | left = layer[i] |
| 63 | right = layer[i + 1] if i + 1 < len(layer) else left | 62 | right = layer[i + 1] if i + 1 < len(layer) else left |
| 64 | next_layer.append(hashlib.sha256(NODE_DOMAIN + left + right).digest()) | 63 | next_layer.append(hashlib.sha256(chain.UTXO_NODE_DOMAIN + left + right).digest()) |
| 65 | layer = next_layer | 64 | layer = next_layer |
| 66 | current_index //= 2 | 65 | current_index //= 2 |
| 67 | level += 1 | 66 | level += 1 |
| @@ -106,8 +105,8 @@ def build_proof(args): | |||
| 106 | }, | 105 | }, |
| 107 | "commitment": { | 106 | "commitment": { |
| 108 | "algorithm": "sorted-outpoint-merkle-sha256", | 107 | "algorithm": "sorted-outpoint-merkle-sha256", |
| 109 | "leaf_domain": "BTFUTXO1", | 108 | "leaf_domain": chain.UTXO_LEAF_DOMAIN, |
| 110 | "node_domain": "BTFNODE1", | 109 | "node_domain": chain.UTXO_NODE_DOMAIN_TAG, |
| 111 | "root": root, | 110 | "root": root, |
| 112 | "leaf_count": len(keys), | 111 | "leaf_count": len(keys), |
| 113 | }, | 112 | }, |
diff --git a/scripts/verify-utxo-proof.py b/scripts/verify-utxo-proof.py index c29caa9..50d9356 100644 --- a/scripts/verify-utxo-proof.py +++ b/scripts/verify-utxo-proof.py | |||
| @@ -10,8 +10,6 @@ from pathlib import Path | |||
| 10 | import bitflash_chain as chain | 10 | import bitflash_chain as chain |
| 11 | 11 | ||
| 12 | SCHEMA = "bitflash-utxo-inclusion-proof-1" | 12 | SCHEMA = "bitflash-utxo-inclusion-proof-1" |
| 13 | LEAF_DOMAIN = "BTFUTXO1" | ||
| 14 | NODE_DOMAIN = b"BTFNODE1|" | ||
| 15 | 13 | ||
| 16 | 14 | ||
| 17 | def canonical_bytes(obj): | 15 | def canonical_bytes(obj): |
| @@ -32,9 +30,9 @@ def verify(proof, expected_root=None): | |||
| 32 | commitment = proof.get("commitment", {}) | 30 | commitment = proof.get("commitment", {}) |
| 33 | if commitment.get("algorithm") != "sorted-outpoint-merkle-sha256": | 31 | if commitment.get("algorithm") != "sorted-outpoint-merkle-sha256": |
| 34 | failures.append("unexpected commitment algorithm") | 32 | failures.append("unexpected commitment algorithm") |
| 35 | if commitment.get("leaf_domain") != LEAF_DOMAIN: | 33 | if commitment.get("leaf_domain") != chain.UTXO_LEAF_DOMAIN: |
| 36 | failures.append("unexpected leaf domain") | 34 | failures.append("unexpected leaf domain") |
| 37 | if commitment.get("node_domain") != "BTFNODE1": | 35 | if commitment.get("node_domain") != chain.UTXO_NODE_DOMAIN_TAG: |
| 38 | failures.append("unexpected node domain") | 36 | failures.append("unexpected node domain") |
| 39 | 37 | ||
| 40 | target = proof.get("target", {}) | 38 | target = proof.get("target", {}) |
| @@ -64,9 +62,9 @@ def verify(proof, expected_root=None): | |||
| 64 | failures.append("bad sibling hash length at proof level %d" % i) | 62 | failures.append("bad sibling hash length at proof level %d" % i) |
| 65 | side = step.get("side") | 63 | side = step.get("side") |
| 66 | if side == "right": | 64 | if side == "right": |
| 67 | current = chain.sha256(NODE_DOMAIN + current + sibling) | 65 | current = chain.sha256(chain.UTXO_NODE_DOMAIN + current + sibling) |
| 68 | elif side == "left": | 66 | elif side == "left": |
| 69 | current = chain.sha256(NODE_DOMAIN + sibling + current) | 67 | current = chain.sha256(chain.UTXO_NODE_DOMAIN + sibling + current) |
| 70 | else: | 68 | else: |
| 71 | failures.append("bad sibling side at proof level %d" % i) | 69 | failures.append("bad sibling side at proof level %d" % i) |
| 72 | 70 | ||