mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-01-13 13:22:38 -03:00
0dd7b23489
586190f0b4
rpc/rest: Take and reuse local Chain/ChainState obj (Carl Dong)bc3bd36902
rpc: style: Improve BuriedForkDescPushBack signature (Carl Dong)f99913969f
rpc: Remove unnecessary casting of block height (Carl Dong)6a3d192020
rpc: Tidy up local references (see commit message) (Carl Dong)038854f31e
rest/rpc: Remove now-unused old Ensure functions (Carl Dong)6fb65b49f4
scripted-diff: rest/rpc: Use renamed EnsureAny*() (Carl Dong)1570c7ee98
rpc: Add renamed EnsureAny*() functions (Carl Dong)306b1cd3ee
rpc: Add alt Ensure* functions acepting NodeContext (Carl Dong)d7824acdb9
rest: Use existing NodeContext (Carl Dong)3f08934799
rest: Pass in NodeContext to rest_block (Carl Dong)7be0671b95
rpc/rawtx: Use existing NodeContext (Carl Dong)60dc05afc6
rpc/mining: Use existing NodeContext (Carl Dong)d485e815e2
rpc/blockchain: Use existing NodeContext (Carl Dong)d0abf0bf42
rpc/*,rest: Add review-only assertion to EnsureChainman (Carl Dong)cced0f46c9
miner: Pass in previous CBlockIndex to RegenerateCommitments (Carl Dong) Pull request description: Overall PR: #20158 (tree-wide: De-globalize ChainstateManager) Based on: - [x] #21270 | [Bundle 4/n] Prune g_chainman usage in validation-adjacent modules - [x] #21525 | [Bundle 4.5/n] Followup fixups to bundle 4 Note to reviewers: 1. This bundle may _apparently_ introduce usage of `g_chainman` or `::Chain(state|)Active()` globals, but these are resolved later on in the overall PR. [Commits of overall PR](https://github.com/bitcoin/bitcoin/pull/20158/commits) 2. There may be seemingly obvious local references to `ChainstateManager` or other validation objects which are not being used in callers of the current function in question, this is done intentionally to **_keep each commit centered around one function/method_** to ease review and to make the overall change systematic. We don't assume anything about our callers. Rest assured that once we are considering that particular caller in later commits, we will use the obvious local references. [Commits of overall PR](https://github.com/bitcoin/bitcoin/pull/20158/commits) 3. When changing a function/method that has many callers (e.g. `LookupBlockIndex` with 55 callers), it is sometimes easier (and less error-prone) to use a scripted-diff. When doing so, there will be 3 commits in sequence so that every commit compiles like so: 1. Add `new_function`, make `old_function` a wrapper of `new_function`, divert all calls to `old_function` to `new_function` **in the local module only** 2. Scripted-diff to divert all calls to `old_function` to `new_function` **in the rest of the codebase** 3. Remove `old_function` ACKs for top commit: ryanofsky: Code review ACK586190f0b4
. Since last review, no changes to existing commits, just some simple new commits added: three new commits renaming std::any Ensure functions (scripted diff commit and manual pre/post commits), and one new commit factoring out a repeated `ActiveChain()` call made in a loop. Thanks for the updates! jnewbery: utACK586190f0b4
MarcoFalke: review ACK586190f0b4
🍯 Tree-SHA512: 64b677fb50141805b55c3f1afe68fcd298f9a071a359bdcd63256d52e334f83e462f31fb3ebee9b630da8f1d912a03a128cfc38179e7aaec29a055744a98478c
713 lines
24 KiB
C++
713 lines
24 KiB
C++
// Copyright (c) 2009-2010 Satoshi Nakamoto
|
|
// Copyright (c) 2009-2020 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 <core_io.h>
|
|
#include <httpserver.h>
|
|
#include <index/txindex.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 <util/system.h>
|
|
#include <validation.h>
|
|
#include <version.h>
|
|
|
|
#include <any>
|
|
|
|
#include <boost/algorithm/string.hpp>
|
|
|
|
#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 std::any& context, HTTPRequest* req)
|
|
{
|
|
auto node_context = util::AnyPtr<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 std::any& context, HTTPRequest* req)
|
|
{
|
|
auto node_context = util::AnyPtr<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();
|
|
}
|
|
|
|
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 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> 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>.");
|
|
|
|
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& chainman = EnsureAnyChainman(context);
|
|
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));
|
|
}
|
|
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 std::any& 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& chainman = EnsureAnyChainman(context);
|
|
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 | RPCSerializationFlags());
|
|
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 | RPCSerializationFlags());
|
|
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, 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 std::any& context, HTTPRequest* req, const std::string& strURIPart)
|
|
{
|
|
return rest_block(context, req, strURIPart, true);
|
|
}
|
|
|
|
static bool rest_block_notxdetails(const std::any& 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 std::any& 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;
|
|
jsonRequest.context = 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 std::any& 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);
|
|
|
|
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 std::any& 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, 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 std::any& 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 | RPCSerializationFlags());
|
|
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 | RPCSerializationFlags());
|
|
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 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> uriParts;
|
|
if (param.length() > 1)
|
|
{
|
|
std::string strUriParams = param.substr(1);
|
|
boost::split(uriParts, strUriParams, boost::is_any_of("/"));
|
|
}
|
|
|
|
// 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& chainman = EnsureAnyChainman(context);
|
|
{
|
|
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 std::any& 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& chainman = EnsureAnyChainman(context);
|
|
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 std::any& 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 std::any& 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);
|
|
}
|
|
}
|