linearize-hashes.py raw
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-present 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') 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]) 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)
147