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
= "."
48 self
.setup_clean_chain
= True
51 def setup_network(self
, split
=False):
52 super().setup_network()
53 connect_nodes_bi(self
.nodes
, 0, 2)
56 url
= urllib
.parse
.urlparse(self
.nodes
[0].url
)
57 self
.log
.info("Mining blocks...")
59 self
.nodes
[0].generate(1)
61 self
.nodes
[2].generate(100)
64 assert_equal(self
.nodes
[0].getbalance(), 50)
66 txid
= self
.nodes
[0].sendtoaddress(self
.nodes
[1].getnewaddress(), 0.1)
68 self
.nodes
[2].generate(1)
70 bb_hash
= self
.nodes
[0].getbestblockhash()
72 assert_equal(self
.nodes
[1].getbalance(), Decimal("0.1")) #balance now should be 0.1 on node 1
74 # load the latest 0.1 tx over the REST API
75 json_string
= http_get_call(url
.hostname
, url
.port
, '/rest/tx/'+txid
+self
.FORMAT_SEPARATOR
+"json")
76 json_obj
= json
.loads(json_string
)
77 vintx
= json_obj
['vin'][0]['txid'] # get the vin to later check for utxo (should be spent by then)
78 # get n of 0.1 outpoint
80 for vout
in json_obj
['vout']:
81 if vout
['value'] == 0.1:
85 #######################################
86 # GETUTXOS: query an unspent outpoint #
87 #######################################
88 json_request
= '/checkmempool/'+txid
+'-'+str(n
)
89 json_string
= http_get_call(url
.hostname
, url
.port
, '/rest/getutxos'+json_request
+self
.FORMAT_SEPARATOR
+'json')
90 json_obj
= json
.loads(json_string
)
92 #check chainTip response
93 assert_equal(json_obj
['chaintipHash'], bb_hash
)
95 #make sure there is one utxo
96 assert_equal(len(json_obj
['utxos']), 1)
97 assert_equal(json_obj
['utxos'][0]['value'], 0.1)
100 #################################################
101 # GETUTXOS: now query an already spent outpoint #
102 #################################################
103 json_request
= '/checkmempool/'+vintx
+'-0'
104 json_string
= http_get_call(url
.hostname
, url
.port
, '/rest/getutxos'+json_request
+self
.FORMAT_SEPARATOR
+'json')
105 json_obj
= json
.loads(json_string
)
107 #check chainTip response
108 assert_equal(json_obj
['chaintipHash'], bb_hash
)
110 #make sure there is no utox in the response because this oupoint has been spent
111 assert_equal(len(json_obj
['utxos']), 0)
114 assert_equal(json_obj
['bitmap'], "0")
117 ##################################################
118 # GETUTXOS: now check both with the same request #
119 ##################################################
120 json_request
= '/checkmempool/'+txid
+'-'+str(n
)+'/'+vintx
+'-0'
121 json_string
= http_get_call(url
.hostname
, url
.port
, '/rest/getutxos'+json_request
+self
.FORMAT_SEPARATOR
+'json')
122 json_obj
= json
.loads(json_string
)
123 assert_equal(len(json_obj
['utxos']), 1)
124 assert_equal(json_obj
['bitmap'], "10")
126 #test binary response
127 bb_hash
= self
.nodes
[0].getbestblockhash()
129 binaryRequest
= b
'\x01\x02'
130 binaryRequest
+= hex_str_to_bytes(txid
)
131 binaryRequest
+= pack("i", n
)
132 binaryRequest
+= hex_str_to_bytes(vintx
)
133 binaryRequest
+= pack("i", 0)
135 bin_response
= http_post_call(url
.hostname
, url
.port
, '/rest/getutxos'+self
.FORMAT_SEPARATOR
+'bin', binaryRequest
)
137 output
.write(bin_response
)
139 chainHeight
= unpack("i", output
.read(4))[0]
140 hashFromBinResponse
= hex(deser_uint256(output
))[2:].zfill(64)
142 assert_equal(bb_hash
, hashFromBinResponse
) #check if getutxo's chaintip during calculation was fine
143 assert_equal(chainHeight
, 102) #chain height must be 102
146 ############################
147 # GETUTXOS: mempool checks #
148 ############################
150 # do a tx and don't sync
151 txid
= self
.nodes
[0].sendtoaddress(self
.nodes
[1].getnewaddress(), 0.1)
152 json_string
= http_get_call(url
.hostname
, url
.port
, '/rest/tx/'+txid
+self
.FORMAT_SEPARATOR
+"json")
153 json_obj
= json
.loads(json_string
)
154 vintx
= json_obj
['vin'][0]['txid'] # get the vin to later check for utxo (should be spent by then)
155 # get n of 0.1 outpoint
157 for vout
in json_obj
['vout']:
158 if vout
['value'] == 0.1:
161 json_request
= '/'+txid
+'-'+str(n
)
162 json_string
= http_get_call(url
.hostname
, url
.port
, '/rest/getutxos'+json_request
+self
.FORMAT_SEPARATOR
+'json')
163 json_obj
= json
.loads(json_string
)
164 assert_equal(len(json_obj
['utxos']), 0) #there should be an outpoint because it has just added to the mempool
166 json_request
= '/checkmempool/'+txid
+'-'+str(n
)
167 json_string
= http_get_call(url
.hostname
, url
.port
, '/rest/getutxos'+json_request
+self
.FORMAT_SEPARATOR
+'json')
168 json_obj
= json
.loads(json_string
)
169 assert_equal(len(json_obj
['utxos']), 1) #there should be an outpoint because it has just added to the mempool
171 #do some invalid requests
172 json_request
= '{"checkmempool'
173 response
= http_post_call(url
.hostname
, url
.port
, '/rest/getutxos'+self
.FORMAT_SEPARATOR
+'json', json_request
, True)
174 assert_equal(response
.status
, 400) #must be a 400 because we send an invalid json request
176 json_request
= '{"checkmempool'
177 response
= http_post_call(url
.hostname
, url
.port
, '/rest/getutxos'+self
.FORMAT_SEPARATOR
+'bin', json_request
, True)
178 assert_equal(response
.status
, 400) #must be a 400 because we send an invalid bin request
180 response
= http_post_call(url
.hostname
, url
.port
, '/rest/getutxos/checkmempool'+self
.FORMAT_SEPARATOR
+'bin', '', True)
181 assert_equal(response
.status
, 400) #must be a 400 because we send an invalid bin request
184 json_request
= '/checkmempool/'
185 for x
in range(0, 20):
186 json_request
+= txid
+'-'+str(n
)+'/'
187 json_request
= json_request
.rstrip("/")
188 response
= http_post_call(url
.hostname
, url
.port
, '/rest/getutxos'+json_request
+self
.FORMAT_SEPARATOR
+'json', '', True)
189 assert_equal(response
.status
, 400) #must be a 400 because we exceeding the limits
191 json_request
= '/checkmempool/'
192 for x
in range(0, 15):
193 json_request
+= txid
+'-'+str(n
)+'/'
194 json_request
= json_request
.rstrip("/")
195 response
= http_post_call(url
.hostname
, url
.port
, '/rest/getutxos'+json_request
+self
.FORMAT_SEPARATOR
+'json', '', True)
196 assert_equal(response
.status
, 200) #must be a 200 because we are within the limits
198 self
.nodes
[0].generate(1) #generate block to not affect upcoming tests
205 # check binary format
206 response
= http_get_call(url
.hostname
, url
.port
, '/rest/block/'+bb_hash
+self
.FORMAT_SEPARATOR
+"bin", True)
207 assert_equal(response
.status
, 200)
208 assert_greater_than(int(response
.getheader('content-length')), 80)
209 response_str
= response
.read()
211 # compare with block header
212 response_header
= http_get_call(url
.hostname
, url
.port
, '/rest/headers/1/'+bb_hash
+self
.FORMAT_SEPARATOR
+"bin", True)
213 assert_equal(response_header
.status
, 200)
214 assert_equal(int(response_header
.getheader('content-length')), 80)
215 response_header_str
= response_header
.read()
216 assert_equal(response_str
[0:80], response_header_str
)
218 # check block hex format
219 response_hex
= http_get_call(url
.hostname
, url
.port
, '/rest/block/'+bb_hash
+self
.FORMAT_SEPARATOR
+"hex", True)
220 assert_equal(response_hex
.status
, 200)
221 assert_greater_than(int(response_hex
.getheader('content-length')), 160)
222 response_hex_str
= response_hex
.read()
223 assert_equal(encode(response_str
, "hex_codec")[0:160], response_hex_str
[0:160])
225 # compare with hex block header
226 response_header_hex
= http_get_call(url
.hostname
, url
.port
, '/rest/headers/1/'+bb_hash
+self
.FORMAT_SEPARATOR
+"hex", True)
227 assert_equal(response_header_hex
.status
, 200)
228 assert_greater_than(int(response_header_hex
.getheader('content-length')), 160)
229 response_header_hex_str
= response_header_hex
.read()
230 assert_equal(response_hex_str
[0:160], response_header_hex_str
[0:160])
231 assert_equal(encode(response_header_str
, "hex_codec")[0:160], response_header_hex_str
[0:160])
234 block_json_string
= http_get_call(url
.hostname
, url
.port
, '/rest/block/'+bb_hash
+self
.FORMAT_SEPARATOR
+'json')
235 block_json_obj
= json
.loads(block_json_string
)
236 assert_equal(block_json_obj
['hash'], bb_hash
)
238 # compare with json block header
239 response_header_json
= http_get_call(url
.hostname
, url
.port
, '/rest/headers/1/'+bb_hash
+self
.FORMAT_SEPARATOR
+"json", True)
240 assert_equal(response_header_json
.status
, 200)
241 response_header_json_str
= response_header_json
.read().decode('utf-8')
242 json_obj
= json
.loads(response_header_json_str
, parse_float
=Decimal
)
243 assert_equal(len(json_obj
), 1) #ensure that there is one header in the json response
244 assert_equal(json_obj
[0]['hash'], bb_hash
) #request/response hash should be the same
246 #compare with normal RPC block response
247 rpc_block_json
= self
.nodes
[0].getblock(bb_hash
)
248 assert_equal(json_obj
[0]['hash'], rpc_block_json
['hash'])
249 assert_equal(json_obj
[0]['confirmations'], rpc_block_json
['confirmations'])
250 assert_equal(json_obj
[0]['height'], rpc_block_json
['height'])
251 assert_equal(json_obj
[0]['version'], rpc_block_json
['version'])
252 assert_equal(json_obj
[0]['merkleroot'], rpc_block_json
['merkleroot'])
253 assert_equal(json_obj
[0]['time'], rpc_block_json
['time'])
254 assert_equal(json_obj
[0]['nonce'], rpc_block_json
['nonce'])
255 assert_equal(json_obj
[0]['bits'], rpc_block_json
['bits'])
256 assert_equal(json_obj
[0]['difficulty'], rpc_block_json
['difficulty'])
257 assert_equal(json_obj
[0]['chainwork'], rpc_block_json
['chainwork'])
258 assert_equal(json_obj
[0]['previousblockhash'], rpc_block_json
['previousblockhash'])
260 #see if we can get 5 headers in one response
261 self
.nodes
[1].generate(5)
263 response_header_json
= http_get_call(url
.hostname
, url
.port
, '/rest/headers/5/'+bb_hash
+self
.FORMAT_SEPARATOR
+"json", True)
264 assert_equal(response_header_json
.status
, 200)
265 response_header_json_str
= response_header_json
.read().decode('utf-8')
266 json_obj
= json
.loads(response_header_json_str
)
267 assert_equal(len(json_obj
), 5) #now we should have 5 header objects
270 tx_hash
= block_json_obj
['tx'][0]['txid']
271 json_string
= http_get_call(url
.hostname
, url
.port
, '/rest/tx/'+tx_hash
+self
.FORMAT_SEPARATOR
+"json")
272 json_obj
= json
.loads(json_string
)
273 assert_equal(json_obj
['txid'], tx_hash
)
275 # check hex format response
276 hex_string
= http_get_call(url
.hostname
, url
.port
, '/rest/tx/'+tx_hash
+self
.FORMAT_SEPARATOR
+"hex", True)
277 assert_equal(hex_string
.status
, 200)
278 assert_greater_than(int(response
.getheader('content-length')), 10)
281 # check block tx details
282 # let's make 3 tx and mine them on node 1
284 txs
.append(self
.nodes
[0].sendtoaddress(self
.nodes
[2].getnewaddress(), 11))
285 txs
.append(self
.nodes
[0].sendtoaddress(self
.nodes
[2].getnewaddress(), 11))
286 txs
.append(self
.nodes
[0].sendtoaddress(self
.nodes
[2].getnewaddress(), 11))
289 # check that there are exactly 3 transactions in the TX memory pool before generating the block
290 json_string
= http_get_call(url
.hostname
, url
.port
, '/rest/mempool/info'+self
.FORMAT_SEPARATOR
+'json')
291 json_obj
= json
.loads(json_string
)
292 assert_equal(json_obj
['size'], 3)
293 # the size of the memory pool should be greater than 3x ~100 bytes
294 assert_greater_than(json_obj
['bytes'], 300)
296 # check that there are our submitted transactions in the TX memory pool
297 json_string
= http_get_call(url
.hostname
, url
.port
, '/rest/mempool/contents'+self
.FORMAT_SEPARATOR
+'json')
298 json_obj
= json
.loads(json_string
)
300 assert_equal(tx
in json_obj
, True)
302 # now mine the transactions
303 newblockhash
= self
.nodes
[1].generate(1)
306 #check if the 3 tx show up in the new block
307 json_string
= http_get_call(url
.hostname
, url
.port
, '/rest/block/'+newblockhash
[0]+self
.FORMAT_SEPARATOR
+'json')
308 json_obj
= json
.loads(json_string
)
309 for tx
in json_obj
['tx']:
310 if not 'coinbase' in tx
['vin'][0]: #exclude coinbase
311 assert_equal(tx
['txid'] in txs
, True)
313 #check the same but without tx details
314 json_string
= http_get_call(url
.hostname
, url
.port
, '/rest/block/notxdetails/'+newblockhash
[0]+self
.FORMAT_SEPARATOR
+'json')
315 json_obj
= json
.loads(json_string
)
317 assert_equal(tx
in json_obj
['tx'], True)
320 bb_hash
= self
.nodes
[0].getbestblockhash()
322 json_string
= http_get_call(url
.hostname
, url
.port
, '/rest/chaininfo.json')
323 json_obj
= json
.loads(json_string
)
324 assert_equal(json_obj
['bestblockhash'], bb_hash
)
326 if __name__
== '__main__':