Skip to content

Commit ad00fbe

Browse files
jnewberyromanz
authored andcommitted
[tests] refactor interface_rest.py to avoid code repetition
Also refactor txout index parsing and formatting.
1 parent 7a3181a commit ad00fbe

File tree

1 file changed

+92
-125
lines changed

1 file changed

+92
-125
lines changed

test/functional/interface_rest.py

Lines changed: 92 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
55
"""Test the REST API."""
66
from decimal import Decimal
7+
from enum import Enum
78
from io import BytesIO
89
import json
910
from codecs import encode
@@ -21,25 +22,20 @@
2122
hex_str_to_bytes,
2223
)
2324

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
2829

29-
if response_object:
30-
return conn.getresponse()
30+
class RetType(Enum):
31+
OBJ = 1
32+
BYTES = 2
33+
JSON = 3
3134

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']
4339

4440
class RESTTest (BitcoinTestFramework):
4541
def set_test_params(self):
@@ -50,10 +46,35 @@ def set_test_params(self):
5046
def setup_network(self, split=False):
5147
super().setup_network()
5248
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)
5376

5477
def run_test(self):
55-
url = urllib.parse.urlparse(self.nodes[0].url)
56-
5778
self.log.info("Mine blocks and send Bitcoin to node 1")
5879

5980
self.nodes[0].generate(1)
@@ -73,49 +94,40 @@ def run_test(self):
7394

7495
self.log.info("Load the transaction using the /tx URI")
7596

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)
8099
# 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)
85102

86103
self.log.info("Query an unspent TXO using the /getutxos URI")
87104

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))
91106

92107
# Check chainTip response
93108
assert_equal(json_obj['chaintipHash'], bb_hash)
94109

95110
# Make sure there is one utxo
96111
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'))
98113

99114
self.log.info("Query a spent TXO using the /getutxos URI")
100115

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))
104117

105118
# Check chainTip response
106119
assert_equal(json_obj['chaintipHash'], bb_hash)
107120

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
109122
assert_equal(len(json_obj['utxos']), 0)
110123

111124
# Check bitmap
112125
assert_equal(json_obj['bitmap'], "0")
113126

114127
self.log.info("Query two TXOs using the /getutxos URI")
115128

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+
119131
assert_equal(len(json_obj['utxos']), 1)
120132
assert_equal(json_obj['bitmap'], "10")
121133

@@ -124,12 +136,11 @@ def run_test(self):
124136
bb_hash = self.nodes[0].getbestblockhash()
125137

126138
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)
131142

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)
133144
output = BytesIO()
134145
output.write(bin_response)
135146
output.seek(0)
@@ -146,115 +157,81 @@ def run_test(self):
146157

147158
# do a tx and don't sync
148159
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))
152161
# 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'])
154163
# 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))
164168
assert_equal(len(json_obj['utxos']), 0)
165169

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))
169171
assert_equal(len(json_obj['utxos']), 1)
170172

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))
174174
assert_equal(len(json_obj['utxos']), 1)
175175

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))
179177
assert_equal(len(json_obj['utxos']), 0)
180178

181179
self.nodes[0].generate(1)
182180
self.sync_all()
183181

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))
187183
assert_equal(len(json_obj['utxos']), 1)
188184

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))
192186
assert_equal(len(json_obj['utxos']), 1)
193187

194188
# 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)
205192

206193
# 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)
210196

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)
214199

215200
self.nodes[0].generate(1) # generate block to not affect upcoming tests
216201
self.sync_all()
217202

218203
self.log.info("Test the /block and /headers URIs")
219204

220205
# 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)
223207
assert_greater_than(int(response.getheader('content-length')), 80)
224208
response_str = response.read()
225209

226210
# 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)
229212
assert_equal(int(response_header.getheader('content-length')), 80)
230213
response_header_str = response_header.read()
231214
assert_equal(response_str[0:80], response_header_str)
232215

233216
# 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)
236218
assert_greater_than(int(response_hex.getheader('content-length')), 160)
237219
response_hex_str = response_hex.read()
238220
assert_equal(encode(response_str, "hex_codec")[0:160], response_hex_str[0:160])
239221

240222
# 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)
243224
assert_greater_than(int(response_header_hex.getheader('content-length')), 160)
244225
response_header_hex_str = response_header_hex.read()
245226
assert_equal(response_hex_str[0:160], response_header_hex_str[0:160])
246227
assert_equal(encode(response_header_str, "hex_codec")[0:160], response_header_hex_str[0:160])
247228

248229
# 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))
251231
assert_equal(block_json_obj['hash'], bb_hash)
252232

253233
# 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))
258235
assert_equal(len(json_obj), 1) # ensure that there is one header in the json response
259236
assert_equal(json_obj[0]['hash'], bb_hash) # request/response hash should be the same
260237

@@ -266,23 +243,18 @@ def run_test(self):
266243
# See if we can get 5 headers in one response
267244
self.nodes[1].generate(5)
268245
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))
273247
assert_equal(len(json_obj), 5) # now we should have 5 header objects
274248

275249
self.log.info("Test the /tx URI")
276250

277251
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))
280253
assert_equal(json_obj['txid'], tx_hash)
281254

282255
# 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)
286258

287259
self.log.info("Test tx inclusion in the /mempool and /block URIs")
288260

@@ -294,17 +266,15 @@ def run_test(self):
294266
self.sync_all()
295267

296268
# 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")
299270
assert_equal(json_obj['size'], 3)
300271
# the size of the memory pool should be greater than 3x ~100 bytes
301272
assert_greater_than(json_obj['bytes'], 300)
302273

303274
# 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")
306276
for i, tx in enumerate(txs):
307-
assert_equal(tx in json_obj, True)
277+
assert tx in json_obj
308278
assert_equal(json_obj[tx]['spentby'], txs[i + 1:i + 2])
309279
assert_equal(json_obj[tx]['depends'], txs[i - 1:i])
310280

@@ -313,24 +283,21 @@ def run_test(self):
313283
self.sync_all()
314284

315285
# 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))
321290

322291
# 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]))
325293
for tx in txs:
326-
assert_equal(tx in json_obj['tx'], True)
294+
assert tx in json_obj['tx']
327295

328296
self.log.info("Test the /chaininfo URI")
329297

330298
bb_hash = self.nodes[0].getbestblockhash()
331299

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")
334301
assert_equal(json_obj['bestblockhash'], bb_hash)
335302

336303
if __name__ == '__main__':

0 commit comments

Comments
 (0)