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)
This commit is contained in:
commit
42746b0476
3 changed files with 113 additions and 97 deletions
|
@ -47,7 +47,7 @@ Only supports JSON as output format.
|
||||||
* chainwork : (string) total amount of work in active chain, in hexadecimal
|
* chainwork : (string) total amount of work in active chain, in hexadecimal
|
||||||
|
|
||||||
####Query UTXO set
|
####Query UTXO set
|
||||||
`GET /rest/getutxos.<bin|hex|json>`
|
`GET /rest/getutxos/<checkmempool>/<txid>-<n>/<txid>-<n>/.../<txid>-<n>.<bin|hex|json>`
|
||||||
|
|
||||||
The getutxo command allows querying of the UTXO set given a set of outpoints.
|
The getutxo command allows querying of the UTXO set given a set of outpoints.
|
||||||
See BIP64 for input and output serialisation:
|
See BIP64 for input and output serialisation:
|
||||||
|
@ -55,7 +55,7 @@ https://github.com/bitcoin/bips/blob/master/bip-0064.mediawiki
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
```
|
```
|
||||||
$ curl --data '{"checkmempool":true,"outpoints":[{"txid":"b2cdfd7b89def827ff8af7cd9bff7627ff72e5e8b0f71210f92ea7a4000c5d75","n":0}]}' localhost:18332/rest/getutxos.json 2>/dev/null | json_pp
|
$ curl localhost:18332/rest/getutxos/checkmempool/b2cdfd7b89def827ff8af7cd9bff7627ff72e5e8b0f71210f92ea7a4000c5d75-0.json 2>/dev/null | json_pp
|
||||||
{
|
{
|
||||||
"chaintipHash" : "00000000fb01a7f3745a717f8caebee056c484e6e0bfe4a9591c235bb70506fb",
|
"chaintipHash" : "00000000fb01a7f3745a717f8caebee056c484e6e0bfe4a9591c235bb70506fb",
|
||||||
"chainHeight" : 325347,
|
"chainHeight" : 325347,
|
||||||
|
|
|
@ -89,8 +89,8 @@ class RESTTest (BitcoinTestFramework):
|
||||||
######################################
|
######################################
|
||||||
# GETUTXOS: query a unspent outpoint #
|
# GETUTXOS: query a unspent outpoint #
|
||||||
######################################
|
######################################
|
||||||
json_request = '{"checkmempool":true,"outpoints":[{"txid":"'+txid+'","n":'+str(n)+'}]}'
|
json_request = '/checkmempool/'+txid+'-'+str(n)
|
||||||
json_string = http_get_call(url.hostname, url.port, '/rest/getutxos'+self.FORMAT_SEPARATOR+'json', json_request)
|
json_string = http_get_call(url.hostname, url.port, '/rest/getutxos'+json_request+self.FORMAT_SEPARATOR+'json')
|
||||||
json_obj = json.loads(json_string)
|
json_obj = json.loads(json_string)
|
||||||
|
|
||||||
#check chainTip response
|
#check chainTip response
|
||||||
|
@ -104,8 +104,8 @@ class RESTTest (BitcoinTestFramework):
|
||||||
################################################
|
################################################
|
||||||
# GETUTXOS: now query a already spent outpoint #
|
# GETUTXOS: now query a already spent outpoint #
|
||||||
################################################
|
################################################
|
||||||
json_request = '{"checkmempool":true,"outpoints":[{"txid":"'+vintx+'","n":0}]}'
|
json_request = '/checkmempool/'+vintx+'-0'
|
||||||
json_string = http_get_call(url.hostname, url.port, '/rest/getutxos'+self.FORMAT_SEPARATOR+'json', json_request)
|
json_string = http_get_call(url.hostname, url.port, '/rest/getutxos'+json_request+self.FORMAT_SEPARATOR+'json')
|
||||||
json_obj = json.loads(json_string)
|
json_obj = json.loads(json_string)
|
||||||
|
|
||||||
#check chainTip response
|
#check chainTip response
|
||||||
|
@ -121,8 +121,8 @@ class RESTTest (BitcoinTestFramework):
|
||||||
##################################################
|
##################################################
|
||||||
# GETUTXOS: now check both with the same request #
|
# GETUTXOS: now check both with the same request #
|
||||||
##################################################
|
##################################################
|
||||||
json_request = '{"checkmempool":true,"outpoints":[{"txid":"'+txid+'","n":'+str(n)+'},{"txid":"'+vintx+'","n":0}]}'
|
json_request = '/checkmempool/'+txid+'-'+str(n)+'/'+vintx+'-0'
|
||||||
json_string = http_get_call(url.hostname, url.port, '/rest/getutxos'+self.FORMAT_SEPARATOR+'json', json_request)
|
json_string = http_get_call(url.hostname, url.port, '/rest/getutxos'+json_request+self.FORMAT_SEPARATOR+'json')
|
||||||
json_obj = json.loads(json_string)
|
json_obj = json.loads(json_string)
|
||||||
assert_equal(len(json_obj['utxos']), 1)
|
assert_equal(len(json_obj['utxos']), 1)
|
||||||
assert_equal(json_obj['bitmap'], "10")
|
assert_equal(json_obj['bitmap'], "10")
|
||||||
|
@ -137,7 +137,6 @@ class RESTTest (BitcoinTestFramework):
|
||||||
binaryRequest += pack("i", 0);
|
binaryRequest += pack("i", 0);
|
||||||
|
|
||||||
bin_response = http_get_call(url.hostname, url.port, '/rest/getutxos'+self.FORMAT_SEPARATOR+'bin', binaryRequest)
|
bin_response = http_get_call(url.hostname, url.port, '/rest/getutxos'+self.FORMAT_SEPARATOR+'bin', binaryRequest)
|
||||||
|
|
||||||
output = StringIO.StringIO()
|
output = StringIO.StringIO()
|
||||||
output.write(bin_response)
|
output.write(bin_response)
|
||||||
output.seek(0)
|
output.seek(0)
|
||||||
|
@ -163,13 +162,13 @@ class RESTTest (BitcoinTestFramework):
|
||||||
if vout['value'] == 0.1:
|
if vout['value'] == 0.1:
|
||||||
n = vout['n']
|
n = vout['n']
|
||||||
|
|
||||||
json_request = '{"checkmempool":false,"outpoints":[{"txid":"'+txid+'","n":'+str(n)+'}]}'
|
json_request = '/'+txid+'-'+str(n)
|
||||||
json_string = http_get_call(url.hostname, url.port, '/rest/getutxos'+self.FORMAT_SEPARATOR+'json', json_request)
|
json_string = http_get_call(url.hostname, url.port, '/rest/getutxos'+json_request+self.FORMAT_SEPARATOR+'json')
|
||||||
json_obj = json.loads(json_string)
|
json_obj = json.loads(json_string)
|
||||||
assert_equal(len(json_obj['utxos']), 0) #there should be a outpoint because it has just added to the mempool
|
assert_equal(len(json_obj['utxos']), 0) #there should be a outpoint because it has just added to the mempool
|
||||||
|
|
||||||
json_request = '{"checkmempool":true,"outpoints":[{"txid":"'+txid+'","n":'+str(n)+'}]}'
|
json_request = '/checkmempool/'+txid+'-'+str(n)
|
||||||
json_string = http_get_call(url.hostname, url.port, '/rest/getutxos'+self.FORMAT_SEPARATOR+'json', json_request)
|
json_string = http_get_call(url.hostname, url.port, '/rest/getutxos'+json_request+self.FORMAT_SEPARATOR+'json')
|
||||||
json_obj = json.loads(json_string)
|
json_obj = json.loads(json_string)
|
||||||
assert_equal(len(json_obj['utxos']), 1) #there should be a outpoint because it has just added to the mempool
|
assert_equal(len(json_obj['utxos']), 1) #there should be a outpoint because it has just added to the mempool
|
||||||
|
|
||||||
|
@ -182,21 +181,22 @@ class RESTTest (BitcoinTestFramework):
|
||||||
response = http_get_call(url.hostname, url.port, '/rest/getutxos'+self.FORMAT_SEPARATOR+'bin', json_request, True)
|
response = http_get_call(url.hostname, url.port, '/rest/getutxos'+self.FORMAT_SEPARATOR+'bin', json_request, True)
|
||||||
assert_equal(response.status, 500) #must be a 500 because we send a invalid bin request
|
assert_equal(response.status, 500) #must be a 500 because we send a invalid bin request
|
||||||
|
|
||||||
|
response = http_get_call(url.hostname, url.port, '/rest/getutxos/checkmempool'+self.FORMAT_SEPARATOR+'bin', '', True)
|
||||||
|
assert_equal(response.status, 500) #must be a 500 because we send a invalid bin request
|
||||||
|
|
||||||
#test limits
|
#test limits
|
||||||
json_request = '{"checkmempool":true,"outpoints":['
|
json_request = '/checkmempool/'
|
||||||
for x in range(0, 200):
|
for x in range(0, 20):
|
||||||
json_request += '{"txid":"'+txid+'","n":'+str(n)+'},'
|
json_request += txid+'-'+str(n)+'/'
|
||||||
json_request = json_request.rstrip(",")
|
json_request = json_request.rstrip("/")
|
||||||
json_request+="]}";
|
response = http_get_call(url.hostname, url.port, '/rest/getutxos'+json_request+self.FORMAT_SEPARATOR+'json', '', True)
|
||||||
response = http_get_call(url.hostname, url.port, '/rest/getutxos'+self.FORMAT_SEPARATOR+'json', json_request, True)
|
|
||||||
assert_equal(response.status, 500) #must be a 500 because we exceeding the limits
|
assert_equal(response.status, 500) #must be a 500 because we exceeding the limits
|
||||||
|
|
||||||
json_request = '{"checkmempool":true,"outpoints":['
|
json_request = '/checkmempool/'
|
||||||
for x in range(0, 90):
|
for x in range(0, 15):
|
||||||
json_request += '{"txid":"'+txid+'","n":'+str(n)+'},'
|
json_request += txid+'-'+str(n)+'/'
|
||||||
json_request = json_request.rstrip(",")
|
json_request = json_request.rstrip("/");
|
||||||
json_request+="]}";
|
response = http_get_call(url.hostname, url.port, '/rest/getutxos'+json_request+self.FORMAT_SEPARATOR+'json', '', True)
|
||||||
response = http_get_call(url.hostname, url.port, '/rest/getutxos'+self.FORMAT_SEPARATOR+'json', json_request, True)
|
|
||||||
assert_equal(response.status, 200) #must be a 500 because we exceeding the limits
|
assert_equal(response.status, 200) #must be a 500 because we exceeding the limits
|
||||||
|
|
||||||
self.nodes[0].generate(1) #generate block to not affect upcomming tests
|
self.nodes[0].generate(1) #generate block to not affect upcomming tests
|
||||||
|
|
76
src/rest.cpp
76
src/rest.cpp
|
@ -19,7 +19,7 @@
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace json_spirit;
|
using namespace json_spirit;
|
||||||
|
|
||||||
static const int MAX_GETUTXOS_OUTPOINTS = 100; //allow a max of 100 outpoints to be queried at once
|
static const int MAX_GETUTXOS_OUTPOINTS = 15; //allow a max of 15 outpoints to be queried at once
|
||||||
|
|
||||||
enum RetFormat {
|
enum RetFormat {
|
||||||
RF_UNDEF,
|
RF_UNDEF,
|
||||||
|
@ -342,16 +342,51 @@ static bool rest_getutxos(AcceptedConnection* conn,
|
||||||
vector<string> params;
|
vector<string> params;
|
||||||
enum RetFormat rf = ParseDataFormat(params, strURIPart);
|
enum RetFormat rf = ParseDataFormat(params, strURIPart);
|
||||||
|
|
||||||
|
vector<string> uriParts;
|
||||||
|
if (params.size() > 0 && params[0].length() > 1)
|
||||||
|
{
|
||||||
|
std::string strUriParams = params[0].substr(1);
|
||||||
|
boost::split(uriParts, strUriParams, boost::is_any_of("/"));
|
||||||
|
}
|
||||||
|
|
||||||
// throw exception in case of a empty request
|
// throw exception in case of a empty request
|
||||||
if (strRequest.length() == 0)
|
if (strRequest.length() == 0 && uriParts.size() == 0)
|
||||||
throw RESTERR(HTTP_INTERNAL_SERVER_ERROR, "Error: empty request");
|
throw RESTERR(HTTP_INTERNAL_SERVER_ERROR, "Error: empty request");
|
||||||
|
|
||||||
|
bool fInputParsed = false;
|
||||||
bool fCheckMemPool = false;
|
bool fCheckMemPool = false;
|
||||||
vector<COutPoint> vOutPoints;
|
vector<COutPoint> vOutPoints;
|
||||||
|
|
||||||
// parse/deserialize input
|
// parse/deserialize input
|
||||||
// input-format = output-format, rest/getutxos/bin requires binary input, gives binary output, ...
|
// input-format = output-format, rest/getutxos/bin requires binary input, gives binary output, ...
|
||||||
|
|
||||||
|
if (uriParts.size() > 0)
|
||||||
|
{
|
||||||
|
|
||||||
|
//inputs is sent over URI scheme (/rest/getutxos/checkmempool/txid1-n/txid2-n/...)
|
||||||
|
if (uriParts.size() > 0 && uriParts[0] == "checkmempool")
|
||||||
|
fCheckMemPool = true;
|
||||||
|
|
||||||
|
for (size_t i = (fCheckMemPool) ? 1 : 0; i < uriParts.size(); i++)
|
||||||
|
{
|
||||||
|
uint256 txid;
|
||||||
|
int32_t nOutput;
|
||||||
|
std::string strTxid = uriParts[i].substr(0, uriParts[i].find("-"));
|
||||||
|
std::string strOutput = uriParts[i].substr(uriParts[i].find("-")+1);
|
||||||
|
|
||||||
|
if (!ParseInt32(strOutput, &nOutput) || !IsHex(strTxid))
|
||||||
|
throw RESTERR(HTTP_INTERNAL_SERVER_ERROR, "Parse error");
|
||||||
|
|
||||||
|
txid.SetHex(strTxid);
|
||||||
|
vOutPoints.push_back(COutPoint(txid, (uint32_t)nOutput));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vOutPoints.size() > 0)
|
||||||
|
fInputParsed = true;
|
||||||
|
else
|
||||||
|
throw RESTERR(HTTP_INTERNAL_SERVER_ERROR, "Error: empty request");
|
||||||
|
}
|
||||||
|
|
||||||
string strRequestMutable = strRequest; //convert const string to string for allowing hex to bin converting
|
string strRequestMutable = strRequest; //convert const string to string for allowing hex to bin converting
|
||||||
|
|
||||||
switch (rf) {
|
switch (rf) {
|
||||||
|
@ -363,11 +398,17 @@ static bool rest_getutxos(AcceptedConnection* conn,
|
||||||
|
|
||||||
case RF_BINARY: {
|
case RF_BINARY: {
|
||||||
try {
|
try {
|
||||||
//deserialize
|
//deserialize only if user sent a request
|
||||||
|
if (strRequestMutable.size() > 0)
|
||||||
|
{
|
||||||
|
if (fInputParsed) //don't allow sending input over URI and HTTP RAW DATA
|
||||||
|
throw RESTERR(HTTP_INTERNAL_SERVER_ERROR, "Combination of URI scheme inputs and raw post data is not allowed");
|
||||||
|
|
||||||
CDataStream oss(SER_NETWORK, PROTOCOL_VERSION);
|
CDataStream oss(SER_NETWORK, PROTOCOL_VERSION);
|
||||||
oss << strRequestMutable;
|
oss << strRequestMutable;
|
||||||
oss >> fCheckMemPool;
|
oss >> fCheckMemPool;
|
||||||
oss >> vOutPoints;
|
oss >> vOutPoints;
|
||||||
|
}
|
||||||
} catch (const std::ios_base::failure& e) {
|
} catch (const std::ios_base::failure& e) {
|
||||||
// abort in case of unreadable binary data
|
// abort in case of unreadable binary data
|
||||||
throw RESTERR(HTTP_INTERNAL_SERVER_ERROR, "Parse error");
|
throw RESTERR(HTTP_INTERNAL_SERVER_ERROR, "Parse error");
|
||||||
|
@ -376,33 +417,8 @@ static bool rest_getutxos(AcceptedConnection* conn,
|
||||||
}
|
}
|
||||||
|
|
||||||
case RF_JSON: {
|
case RF_JSON: {
|
||||||
try {
|
if (!fInputParsed)
|
||||||
// parse json request
|
throw RESTERR(HTTP_INTERNAL_SERVER_ERROR, "Error: empty request");
|
||||||
Value valRequest;
|
|
||||||
if (!read_string(strRequest, valRequest))
|
|
||||||
throw RESTERR(HTTP_INTERNAL_SERVER_ERROR, "Parse error");
|
|
||||||
|
|
||||||
Object jsonObject = valRequest.get_obj();
|
|
||||||
const Value& checkMempoolValue = find_value(jsonObject, "checkmempool");
|
|
||||||
|
|
||||||
if (!checkMempoolValue.is_null()) {
|
|
||||||
fCheckMemPool = checkMempoolValue.get_bool();
|
|
||||||
}
|
|
||||||
const Value& outpointsValue = find_value(jsonObject, "outpoints");
|
|
||||||
if (!outpointsValue.is_null()) {
|
|
||||||
Array outPoints = outpointsValue.get_array();
|
|
||||||
BOOST_FOREACH (const Value& outPoint, outPoints) {
|
|
||||||
Object outpointObject = outPoint.get_obj();
|
|
||||||
uint256 txid = ParseHashO(outpointObject, "txid");
|
|
||||||
Value nValue = find_value(outpointObject, "n");
|
|
||||||
int nOutput = nValue.get_int();
|
|
||||||
vOutPoints.push_back(COutPoint(txid, nOutput));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (...) {
|
|
||||||
// return HTTP 500 if there was a json parsing error
|
|
||||||
throw RESTERR(HTTP_INTERNAL_SERVER_ERROR, "Parse error");
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
|
|
Loading…
Reference in a new issue