Skip to content

Commit 42746b0

Browse files
committed
Merge pull request #6193
6e71efa [REST] remove json input for getutxos, limit to query max. 15 outpoints (Jonas Schnelli) 64b8027 rest.cpp: strip whitespace (Jonas Schnelli)
2 parents 921ea89 + 6e71efa commit 42746b0

File tree

3 files changed

+113
-97
lines changed

3 files changed

+113
-97
lines changed

doc/REST-interface.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,15 @@ Only supports JSON as output format.
4747
* chainwork : (string) total amount of work in active chain, in hexadecimal
4848

4949
####Query UTXO set
50-
`GET /rest/getutxos.<bin|hex|json>`
50+
`GET /rest/getutxos/<checkmempool>/<txid>-<n>/<txid>-<n>/.../<txid>-<n>.<bin|hex|json>`
5151

5252
The getutxo command allows querying of the UTXO set given a set of outpoints.
5353
See BIP64 for input and output serialisation:
5454
https://github.com/bitcoin/bips/blob/master/bip-0064.mediawiki
5555

5656
Example:
5757
```
58-
$ curl --data '{"checkmempool":true,"outpoints":[{"txid":"b2cdfd7b89def827ff8af7cd9bff7627ff72e5e8b0f71210f92ea7a4000c5d75","n":0}]}' localhost:18332/rest/getutxos.json 2>/dev/null | json_pp
58+
$ curl localhost:18332/rest/getutxos/checkmempool/b2cdfd7b89def827ff8af7cd9bff7627ff72e5e8b0f71210f92ea7a4000c5d75-0.json 2>/dev/null | json_pp
5959
{
6060
"chaintipHash" : "00000000fb01a7f3745a717f8caebee056c484e6e0bfe4a9591c235bb70506fb",
6161
"chainHeight" : 325347,

qa/rpc-tests/rest.py

Lines changed: 56 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -55,78 +55,78 @@ def setup_network(self, split=False):
5555
connect_nodes_bi(self.nodes,0,2)
5656
self.is_network_split=False
5757
self.sync_all()
58-
58+
5959
def run_test(self):
6060
url = urlparse.urlparse(self.nodes[0].url)
6161
print "Mining blocks..."
62-
62+
6363
self.nodes[0].generate(1)
6464
self.sync_all()
6565
self.nodes[2].generate(100)
6666
self.sync_all()
67-
67+
6868
assert_equal(self.nodes[0].getbalance(), 50)
69-
69+
7070
txid = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 0.1)
7171
self.sync_all()
7272
self.nodes[2].generate(1)
7373
self.sync_all()
7474
bb_hash = self.nodes[0].getbestblockhash()
75-
75+
7676
assert_equal(self.nodes[1].getbalance(), Decimal("0.1")) #balance now should be 0.1 on node 1
77-
77+
7878
# load the latest 0.1 tx over the REST API
7979
json_string = http_get_call(url.hostname, url.port, '/rest/tx/'+txid+self.FORMAT_SEPARATOR+"json")
8080
json_obj = json.loads(json_string)
8181
vintx = json_obj['vin'][0]['txid'] # get the vin to later check for utxo (should be spent by then)
82-
# get n of 0.1 outpoint
82+
# get n of 0.1 outpoint
8383
n = 0
8484
for vout in json_obj['vout']:
8585
if vout['value'] == 0.1:
8686
n = vout['n']
87-
88-
87+
88+
8989
######################################
9090
# GETUTXOS: query a unspent outpoint #
9191
######################################
92-
json_request = '{"checkmempool":true,"outpoints":[{"txid":"'+txid+'","n":'+str(n)+'}]}'
93-
json_string = http_get_call(url.hostname, url.port, '/rest/getutxos'+self.FORMAT_SEPARATOR+'json', json_request)
92+
json_request = '/checkmempool/'+txid+'-'+str(n)
93+
json_string = http_get_call(url.hostname, url.port, '/rest/getutxos'+json_request+self.FORMAT_SEPARATOR+'json')
9494
json_obj = json.loads(json_string)
95-
95+
9696
#check chainTip response
9797
assert_equal(json_obj['chaintipHash'], bb_hash)
98-
98+
9999
#make sure there is one utxo
100100
assert_equal(len(json_obj['utxos']), 1)
101101
assert_equal(json_obj['utxos'][0]['value'], 0.1)
102-
103-
102+
103+
104104
################################################
105105
# GETUTXOS: now query a already spent outpoint #
106106
################################################
107-
json_request = '{"checkmempool":true,"outpoints":[{"txid":"'+vintx+'","n":0}]}'
108-
json_string = http_get_call(url.hostname, url.port, '/rest/getutxos'+self.FORMAT_SEPARATOR+'json', json_request)
107+
json_request = '/checkmempool/'+vintx+'-0'
108+
json_string = http_get_call(url.hostname, url.port, '/rest/getutxos'+json_request+self.FORMAT_SEPARATOR+'json')
109109
json_obj = json.loads(json_string)
110-
110+
111111
#check chainTip response
112112
assert_equal(json_obj['chaintipHash'], bb_hash)
113113

114114
#make sure there is no utox in the response because this oupoint has been spent
115115
assert_equal(len(json_obj['utxos']), 0)
116-
116+
117117
#check bitmap
118118
assert_equal(json_obj['bitmap'], "0")
119-
120-
119+
120+
121121
##################################################
122122
# GETUTXOS: now check both with the same request #
123123
##################################################
124-
json_request = '{"checkmempool":true,"outpoints":[{"txid":"'+txid+'","n":'+str(n)+'},{"txid":"'+vintx+'","n":0}]}'
125-
json_string = http_get_call(url.hostname, url.port, '/rest/getutxos'+self.FORMAT_SEPARATOR+'json', json_request)
124+
json_request = '/checkmempool/'+txid+'-'+str(n)+'/'+vintx+'-0'
125+
json_string = http_get_call(url.hostname, url.port, '/rest/getutxos'+json_request+self.FORMAT_SEPARATOR+'json')
126126
json_obj = json.loads(json_string)
127127
assert_equal(len(json_obj['utxos']), 1)
128128
assert_equal(json_obj['bitmap'], "10")
129-
129+
130130
#test binary response
131131
bb_hash = self.nodes[0].getbestblockhash()
132132

@@ -135,19 +135,18 @@ def run_test(self):
135135
binaryRequest += pack("i", n);
136136
binaryRequest += binascii.unhexlify(vintx);
137137
binaryRequest += pack("i", 0);
138-
138+
139139
bin_response = http_get_call(url.hostname, url.port, '/rest/getutxos'+self.FORMAT_SEPARATOR+'bin', binaryRequest)
140-
141140
output = StringIO.StringIO()
142141
output.write(bin_response)
143142
output.seek(0)
144143
chainHeight = unpack("i", output.read(4))[0]
145144
hashFromBinResponse = hex(deser_uint256(output))[2:].zfill(65).rstrip("L")
146-
145+
147146
assert_equal(bb_hash, hashFromBinResponse) #check if getutxo's chaintip during calculation was fine
148147
assert_equal(chainHeight, 102) #chain height must be 102
149-
150-
148+
149+
151150
############################
152151
# GETUTXOS: mempool checks #
153152
############################
@@ -157,55 +156,56 @@ def run_test(self):
157156
json_string = http_get_call(url.hostname, url.port, '/rest/tx/'+txid+self.FORMAT_SEPARATOR+"json")
158157
json_obj = json.loads(json_string)
159158
vintx = json_obj['vin'][0]['txid'] # get the vin to later check for utxo (should be spent by then)
160-
# get n of 0.1 outpoint
159+
# get n of 0.1 outpoint
161160
n = 0
162161
for vout in json_obj['vout']:
163162
if vout['value'] == 0.1:
164163
n = vout['n']
165-
166-
json_request = '{"checkmempool":false,"outpoints":[{"txid":"'+txid+'","n":'+str(n)+'}]}'
167-
json_string = http_get_call(url.hostname, url.port, '/rest/getutxos'+self.FORMAT_SEPARATOR+'json', json_request)
164+
165+
json_request = '/'+txid+'-'+str(n)
166+
json_string = http_get_call(url.hostname, url.port, '/rest/getutxos'+json_request+self.FORMAT_SEPARATOR+'json')
168167
json_obj = json.loads(json_string)
169168
assert_equal(len(json_obj['utxos']), 0) #there should be a outpoint because it has just added to the mempool
170-
171-
json_request = '{"checkmempool":true,"outpoints":[{"txid":"'+txid+'","n":'+str(n)+'}]}'
172-
json_string = http_get_call(url.hostname, url.port, '/rest/getutxos'+self.FORMAT_SEPARATOR+'json', json_request)
169+
170+
json_request = '/checkmempool/'+txid+'-'+str(n)
171+
json_string = http_get_call(url.hostname, url.port, '/rest/getutxos'+json_request+self.FORMAT_SEPARATOR+'json')
173172
json_obj = json.loads(json_string)
174173
assert_equal(len(json_obj['utxos']), 1) #there should be a outpoint because it has just added to the mempool
175-
174+
176175
#do some invalid requests
177176
json_request = '{"checkmempool'
178177
response = http_get_call(url.hostname, url.port, '/rest/getutxos'+self.FORMAT_SEPARATOR+'json', json_request, True)
179178
assert_equal(response.status, 500) #must be a 500 because we send a invalid json request
180-
179+
181180
json_request = '{"checkmempool'
182181
response = http_get_call(url.hostname, url.port, '/rest/getutxos'+self.FORMAT_SEPARATOR+'bin', json_request, True)
183182
assert_equal(response.status, 500) #must be a 500 because we send a invalid bin request
184-
183+
184+
response = http_get_call(url.hostname, url.port, '/rest/getutxos/checkmempool'+self.FORMAT_SEPARATOR+'bin', '', True)
185+
assert_equal(response.status, 500) #must be a 500 because we send a invalid bin request
186+
185187
#test limits
186-
json_request = '{"checkmempool":true,"outpoints":['
187-
for x in range(0, 200):
188-
json_request += '{"txid":"'+txid+'","n":'+str(n)+'},'
189-
json_request = json_request.rstrip(",")
190-
json_request+="]}";
191-
response = http_get_call(url.hostname, url.port, '/rest/getutxos'+self.FORMAT_SEPARATOR+'json', json_request, True)
188+
json_request = '/checkmempool/'
189+
for x in range(0, 20):
190+
json_request += txid+'-'+str(n)+'/'
191+
json_request = json_request.rstrip("/")
192+
response = http_get_call(url.hostname, url.port, '/rest/getutxos'+json_request+self.FORMAT_SEPARATOR+'json', '', True)
192193
assert_equal(response.status, 500) #must be a 500 because we exceeding the limits
193-
194-
json_request = '{"checkmempool":true,"outpoints":['
195-
for x in range(0, 90):
196-
json_request += '{"txid":"'+txid+'","n":'+str(n)+'},'
197-
json_request = json_request.rstrip(",")
198-
json_request+="]}";
199-
response = http_get_call(url.hostname, url.port, '/rest/getutxos'+self.FORMAT_SEPARATOR+'json', json_request, True)
194+
195+
json_request = '/checkmempool/'
196+
for x in range(0, 15):
197+
json_request += txid+'-'+str(n)+'/'
198+
json_request = json_request.rstrip("/");
199+
response = http_get_call(url.hostname, url.port, '/rest/getutxos'+json_request+self.FORMAT_SEPARATOR+'json', '', True)
200200
assert_equal(response.status, 200) #must be a 500 because we exceeding the limits
201201

202202
self.nodes[0].generate(1) #generate block to not affect upcomming tests
203203
self.sync_all()
204-
204+
205205
################
206206
# /rest/block/ #
207207
################
208-
208+
209209
# check binary format
210210
response = http_get_call(url.hostname, url.port, '/rest/block/'+bb_hash+self.FORMAT_SEPARATOR+"bin", "", True)
211211
assert_equal(response.status, 200)
@@ -249,7 +249,7 @@ def run_test(self):
249249
hex_string = http_get_call(url.hostname, url.port, '/rest/tx/'+tx_hash+self.FORMAT_SEPARATOR+"hex", "", True)
250250
assert_equal(hex_string.status, 200)
251251
assert_greater_than(int(response.getheader('content-length')), 10)
252-
252+
253253

254254

255255
# check block tx details
@@ -279,7 +279,7 @@ def run_test(self):
279279

280280
#test rest bestblock
281281
bb_hash = self.nodes[0].getbestblockhash()
282-
282+
283283
json_string = http_get_call(url.hostname, url.port, '/rest/chaininfo.json')
284284
json_obj = json.loads(json_string)
285285
assert_equal(json_obj['bestblockhash'], bb_hash)

src/rest.cpp

Lines changed: 55 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
using namespace std;
2020
using namespace json_spirit;
2121

22-
static const int MAX_GETUTXOS_OUTPOINTS = 100; //allow a max of 100 outpoints to be queried at once
22+
static const int MAX_GETUTXOS_OUTPOINTS = 15; //allow a max of 15 outpoints to be queried at once
2323

2424
enum RetFormat {
2525
RF_UNDEF,
@@ -262,12 +262,12 @@ static bool rest_chaininfo(AcceptedConnection* conn,
262262
{
263263
vector<string> params;
264264
const RetFormat rf = ParseDataFormat(params, strURIPart);
265-
265+
266266
switch (rf) {
267267
case RF_JSON: {
268268
Array rpcParams;
269269
Value chainInfoObject = getblockchaininfo(rpcParams, false);
270-
270+
271271
string strJSON = write_string(chainInfoObject, false) + "\n";
272272
conn->stream() << HTTPReply(HTTP_OK, strJSON, fRun) << std::flush;
273273
return true;
@@ -276,7 +276,7 @@ static bool rest_chaininfo(AcceptedConnection* conn,
276276
throw RESTERR(HTTP_NOT_FOUND, "output format not found (available: json)");
277277
}
278278
}
279-
279+
280280
// not reached
281281
return true; // continue to process further HTTP reqs on this cxn
282282
}
@@ -342,18 +342,53 @@ static bool rest_getutxos(AcceptedConnection* conn,
342342
vector<string> params;
343343
enum RetFormat rf = ParseDataFormat(params, strURIPart);
344344

345+
vector<string> uriParts;
346+
if (params.size() > 0 && params[0].length() > 1)
347+
{
348+
std::string strUriParams = params[0].substr(1);
349+
boost::split(uriParts, strUriParams, boost::is_any_of("/"));
350+
}
351+
345352
// throw exception in case of a empty request
346-
if (strRequest.length() == 0)
353+
if (strRequest.length() == 0 && uriParts.size() == 0)
347354
throw RESTERR(HTTP_INTERNAL_SERVER_ERROR, "Error: empty request");
348355

356+
bool fInputParsed = false;
349357
bool fCheckMemPool = false;
350358
vector<COutPoint> vOutPoints;
351359

352360
// parse/deserialize input
353361
// input-format = output-format, rest/getutxos/bin requires binary input, gives binary output, ...
354-
362+
363+
if (uriParts.size() > 0)
364+
{
365+
366+
//inputs is sent over URI scheme (/rest/getutxos/checkmempool/txid1-n/txid2-n/...)
367+
if (uriParts.size() > 0 && uriParts[0] == "checkmempool")
368+
fCheckMemPool = true;
369+
370+
for (size_t i = (fCheckMemPool) ? 1 : 0; i < uriParts.size(); i++)
371+
{
372+
uint256 txid;
373+
int32_t nOutput;
374+
std::string strTxid = uriParts[i].substr(0, uriParts[i].find("-"));
375+
std::string strOutput = uriParts[i].substr(uriParts[i].find("-")+1);
376+
377+
if (!ParseInt32(strOutput, &nOutput) || !IsHex(strTxid))
378+
throw RESTERR(HTTP_INTERNAL_SERVER_ERROR, "Parse error");
379+
380+
txid.SetHex(strTxid);
381+
vOutPoints.push_back(COutPoint(txid, (uint32_t)nOutput));
382+
}
383+
384+
if (vOutPoints.size() > 0)
385+
fInputParsed = true;
386+
else
387+
throw RESTERR(HTTP_INTERNAL_SERVER_ERROR, "Error: empty request");
388+
}
389+
355390
string strRequestMutable = strRequest; //convert const string to string for allowing hex to bin converting
356-
391+
357392
switch (rf) {
358393
case RF_HEX: {
359394
// convert hex to bin, continue then with bin part
@@ -363,11 +398,17 @@ static bool rest_getutxos(AcceptedConnection* conn,
363398

364399
case RF_BINARY: {
365400
try {
366-
//deserialize
367-
CDataStream oss(SER_NETWORK, PROTOCOL_VERSION);
368-
oss << strRequestMutable;
369-
oss >> fCheckMemPool;
370-
oss >> vOutPoints;
401+
//deserialize only if user sent a request
402+
if (strRequestMutable.size() > 0)
403+
{
404+
if (fInputParsed) //don't allow sending input over URI and HTTP RAW DATA
405+
throw RESTERR(HTTP_INTERNAL_SERVER_ERROR, "Combination of URI scheme inputs and raw post data is not allowed");
406+
407+
CDataStream oss(SER_NETWORK, PROTOCOL_VERSION);
408+
oss << strRequestMutable;
409+
oss >> fCheckMemPool;
410+
oss >> vOutPoints;
411+
}
371412
} catch (const std::ios_base::failure& e) {
372413
// abort in case of unreadable binary data
373414
throw RESTERR(HTTP_INTERNAL_SERVER_ERROR, "Parse error");
@@ -376,33 +417,8 @@ static bool rest_getutxos(AcceptedConnection* conn,
376417
}
377418

378419
case RF_JSON: {
379-
try {
380-
// parse json request
381-
Value valRequest;
382-
if (!read_string(strRequest, valRequest))
383-
throw RESTERR(HTTP_INTERNAL_SERVER_ERROR, "Parse error");
384-
385-
Object jsonObject = valRequest.get_obj();
386-
const Value& checkMempoolValue = find_value(jsonObject, "checkmempool");
387-
388-
if (!checkMempoolValue.is_null()) {
389-
fCheckMemPool = checkMempoolValue.get_bool();
390-
}
391-
const Value& outpointsValue = find_value(jsonObject, "outpoints");
392-
if (!outpointsValue.is_null()) {
393-
Array outPoints = outpointsValue.get_array();
394-
BOOST_FOREACH (const Value& outPoint, outPoints) {
395-
Object outpointObject = outPoint.get_obj();
396-
uint256 txid = ParseHashO(outpointObject, "txid");
397-
Value nValue = find_value(outpointObject, "n");
398-
int nOutput = nValue.get_int();
399-
vOutPoints.push_back(COutPoint(txid, nOutput));
400-
}
401-
}
402-
} catch (...) {
403-
// return HTTP 500 if there was a json parsing error
404-
throw RESTERR(HTTP_INTERNAL_SERVER_ERROR, "Parse error");
405-
}
420+
if (!fInputParsed)
421+
throw RESTERR(HTTP_INTERNAL_SERVER_ERROR, "Error: empty request");
406422
break;
407423
}
408424
default: {

0 commit comments

Comments
 (0)