aboutsummaryrefslogtreecommitdiff
path: root/src/rest.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/rest.cpp')
-rw-r--r--src/rest.cpp235
1 files changed, 223 insertions, 12 deletions
diff --git a/src/rest.cpp b/src/rest.cpp
index e50ab33e54..addce6b0ea 100644
--- a/src/rest.cpp
+++ b/src/rest.cpp
@@ -1,12 +1,14 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
-// Copyright (c) 2009-2020 The Bitcoin Core developers
+// Copyright (c) 2009-2021 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#include <blockfilter.h>
#include <chain.h>
#include <chainparams.h>
#include <core_io.h>
#include <httpserver.h>
+#include <index/blockfilterindex.h>
#include <index/txindex.h>
#include <node/blockstorage.h>
#include <node/context.h>
@@ -15,6 +17,7 @@
#include <rpc/blockchain.h>
#include <rpc/protocol.h>
#include <rpc/server.h>
+#include <rpc/server_util.h>
#include <streams.h>
#include <sync.h>
#include <txmempool.h>
@@ -30,6 +33,7 @@
#include <univalue.h>
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,
@@ -189,9 +193,10 @@ static bool rest_headers(const std::any& context,
if (path.size() != 2)
return RESTERR(req, HTTP_BAD_REQUEST, "No header count specified. Use /rest/headers/<count>/<hash>.<ext>.");
- long count = strtol(path[0].c_str(), nullptr, 10);
- if (count < 1 || count > 2000)
- return RESTERR(req, HTTP_BAD_REQUEST, "Header count out of range: " + path[0]);
+ const auto parsed_count{ToIntegral<size_t>(path[0])};
+ 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]));
+ }
std::string hashStr = path[1];
uint256 hash;
@@ -199,8 +204,8 @@ static bool rest_headers(const std::any& context,
return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
const CBlockIndex* tip = nullptr;
- std::vector<const CBlockIndex *> headers;
- headers.reserve(count);
+ std::vector<const CBlockIndex*> headers;
+ headers.reserve(*parsed_count);
{
ChainstateManager* maybe_chainman = GetChainman(context, req);
if (!maybe_chainman) return false;
@@ -211,8 +216,9 @@ static bool rest_headers(const std::any& context,
const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(hash);
while (pindex != nullptr && active_chain.Contains(pindex)) {
headers.push_back(pindex);
- if (headers.size() == (unsigned long)count)
+ if (headers.size() == *parsed_count) {
break;
+ }
pindex = active_chain.Next(pindex);
}
}
@@ -252,7 +258,7 @@ static bool rest_headers(const std::any& context,
return true;
}
default: {
- return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: .bin, .hex, .json)");
+ return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
}
}
}
@@ -260,7 +266,7 @@ static bool rest_headers(const std::any& context,
static bool rest_block(const std::any& context,
HTTPRequest* req,
const std::string& strURIPart,
- bool showTxDetails)
+ TxVerbosity tx_verbosity)
{
if (!CheckWarmup(req))
return false;
@@ -312,7 +318,7 @@ static bool rest_block(const std::any& context,
}
case RetFormat::JSON: {
- UniValue objBlock = blockToJSON(block, tip, pblockindex, showTxDetails);
+ UniValue objBlock = blockToJSON(block, tip, pblockindex, tx_verbosity);
std::string strJSON = objBlock.write() + "\n";
req->WriteHeader("Content-Type", "application/json");
req->WriteReply(HTTP_OK, strJSON);
@@ -327,12 +333,215 @@ static bool rest_block(const std::any& context,
static bool rest_block_extended(const std::any& context, HTTPRequest* req, const std::string& strURIPart)
{
- return rest_block(context, req, strURIPart, true);
+ return rest_block(context, req, strURIPart, TxVerbosity::SHOW_DETAILS_AND_PREVOUT);
}
static bool rest_block_notxdetails(const std::any& context, HTTPRequest* req, const std::string& strURIPart)
{
- return rest_block(context, req, strURIPart, false);
+ return rest_block(context, req, strURIPart, TxVerbosity::SHOW_TXID);
+}
+
+static bool rest_filter_header(const std::any& context, HTTPRequest* req, const std::string& strURIPart)
+{
+ if (!CheckWarmup(req)) return false;
+
+ std::string param;
+ const RetFormat 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>");
+ }
+
+ uint256 block_hash;
+ if (!ParseHashStr(uri_parts[2], block_hash)) {
+ return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + uri_parts[2]);
+ }
+
+ BlockFilterType filtertype;
+ if (!BlockFilterTypeByName(uri_parts[0], filtertype)) {
+ return RESTERR(req, HTTP_BAD_REQUEST, "Unknown filtertype " + uri_parts[0]);
+ }
+
+ BlockFilterIndex* index = GetBlockFilterIndex(filtertype);
+ if (!index) {
+ 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);
+ {
+ ChainstateManager* maybe_chainman = GetChainman(context, req);
+ if (!maybe_chainman) return false;
+ ChainstateManager& chainman = *maybe_chainman;
+ LOCK(cs_main);
+ CChain& active_chain = chainman.ActiveChain();
+ const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(block_hash);
+ while (pindex != nullptr && active_chain.Contains(pindex)) {
+ headers.push_back(pindex);
+ if (headers.size() == *parsed_count)
+ break;
+ pindex = active_chain.Next(pindex);
+ }
+ }
+
+ bool index_ready = index->BlockUntilSyncedToCurrentChain();
+
+ std::vector<uint256> filter_headers;
+ filter_headers.reserve(*parsed_count);
+ for (const CBlockIndex* pindex : headers) {
+ uint256 filter_header;
+ if (!index->LookupFilterHeader(pindex, filter_header)) {
+ std::string errmsg = "Filter not found.";
+
+ if (!index_ready) {
+ errmsg += " Block filters are still in the process of being indexed.";
+ } else {
+ errmsg += " This error is unexpected and indicates index corruption.";
+ }
+
+ return RESTERR(req, HTTP_NOT_FOUND, errmsg);
+ }
+ filter_headers.push_back(filter_header);
+ }
+
+ switch (rf) {
+ case RetFormat::BINARY: {
+ CDataStream ssHeader{SER_NETWORK, PROTOCOL_VERSION};
+ for (const uint256& header : filter_headers) {
+ ssHeader << header;
+ }
+
+ std::string binaryHeader = ssHeader.str();
+ req->WriteHeader("Content-Type", "application/octet-stream");
+ req->WriteReply(HTTP_OK, binaryHeader);
+ return true;
+ }
+ case RetFormat::HEX: {
+ CDataStream ssHeader{SER_NETWORK, PROTOCOL_VERSION};
+ for (const uint256& header : filter_headers) {
+ ssHeader << header;
+ }
+
+ std::string strHex = HexStr(ssHeader) + "\n";
+ req->WriteHeader("Content-Type", "text/plain");
+ req->WriteReply(HTTP_OK, strHex);
+ return true;
+ }
+ case RetFormat::JSON: {
+ UniValue jsonHeaders(UniValue::VARR);
+ for (const uint256& header : filter_headers) {
+ jsonHeaders.push_back(header.GetHex());
+ }
+
+ std::string strJSON = jsonHeaders.write() + "\n";
+ req->WriteHeader("Content-Type", "application/json");
+ req->WriteReply(HTTP_OK, strJSON);
+ return true;
+ }
+ default: {
+ return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
+ }
+ }
+}
+
+static bool rest_block_filter(const std::any& context, HTTPRequest* req, const std::string& strURIPart)
+{
+ if (!CheckWarmup(req)) return false;
+
+ std::string param;
+ const RetFormat rf = ParseDataFormat(param, strURIPart);
+
+ // request is sent over URI scheme /rest/blockfilter/filtertype/blockhash
+ std::vector<std::string> uri_parts;
+ boost::split(uri_parts, param, boost::is_any_of("/"));
+ if (uri_parts.size() != 2) {
+ return RESTERR(req, HTTP_BAD_REQUEST, "Invalid URI format. Expected /rest/blockfilter/<filtertype>/<blockhash>");
+ }
+
+ uint256 block_hash;
+ if (!ParseHashStr(uri_parts[1], block_hash)) {
+ return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + uri_parts[1]);
+ }
+
+ BlockFilterType filtertype;
+ if (!BlockFilterTypeByName(uri_parts[0], filtertype)) {
+ return RESTERR(req, HTTP_BAD_REQUEST, "Unknown filtertype " + uri_parts[0]);
+ }
+
+ BlockFilterIndex* index = GetBlockFilterIndex(filtertype);
+ if (!index) {
+ return RESTERR(req, HTTP_BAD_REQUEST, "Index is not enabled for filtertype " + uri_parts[0]);
+ }
+
+ const CBlockIndex* block_index;
+ bool block_was_connected;
+ {
+ ChainstateManager* maybe_chainman = GetChainman(context, req);
+ if (!maybe_chainman) return false;
+ ChainstateManager& chainman = *maybe_chainman;
+ LOCK(cs_main);
+ block_index = chainman.m_blockman.LookupBlockIndex(block_hash);
+ if (!block_index) {
+ return RESTERR(req, HTTP_NOT_FOUND, uri_parts[1] + " not found");
+ }
+ block_was_connected = block_index->IsValid(BLOCK_VALID_SCRIPTS);
+ }
+
+ bool index_ready = index->BlockUntilSyncedToCurrentChain();
+
+ BlockFilter filter;
+ if (!index->LookupFilter(block_index, filter)) {
+ std::string errmsg = "Filter not found.";
+
+ if (!block_was_connected) {
+ errmsg += " Block was not connected to active chain.";
+ } else if (!index_ready) {
+ errmsg += " Block filters are still in the process of being indexed.";
+ } else {
+ errmsg += " This error is unexpected and indicates index corruption.";
+ }
+
+ return RESTERR(req, HTTP_NOT_FOUND, errmsg);
+ }
+
+ switch (rf) {
+ case RetFormat::BINARY: {
+ CDataStream ssResp{SER_NETWORK, PROTOCOL_VERSION};
+ ssResp << filter;
+
+ std::string binaryResp = ssResp.str();
+ req->WriteHeader("Content-Type", "application/octet-stream");
+ req->WriteReply(HTTP_OK, binaryResp);
+ return true;
+ }
+ case RetFormat::HEX: {
+ CDataStream ssResp{SER_NETWORK, PROTOCOL_VERSION};
+ ssResp << filter;
+
+ std::string strHex = HexStr(ssResp) + "\n";
+ req->WriteHeader("Content-Type", "text/plain");
+ req->WriteReply(HTTP_OK, strHex);
+ return true;
+ }
+ case RetFormat::JSON: {
+ UniValue ret(UniValue::VOBJ);
+ ret.pushKV("filter", HexStr(filter.GetEncodedFilter()));
+ std::string strJSON = ret.write() + "\n";
+ req->WriteHeader("Content-Type", "application/json");
+ req->WriteReply(HTTP_OK, strJSON);
+ return true;
+ }
+ default: {
+ return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
+ }
+ }
}
// A bit of a hack - dependency on a function defined in rpc/blockchain.cpp
@@ -715,6 +924,8 @@ static const struct {
{"/rest/tx/", rest_tx},
{"/rest/block/notxdetails/", rest_block_notxdetails},
{"/rest/block/", rest_block_extended},
+ {"/rest/blockfilter/", rest_block_filter},
+ {"/rest/blockfilterheaders/", rest_filter_header},
{"/rest/chaininfo", rest_chaininfo},
{"/rest/mempool/info", rest_mempool_info},
{"/rest/mempool/contents", rest_mempool_contents},