dash/src/rest.cpp
MarcoFalke c30c8f22dd
Merge #19849: Assert that RPCArg names are equal to CRPCCommand ones (blockchain,rawtransaction)
fa6bb0ce5dba33970e2c1e47ea4d0d2c0718eccb Assert that RPCArg names are equal to CRPCCommand ones (rawtransaction) (MarcoFalke)
fa80c814874a2893e4323ba5148fba21d7f421cd Assert that RPCArg names are equal to CRPCCommand ones (blockchain) (MarcoFalke)

Pull request description:

  This is split out from #18531 to just touch some RPC methods. Description from the main pr:

  ### Motivation

  RPCArg names in the rpc help are currently only used for documentation. However, in the future they could be used to teach the server the named arguments. Named arguments are currently registered by the `CRPCCommand`s and duplicate the RPCArg names from the documentation. This redundancy is fragile, and has lead to errors in the past (despite having linters to catch those kind of errors). See section "bugs found" for a list of bugs that have been found as a result of the changes here.

  ### Changes

  The changes here add an assert in the `CRPCCommand` constructor that the RPCArg names are identical to the ones in the `CRPCCommand`.

  ### Future work

  > Here or follow up, makes sense to also assert type of returned UniValue?

  Sure, but let's not get ahead of ourselves. I am going to submit any further works as follow-ups, including:

  * Removing the CRPCCommand arguments, now that they are asserted to be equal and thus redundant
  * Removing all python regex linters on the args, now that RPCMan can be used to generate any output, including the cli.cpp table
  * Auto-formatting and sanity checking the RPCExamples with RPCMan
  * Checking passed-in json in self-check. Removing redundant checks
  * Checking returned json against documentation to avoid regressions or false documentation
  * Compile the RPC documentation at compile-time to ensure it doesn't change at runtime and is completely static

  ### Bugs found

  * The assert identified issue #18607
  * The changes itself fixed bug #19250

ACKs for top commit:
  fjahr:
    utACK fa6bb0ce5dba33970e2c1e47ea4d0d2c0718eccb
  tryphe:
    utACK fa6bb0ce5dba33970e2c1e47ea4d0d2c0718eccb. Reducing data duplication is nice. Code changes are minimal and concise.

Tree-SHA512: deb0edc3f999baf055526eaa199b98c500635e12502dece7aa3cad5319db330eb5ee7459a5c8f040a83671a7f20c560c19a2026fb76c8416f138aa332727cbce
2024-03-17 13:02:58 -05:00

740 lines
25 KiB
C++

// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2019 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 <chain.h>
#include <chainparams.h>
#include <context.h>
#include <core_io.h>
#include <httpserver.h>
#include <index/txindex.h>
#include <llmq/chainlocks.h>
#include <llmq/context.h>
#include <llmq/instantsend.h>
#include <node/blockstorage.h>
#include <node/context.h>
#include <primitives/block.h>
#include <primitives/transaction.h>
#include <rpc/blockchain.h>
#include <rpc/protocol.h>
#include <rpc/server.h>
#include <streams.h>
#include <sync.h>
#include <txmempool.h>
#include <util/check.h>
#include <validation.h>
#include <version.h>
#include <univalue.h>
static const size_t MAX_GETUTXOS_OUTPOINTS = 15; //allow a max of 15 outpoints to be queried at once
enum class RetFormat {
UNDEF,
BINARY,
HEX,
JSON,
};
static const struct {
RetFormat rf;
const char* name;
} rf_names[] = {
{RetFormat::UNDEF, ""},
{RetFormat::BINARY, "bin"},
{RetFormat::HEX, "hex"},
{RetFormat::JSON, "json"},
};
struct CCoin {
uint32_t nHeight;
CTxOut out;
CCoin() : nHeight(0) {}
explicit CCoin(Coin&& in) : nHeight(in.nHeight), out(std::move(in.out)) {}
SERIALIZE_METHODS(CCoin, obj)
{
uint32_t nTxVerDummy = 0;
READWRITE(nTxVerDummy, obj.nHeight, obj.out);
}
};
static bool RESTERR(HTTPRequest* req, enum HTTPStatusCode status, std::string message)
{
req->WriteHeader("Content-Type", "text/plain");
req->WriteReply(status, message + "\r\n");
return false;
}
/**
* Get the node context.
*
* @param[in] req The HTTP request, whose status code will be set if node
* context is not found.
* @returns Pointer to the node context or nullptr if not found.
*/
static NodeContext* GetNodeContext(const CoreContext& context, HTTPRequest* req)
{
auto* node_context = GetContext<NodeContext>(context);
if (!node_context) {
RESTERR(req, HTTP_INTERNAL_SERVER_ERROR,
strprintf("%s:%d (%s)\n"
"Internal bug detected: Node context not found!\n"
"You may report this issue here: %s\n",
__FILE__, __LINE__, __func__, PACKAGE_BUGREPORT));
return nullptr;
}
return node_context;
}
/**
* Get the node context mempool.
*
* @param[in] req The HTTP request, whose status code will be set if node
* context mempool is not found.
* @returns Pointer to the mempool or nullptr if no mempool found.
*/
static CTxMemPool* GetMemPool(const CoreContext& context, HTTPRequest* req)
{
auto* node_context = GetContext<NodeContext>(context);
if (!node_context || !node_context->mempool) {
RESTERR(req, HTTP_NOT_FOUND, "Mempool disabled or instance not found");
return nullptr;
}
return node_context->mempool.get();
}
/**
* Get the node context chainstatemanager.
*
* @param[in] req The HTTP request, whose status code will be set if node
* context chainstatemanager is not found.
* @returns Pointer to the chainstatemanager or nullptr if none found.
*/
static ChainstateManager* GetChainman(const CoreContext& context, HTTPRequest* req)
{
auto node_context = GetContext<NodeContext>(context);
if (!node_context || !node_context->chainman) {
RESTERR(req, HTTP_INTERNAL_SERVER_ERROR,
strprintf("%s:%d (%s)\n"
"Internal bug detected: Chainman disabled or instance not found!\n"
"You may report this issue here: %s\n",
__FILE__, __LINE__, __func__, PACKAGE_BUGREPORT));
return nullptr;
}
return node_context->chainman;
}
static RetFormat ParseDataFormat(std::string& param, const std::string& strReq)
{
const std::string::size_type pos = strReq.rfind('.');
if (pos == std::string::npos)
{
param = strReq;
return rf_names[0].rf;
}
param = strReq.substr(0, pos);
const std::string suff(strReq, pos + 1);
for (const auto& rf_name : rf_names) {
if (suff == rf_name.name)
return rf_name.rf;
}
/* If no suffix is found, return original string. */
param = strReq;
return rf_names[0].rf;
}
static std::string AvailableDataFormatsString()
{
std::string formats;
for (const auto& rf_name : rf_names) {
if (strlen(rf_name.name) > 0) {
formats.append(".");
formats.append(rf_name.name);
formats.append(", ");
}
}
if (formats.length() > 0)
return formats.substr(0, formats.length() - 2);
return formats;
}
static bool CheckWarmup(HTTPRequest* req)
{
std::string statusmessage;
if (RPCIsInWarmup(&statusmessage))
return RESTERR(req, HTTP_SERVICE_UNAVAILABLE, "Service temporarily unavailable: " + statusmessage);
return true;
}
static bool rest_headers(const CoreContext& 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> path = SplitString(param, '/');
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]);
std::string hashStr = path[1];
uint256 hash;
if (!ParseHashStr(hashStr, hash))
return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
const CBlockIndex* tip = nullptr;
std::vector<const CBlockIndex *> headers;
headers.reserve(count);
{
ChainstateManager* maybe_chainman = GetChainman(context, req);
if (!maybe_chainman) return false;
ChainstateManager& chainman = *maybe_chainman;
LOCK(cs_main);
CChain& active_chain = chainman.ActiveChain();
tip = active_chain.Tip();
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)
break;
pindex = active_chain.Next(pindex);
}
}
switch (rf) {
case RetFormat::BINARY: {
CDataStream ssHeader(SER_NETWORK, PROTOCOL_VERSION);
for (const CBlockIndex *pindex : headers) {
ssHeader << pindex->GetBlockHeader();
}
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 CBlockIndex *pindex : headers) {
ssHeader << pindex->GetBlockHeader();
}
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 CBlockIndex *pindex : headers) {
jsonHeaders.push_back(blockheaderToJSON(tip, pindex, *llmq::chainLocksHandler, *llmq::quorumInstantSendManager));
}
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: .bin, .hex, .json)");
}
}
}
static bool rest_block(const CoreContext& context,
HTTPRequest* req,
const std::string& strURIPart,
bool showTxDetails)
{
if (!CheckWarmup(req))
return false;
std::string hashStr;
const RetFormat 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;
{
ChainstateManager* maybe_chainman = GetChainman(context, req);
if (!maybe_chainman) return false;
ChainstateManager& chainman = *maybe_chainman;
LOCK(cs_main);
tip = chainman.ActiveChain().Tip();
pblockindex = chainman.m_blockman.LookupBlockIndex(hash);
if (!pblockindex) {
return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found");
}
if (IsBlockPruned(pblockindex))
return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not available (pruned data)");
if (!ReadBlockFromDisk(block, pblockindex, Params().GetConsensus()))
return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found");
}
switch (rf) {
case RetFormat::BINARY: {
CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION);
ssBlock << block;
std::string binaryBlock = ssBlock.str();
req->WriteHeader("Content-Type", "application/octet-stream");
req->WriteReply(HTTP_OK, binaryBlock);
return true;
}
case RetFormat::HEX: {
CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION);
ssBlock << block;
std::string strHex = HexStr(ssBlock) + "\n";
req->WriteHeader("Content-Type", "text/plain");
req->WriteReply(HTTP_OK, strHex);
return true;
}
case RetFormat::JSON: {
UniValue objBlock = blockToJSON(block, tip, pblockindex, *llmq::chainLocksHandler, *llmq::quorumInstantSendManager, showTxDetails);
std::string strJSON = objBlock.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_extended(const CoreContext& context, HTTPRequest* req, const std::string& strURIPart)
{
return rest_block(context, req, strURIPart, true);
}
static bool rest_block_notxdetails(const CoreContext& context, HTTPRequest* req, const std::string& strURIPart)
{
return rest_block(context, req, strURIPart, false);
}
// A bit of a hack - dependency on a function defined in rpc/blockchain.cpp
RPCHelpMan getblockchaininfo();
static bool rest_chaininfo(const CoreContext& context, HTTPRequest* req, const std::string& strURIPart)
{
if (!CheckWarmup(req))
return false;
std::string param;
const RetFormat rf = ParseDataFormat(param, strURIPart);
switch (rf) {
case RetFormat::JSON: {
JSONRPCRequest jsonRequest(context);
jsonRequest.params = UniValue(UniValue::VARR);
UniValue chainInfoObject = getblockchaininfo().HandleRequest(jsonRequest);
std::string strJSON = chainInfoObject.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: json)");
}
}
}
static bool rest_mempool_info(const CoreContext& context, HTTPRequest* req, const std::string& strURIPart)
{
if (!CheckWarmup(req))
return false;
const CTxMemPool* mempool = GetMemPool(context, req);
if (!mempool) return false;
std::string param;
const RetFormat rf = ParseDataFormat(param, strURIPart);
switch (rf) {
case RetFormat::JSON: {
UniValue mempoolInfoObject = MempoolInfoToJSON(*mempool, *llmq::quorumInstantSendManager);
std::string strJSON = mempoolInfoObject.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: json)");
}
}
}
static bool rest_mempool_contents(const CoreContext& context, HTTPRequest* req, const std::string& strURIPart)
{
if (!CheckWarmup(req)) return false;
const CTxMemPool* mempool = GetMemPool(context, req);
if (!mempool) return false;
std::string param;
const RetFormat rf = ParseDataFormat(param, strURIPart);
switch (rf) {
case RetFormat::JSON: {
UniValue mempoolObject = MempoolToJSON(*mempool, llmq::quorumInstantSendManager.get(), true);
std::string strJSON = mempoolObject.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: json)");
}
}
}
static bool rest_tx(const CoreContext& context, HTTPRequest* req, const std::string& strURIPart)
{
if (!CheckWarmup(req))
return false;
std::string hashStr;
const RetFormat rf = ParseDataFormat(hashStr, strURIPart);
uint256 hash;
if (!ParseHashStr(hashStr, hash))
return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
if (g_txindex) {
g_txindex->BlockUntilSyncedToCurrentChain();
}
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);
if (!tx) {
return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found");
}
switch (rf) {
case RetFormat::BINARY: {
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
ssTx << tx;
std::string binaryTx = ssTx.str();
req->WriteHeader("Content-Type", "application/octet-stream");
req->WriteReply(HTTP_OK, binaryTx);
return true;
}
case RetFormat::HEX: {
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
ssTx << tx;
std::string strHex = HexStr(ssTx) + "\n";
req->WriteHeader("Content-Type", "text/plain");
req->WriteReply(HTTP_OK, strHex);
return true;
}
case RetFormat::JSON: {
UniValue objTx(UniValue::VOBJ);
TxToUniv(*tx, hashBlock, objTx);
std::string strJSON = objTx.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_getutxos(const CoreContext& 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> uriParts;
if (param.length() > 1)
{
std::string strUriParams = param.substr(1);
uriParts = SplitString(strUriParams, '/');
}
// throw exception in case of an empty request
std::string strRequestMutable = req->ReadBody();
if (strRequestMutable.length() == 0 && uriParts.size() == 0)
return RESTERR(req, HTTP_BAD_REQUEST, "Error: empty request");
bool fInputParsed = false;
bool fCheckMemPool = false;
std::vector<COutPoint> vOutPoints;
// parse/deserialize input
// 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[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))
return RESTERR(req, HTTP_BAD_REQUEST, "Parse error");
txid.SetHex(strTxid);
vOutPoints.push_back(COutPoint(txid, (uint32_t)nOutput));
}
if (vOutPoints.size() > 0)
fInputParsed = true;
else
return RESTERR(req, HTTP_BAD_REQUEST, "Error: empty request");
}
switch (rf) {
case RetFormat::HEX: {
// convert hex to bin, continue then with bin part
std::vector<unsigned char> strRequestV = ParseHex(strRequestMutable);
strRequestMutable.assign(strRequestV.begin(), strRequestV.end());
}
case RetFormat::BINARY: {
try {
//deserialize only if user sent a request
if (strRequestMutable.size() > 0)
{
if (fInputParsed) //don't allow sending input over URI and HTTP RAW DATA
return RESTERR(req, HTTP_BAD_REQUEST, "Combination of URI scheme inputs and raw post data is not allowed");
CDataStream oss(SER_NETWORK, PROTOCOL_VERSION);
oss << strRequestMutable;
oss >> fCheckMemPool;
oss >> vOutPoints;
}
} catch (const std::ios_base::failure&) {
// abort in case of unreadable binary data
return RESTERR(req, HTTP_BAD_REQUEST, "Parse error");
}
break;
}
case RetFormat::JSON: {
if (!fInputParsed)
return RESTERR(req, HTTP_BAD_REQUEST, "Error: empty request");
break;
}
default: {
return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
}
}
// limit max outpoints
if (vOutPoints.size() > MAX_GETUTXOS_OUTPOINTS)
return RESTERR(req, HTTP_BAD_REQUEST, strprintf("Error: max outpoints exceeded (max: %d, tried: %d)", MAX_GETUTXOS_OUTPOINTS, vOutPoints.size()));
// check spentness and form a bitmap (as well as a JSON capable human-readable string representation)
std::vector<unsigned char> bitmap;
std::vector<CCoin> outs;
std::string bitmapStringRepresentation;
std::vector<bool> hits;
bitmap.resize((vOutPoints.size() + 7) / 8);
ChainstateManager* maybe_chainman = GetChainman(context, req);
if (!maybe_chainman) return false;
ChainstateManager& chainman = *maybe_chainman;
{
auto process_utxos = [&vOutPoints, &outs, &hits](const CCoinsView& view, const CTxMemPool& mempool) {
for (const COutPoint& vOutPoint : vOutPoints) {
Coin coin;
bool hit = !mempool.isSpent(vOutPoint) && view.GetCoin(vOutPoint, coin);
hits.push_back(hit);
if (hit) outs.emplace_back(std::move(coin));
}
};
if (fCheckMemPool) {
const CTxMemPool* mempool = GetMemPool(context, req);
if (!mempool) return false;
// use db+mempool as cache backend in case user likes to query mempool
LOCK2(cs_main, mempool->cs);
CCoinsViewCache& viewChain = chainman.ActiveChainstate().CoinsTip();
CCoinsViewMemPool viewMempool(&viewChain, *mempool);
process_utxos(viewMempool, *mempool);
} else {
LOCK(cs_main); // no need to lock mempool!
process_utxos(chainman.ActiveChainstate().CoinsTip(), CTxMemPool());
}
for (size_t i = 0; i < hits.size(); ++i) {
const bool hit = hits[i];
bitmapStringRepresentation.append(hit ? "1" : "0"); // form a binary string representation (human-readable for json output)
bitmap[i / 8] |= ((uint8_t)hit) << (i % 8);
}
}
switch (rf) {
case RetFormat::BINARY: {
// serialize data
// use exact same output as mentioned in Bip64
CDataStream ssGetUTXOResponse(SER_NETWORK, PROTOCOL_VERSION);
ssGetUTXOResponse << chainman.ActiveChain().Height() << chainman.ActiveChain().Tip()->GetBlockHash() << bitmap << outs;
std::string ssGetUTXOResponseString = ssGetUTXOResponse.str();
req->WriteHeader("Content-Type", "application/octet-stream");
req->WriteReply(HTTP_OK, ssGetUTXOResponseString);
return true;
}
case RetFormat::HEX: {
CDataStream ssGetUTXOResponse(SER_NETWORK, PROTOCOL_VERSION);
ssGetUTXOResponse << chainman.ActiveChain().Height() << chainman.ActiveChain().Tip()->GetBlockHash() << bitmap << outs;
std::string strHex = HexStr(ssGetUTXOResponse) + "\n";
req->WriteHeader("Content-Type", "text/plain");
req->WriteReply(HTTP_OK, strHex);
return true;
}
case RetFormat::JSON: {
UniValue objGetUTXOResponse(UniValue::VOBJ);
// pack in some essentials
// use more or less the same output as mentioned in Bip64
objGetUTXOResponse.pushKV("chainHeight", chainman.ActiveChain().Height());
objGetUTXOResponse.pushKV("chaintipHash", chainman.ActiveChain().Tip()->GetBlockHash().GetHex());
objGetUTXOResponse.pushKV("bitmap", bitmapStringRepresentation);
UniValue utxos(UniValue::VARR);
for (const CCoin& coin : outs) {
UniValue utxo(UniValue::VOBJ);
utxo.pushKV("height", (int32_t)coin.nHeight);
utxo.pushKV("value", ValueFromAmount(coin.out.nValue));
// include the script in a json output
UniValue o(UniValue::VOBJ);
ScriptPubKeyToUniv(coin.out.scriptPubKey, o, true);
utxo.pushKV("scriptPubKey", o);
utxos.push_back(utxo);
}
objGetUTXOResponse.pushKV("utxos", utxos);
// return json string
std::string strJSON = objGetUTXOResponse.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_blockhash_by_height(const CoreContext& context, HTTPRequest* req,
const std::string& str_uri_part)
{
if (!CheckWarmup(req)) return false;
std::string height_str;
const RetFormat 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) {
return RESTERR(req, HTTP_BAD_REQUEST, "Invalid height: " + SanitizeString(height_str));
}
CBlockIndex* pblockindex = nullptr;
{
ChainstateManager* maybe_chainman = GetChainman(context, req);
if (!maybe_chainman) return false;
ChainstateManager& chainman = *maybe_chainman;
LOCK(cs_main);
const CChain& active_chain = chainman.ActiveChain();
if (blockheight > active_chain.Height()) {
return RESTERR(req, HTTP_NOT_FOUND, "Block height out of range");
}
pblockindex = active_chain[blockheight];
}
switch (rf) {
case RetFormat::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: {
req->WriteHeader("Content-Type", "text/plain");
req->WriteReply(HTTP_OK, pblockindex->GetBlockHash().GetHex() + "\n");
return true;
}
case RetFormat::JSON: {
req->WriteHeader("Content-Type", "application/json");
UniValue resp = UniValue(UniValue::VOBJ);
resp.pushKV("blockhash", pblockindex->GetBlockHash().GetHex());
req->WriteReply(HTTP_OK, resp.write() + "\n");
return true;
}
default: {
return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
}
}
}
static const struct {
const char* prefix;
bool (*handler)(const CoreContext& context, HTTPRequest* req, const std::string& strReq);
} uri_prefixes[] = {
{"/rest/tx/", rest_tx},
{"/rest/block/notxdetails/", rest_block_notxdetails},
{"/rest/block/", rest_block_extended},
{"/rest/chaininfo", rest_chaininfo},
{"/rest/mempool/info", rest_mempool_info},
{"/rest/mempool/contents", rest_mempool_contents},
{"/rest/headers/", rest_headers},
{"/rest/getutxos", rest_getutxos},
{"/rest/blockhashbyheight/", rest_blockhash_by_height},
};
void StartREST(const CoreContext& context)
{
for (const auto& up : uri_prefixes) {
auto handler = [&context, up](HTTPRequest* req, const std::string& prefix) { return up.handler(context, req, prefix); };
RegisterHTTPHandler(up.prefix, false, handler);
}
}
void InterruptREST()
{
}
void StopREST()
{
for (const auto& up : uri_prefixes) {
UnregisterHTTPHandler(up.prefix, false);
}
}