1#!/usr/bin/env python3 2# Copyright (c) 2014-2016 The Bitcoin Core developers 3# Distributed under the MIT software license, see the accompanying 4# file COPYING or http://www.opensource.org/licenses/mit-license.php. 5 6# 7# Test REST interface 8# 9 10 11from test_framework.test_framework import BitcoinTestFramework 12from test_framework.util import * 13from struct import * 14from io import BytesIO 15from codecs import encode 16 17import http.client 18import urllib.parse 19 20def deser_uint256(f): 21 r = 0 22 for i in range(8): 23 t = unpack(b"<I", f.read(4))[0] 24 r += t << (i * 32) 25 return r 26 27#allows simple http get calls 28def http_get_call(host, port, path, response_object = 0): 29 conn = http.client.HTTPConnection(host, port) 30 conn.request('GET', path) 31 32 if response_object: 33 return conn.getresponse() 34 35 return conn.getresponse().read().decode('utf-8') 36 37#allows simple http post calls with a request body 38def http_post_call(host, port, path, requestdata = '', response_object = 0): 39 conn = http.client.HTTPConnection(host, port) 40 conn.request('POST', path, requestdata) 41 42 if response_object: 43 return conn.getresponse() 44 45 return conn.getresponse().read() 46 47class RESTTest (BitcoinTestFramework): 48 FORMAT_SEPARATOR = "." 49 50 def __init__(self): 51 super().__init__() 52 self.setup_clean_chain = True 53 self.num_nodes = 3 54 55 def setup_network(self, split=False): 56 self.nodes = start_nodes(self.num_nodes, self.options.tmpdir) 57 connect_nodes_bi(self.nodes,0,1) 58 connect_nodes_bi(self.nodes,1,2) 59 connect_nodes_bi(self.nodes,0,2) 60 self.is_network_split=False 61 self.sync_all() 62 63 def run_test(self): 64 url = urllib.parse.urlparse(self.nodes[0].url) 65 print("Mining blocks...") 66 67 self.nodes[0].generate(1) 68 self.sync_all() 69 self.nodes[2].generate(100) 70 self.sync_all() 71 72 assert_equal(self.nodes[0].getbalance(), 50) 73 74 txid = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 0.1) 75 self.sync_all() 76 self.nodes[2].generate(1) 77 self.sync_all() 78 bb_hash = self.nodes[0].getbestblockhash() 79 80 assert_equal(self.nodes[1].getbalance(), Decimal("0.1")) #balance now should be 0.1 on node 1 81 82 # load the latest 0.1 tx over the REST API 83 json_string = http_get_call(url.hostname, url.port, '/rest/tx/'+txid+self.FORMAT_SEPARATOR+"json") 84 json_obj = json.loads(json_string) 85 vintx = json_obj['vin'][0]['txid'] # get the vin to later check for utxo (should be spent by then) 86 # get n of 0.1 outpoint 87 n = 0 88 for vout in json_obj['vout']: 89 if vout['value'] == 0.1: 90 n = vout['n'] 91 92 93 ###################################### 94 # GETUTXOS: query a unspent outpoint # 95 ###################################### 96 json_request = '/checkmempool/'+txid+'-'+str(n) 97 json_string = http_get_call(url.hostname, url.port, '/rest/getutxos'+json_request+self.FORMAT_SEPARATOR+'json') 98 json_obj = json.loads(json_string) 99 100 #check chainTip response 101 assert_equal(json_obj['chaintipHash'], bb_hash) 102 103 #make sure there is one utxo 104 assert_equal(len(json_obj['utxos']), 1) 105 assert_equal(json_obj['utxos'][0]['value'], 0.1) 106 107 108 ################################################ 109 # GETUTXOS: now query a already spent outpoint # 110 ################################################ 111 json_request = '/checkmempool/'+vintx+'-0' 112 json_string = http_get_call(url.hostname, url.port, '/rest/getutxos'+json_request+self.FORMAT_SEPARATOR+'json') 113 json_obj = json.loads(json_string) 114 115 #check chainTip response 116 assert_equal(json_obj['chaintipHash'], bb_hash) 117 118 #make sure there is no utox in the response because this oupoint has been spent 119 assert_equal(len(json_obj['utxos']), 0) 120 121 #check bitmap 122 assert_equal(json_obj['bitmap'], "0") 123 124 125 ################################################## 126 # GETUTXOS: now check both with the same request # 127 ################################################## 128 json_request = '/checkmempool/'+txid+'-'+str(n)+'/'+vintx+'-0' 129 json_string = http_get_call(url.hostname, url.port, '/rest/getutxos'+json_request+self.FORMAT_SEPARATOR+'json') 130 json_obj = json.loads(json_string) 131 assert_equal(len(json_obj['utxos']), 1) 132 assert_equal(json_obj['bitmap'], "10") 133 134 #test binary response 135 bb_hash = self.nodes[0].getbestblockhash() 136 137 binaryRequest = b'\x01\x02' 138 binaryRequest += hex_str_to_bytes(txid) 139 binaryRequest += pack("i", n) 140 binaryRequest += hex_str_to_bytes(vintx) 141 binaryRequest += pack("i", 0) 142 143 bin_response = http_post_call(url.hostname, url.port, '/rest/getutxos'+self.FORMAT_SEPARATOR+'bin', binaryRequest) 144 output = BytesIO() 145 output.write(bin_response) 146 output.seek(0) 147 chainHeight = unpack("i", output.read(4))[0] 148 hashFromBinResponse = hex(deser_uint256(output))[2:].zfill(64) 149 150 assert_equal(bb_hash, hashFromBinResponse) #check if getutxo's chaintip during calculation was fine 151 assert_equal(chainHeight, 102) #chain height must be 102 152 153 154 ############################ 155 # GETUTXOS: mempool checks # 156 ############################ 157 158 # do a tx and don't sync 159 txid = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 0.1) 160 json_string = http_get_call(url.hostname, url.port, '/rest/tx/'+txid+self.FORMAT_SEPARATOR+"json") 161 json_obj = json.loads(json_string) 162 vintx = json_obj['vin'][0]['txid'] # get the vin to later check for utxo (should be spent by then) 163 # get n of 0.1 outpoint 164 n = 0 165 for vout in json_obj['vout']: 166 if vout['value'] == 0.1: 167 n = vout['n'] 168 169 json_request = '/'+txid+'-'+str(n) 170 json_string = http_get_call(url.hostname, url.port, '/rest/getutxos'+json_request+self.FORMAT_SEPARATOR+'json') 171 json_obj = json.loads(json_string) 172 assert_equal(len(json_obj['utxos']), 0) #there should be a outpoint because it has just added to the mempool 173 174 json_request = '/checkmempool/'+txid+'-'+str(n) 175 json_string = http_get_call(url.hostname, url.port, '/rest/getutxos'+json_request+self.FORMAT_SEPARATOR+'json') 176 json_obj = json.loads(json_string) 177 assert_equal(len(json_obj['utxos']), 1) #there should be a outpoint because it has just added to the mempool 178 179 #do some invalid requests 180 json_request = '{"checkmempool' 181 response = http_post_call(url.hostname, url.port, '/rest/getutxos'+self.FORMAT_SEPARATOR+'json', json_request, True) 182 assert_equal(response.status, 500) #must be a 500 because we send a invalid json request 183 184 json_request = '{"checkmempool' 185 response = http_post_call(url.hostname, url.port, '/rest/getutxos'+self.FORMAT_SEPARATOR+'bin', json_request, True) 186 assert_equal(response.status, 500) #must be a 500 because we send a invalid bin request 187 188 response = http_post_call(url.hostname, url.port, '/rest/getutxos/checkmempool'+self.FORMAT_SEPARATOR+'bin', '', True) 189 assert_equal(response.status, 500) #must be a 500 because we send a invalid bin request 190 191 #test limits 192 json_request = '/checkmempool/' 193 for x in range(0, 20): 194 json_request += txid+'-'+str(n)+'/' 195 json_request = json_request.rstrip("/") 196 response = http_post_call(url.hostname, url.port, '/rest/getutxos'+json_request+self.FORMAT_SEPARATOR+'json', '', True) 197 assert_equal(response.status, 500) #must be a 500 because we exceeding the limits 198 199 json_request = '/checkmempool/' 200 for x in range(0, 15): 201 json_request += txid+'-'+str(n)+'/' 202 json_request = json_request.rstrip("/") 203 response = http_post_call(url.hostname, url.port, '/rest/getutxos'+json_request+self.FORMAT_SEPARATOR+'json', '', True) 204 assert_equal(response.status, 200) #must be a 500 because we exceeding the limits 205 206 self.nodes[0].generate(1) #generate block to not affect upcoming tests 207 self.sync_all() 208 209 ################ 210 # /rest/block/ # 211 ################ 212 213 # check binary format 214 response = http_get_call(url.hostname, url.port, '/rest/block/'+bb_hash+self.FORMAT_SEPARATOR+"bin", True) 215 assert_equal(response.status, 200) 216 assert_greater_than(int(response.getheader('content-length')), 80) 217 response_str = response.read() 218 219 # compare with block header 220 response_header = http_get_call(url.hostname, url.port, '/rest/headers/1/'+bb_hash+self.FORMAT_SEPARATOR+"bin", True) 221 assert_equal(response_header.status, 200) 222 assert_equal(int(response_header.getheader('content-length')), 80) 223 response_header_str = response_header.read() 224 assert_equal(response_str[0:80], response_header_str) 225 226 # check block hex format 227 response_hex = http_get_call(url.hostname, url.port, '/rest/block/'+bb_hash+self.FORMAT_SEPARATOR+"hex", True) 228 assert_equal(response_hex.status, 200) 229 assert_greater_than(int(response_hex.getheader('content-length')), 160) 230 response_hex_str = response_hex.read() 231 assert_equal(encode(response_str, "hex_codec")[0:160], response_hex_str[0:160]) 232 233 # compare with hex block header 234 response_header_hex = http_get_call(url.hostname, url.port, '/rest/headers/1/'+bb_hash+self.FORMAT_SEPARATOR+"hex", True) 235 assert_equal(response_header_hex.status, 200) 236 assert_greater_than(int(response_header_hex.getheader('content-length')), 160) 237 response_header_hex_str = response_header_hex.read() 238 assert_equal(response_hex_str[0:160], response_header_hex_str[0:160]) 239 assert_equal(encode(response_header_str, "hex_codec")[0:160], response_header_hex_str[0:160]) 240 241 # check json format 242 block_json_string = http_get_call(url.hostname, url.port, '/rest/block/'+bb_hash+self.FORMAT_SEPARATOR+'json') 243 block_json_obj = json.loads(block_json_string) 244 assert_equal(block_json_obj['hash'], bb_hash) 245 246 # compare with json block header 247 response_header_json = http_get_call(url.hostname, url.port, '/rest/headers/1/'+bb_hash+self.FORMAT_SEPARATOR+"json", True) 248 assert_equal(response_header_json.status, 200) 249 response_header_json_str = response_header_json.read().decode('utf-8') 250 json_obj = json.loads(response_header_json_str, parse_float=Decimal) 251 assert_equal(len(json_obj), 1) #ensure that there is one header in the json response 252 assert_equal(json_obj[0]['hash'], bb_hash) #request/response hash should be the same 253 254 #compare with normal RPC block response 255 rpc_block_json = self.nodes[0].getblock(bb_hash) 256 assert_equal(json_obj[0]['hash'], rpc_block_json['hash']) 257 assert_equal(json_obj[0]['confirmations'], rpc_block_json['confirmations']) 258 assert_equal(json_obj[0]['height'], rpc_block_json['height']) 259 assert_equal(json_obj[0]['version'], rpc_block_json['version']) 260 assert_equal(json_obj[0]['merkleroot'], rpc_block_json['merkleroot']) 261 assert_equal(json_obj[0]['time'], rpc_block_json['time']) 262 assert_equal(json_obj[0]['nonce'], rpc_block_json['nonce']) 263 assert_equal(json_obj[0]['bits'], rpc_block_json['bits']) 264 assert_equal(json_obj[0]['difficulty'], rpc_block_json['difficulty']) 265 assert_equal(json_obj[0]['chainwork'], rpc_block_json['chainwork']) 266 assert_equal(json_obj[0]['previousblockhash'], rpc_block_json['previousblockhash']) 267 268 #see if we can get 5 headers in one response 269 self.nodes[1].generate(5) 270 self.sync_all() 271 response_header_json = http_get_call(url.hostname, url.port, '/rest/headers/5/'+bb_hash+self.FORMAT_SEPARATOR+"json", True) 272 assert_equal(response_header_json.status, 200) 273 response_header_json_str = response_header_json.read().decode('utf-8') 274 json_obj = json.loads(response_header_json_str) 275 assert_equal(len(json_obj), 5) #now we should have 5 header objects 276 277 # do tx test 278 tx_hash = block_json_obj['tx'][0]['txid'] 279 json_string = http_get_call(url.hostname, url.port, '/rest/tx/'+tx_hash+self.FORMAT_SEPARATOR+"json") 280 json_obj = json.loads(json_string) 281 assert_equal(json_obj['txid'], tx_hash) 282 283 # check hex format response 284 hex_string = http_get_call(url.hostname, url.port, '/rest/tx/'+tx_hash+self.FORMAT_SEPARATOR+"hex", True) 285 assert_equal(hex_string.status, 200) 286 assert_greater_than(int(response.getheader('content-length')), 10) 287 288 289 # check block tx details 290 # let's make 3 tx and mine them on node 1 291 txs = [] 292 txs.append(self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 11)) 293 txs.append(self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 11)) 294 txs.append(self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 11)) 295 self.sync_all() 296 297 # check that there are exactly 3 transactions in the TX memory pool before generating the block 298 json_string = http_get_call(url.hostname, url.port, '/rest/mempool/info'+self.FORMAT_SEPARATOR+'json') 299 json_obj = json.loads(json_string) 300 assert_equal(json_obj['size'], 3) 301 # the size of the memory pool should be greater than 3x ~100 bytes 302 assert_greater_than(json_obj['bytes'], 300) 303 304 # check that there are our submitted transactions in the TX memory pool 305 json_string = http_get_call(url.hostname, url.port, '/rest/mempool/contents'+self.FORMAT_SEPARATOR+'json') 306 json_obj = json.loads(json_string) 307 for tx in txs: 308 assert_equal(tx in json_obj, True) 309 310 # now mine the transactions 311 newblockhash = self.nodes[1].generate(1) 312 self.sync_all() 313 314 #check if the 3 tx show up in the new block 315 json_string = http_get_call(url.hostname, url.port, '/rest/block/'+newblockhash[0]+self.FORMAT_SEPARATOR+'json') 316 json_obj = json.loads(json_string) 317 for tx in json_obj['tx']: 318 if not 'coinbase' in tx['vin'][0]: #exclude coinbase 319 assert_equal(tx['txid'] in txs, True) 320 321 #check the same but without tx details 322 json_string = http_get_call(url.hostname, url.port, '/rest/block/notxdetails/'+newblockhash[0]+self.FORMAT_SEPARATOR+'json') 323 json_obj = json.loads(json_string) 324 for tx in txs: 325 assert_equal(tx in json_obj['tx'], True) 326 327 #test rest bestblock 328 bb_hash = self.nodes[0].getbestblockhash() 329 330 json_string = http_get_call(url.hostname, url.port, '/rest/chaininfo.json') 331 json_obj = json.loads(json_string) 332 assert_equal(json_obj['bestblockhash'], bb_hash) 333 334if __name__ == '__main__': 335 RESTTest ().main () 336