#!/usr/bin/env python3 # Copyright (C) 2024-2026 Whiterun LLC, # This software is licensed under the GNU Affero General Public License (AGPL), version 3.0 or later., # A copy of the license can be found in the LICENSE file or at https://www.gnu.org/licenses/agpl-3.0.html import os import sys import re """ Disallow SQL hex() function on Bitcoin hash columns. Bitcoin hash types (TokenID, Txid, BlockHash, PoolID, OutPointHash) use reversed byte display format. SQL hex() returns bytes in internal order, not display format. Use blob_to_display_hex::() in Rust instead of SQL hex() for these columns. Exception: owner_pkh (PubkeyHash) does NOT reverse bytes, so hex() is allowed. """ OUR_PATH = os.path.dirname(os.path.realpath(__file__)) # Hash columns that use byte reversal (hex() is WRONG for these) REVERSED_HASH_COLUMNS = [ 'token_id', 'txid', 'blockhash', 'pool', 'creation_utxo', 'utxo', 'outpoint_hash', ] # Columns that do NOT reverse bytes (hex() is OK for these) NON_REVERSED_COLUMNS = [ 'owner_pkh', ] def build_pattern(): """Build regex pattern to match hex(column_name) for reversed hash columns.""" # Match hex(column) or hex(alias.column) for each reversed column columns_pattern = '|'.join(REVERSED_HASH_COLUMNS) # Pattern: hex( optional_whitespace optional_alias. column_name optional_whitespace ) return re.compile( r'\bhex\s*\(\s*(?:\w+\.)?\s*(' + columns_pattern + r')\s*\)', re.IGNORECASE ) FORBIDDEN_PATTERN = build_pattern() def check_file(file_path): """Check if the file contains forbidden hex() usage on hash columns.""" errors = [] with open(file_path, 'r', encoding='utf-8') as file: for line_num, line in enumerate(file, 1): matches = FORBIDDEN_PATTERN.findall(line) if matches: for match in matches: errors.append((line_num, match, line.strip())) return errors def check_files(directories): """ Traverse the specified directories and check each .rs file. """ all_errors = [] for directory in directories: dir_path = os.path.join(OUR_PATH, "..", directory) for root, dirs, files in os.walk(dir_path): for file in files: if file.endswith('.rs'): file_path = os.path.join(root, file) errors = check_file(file_path) if errors: rel_path = os.path.relpath(file_path, os.path.join(OUR_PATH, "..")) for line_num, column, line in errors: all_errors.append((rel_path, line_num, column, line)) if all_errors: print("ERROR: SQL hex() used on Bitcoin hash columns that require byte reversal.") print("Use blob_to_display_hex::() in Rust instead.\n") for rel_path, line_num, column, line in all_errors: print(f"{rel_path}:{line_num}: hex({column}) found") print(f" {line}\n") print(f"\nAffected columns: {', '.join(REVERSED_HASH_COLUMNS)}") print("These columns store Bitcoin hashes that use reversed byte display format.") print("SQL hex() returns internal byte order, which is incorrect for API output.") sys.exit(1) else: print("OK") sys.exit(0) if __name__ == "__main__": check_files(['src'])