aboutsummaryrefslogtreecommitdiff
path: root/src/rest.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/rest.cpp')
-rw-r--r--src/rest.cpp179
1 files changed, 99 insertions, 80 deletions
diff --git a/src/rest.cpp b/src/rest.cpp
index d86a62030c..a8eba05c3f 100644
--- a/src/rest.cpp
+++ b/src/rest.cpp
@@ -3,6 +3,8 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#include <rest.h>
+
#include <blockfilter.h>
#include <chain.h>
#include <chainparams.h>
@@ -28,6 +30,7 @@
#include <version.h>
#include <any>
+#include <string>
#include <boost/algorithm/string.hpp>
@@ -41,21 +44,14 @@ using node::ReadBlockFromDisk;
static const size_t MAX_GETUTXOS_OUTPOINTS = 15; //allow a max of 15 outpoints to be queried at once
static constexpr unsigned int MAX_REST_HEADERS_RESULTS = 2000;
-enum class RetFormat {
- UNDEF,
- BINARY,
- HEX,
- JSON,
-};
-
static const struct {
- RetFormat rf;
+ RESTResponseFormat rf;
const char* name;
} rf_names[] = {
- {RetFormat::UNDEF, ""},
- {RetFormat::BINARY, "bin"},
- {RetFormat::HEX, "hex"},
- {RetFormat::JSON, "json"},
+ {RESTResponseFormat::UNDEF, ""},
+ {RESTResponseFormat::BINARY, "bin"},
+ {RESTResponseFormat::HEX, "hex"},
+ {RESTResponseFormat::JSON, "json"},
};
struct CCoin {
@@ -138,25 +134,28 @@ static ChainstateManager* GetChainman(const std::any& context, HTTPRequest* req)
return node_context->chainman.get();
}
-static RetFormat ParseDataFormat(std::string& param, const std::string& strReq)
+RESTResponseFormat ParseDataFormat(std::string& param, const std::string& strReq)
{
- const std::string::size_type pos = strReq.rfind('.');
- if (pos == std::string::npos)
- {
- param = strReq;
+ // Remove query string (if any, separated with '?') as it should not interfere with
+ // parsing param and data format
+ param = strReq.substr(0, strReq.rfind('?'));
+ const std::string::size_type pos_format{param.rfind('.')};
+
+ // No format string is found
+ if (pos_format == std::string::npos) {
return rf_names[0].rf;
}
- param = strReq.substr(0, pos);
- const std::string suff(strReq, pos + 1);
-
+ // Match format string to available formats
+ const std::string suffix(param, pos_format + 1);
for (const auto& rf_name : rf_names) {
- if (suff == rf_name.name)
+ if (suffix == rf_name.name) {
+ param.erase(pos_format);
return rf_name.rf;
+ }
}
- /* If no suffix is found, return original string. */
- param = strReq;
+ // If no suffix is found, return RESTResponseFormat::UNDEF and original string without query string
return rf_names[0].rf;
}
@@ -192,19 +191,29 @@ static bool rest_headers(const std::any& context,
if (!CheckWarmup(req))
return false;
std::string param;
- const RetFormat rf = ParseDataFormat(param, strURIPart);
+ const RESTResponseFormat rf = ParseDataFormat(param, strURIPart);
std::vector<std::string> path;
boost::split(path, param, boost::is_any_of("/"));
- if (path.size() != 2)
- return RESTERR(req, HTTP_BAD_REQUEST, "No header count specified. Use /rest/headers/<count>/<hash>.<ext>.");
-
- const auto parsed_count{ToIntegral<size_t>(path[0])};
+ std::string raw_count;
+ std::string hashStr;
+ if (path.size() == 2) {
+ // deprecated path: /rest/headers/<count>/<hash>
+ hashStr = path[1];
+ raw_count = path[0];
+ } else if (path.size() == 1) {
+ // new path with query parameter: /rest/headers/<hash>?count=<count>
+ hashStr = path[0];
+ raw_count = req->GetQueryParameter("count").value_or("5");
+ } else {
+ return RESTERR(req, HTTP_BAD_REQUEST, "Invalid URI format. Expected /rest/headers/<hash>.<ext>?count=<count>");
+ }
+
+ const auto parsed_count{ToIntegral<size_t>(raw_count)};
if (!parsed_count.has_value() || *parsed_count < 1 || *parsed_count > MAX_REST_HEADERS_RESULTS) {
- return RESTERR(req, HTTP_BAD_REQUEST, strprintf("Header count is invalid or out of acceptable range (1-%u): %s", MAX_REST_HEADERS_RESULTS, path[0]));
+ return RESTERR(req, HTTP_BAD_REQUEST, strprintf("Header count is invalid or out of acceptable range (1-%u): %s", MAX_REST_HEADERS_RESULTS, raw_count));
}
- std::string hashStr = path[1];
uint256 hash;
if (!ParseHashStr(hashStr, hash))
return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
@@ -230,7 +239,7 @@ static bool rest_headers(const std::any& context,
}
switch (rf) {
- case RetFormat::BINARY: {
+ case RESTResponseFormat::BINARY: {
CDataStream ssHeader(SER_NETWORK, PROTOCOL_VERSION);
for (const CBlockIndex *pindex : headers) {
ssHeader << pindex->GetBlockHeader();
@@ -242,7 +251,7 @@ static bool rest_headers(const std::any& context,
return true;
}
- case RetFormat::HEX: {
+ case RESTResponseFormat::HEX: {
CDataStream ssHeader(SER_NETWORK, PROTOCOL_VERSION);
for (const CBlockIndex *pindex : headers) {
ssHeader << pindex->GetBlockHeader();
@@ -253,7 +262,7 @@ static bool rest_headers(const std::any& context,
req->WriteReply(HTTP_OK, strHex);
return true;
}
- case RetFormat::JSON: {
+ case RESTResponseFormat::JSON: {
UniValue jsonHeaders(UniValue::VARR);
for (const CBlockIndex *pindex : headers) {
jsonHeaders.push_back(blockheaderToJSON(tip, pindex));
@@ -277,15 +286,15 @@ static bool rest_block(const std::any& context,
if (!CheckWarmup(req))
return false;
std::string hashStr;
- const RetFormat rf = ParseDataFormat(hashStr, strURIPart);
+ const RESTResponseFormat rf = ParseDataFormat(hashStr, strURIPart);
uint256 hash;
if (!ParseHashStr(hashStr, hash))
return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
CBlock block;
- CBlockIndex* pblockindex = nullptr;
- CBlockIndex* tip = nullptr;
+ const CBlockIndex* pblockindex = nullptr;
+ const CBlockIndex* tip = nullptr;
{
ChainstateManager* maybe_chainman = GetChainman(context, req);
if (!maybe_chainman) return false;
@@ -305,7 +314,7 @@ static bool rest_block(const std::any& context,
}
switch (rf) {
- case RetFormat::BINARY: {
+ case RESTResponseFormat::BINARY: {
CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION | RPCSerializationFlags());
ssBlock << block;
std::string binaryBlock = ssBlock.str();
@@ -314,7 +323,7 @@ static bool rest_block(const std::any& context,
return true;
}
- case RetFormat::HEX: {
+ case RESTResponseFormat::HEX: {
CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION | RPCSerializationFlags());
ssBlock << block;
std::string strHex = HexStr(ssBlock) + "\n";
@@ -323,7 +332,7 @@ static bool rest_block(const std::any& context,
return true;
}
- case RetFormat::JSON: {
+ case RESTResponseFormat::JSON: {
UniValue objBlock = blockToJSON(block, tip, pblockindex, tx_verbosity);
std::string strJSON = objBlock.write() + "\n";
req->WriteHeader("Content-Type", "application/json");
@@ -352,17 +361,32 @@ static bool rest_filter_header(const std::any& context, HTTPRequest* req, const
if (!CheckWarmup(req)) return false;
std::string param;
- const RetFormat rf = ParseDataFormat(param, strURIPart);
+ const RESTResponseFormat rf = ParseDataFormat(param, strURIPart);
std::vector<std::string> uri_parts;
boost::split(uri_parts, param, boost::is_any_of("/"));
- if (uri_parts.size() != 3) {
- return RESTERR(req, HTTP_BAD_REQUEST, "Invalid URI format. Expected /rest/blockfilterheaders/<filtertype>/<count>/<blockhash>");
+ std::string raw_count;
+ std::string raw_blockhash;
+ if (uri_parts.size() == 3) {
+ // deprecated path: /rest/blockfilterheaders/<filtertype>/<count>/<blockhash>
+ raw_blockhash = uri_parts[2];
+ raw_count = uri_parts[1];
+ } else if (uri_parts.size() == 2) {
+ // new path with query parameter: /rest/blockfilterheaders/<filtertype>/<blockhash>?count=<count>
+ raw_blockhash = uri_parts[1];
+ raw_count = req->GetQueryParameter("count").value_or("5");
+ } else {
+ return RESTERR(req, HTTP_BAD_REQUEST, "Invalid URI format. Expected /rest/blockfilterheaders/<filtertype>/<blockhash>.<ext>?count=<count>");
+ }
+
+ const auto parsed_count{ToIntegral<size_t>(raw_count)};
+ if (!parsed_count.has_value() || *parsed_count < 1 || *parsed_count > MAX_REST_HEADERS_RESULTS) {
+ return RESTERR(req, HTTP_BAD_REQUEST, strprintf("Header count is invalid or out of acceptable range (1-%u): %s", MAX_REST_HEADERS_RESULTS, raw_count));
}
uint256 block_hash;
- if (!ParseHashStr(uri_parts[2], block_hash)) {
- return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + uri_parts[2]);
+ if (!ParseHashStr(raw_blockhash, block_hash)) {
+ return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + raw_blockhash);
}
BlockFilterType filtertype;
@@ -375,11 +399,6 @@ static bool rest_filter_header(const std::any& context, HTTPRequest* req, const
return RESTERR(req, HTTP_BAD_REQUEST, "Index is not enabled for filtertype " + uri_parts[0]);
}
- const auto parsed_count{ToIntegral<size_t>(uri_parts[1])};
- if (!parsed_count.has_value() || *parsed_count < 1 || *parsed_count > MAX_REST_HEADERS_RESULTS) {
- return RESTERR(req, HTTP_BAD_REQUEST, strprintf("Header count is invalid or out of acceptable range (1-%u): %s", MAX_REST_HEADERS_RESULTS, uri_parts[1]));
- }
-
std::vector<const CBlockIndex*> headers;
headers.reserve(*parsed_count);
{
@@ -418,7 +437,7 @@ static bool rest_filter_header(const std::any& context, HTTPRequest* req, const
}
switch (rf) {
- case RetFormat::BINARY: {
+ case RESTResponseFormat::BINARY: {
CDataStream ssHeader{SER_NETWORK, PROTOCOL_VERSION};
for (const uint256& header : filter_headers) {
ssHeader << header;
@@ -429,7 +448,7 @@ static bool rest_filter_header(const std::any& context, HTTPRequest* req, const
req->WriteReply(HTTP_OK, binaryHeader);
return true;
}
- case RetFormat::HEX: {
+ case RESTResponseFormat::HEX: {
CDataStream ssHeader{SER_NETWORK, PROTOCOL_VERSION};
for (const uint256& header : filter_headers) {
ssHeader << header;
@@ -440,7 +459,7 @@ static bool rest_filter_header(const std::any& context, HTTPRequest* req, const
req->WriteReply(HTTP_OK, strHex);
return true;
}
- case RetFormat::JSON: {
+ case RESTResponseFormat::JSON: {
UniValue jsonHeaders(UniValue::VARR);
for (const uint256& header : filter_headers) {
jsonHeaders.push_back(header.GetHex());
@@ -462,7 +481,7 @@ static bool rest_block_filter(const std::any& context, HTTPRequest* req, const s
if (!CheckWarmup(req)) return false;
std::string param;
- const RetFormat rf = ParseDataFormat(param, strURIPart);
+ const RESTResponseFormat rf = ParseDataFormat(param, strURIPart);
// request is sent over URI scheme /rest/blockfilter/filtertype/blockhash
std::vector<std::string> uri_parts;
@@ -518,7 +537,7 @@ static bool rest_block_filter(const std::any& context, HTTPRequest* req, const s
}
switch (rf) {
- case RetFormat::BINARY: {
+ case RESTResponseFormat::BINARY: {
CDataStream ssResp{SER_NETWORK, PROTOCOL_VERSION};
ssResp << filter;
@@ -527,7 +546,7 @@ static bool rest_block_filter(const std::any& context, HTTPRequest* req, const s
req->WriteReply(HTTP_OK, binaryResp);
return true;
}
- case RetFormat::HEX: {
+ case RESTResponseFormat::HEX: {
CDataStream ssResp{SER_NETWORK, PROTOCOL_VERSION};
ssResp << filter;
@@ -536,7 +555,7 @@ static bool rest_block_filter(const std::any& context, HTTPRequest* req, const s
req->WriteReply(HTTP_OK, strHex);
return true;
}
- case RetFormat::JSON: {
+ case RESTResponseFormat::JSON: {
UniValue ret(UniValue::VOBJ);
ret.pushKV("filter", HexStr(filter.GetEncodedFilter()));
std::string strJSON = ret.write() + "\n";
@@ -558,10 +577,10 @@ static bool rest_chaininfo(const std::any& context, HTTPRequest* req, const std:
if (!CheckWarmup(req))
return false;
std::string param;
- const RetFormat rf = ParseDataFormat(param, strURIPart);
+ const RESTResponseFormat rf = ParseDataFormat(param, strURIPart);
switch (rf) {
- case RetFormat::JSON: {
+ case RESTResponseFormat::JSON: {
JSONRPCRequest jsonRequest;
jsonRequest.context = context;
jsonRequest.params = UniValue(UniValue::VARR);
@@ -584,10 +603,10 @@ static bool rest_mempool_info(const std::any& context, HTTPRequest* req, const s
const CTxMemPool* mempool = GetMemPool(context, req);
if (!mempool) return false;
std::string param;
- const RetFormat rf = ParseDataFormat(param, strURIPart);
+ const RESTResponseFormat rf = ParseDataFormat(param, strURIPart);
switch (rf) {
- case RetFormat::JSON: {
+ case RESTResponseFormat::JSON: {
UniValue mempoolInfoObject = MempoolInfoToJSON(*mempool);
std::string strJSON = mempoolInfoObject.write() + "\n";
@@ -607,10 +626,10 @@ static bool rest_mempool_contents(const std::any& context, HTTPRequest* req, con
const CTxMemPool* mempool = GetMemPool(context, req);
if (!mempool) return false;
std::string param;
- const RetFormat rf = ParseDataFormat(param, strURIPart);
+ const RESTResponseFormat rf = ParseDataFormat(param, strURIPart);
switch (rf) {
- case RetFormat::JSON: {
+ case RESTResponseFormat::JSON: {
UniValue mempoolObject = MempoolToJSON(*mempool, true);
std::string strJSON = mempoolObject.write() + "\n";
@@ -629,7 +648,7 @@ static bool rest_tx(const std::any& context, HTTPRequest* req, const std::string
if (!CheckWarmup(req))
return false;
std::string hashStr;
- const RetFormat rf = ParseDataFormat(hashStr, strURIPart);
+ const RESTResponseFormat rf = ParseDataFormat(hashStr, strURIPart);
uint256 hash;
if (!ParseHashStr(hashStr, hash))
@@ -642,13 +661,13 @@ static bool rest_tx(const std::any& context, HTTPRequest* req, const std::string
const NodeContext* const node = GetNodeContext(context, req);
if (!node) return false;
uint256 hashBlock = uint256();
- const CTransactionRef tx = GetTransaction(/* block_index */ nullptr, node->mempool.get(), hash, Params().GetConsensus(), hashBlock);
+ const CTransactionRef tx = GetTransaction(/*block_index=*/nullptr, node->mempool.get(), hash, Params().GetConsensus(), hashBlock);
if (!tx) {
return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found");
}
switch (rf) {
- case RetFormat::BINARY: {
+ case RESTResponseFormat::BINARY: {
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION | RPCSerializationFlags());
ssTx << tx;
@@ -658,7 +677,7 @@ static bool rest_tx(const std::any& context, HTTPRequest* req, const std::string
return true;
}
- case RetFormat::HEX: {
+ case RESTResponseFormat::HEX: {
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION | RPCSerializationFlags());
ssTx << tx;
@@ -668,9 +687,9 @@ static bool rest_tx(const std::any& context, HTTPRequest* req, const std::string
return true;
}
- case RetFormat::JSON: {
+ case RESTResponseFormat::JSON: {
UniValue objTx(UniValue::VOBJ);
- TxToUniv(*tx, hashBlock, objTx);
+ TxToUniv(*tx, /*block_hash=*/hashBlock, /*entry=*/ objTx);
std::string strJSON = objTx.write() + "\n";
req->WriteHeader("Content-Type", "application/json");
req->WriteReply(HTTP_OK, strJSON);
@@ -688,7 +707,7 @@ static bool rest_getutxos(const std::any& context, HTTPRequest* req, const std::
if (!CheckWarmup(req))
return false;
std::string param;
- const RetFormat rf = ParseDataFormat(param, strURIPart);
+ const RESTResponseFormat rf = ParseDataFormat(param, strURIPart);
std::vector<std::string> uriParts;
if (param.length() > 1)
@@ -735,14 +754,14 @@ static bool rest_getutxos(const std::any& context, HTTPRequest* req, const std::
}
switch (rf) {
- case RetFormat::HEX: {
+ case RESTResponseFormat::HEX: {
// convert hex to bin, continue then with bin part
std::vector<unsigned char> strRequestV = ParseHex(strRequestMutable);
strRequestMutable.assign(strRequestV.begin(), strRequestV.end());
[[fallthrough]];
}
- case RetFormat::BINARY: {
+ case RESTResponseFormat::BINARY: {
try {
//deserialize only if user sent a request
if (strRequestMutable.size() > 0)
@@ -762,7 +781,7 @@ static bool rest_getutxos(const std::any& context, HTTPRequest* req, const std::
break;
}
- case RetFormat::JSON: {
+ case RESTResponseFormat::JSON: {
if (!fInputParsed)
return RESTERR(req, HTTP_BAD_REQUEST, "Error: empty request");
break;
@@ -816,7 +835,7 @@ static bool rest_getutxos(const std::any& context, HTTPRequest* req, const std::
}
switch (rf) {
- case RetFormat::BINARY: {
+ case RESTResponseFormat::BINARY: {
// serialize data
// use exact same output as mentioned in Bip64
CDataStream ssGetUTXOResponse(SER_NETWORK, PROTOCOL_VERSION);
@@ -828,7 +847,7 @@ static bool rest_getutxos(const std::any& context, HTTPRequest* req, const std::
return true;
}
- case RetFormat::HEX: {
+ case RESTResponseFormat::HEX: {
CDataStream ssGetUTXOResponse(SER_NETWORK, PROTOCOL_VERSION);
ssGetUTXOResponse << chainman.ActiveChain().Height() << chainman.ActiveChain().Tip()->GetBlockHash() << bitmap << outs;
std::string strHex = HexStr(ssGetUTXOResponse) + "\n";
@@ -838,7 +857,7 @@ static bool rest_getutxos(const std::any& context, HTTPRequest* req, const std::
return true;
}
- case RetFormat::JSON: {
+ case RESTResponseFormat::JSON: {
UniValue objGetUTXOResponse(UniValue::VOBJ);
// pack in some essentials
@@ -855,7 +874,7 @@ static bool rest_getutxos(const std::any& context, HTTPRequest* req, const std::
// include the script in a json output
UniValue o(UniValue::VOBJ);
- ScriptPubKeyToUniv(coin.out.scriptPubKey, o, true);
+ ScriptToUniv(coin.out.scriptPubKey, /*out=*/o, /*include_hex=*/true, /*include_address=*/true);
utxo.pushKV("scriptPubKey", o);
utxos.push_back(utxo);
}
@@ -878,7 +897,7 @@ static bool rest_blockhash_by_height(const std::any& context, HTTPRequest* req,
{
if (!CheckWarmup(req)) return false;
std::string height_str;
- const RetFormat rf = ParseDataFormat(height_str, str_uri_part);
+ const RESTResponseFormat rf = ParseDataFormat(height_str, str_uri_part);
int32_t blockheight = -1; // Initialization done only to prevent valgrind false positive, see https://github.com/bitcoin/bitcoin/pull/18785
if (!ParseInt32(height_str, &blockheight) || blockheight < 0) {
@@ -898,19 +917,19 @@ static bool rest_blockhash_by_height(const std::any& context, HTTPRequest* req,
pblockindex = active_chain[blockheight];
}
switch (rf) {
- case RetFormat::BINARY: {
+ case RESTResponseFormat::BINARY: {
CDataStream ss_blockhash(SER_NETWORK, PROTOCOL_VERSION);
ss_blockhash << pblockindex->GetBlockHash();
req->WriteHeader("Content-Type", "application/octet-stream");
req->WriteReply(HTTP_OK, ss_blockhash.str());
return true;
}
- case RetFormat::HEX: {
+ case RESTResponseFormat::HEX: {
req->WriteHeader("Content-Type", "text/plain");
req->WriteReply(HTTP_OK, pblockindex->GetBlockHash().GetHex() + "\n");
return true;
}
- case RetFormat::JSON: {
+ case RESTResponseFormat::JSON: {
req->WriteHeader("Content-Type", "application/json");
UniValue resp = UniValue(UniValue::VOBJ);
resp.pushKV("blockhash", pblockindex->GetBlockHash().GetHex());