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 """Test the REST API."""
7 from test_framework
.test_framework
import BitcoinTestFramework
8 from test_framework
.util
import *
10 from io
import BytesIO
11 from codecs
import encode
19 t
= unpack(b
"<I", f
.read(4))[0]
23 #allows simple http get calls
24 def http_get_call(host
, port
, path
, response_object
= 0):
25 conn
= http
.client
.HTTPConnection(host
, port
)
26 conn
.request('GET', path
)
29 return conn
.getresponse()
31 return conn
.getresponse().read().decode('utf-8')
33 #allows simple http post calls with a request body
34 def http_post_call(host
, port
, path
, requestdata
= '', response_object
= 0):
35 conn
= http
.client
.HTTPConnection(host
, port
)
36 conn
.request('POST', path
, requestdata
)
39 return conn
.getresponse()
41 return conn
.getresponse().read()
43 class RESTTest (BitcoinTestFramework
):
44 FORMAT_SEPARATOR
= "."
46 def set_test_params(self
):
47 self
.setup_clean_chain
= True
50 def setup_network(self
, split
=False):
51 super().setup_network()
52 connect_nodes_bi(self
.nodes
, 0, 2)
55 url
= urllib
.parse
.urlparse(self
.nodes
[0].url
)
56 self
.log
.info("Mining blocks...")
58 self
.nodes
[0].generate(1)
60 self
.nodes
[2].generate(100)
63 assert_equal(self
.nodes
[0].getbalance(), 50)
65 txid
= self
.nodes
[0].sendtoaddress(self
.nodes
[1].getnewaddress(), 0.1)
67 self
.nodes
[2].generate(1)
69 bb_hash
= self
.nodes
[0].getbestblockhash()
71 assert_equal(self
.nodes
[1].getbalance(), Decimal("0.1")) #balance now should be 0.1 on node 1
73 # load the latest 0.1 tx over the REST API
74 json_string
= http_get_call(url
.hostname
, url
.port
, '/rest/tx/'+txid
+self
.FORMAT_SEPARATOR
+"json")
75 json_obj
= json
.loads(json_string
)
76 vintx
= json_obj
['vin'][0]['txid'] # get the vin to later check for utxo (should be spent by then)
77 # get n of 0.1 outpoint
79 for vout
in json_obj
['vout']:
80 if vout
['value'] == 0.1:
84 #######################################
85 # GETUTXOS: query an unspent outpoint #
86 #######################################
87 json_request
= '/checkmempool/'+txid
+'-'+str(n
)
88 json_string
= http_get_call(url
.hostname
, url
.port
, '/rest/getutxos'+json_request
+self
.FORMAT_SEPARATOR
+'json')
89 json_obj
= json
.loads(json_string
)
91 #check chainTip response
92 assert_equal(json_obj
['chaintipHash'], bb_hash
)
94 #make sure there is one utxo
95 assert_equal(len(json_obj
['utxos']), 1)
96 assert_equal(json_obj
['utxos'][0]['value'], 0.1)
99 #################################################
100 # GETUTXOS: now query an already spent outpoint #
101 #################################################
102 json_request
= '/checkmempool/'+vintx
+'-0'
103 json_string
= http_get_call(url
.hostname
, url
.port
, '/rest/getutxos'+json_request
+self
.FORMAT_SEPARATOR
+'json')
104 json_obj
= json
.loads(json_string
)
106 #check chainTip response
107 assert_equal(json_obj
['chaintipHash'], bb_hash
)
109 #make sure there is no utox in the response because this oupoint has been spent
110 assert_equal(len(json_obj
['utxos']), 0)
113 assert_equal(json_obj
['bitmap'], "0")
116 ##################################################
117 # GETUTXOS: now check both with the same request #
118 ##################################################
119 json_request
= '/checkmempool/'+txid
+'-'+str(n
)+'/'+vintx
+'-0'
120 json_string
= http_get_call(url
.hostname
, url
.port
, '/rest/getutxos'+json_request
+self
.FORMAT_SEPARATOR
+'json')
121 json_obj
= json
.loads(json_string
)
122 assert_equal(len(json_obj
['utxos']), 1)
123 assert_equal(json_obj
['bitmap'], "10")
125 #test binary response
126 bb_hash
= self
.nodes
[0].getbestblockhash()
128 binaryRequest
= b
'\x01\x02'
129 binaryRequest
+= hex_str_to_bytes(txid
)
130 binaryRequest
+= pack("i", n
)
131 binaryRequest
+= hex_str_to_bytes(vintx
)
132 binaryRequest
+= pack("i", 0)
134 bin_response
= http_post_call(url
.hostname
, url
.port
, '/rest/getutxos'+self
.FORMAT_SEPARATOR
+'bin', binaryRequest
)
136 output
.write(bin_response
)
138 chainHeight
= unpack("i", output
.read(4))[0]
139 hashFromBinResponse
= hex(deser_uint256(output
))[2:].zfill(64)
141 assert_equal(bb_hash
, hashFromBinResponse
) #check if getutxo's chaintip during calculation was fine
142 assert_equal(chainHeight
, 102) #chain height must be 102
145 ############################
146 # GETUTXOS: mempool checks #
147 ############################
149 # do a tx and don't sync
150 txid
= self
.nodes
[0].sendtoaddress(self
.nodes
[1].getnewaddress(), 0.1)
151 json_string
= http_get_call(url
.hostname
, url
.port
, '/rest/tx/'+txid
+self
.FORMAT_SEPARATOR
+"json")
152 json_obj
= json
.loads(json_string
)
153 vintx
= json_obj
['vin'][0]['txid'] # get the vin to later check for utxo (should be spent by then)
154 # get n of 0.1 outpoint
156 for vout
in json_obj
['vout']:
157 if vout
['value'] == 0.1:
160 json_request
= '/'+txid
+'-'+str(n
)
161 json_string
= http_get_call(url
.hostname
, url
.port
, '/rest/getutxos'+json_request
+self
.FORMAT_SEPARATOR
+'json')
162 json_obj
= json
.loads(json_string
)
163 assert_equal(len(json_obj
['utxos']), 0) #there should be an outpoint because it has just added to the mempool
165 json_request
= '/checkmempool/'+txid
+'-'+str(n
)
166 json_string
= http_get_call(url
.hostname
, url
.port
, '/rest/getutxos'+json_request
+self
.FORMAT_SEPARATOR
+'json')
167 json_obj
= json
.loads(json_string
)
168 assert_equal(len(json_obj
['utxos']), 1) #there should be an outpoint because it has just added to the mempool
170 #do some invalid requests
171 json_request
= '{"checkmempool'
172 response
= http_post_call(url
.hostname
, url
.port
, '/rest/getutxos'+self
.FORMAT_SEPARATOR
+'json', json_request
, True)
173 assert_equal(response
.status
, 400) #must be a 400 because we send an invalid json request
175 json_request
= '{"checkmempool'
176 response
= http_post_call(url
.hostname
, url
.port
, '/rest/getutxos'+self
.FORMAT_SEPARATOR
+'bin', json_request
, True)
177 assert_equal(response
.status
, 400) #must be a 400 because we send an invalid bin request
179 response
= http_post_call(url
.hostname
, url
.port
, '/rest/getutxos/checkmempool'+self
.FORMAT_SEPARATOR
+'bin', '', True)
180 assert_equal(response
.status
, 400) #must be a 400 because we send an invalid bin request
183 json_request
= '/checkmempool/'
184 for x
in range(0, 20):
185 json_request
+= txid
+'-'+str(n
)+'/'
186 json_request
= json_request
.rstrip("/")
187 response
= http_post_call(url
.hostname
, url
.port
, '/rest/getutxos'+json_request
+self
.FORMAT_SEPARATOR
+'json', '', True)
188 assert_equal(response
.status
, 400) #must be a 400 because we exceeding the limits
190 json_request
= '/checkmempool/'
191 for x
in range(0, 15):
192 json_request
+= txid
+'-'+str(n
)+'/'
193 json_request
= json_request
.rstrip("/")
194 response
= http_post_call(url
.hostname
, url
.port
, '/rest/getutxos'+json_request
+self
.FORMAT_SEPARATOR
+'json', '', True)
195 assert_equal(response
.status
, 200) #must be a 200 because we are within the limits
197 self
.nodes
[0].generate(1) #generate block to not affect upcoming tests
204 # check binary format
205 response
= http_get_call(url
.hostname
, url
.port
, '/rest/block/'+bb_hash
+self
.FORMAT_SEPARATOR
+"bin", True)
206 assert_equal(response
.status
, 200)
207 assert_greater_than(int(response
.getheader('content-length')), 80)
208 response_str
= response
.read()
210 # compare with block header
211 response_header
= http_get_call(url
.hostname
, url
.port
, '/rest/headers/1/'+bb_hash
+self
.FORMAT_SEPARATOR
+"bin", True)
212 assert_equal(response_header
.status
, 200)
213 assert_equal(int(response_header
.getheader('content-length')), 80)
214 response_header_str
= response_header
.read()
215 assert_equal(response_str
[0:80], response_header_str
)
217 # check block hex format
218 response_hex
= http_get_call(url
.hostname
, url
.port
, '/rest/block/'+bb_hash
+self
.FORMAT_SEPARATOR
+"hex", True)
219 assert_equal(response_hex
.status
, 200)
220 assert_greater_than(int(response_hex
.getheader('content-length')), 160)
221 response_hex_str
= response_hex
.read()
222 assert_equal(encode(response_str
, "hex_codec")[0:160], response_hex_str
[0:160])
224 # compare with hex block header
225 response_header_hex
= http_get_call(url
.hostname
, url
.port
, '/rest/headers/1/'+bb_hash
+self
.FORMAT_SEPARATOR
+"hex", True)
226 assert_equal(response_header_hex
.status
, 200)
227 assert_greater_than(int(response_header_hex
.getheader('content-length')), 160)
228 response_header_hex_str
= response_header_hex
.read()
229 assert_equal(response_hex_str
[0:160], response_header_hex_str
[0:160])
230 assert_equal(encode(response_header_str
, "hex_codec")[0:160], response_header_hex_str
[0:160])
233 block_json_string
= http_get_call(url
.hostname
, url
.port
, '/rest/block/'+bb_hash
+self
.FORMAT_SEPARATOR
+'json')
234 block_json_obj
= json
.loads(block_json_string
)
235 assert_equal(block_json_obj
['hash'], bb_hash
)
237 # compare with json block header
238 response_header_json
= http_get_call(url
.hostname
, url
.port
, '/rest/headers/1/'+bb_hash
+self
.FORMAT_SEPARATOR
+"json", True)
239 assert_equal(response_header_json
.status
, 200)
240 response_header_json_str
= response_header_json
.read().decode('utf-8')
241 json_obj
= json
.loads(response_header_json_str
, parse_float
=Decimal
)
242 assert_equal(len(json_obj
), 1) #ensure that there is one header in the json response
243 assert_equal(json_obj
[0]['hash'], bb_hash
) #request/response hash should be the same
245 #compare with normal RPC block response
246 rpc_block_json
= self
.nodes
[0].getblock(bb_hash
)
247 assert_equal(json_obj
[0]['hash'], rpc_block_json
['hash'])
248 assert_equal(json_obj
[0]['confirmations'], rpc_block_json
['confirmations'])
249 assert_equal(json_obj
[0]['height'], rpc_block_json
['height'])
250 assert_equal(json_obj
[0]['version'], rpc_block_json
['version'])
251 assert_equal(json_obj
[0]['merkleroot'], rpc_block_json
['merkleroot'])
252 assert_equal(json_obj
[0]['time'], rpc_block_json
['time'])
253 assert_equal(json_obj
[0]['nonce'], rpc_block_json
['nonce'])
254 assert_equal(json_obj
[0]['bits'], rpc_block_json
['bits'])
255 assert_equal(json_obj
[0]['difficulty'], rpc_block_json
['difficulty'])
256 assert_equal(json_obj
[0]['chainwork'], rpc_block_json
['chainwork'])
257 assert_equal(json_obj
[0]['previousblockhash'], rpc_block_json
['previousblockhash'])
259 #see if we can get 5 headers in one response
260 self
.nodes
[1].generate(5)
262 response_header_json
= http_get_call(url
.hostname
, url
.port
, '/rest/headers/5/'+bb_hash
+self
.FORMAT_SEPARATOR
+"json", True)
263 assert_equal(response_header_json
.status
, 200)
264 response_header_json_str
= response_header_json
.read().decode('utf-8')
265 json_obj
= json
.loads(response_header_json_str
)
266 assert_equal(len(json_obj
), 5) #now we should have 5 header objects
269 tx_hash
= block_json_obj
['tx'][0]['txid']
270 json_string
= http_get_call(url
.hostname
, url
.port
, '/rest/tx/'+tx_hash
+self
.FORMAT_SEPARATOR
+"json")
271 json_obj
= json
.loads(json_string
)
272 assert_equal(json_obj
['txid'], tx_hash
)
274 # check hex format response
275 hex_string
= http_get_call(url
.hostname
, url
.port
, '/rest/tx/'+tx_hash
+self
.FORMAT_SEPARATOR
+"hex", True)
276 assert_equal(hex_string
.status
, 200)
277 assert_greater_than(int(response
.getheader('content-length')), 10)
280 # check block tx details
281 # let's make 3 tx and mine them on node 1
283 txs
.append(self
.nodes
[0].sendtoaddress(self
.nodes
[2].getnewaddress(), 11))
284 txs
.append(self
.nodes
[0].sendtoaddress(self
.nodes
[2].getnewaddress(), 11))
285 txs
.append(self
.nodes
[0].sendtoaddress(self
.nodes
[2].getnewaddress(), 11))
288 # check that there are exactly 3 transactions in the TX memory pool before generating the block
289 json_string
= http_get_call(url
.hostname
, url
.port
, '/rest/mempool/info'+self
.FORMAT_SEPARATOR
+'json')
290 json_obj
= json
.loads(json_string
)
291 assert_equal(json_obj
['size'], 3)
292 # the size of the memory pool should be greater than 3x ~100 bytes
293 assert_greater_than(json_obj
['bytes'], 300)
295 # check that there are our submitted transactions in the TX memory pool
296 json_string
= http_get_call(url
.hostname
, url
.port
, '/rest/mempool/contents'+self
.FORMAT_SEPARATOR
+'json')
297 json_obj
= json
.loads(json_string
)
299 assert_equal(tx
in json_obj
, True)
301 # now mine the transactions
302 newblockhash
= self
.nodes
[1].generate(1)
305 #check if the 3 tx show up in the new block
306 json_string
= http_get_call(url
.hostname
, url
.port
, '/rest/block/'+newblockhash
[0]+self
.FORMAT_SEPARATOR
+'json')
307 json_obj
= json
.loads(json_string
)
308 for tx
in json_obj
['tx']:
309 if not 'coinbase' in tx
['vin'][0]: #exclude coinbase
310 assert_equal(tx
['txid'] in txs
, True)
312 #check the same but without tx details
313 json_string
= http_get_call(url
.hostname
, url
.port
, '/rest/block/notxdetails/'+newblockhash
[0]+self
.FORMAT_SEPARATOR
+'json')
314 json_obj
= json
.loads(json_string
)
316 assert_equal(tx
in json_obj
['tx'], True)
319 bb_hash
= self
.nodes
[0].getbestblockhash()
321 json_string
= http_get_call(url
.hostname
, url
.port
, '/rest/chaininfo.json')
322 json_obj
= json
.loads(json_string
)
323 assert_equal(json_obj
['bestblockhash'], bb_hash
)
325 if __name__
== '__main__':