4
4
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5
5
"""Test the REST API."""
6
6
from decimal import Decimal
7
+ from enum import Enum
7
8
from io import BytesIO
8
9
import json
9
10
from codecs import encode
21
22
hex_str_to_bytes ,
22
23
)
23
24
24
- def http_get_call ( host , port , path , response_object = 0 ):
25
- """Make a simple HTTP GET request."""
26
- conn = http . client . HTTPConnection ( host , port )
27
- conn . request ( 'GET' , path )
25
+ class ReqType ( Enum ):
26
+ JSON = 1
27
+ BIN = 2
28
+ HEX = 3
28
29
29
- if response_object :
30
- return conn .getresponse ()
30
+ class RetType (Enum ):
31
+ OBJ = 1
32
+ BYTES = 2
33
+ JSON = 3
31
34
32
- return conn .getresponse ().read ().decode ('utf-8' )
33
-
34
- def http_post_call (host , port , path , requestdata = '' , response_object = 0 ):
35
- """Make a simple HTTP POST request with a request body."""
36
- conn = http .client .HTTPConnection (host , port )
37
- conn .request ('POST' , path , requestdata )
38
-
39
- if response_object :
40
- return conn .getresponse ()
41
-
42
- return conn .getresponse ().read ()
35
+ def filter_output_indices_by_value (vouts , value ):
36
+ for vout in vouts :
37
+ if vout ['value' ] == value :
38
+ yield vout ['n' ]
43
39
44
40
class RESTTest (BitcoinTestFramework ):
45
41
def set_test_params (self ):
@@ -50,10 +46,35 @@ def set_test_params(self):
50
46
def setup_network (self , split = False ):
51
47
super ().setup_network ()
52
48
connect_nodes_bi (self .nodes , 0 , 2 )
49
+ self .url = urllib .parse .urlparse (self .nodes [0 ].url )
50
+
51
+ def test_rest_request (self , uri , http_method = 'GET' , req_type = ReqType .JSON , body = '' , status = 200 , ret_type = RetType .JSON ):
52
+ rest_uri = '/rest' + uri
53
+ if req_type == ReqType .JSON :
54
+ rest_uri += '.json'
55
+ elif req_type == ReqType .BIN :
56
+ rest_uri += '.bin'
57
+ elif req_type == ReqType .HEX :
58
+ rest_uri += '.hex'
59
+
60
+ conn = http .client .HTTPConnection (self .url .hostname , self .url .port )
61
+ self .log .debug ('%s %s %s' , http_method , rest_uri , body )
62
+ if http_method == 'GET' :
63
+ conn .request ('GET' , rest_uri )
64
+ elif http_method == 'POST' :
65
+ conn .request ('POST' , rest_uri , body )
66
+ resp = conn .getresponse ()
67
+
68
+ assert_equal (resp .status , status )
69
+
70
+ if ret_type == RetType .OBJ :
71
+ return resp
72
+ elif ret_type == RetType .BYTES :
73
+ return resp .read ()
74
+ elif ret_type == RetType .JSON :
75
+ return json .loads (resp .read ().decode ('utf-8' ), parse_float = Decimal )
53
76
54
77
def run_test (self ):
55
- url = urllib .parse .urlparse (self .nodes [0 ].url )
56
-
57
78
self .log .info ("Mine blocks and send Bitcoin to node 1" )
58
79
59
80
self .nodes [0 ].generate (1 )
@@ -73,49 +94,40 @@ def run_test(self):
73
94
74
95
self .log .info ("Load the transaction using the /tx URI" )
75
96
76
- json_request = "/rest/tx/{}.json" .format (txid )
77
- json_string = http_get_call (url .hostname , url .port , json_request )
78
- json_obj = json .loads (json_string )
79
- vintx = json_obj ['vin' ][0 ]['txid' ] # get the vin to later check for utxo (should be spent by then)
97
+ json_obj = self .test_rest_request ("/tx/{}" .format (txid ))
98
+ spent = (json_obj ['vin' ][0 ]['txid' ], json_obj ['vin' ][0 ]['vout' ]) # get the vin to later check for utxo (should be spent by then)
80
99
# get n of 0.1 outpoint
81
- n = 0
82
- for vout in json_obj ['vout' ]:
83
- if vout ['value' ] == 0.1 :
84
- n = vout ['n' ]
100
+ n , = filter_output_indices_by_value (json_obj ['vout' ], Decimal ('0.1' ))
101
+ spending = (txid , n )
85
102
86
103
self .log .info ("Query an unspent TXO using the /getutxos URI" )
87
104
88
- json_request = "/rest/getutxos/{}-{}.json" .format (txid , str (n ))
89
- json_string = http_get_call (url .hostname , url .port , json_request )
90
- json_obj = json .loads (json_string )
105
+ json_obj = self .test_rest_request ("/getutxos/{}-{}" .format (* spending ))
91
106
92
107
# Check chainTip response
93
108
assert_equal (json_obj ['chaintipHash' ], bb_hash )
94
109
95
110
# Make sure there is one utxo
96
111
assert_equal (len (json_obj ['utxos' ]), 1 )
97
- assert_equal (json_obj ['utxos' ][0 ]['value' ], 0.1 )
112
+ assert_equal (json_obj ['utxos' ][0 ]['value' ], Decimal ( ' 0.1' ) )
98
113
99
114
self .log .info ("Query a spent TXO using the /getutxos URI" )
100
115
101
- json_request = "/rest/getutxos/{}-0.json" .format (vintx )
102
- json_string = http_get_call (url .hostname , url .port , json_request )
103
- json_obj = json .loads (json_string )
116
+ json_obj = self .test_rest_request ("/getutxos/{}-{}" .format (* spent ))
104
117
105
118
# Check chainTip response
106
119
assert_equal (json_obj ['chaintipHash' ], bb_hash )
107
120
108
- # Make sure there is no utxo in the response because this oupoint has been spent
121
+ # Make sure there is no utxo in the response because this outpoint has been spent
109
122
assert_equal (len (json_obj ['utxos' ]), 0 )
110
123
111
124
# Check bitmap
112
125
assert_equal (json_obj ['bitmap' ], "0" )
113
126
114
127
self .log .info ("Query two TXOs using the /getutxos URI" )
115
128
116
- json_request = "/rest/getutxos/{}-{}/{}-0.json" .format (txid , str (n ), vintx )
117
- json_string = http_get_call (url .hostname , url .port , json_request )
118
- json_obj = json .loads (json_string )
129
+ json_obj = self .test_rest_request ("/getutxos/{}-{}/{}-{}" .format (* (spending + spent )))
130
+
119
131
assert_equal (len (json_obj ['utxos' ]), 1 )
120
132
assert_equal (json_obj ['bitmap' ], "10" )
121
133
@@ -124,12 +136,11 @@ def run_test(self):
124
136
bb_hash = self .nodes [0 ].getbestblockhash ()
125
137
126
138
bin_request = b'\x01 \x02 '
127
- bin_request += hex_str_to_bytes (txid )
128
- bin_request += pack ("i" , n )
129
- bin_request += hex_str_to_bytes (vintx )
130
- bin_request += pack ("i" , 0 )
139
+ for txid , n in [spending , spent ]:
140
+ bin_request += hex_str_to_bytes (txid )
141
+ bin_request += pack ("i" , n )
131
142
132
- bin_response = http_post_call ( url . hostname , url . port , '/rest/getutxos.bin' , bin_request )
143
+ bin_response = self . test_rest_request ( "/getutxos" , http_method = 'POST' , req_type = ReqType . BIN , body = bin_request , ret_type = RetType . BYTES )
133
144
output = BytesIO ()
134
145
output .write (bin_response )
135
146
output .seek (0 )
@@ -146,115 +157,81 @@ def run_test(self):
146
157
147
158
# do a tx and don't sync
148
159
txid = self .nodes [0 ].sendtoaddress (self .nodes [1 ].getnewaddress (), 0.1 )
149
- json_request = "/rest/tx/{}.json" .format (txid )
150
- json_string = http_get_call (url .hostname , url .port , json_request )
151
- json_obj = json .loads (json_string )
160
+ json_obj = self .test_rest_request ("/tx/{}" .format (txid ))
152
161
# get the spent output to later check for utxo (should be spent by then)
153
- spent = '{}-{}' . format (json_obj ['vin' ][0 ]['txid' ], json_obj ['vin' ][0 ]['vout' ])
162
+ spent = (json_obj ['vin' ][0 ]['txid' ], json_obj ['vin' ][0 ]['vout' ])
154
163
# get n of 0.1 outpoint
155
- n = 0
156
- for vout in json_obj ['vout' ]:
157
- if vout ['value' ] == 0.1 :
158
- n = vout ['n' ]
159
- spending = '{}-{}' .format (txid , n )
160
-
161
- json_request = '/rest/getutxos/{}.json' .format (spending )
162
- json_string = http_get_call (url .hostname , url .port , json_request )
163
- json_obj = json .loads (json_string )
164
+ n , = filter_output_indices_by_value (json_obj ['vout' ], Decimal ('0.1' ))
165
+ spending = (txid , n )
166
+
167
+ json_obj = self .test_rest_request ("/getutxos/{}-{}" .format (* spending ))
164
168
assert_equal (len (json_obj ['utxos' ]), 0 )
165
169
166
- json_request = '/rest/getutxos/checkmempool/{}.json' .format (spending )
167
- json_string = http_get_call (url .hostname , url .port , json_request )
168
- json_obj = json .loads (json_string )
170
+ json_obj = self .test_rest_request ("/getutxos/checkmempool/{}-{}" .format (* spending ))
169
171
assert_equal (len (json_obj ['utxos' ]), 1 )
170
172
171
- json_request = '/rest/getutxos/{}.json' .format (spent )
172
- json_string = http_get_call (url .hostname , url .port , json_request )
173
- json_obj = json .loads (json_string )
173
+ json_obj = self .test_rest_request ("/getutxos/{}-{}" .format (* spent ))
174
174
assert_equal (len (json_obj ['utxos' ]), 1 )
175
175
176
- json_request = '/rest/getutxos/checkmempool/{}.json' .format (spent )
177
- json_string = http_get_call (url .hostname , url .port , json_request )
178
- json_obj = json .loads (json_string )
176
+ json_obj = self .test_rest_request ("/getutxos/checkmempool/{}-{}" .format (* spent ))
179
177
assert_equal (len (json_obj ['utxos' ]), 0 )
180
178
181
179
self .nodes [0 ].generate (1 )
182
180
self .sync_all ()
183
181
184
- json_request = '/rest/getutxos/{}.json' .format (spending )
185
- json_string = http_get_call (url .hostname , url .port , json_request )
186
- json_obj = json .loads (json_string )
182
+ json_obj = self .test_rest_request ("/getutxos/{}-{}" .format (* spending ))
187
183
assert_equal (len (json_obj ['utxos' ]), 1 )
188
184
189
- json_request = '/rest/getutxos/checkmempool/{}.json' .format (spending )
190
- json_string = http_get_call (url .hostname , url .port , json_request )
191
- json_obj = json .loads (json_string )
185
+ json_obj = self .test_rest_request ("/getutxos/checkmempool/{}-{}" .format (* spending ))
192
186
assert_equal (len (json_obj ['utxos' ]), 1 )
193
187
194
188
# Do some invalid requests
195
- json_request = '{"checkmempool'
196
- response = http_post_call (url .hostname , url .port , '/rest/getutxos.json' , json_request , True )
197
- assert_equal (response .status , 400 ) # must be a 400 because we send an invalid json request
198
-
199
- json_request = '{"checkmempool'
200
- response = http_post_call (url .hostname , url .port , '/rest/getutxos.bin' , json_request , True )
201
- assert_equal (response .status , 400 ) # must be a 400 because we send an invalid bin request
202
-
203
- response = http_post_call (url .hostname , url .port , '/rest/getutxos/checkmempool.json' , '' , True )
204
- assert_equal (response .status , 400 ) # must be a 400 because we send an invalid bin request
189
+ self .test_rest_request ("/getutxos" , http_method = 'POST' , req_type = ReqType .JSON , body = '{"checkmempool' , status = 400 , ret_type = RetType .OBJ )
190
+ self .test_rest_request ("/getutxos" , http_method = 'POST' , req_type = ReqType .BIN , body = '{"checkmempool' , status = 400 , ret_type = RetType .OBJ )
191
+ self .test_rest_request ("/getutxos/checkmempool" , http_method = 'POST' , req_type = ReqType .JSON , status = 400 , ret_type = RetType .OBJ )
205
192
206
193
# Test limits
207
- json_request = '/rest/getutxos/checkmempool/' + '/' .join (["{}-{}" .format (txid , n ) for n in range (20 )]) + '.json'
208
- response = http_post_call (url .hostname , url .port , json_request , '' , True )
209
- assert_equal (response .status , 400 ) # must be a 400 because we exceeding the limits
194
+ long_uri = '/' .join (["{}-{}" .format (txid , n ) for n in range (20 )])
195
+ self .test_rest_request ("/getutxos/checkmempool/{}" .format (long_uri ), http_method = 'POST' , status = 400 , ret_type = RetType .OBJ )
210
196
211
- json_request = '/rest/getutxos/checkmempool/' + '/' .join (['{}-{}' .format (txid , n ) for n in range (15 )]) + '.json'
212
- response = http_post_call (url .hostname , url .port , json_request , '' , True )
213
- assert_equal (response .status , 200 ) # must be a 200 because we are within the limits
197
+ long_uri = '/' .join (['{}-{}' .format (txid , n ) for n in range (15 )])
198
+ self .test_rest_request ("/getutxos/checkmempool/{}" .format (long_uri ), http_method = 'POST' , status = 200 )
214
199
215
200
self .nodes [0 ].generate (1 ) # generate block to not affect upcoming tests
216
201
self .sync_all ()
217
202
218
203
self .log .info ("Test the /block and /headers URIs" )
219
204
220
205
# Check binary format
221
- response = http_get_call (url .hostname , url .port , '/rest/block/{}.bin' .format (bb_hash ), True )
222
- assert_equal (response .status , 200 )
206
+ response = self .test_rest_request ("/block/{}" .format (bb_hash ), req_type = ReqType .BIN , ret_type = RetType .OBJ )
223
207
assert_greater_than (int (response .getheader ('content-length' )), 80 )
224
208
response_str = response .read ()
225
209
226
210
# Compare with block header
227
- response_header = http_get_call (url .hostname , url .port , '/rest/headers/1/{}.bin' .format (bb_hash ), True )
228
- assert_equal (response_header .status , 200 )
211
+ response_header = self .test_rest_request ("/headers/1/{}" .format (bb_hash ), req_type = ReqType .BIN , ret_type = RetType .OBJ )
229
212
assert_equal (int (response_header .getheader ('content-length' )), 80 )
230
213
response_header_str = response_header .read ()
231
214
assert_equal (response_str [0 :80 ], response_header_str )
232
215
233
216
# Check block hex format
234
- response_hex = http_get_call (url .hostname , url .port , '/rest/block/{}.hex' .format (bb_hash ), True )
235
- assert_equal (response_hex .status , 200 )
217
+ response_hex = self .test_rest_request ("/block/{}" .format (bb_hash ), req_type = ReqType .HEX , ret_type = RetType .OBJ )
236
218
assert_greater_than (int (response_hex .getheader ('content-length' )), 160 )
237
219
response_hex_str = response_hex .read ()
238
220
assert_equal (encode (response_str , "hex_codec" )[0 :160 ], response_hex_str [0 :160 ])
239
221
240
222
# Compare with hex block header
241
- response_header_hex = http_get_call (url .hostname , url .port , '/rest/headers/1/{}.hex' .format (bb_hash ), True )
242
- assert_equal (response_header_hex .status , 200 )
223
+ response_header_hex = self .test_rest_request ("/headers/1/{}" .format (bb_hash ), req_type = ReqType .HEX , ret_type = RetType .OBJ )
243
224
assert_greater_than (int (response_header_hex .getheader ('content-length' )), 160 )
244
225
response_header_hex_str = response_header_hex .read ()
245
226
assert_equal (response_hex_str [0 :160 ], response_header_hex_str [0 :160 ])
246
227
assert_equal (encode (response_header_str , "hex_codec" )[0 :160 ], response_header_hex_str [0 :160 ])
247
228
248
229
# Check json format
249
- block_json_string = http_get_call (url .hostname , url .port , '/rest/block/{}.json' .format (bb_hash ))
250
- block_json_obj = json .loads (block_json_string )
230
+ block_json_obj = self .test_rest_request ("/block/{}" .format (bb_hash ))
251
231
assert_equal (block_json_obj ['hash' ], bb_hash )
252
232
253
233
# Compare with json block header
254
- response_header_json = http_get_call (url .hostname , url .port , '/rest/headers/1/{}.json' .format (bb_hash ), True )
255
- assert_equal (response_header_json .status , 200 )
256
- response_header_json_str = response_header_json .read ().decode ('utf-8' )
257
- json_obj = json .loads (response_header_json_str , parse_float = Decimal )
234
+ json_obj = self .test_rest_request ("/headers/1/{}" .format (bb_hash ))
258
235
assert_equal (len (json_obj ), 1 ) # ensure that there is one header in the json response
259
236
assert_equal (json_obj [0 ]['hash' ], bb_hash ) # request/response hash should be the same
260
237
@@ -266,23 +243,18 @@ def run_test(self):
266
243
# See if we can get 5 headers in one response
267
244
self .nodes [1 ].generate (5 )
268
245
self .sync_all ()
269
- response_header_json = http_get_call (url .hostname , url .port , '/rest/headers/5/{}.json' .format (bb_hash ), True )
270
- assert_equal (response_header_json .status , 200 )
271
- response_header_json_str = response_header_json .read ().decode ('utf-8' )
272
- json_obj = json .loads (response_header_json_str )
246
+ json_obj = self .test_rest_request ("/headers/5/{}" .format (bb_hash ))
273
247
assert_equal (len (json_obj ), 5 ) # now we should have 5 header objects
274
248
275
249
self .log .info ("Test the /tx URI" )
276
250
277
251
tx_hash = block_json_obj ['tx' ][0 ]['txid' ]
278
- json_string = http_get_call (url .hostname , url .port , '/rest/tx/{}.json' .format (tx_hash ))
279
- json_obj = json .loads (json_string )
252
+ json_obj = self .test_rest_request ("/tx/{}" .format (tx_hash ))
280
253
assert_equal (json_obj ['txid' ], tx_hash )
281
254
282
255
# Check hex format response
283
- hex_string = http_get_call (url .hostname , url .port , '/rest/tx/{}.hex' .format (tx_hash ), True )
284
- assert_equal (hex_string .status , 200 )
285
- assert_greater_than (int (response .getheader ('content-length' )), 10 )
256
+ hex_response = self .test_rest_request ("/tx/{}" .format (tx_hash ), req_type = ReqType .HEX , ret_type = RetType .OBJ )
257
+ assert_greater_than (int (hex_response .getheader ('content-length' )), 10 )
286
258
287
259
self .log .info ("Test tx inclusion in the /mempool and /block URIs" )
288
260
@@ -294,17 +266,15 @@ def run_test(self):
294
266
self .sync_all ()
295
267
296
268
# Check that there are exactly 3 transactions in the TX memory pool before generating the block
297
- json_string = http_get_call (url .hostname , url .port , '/rest/mempool/info.json' )
298
- json_obj = json .loads (json_string )
269
+ json_obj = self .test_rest_request ("/mempool/info" )
299
270
assert_equal (json_obj ['size' ], 3 )
300
271
# the size of the memory pool should be greater than 3x ~100 bytes
301
272
assert_greater_than (json_obj ['bytes' ], 300 )
302
273
303
274
# Check that there are our submitted transactions in the TX memory pool
304
- json_string = http_get_call (url .hostname , url .port , '/rest/mempool/contents.json' )
305
- json_obj = json .loads (json_string )
275
+ json_obj = self .test_rest_request ("/mempool/contents" )
306
276
for i , tx in enumerate (txs ):
307
- assert_equal ( tx in json_obj , True )
277
+ assert tx in json_obj
308
278
assert_equal (json_obj [tx ]['spentby' ], txs [i + 1 :i + 2 ])
309
279
assert_equal (json_obj [tx ]['depends' ], txs [i - 1 :i ])
310
280
@@ -313,24 +283,21 @@ def run_test(self):
313
283
self .sync_all ()
314
284
315
285
# Check if the 3 tx show up in the new block
316
- json_string = http_get_call (url .hostname , url .port , '/rest/block/{}.json' .format (newblockhash [0 ]))
317
- json_obj = json .loads (json_string )
318
- for tx in json_obj ['tx' ]:
319
- if 'coinbase' not in tx ['vin' ][0 ]: # exclude coinbase
320
- assert_equal (tx ['txid' ] in txs , True )
286
+ json_obj = self .test_rest_request ("/block/{}" .format (newblockhash [0 ]))
287
+ non_coinbase_txs = {tx ['txid' ] for tx in json_obj ['tx' ]
288
+ if 'coinbase' not in tx ['vin' ][0 ]}
289
+ assert_equal (non_coinbase_txs , set (txs ))
321
290
322
291
# Check the same but without tx details
323
- json_string = http_get_call (url .hostname , url .port , '/rest/block/notxdetails/{}.json' .format (newblockhash [0 ]))
324
- json_obj = json .loads (json_string )
292
+ json_obj = self .test_rest_request ("/block/notxdetails/{}" .format (newblockhash [0 ]))
325
293
for tx in txs :
326
- assert_equal ( tx in json_obj ['tx' ], True )
294
+ assert tx in json_obj ['tx' ]
327
295
328
296
self .log .info ("Test the /chaininfo URI" )
329
297
330
298
bb_hash = self .nodes [0 ].getbestblockhash ()
331
299
332
- json_string = http_get_call (url .hostname , url .port , '/rest/chaininfo.json' )
333
- json_obj = json .loads (json_string )
300
+ json_obj = self .test_rest_request ("/chaininfo" )
334
301
assert_equal (json_obj ['bestblockhash' ], bb_hash )
335
302
336
303
if __name__ == '__main__' :
0 commit comments