linearize-hashes.py
1 #!/usr/bin/env python3 2 # 3 # linearize-hashes.py: List blocks in a linear, no-fork version of the chain. 4 # 5 # Copyright (c) 2013-2022 The Bitcoin Core developers 6 # Distributed under the MIT software license, see the accompanying 7 # file COPYING or http://www.opensource.org/licenses/mit-license.php. 8 # 9 10 from http.client import HTTPConnection 11 import json 12 import re 13 import base64 14 import sys 15 import os 16 import os.path 17 18 settings = {} 19 20 class BitcoinRPC: 21 def __init__(self, host, port, username, password): 22 authpair = "%s:%s" % (username, password) 23 authpair = authpair.encode('utf-8') 24 self.authhdr = b"Basic " + base64.b64encode(authpair) 25 self.conn = HTTPConnection(host, port=port, timeout=30) 26 27 def execute(self, obj): 28 try: 29 self.conn.request('POST', '/', json.dumps(obj), 30 { 'Authorization' : self.authhdr, 31 'Content-type' : 'application/json' }) 32 except ConnectionRefusedError: 33 print('RPC connection refused. Check RPC settings and the server status.', 34 file=sys.stderr) 35 return None 36 37 resp = self.conn.getresponse() 38 if resp is None: 39 print("JSON-RPC: no response", file=sys.stderr) 40 return None 41 42 body = resp.read().decode('utf-8') 43 resp_obj = json.loads(body) 44 return resp_obj 45 46 @staticmethod 47 def build_request(idx, method, params): 48 obj = { 'version' : '1.1', 49 'method' : method, 50 'id' : idx } 51 if params is None: 52 obj['params'] = [] 53 else: 54 obj['params'] = params 55 return obj 56 57 @staticmethod 58 def response_is_error(resp_obj): 59 return 'error' in resp_obj and resp_obj['error'] is not None 60 61 def get_block_hashes(settings, max_blocks_per_call=10000): 62 rpc = BitcoinRPC(settings['host'], settings['port'], 63 settings['rpcuser'], settings['rpcpassword']) 64 65 height = settings['min_height'] 66 while height < settings['max_height']+1: 67 num_blocks = min(settings['max_height']+1-height, max_blocks_per_call) 68 batch = [] 69 for x in range(num_blocks): 70 batch.append(rpc.build_request(x, 'getblockhash', [height + x])) 71 72 reply = rpc.execute(batch) 73 if reply is None: 74 print('Cannot continue. Program will halt.') 75 return None 76 77 for x,resp_obj in enumerate(reply): 78 if rpc.response_is_error(resp_obj): 79 print('JSON-RPC: error at height', height+x, ': ', resp_obj['error'], file=sys.stderr) 80 sys.exit(1) 81 assert resp_obj['id'] == x # assume replies are in-sequence 82 if settings['rev_hash_bytes'] == 'true': 83 resp_obj['result'] = bytes.fromhex(resp_obj['result'])[::-1].hex() 84 print(resp_obj['result']) 85 86 height += num_blocks 87 88 def get_rpc_cookie(): 89 # Open the cookie file 90 with open(os.path.join(os.path.expanduser(settings['datadir']), '.cookie'), 'r', encoding="ascii") as f: 91 combined = f.readline() 92 combined_split = combined.split(":") 93 settings['rpcuser'] = combined_split[0] 94 settings['rpcpassword'] = combined_split[1] 95 96 if __name__ == '__main__': 97 if len(sys.argv) != 2: 98 print("Usage: linearize-hashes.py CONFIG-FILE") 99 sys.exit(1) 100 101 with open(sys.argv[1], encoding="utf8") as f: 102 for line in f: 103 # skip comment lines 104 m = re.search(r'^\s*#', line) 105 if m: 106 continue 107 108 # parse key=value lines 109 m = re.search(r'^(\w+)\s*=\s*(\S.*)$', line) 110 if m is None: 111 continue 112 settings[m.group(1)] = m.group(2) 113 114 if 'host' not in settings: 115 settings['host'] = '127.0.0.1' 116 if 'port' not in settings: 117 settings['port'] = 8332 118 if 'min_height' not in settings: 119 settings['min_height'] = 0 120 if 'max_height' not in settings: 121 settings['max_height'] = 313000 122 if 'rev_hash_bytes' not in settings: 123 settings['rev_hash_bytes'] = 'false' 124 125 use_userpass = True 126 use_datadir = False 127 if 'rpcuser' not in settings or 'rpcpassword' not in settings: 128 use_userpass = False 129 if 'datadir' in settings and not use_userpass: 130 use_datadir = True 131 if not use_userpass and not use_datadir: 132 print("Missing datadir or username and/or password in cfg file", file=sys.stderr) 133 sys.exit(1) 134 135 settings['port'] = int(settings['port']) 136 settings['min_height'] = int(settings['min_height']) 137 settings['max_height'] = int(settings['max_height']) 138 139 # Force hash byte format setting to be lowercase to make comparisons easier. 140 settings['rev_hash_bytes'] = settings['rev_hash_bytes'].lower() 141 142 # Get the rpc user and pass from the cookie if the datadir is set 143 if use_datadir: 144 get_rpc_cookie() 145 146 get_block_hashes(settings)