Branch data Line data Source code
# 1 : : // Copyright (c) 2009-2010 Satoshi Nakamoto
# 2 : : // Copyright (c) 2009-2020 The Bitcoin Core developers
# 3 : : // Distributed under the MIT software license, see the accompanying
# 4 : : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
# 5 : :
# 6 : : #include <chain.h>
# 7 : : #include <chainparams.h>
# 8 : : #include <core_io.h>
# 9 : : #include <httpserver.h>
# 10 : : #include <index/txindex.h>
# 11 : : #include <node/blockstorage.h>
# 12 : : #include <node/context.h>
# 13 : : #include <primitives/block.h>
# 14 : : #include <primitives/transaction.h>
# 15 : : #include <rpc/blockchain.h>
# 16 : : #include <rpc/protocol.h>
# 17 : : #include <rpc/server.h>
# 18 : : #include <streams.h>
# 19 : : #include <sync.h>
# 20 : : #include <txmempool.h>
# 21 : : #include <util/check.h>
# 22 : : #include <util/system.h>
# 23 : : #include <validation.h>
# 24 : : #include <version.h>
# 25 : :
# 26 : : #include <any>
# 27 : :
# 28 : : #include <boost/algorithm/string.hpp>
# 29 : :
# 30 : : #include <univalue.h>
# 31 : :
# 32 : : static const size_t MAX_GETUTXOS_OUTPOINTS = 15; //allow a max of 15 outpoints to be queried at once
# 33 : :
# 34 : : enum class RetFormat {
# 35 : : UNDEF,
# 36 : : BINARY,
# 37 : : HEX,
# 38 : : JSON,
# 39 : : };
# 40 : :
# 41 : : static const struct {
# 42 : : RetFormat rf;
# 43 : : const char* name;
# 44 : : } rf_names[] = {
# 45 : : {RetFormat::UNDEF, ""},
# 46 : : {RetFormat::BINARY, "bin"},
# 47 : : {RetFormat::HEX, "hex"},
# 48 : : {RetFormat::JSON, "json"},
# 49 : : };
# 50 : :
# 51 : : struct CCoin {
# 52 : : uint32_t nHeight;
# 53 : : CTxOut out;
# 54 : :
# 55 : 1 : CCoin() : nHeight(0) {}
# 56 : 8 : explicit CCoin(Coin&& in) : nHeight(in.nHeight), out(std::move(in.out)) {}
# 57 : :
# 58 : : SERIALIZE_METHODS(CCoin, obj)
# 59 : 0 : {
# 60 : 0 : uint32_t nTxVerDummy = 0;
# 61 : 0 : READWRITE(nTxVerDummy, obj.nHeight, obj.out);
# 62 : 0 : }
# 63 : : };
# 64 : :
# 65 : : static bool RESTERR(HTTPRequest* req, enum HTTPStatusCode status, std::string message)
# 66 : 9 : {
# 67 : 9 : req->WriteHeader("Content-Type", "text/plain");
# 68 : 9 : req->WriteReply(status, message + "\r\n");
# 69 : 9 : return false;
# 70 : 9 : }
# 71 : :
# 72 : : /**
# 73 : : * Get the node context.
# 74 : : *
# 75 : : * @param[in] req The HTTP request, whose status code will be set if node
# 76 : : * context is not found.
# 77 : : * @returns Pointer to the node context or nullptr if not found.
# 78 : : */
# 79 : : static NodeContext* GetNodeContext(const std::any& context, HTTPRequest* req)
# 80 : 3 : {
# 81 : 3 : auto node_context = util::AnyPtr<NodeContext>(context);
# 82 [ - + ]: 3 : if (!node_context) {
# 83 : 0 : RESTERR(req, HTTP_INTERNAL_SERVER_ERROR,
# 84 : 0 : strprintf("%s:%d (%s)\n"
# 85 : 0 : "Internal bug detected: Node context not found!\n"
# 86 : 0 : "You may report this issue here: %s\n",
# 87 : 0 : __FILE__, __LINE__, __func__, PACKAGE_BUGREPORT));
# 88 : 0 : return nullptr;
# 89 : 0 : }
# 90 : 3 : return node_context;
# 91 : 3 : }
# 92 : :
# 93 : : /**
# 94 : : * Get the node context mempool.
# 95 : : *
# 96 : : * @param[in] req The HTTP request, whose status code will be set if node
# 97 : : * context mempool is not found.
# 98 : : * @returns Pointer to the mempool or nullptr if no mempool found.
# 99 : : */
# 100 : : static CTxMemPool* GetMemPool(const std::any& context, HTTPRequest* req)
# 101 : 7 : {
# 102 : 7 : auto node_context = util::AnyPtr<NodeContext>(context);
# 103 [ - + ][ - + ]: 7 : if (!node_context || !node_context->mempool) {
# 104 : 0 : RESTERR(req, HTTP_NOT_FOUND, "Mempool disabled or instance not found");
# 105 : 0 : return nullptr;
# 106 : 0 : }
# 107 : 7 : return node_context->mempool.get();
# 108 : 7 : }
# 109 : :
# 110 : : static RetFormat ParseDataFormat(std::string& param, const std::string& strReq)
# 111 : 41 : {
# 112 : 41 : const std::string::size_type pos = strReq.rfind('.');
# 113 [ - + ]: 41 : if (pos == std::string::npos)
# 114 : 0 : {
# 115 : 0 : param = strReq;
# 116 : 0 : return rf_names[0].rf;
# 117 : 0 : }
# 118 : :
# 119 : 41 : param = strReq.substr(0, pos);
# 120 : 41 : const std::string suff(strReq, pos + 1);
# 121 : :
# 122 [ + - ]: 150 : for (const auto& rf_name : rf_names) {
# 123 [ + + ]: 150 : if (suff == rf_name.name)
# 124 : 41 : return rf_name.rf;
# 125 : 150 : }
# 126 : :
# 127 : : /* If no suffix is found, return original string. */
# 128 : 41 : param = strReq;
# 129 : 0 : return rf_names[0].rf;
# 130 : 41 : }
# 131 : :
# 132 : : static std::string AvailableDataFormatsString()
# 133 : 0 : {
# 134 : 0 : std::string formats;
# 135 [ # # ]: 0 : for (const auto& rf_name : rf_names) {
# 136 [ # # ]: 0 : if (strlen(rf_name.name) > 0) {
# 137 : 0 : formats.append(".");
# 138 : 0 : formats.append(rf_name.name);
# 139 : 0 : formats.append(", ");
# 140 : 0 : }
# 141 : 0 : }
# 142 : :
# 143 [ # # ]: 0 : if (formats.length() > 0)
# 144 : 0 : return formats.substr(0, formats.length() - 2);
# 145 : :
# 146 : 0 : return formats;
# 147 : 0 : }
# 148 : :
# 149 : : static bool CheckWarmup(HTTPRequest* req)
# 150 : 41 : {
# 151 : 41 : std::string statusmessage;
# 152 [ - + ]: 41 : if (RPCIsInWarmup(&statusmessage))
# 153 : 0 : return RESTERR(req, HTTP_SERVICE_UNAVAILABLE, "Service temporarily unavailable: " + statusmessage);
# 154 : 41 : return true;
# 155 : 41 : }
# 156 : :
# 157 : : static bool rest_headers(const std::any& context,
# 158 : : HTTPRequest* req,
# 159 : : const std::string& strURIPart)
# 160 : 6 : {
# 161 [ - + ]: 6 : if (!CheckWarmup(req))
# 162 : 0 : return false;
# 163 : 6 : std::string param;
# 164 : 6 : const RetFormat rf = ParseDataFormat(param, strURIPart);
# 165 : 6 : std::vector<std::string> path;
# 166 : 6 : boost::split(path, param, boost::is_any_of("/"));
# 167 : :
# 168 [ - + ]: 6 : if (path.size() != 2)
# 169 : 0 : return RESTERR(req, HTTP_BAD_REQUEST, "No header count specified. Use /rest/headers/<count>/<hash>.<ext>.");
# 170 : :
# 171 : 6 : long count = strtol(path[0].c_str(), nullptr, 10);
# 172 [ - + ][ - + ]: 6 : if (count < 1 || count > 2000)
# 173 : 0 : return RESTERR(req, HTTP_BAD_REQUEST, "Header count out of range: " + path[0]);
# 174 : :
# 175 : 6 : std::string hashStr = path[1];
# 176 : 6 : uint256 hash;
# 177 [ - + ]: 6 : if (!ParseHashStr(hashStr, hash))
# 178 : 0 : return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
# 179 : :
# 180 : 6 : const CBlockIndex* tip = nullptr;
# 181 : 6 : std::vector<const CBlockIndex *> headers;
# 182 : 6 : headers.reserve(count);
# 183 : 6 : {
# 184 : 6 : ChainstateManager& chainman = EnsureAnyChainman(context);
# 185 : 6 : LOCK(cs_main);
# 186 : 6 : CChain& active_chain = chainman.ActiveChain();
# 187 : 6 : tip = active_chain.Tip();
# 188 : 6 : const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(hash);
# 189 [ + + ][ + + ]: 10 : while (pindex != nullptr && active_chain.Contains(pindex)) {
# 190 : 8 : headers.push_back(pindex);
# 191 [ + + ]: 8 : if (headers.size() == (unsigned long)count)
# 192 : 4 : break;
# 193 : 4 : pindex = active_chain.Next(pindex);
# 194 : 4 : }
# 195 : 6 : }
# 196 : :
# 197 : 6 : switch (rf) {
# 198 [ + + ]: 1 : case RetFormat::BINARY: {
# 199 : 1 : CDataStream ssHeader(SER_NETWORK, PROTOCOL_VERSION);
# 200 [ + + ]: 1 : for (const CBlockIndex *pindex : headers) {
# 201 : 1 : ssHeader << pindex->GetBlockHeader();
# 202 : 1 : }
# 203 : :
# 204 : 1 : std::string binaryHeader = ssHeader.str();
# 205 : 1 : req->WriteHeader("Content-Type", "application/octet-stream");
# 206 : 1 : req->WriteReply(HTTP_OK, binaryHeader);
# 207 : 1 : return true;
# 208 : 0 : }
# 209 : :
# 210 [ + + ]: 1 : case RetFormat::HEX: {
# 211 : 1 : CDataStream ssHeader(SER_NETWORK, PROTOCOL_VERSION);
# 212 [ + + ]: 1 : for (const CBlockIndex *pindex : headers) {
# 213 : 1 : ssHeader << pindex->GetBlockHeader();
# 214 : 1 : }
# 215 : :
# 216 : 1 : std::string strHex = HexStr(ssHeader) + "\n";
# 217 : 1 : req->WriteHeader("Content-Type", "text/plain");
# 218 : 1 : req->WriteReply(HTTP_OK, strHex);
# 219 : 1 : return true;
# 220 : 0 : }
# 221 [ + + ]: 4 : case RetFormat::JSON: {
# 222 : 4 : UniValue jsonHeaders(UniValue::VARR);
# 223 [ + + ]: 6 : for (const CBlockIndex *pindex : headers) {
# 224 : 6 : jsonHeaders.push_back(blockheaderToJSON(tip, pindex));
# 225 : 6 : }
# 226 : 4 : std::string strJSON = jsonHeaders.write() + "\n";
# 227 : 4 : req->WriteHeader("Content-Type", "application/json");
# 228 : 4 : req->WriteReply(HTTP_OK, strJSON);
# 229 : 4 : return true;
# 230 : 0 : }
# 231 [ - + ]: 0 : default: {
# 232 : 0 : return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: .bin, .hex, .json)");
# 233 : 0 : }
# 234 : 6 : }
# 235 : 6 : }
# 236 : :
# 237 : : static bool rest_block(const std::any& context,
# 238 : : HTTPRequest* req,
# 239 : : const std::string& strURIPart,
# 240 : : bool showTxDetails)
# 241 : 7 : {
# 242 [ - + ]: 7 : if (!CheckWarmup(req))
# 243 : 0 : return false;
# 244 : 7 : std::string hashStr;
# 245 : 7 : const RetFormat rf = ParseDataFormat(hashStr, strURIPart);
# 246 : :
# 247 : 7 : uint256 hash;
# 248 [ - + ]: 7 : if (!ParseHashStr(hashStr, hash))
# 249 : 0 : return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
# 250 : :
# 251 : 7 : CBlock block;
# 252 : 7 : CBlockIndex* pblockindex = nullptr;
# 253 : 7 : CBlockIndex* tip = nullptr;
# 254 : 7 : {
# 255 : 7 : ChainstateManager& chainman = EnsureAnyChainman(context);
# 256 : 7 : LOCK(cs_main);
# 257 : 7 : tip = chainman.ActiveChain().Tip();
# 258 : 7 : pblockindex = chainman.m_blockman.LookupBlockIndex(hash);
# 259 [ + + ]: 7 : if (!pblockindex) {
# 260 : 1 : return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found");
# 261 : 1 : }
# 262 : :
# 263 [ - + ]: 6 : if (IsBlockPruned(pblockindex))
# 264 : 0 : return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not available (pruned data)");
# 265 : :
# 266 [ - + ]: 6 : if (!ReadBlockFromDisk(block, pblockindex, Params().GetConsensus()))
# 267 : 0 : return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found");
# 268 : 6 : }
# 269 : :
# 270 : 6 : switch (rf) {
# 271 [ + + ]: 1 : case RetFormat::BINARY: {
# 272 : 1 : CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION | RPCSerializationFlags());
# 273 : 1 : ssBlock << block;
# 274 : 1 : std::string binaryBlock = ssBlock.str();
# 275 : 1 : req->WriteHeader("Content-Type", "application/octet-stream");
# 276 : 1 : req->WriteReply(HTTP_OK, binaryBlock);
# 277 : 1 : return true;
# 278 : 0 : }
# 279 : :
# 280 [ + + ]: 1 : case RetFormat::HEX: {
# 281 : 1 : CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION | RPCSerializationFlags());
# 282 : 1 : ssBlock << block;
# 283 : 1 : std::string strHex = HexStr(ssBlock) + "\n";
# 284 : 1 : req->WriteHeader("Content-Type", "text/plain");
# 285 : 1 : req->WriteReply(HTTP_OK, strHex);
# 286 : 1 : return true;
# 287 : 0 : }
# 288 : :
# 289 [ + + ]: 4 : case RetFormat::JSON: {
# 290 : 4 : UniValue objBlock = blockToJSON(block, tip, pblockindex, showTxDetails);
# 291 : 4 : std::string strJSON = objBlock.write() + "\n";
# 292 : 4 : req->WriteHeader("Content-Type", "application/json");
# 293 : 4 : req->WriteReply(HTTP_OK, strJSON);
# 294 : 4 : return true;
# 295 : 0 : }
# 296 : :
# 297 [ - + ]: 0 : default: {
# 298 : 0 : return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
# 299 : 0 : }
# 300 : 6 : }
# 301 : 6 : }
# 302 : :
# 303 : : static bool rest_block_extended(const std::any& context, HTTPRequest* req, const std::string& strURIPart)
# 304 : 6 : {
# 305 : 6 : return rest_block(context, req, strURIPart, true);
# 306 : 6 : }
# 307 : :
# 308 : : static bool rest_block_notxdetails(const std::any& context, HTTPRequest* req, const std::string& strURIPart)
# 309 : 1 : {
# 310 : 1 : return rest_block(context, req, strURIPart, false);
# 311 : 1 : }
# 312 : :
# 313 : : // A bit of a hack - dependency on a function defined in rpc/blockchain.cpp
# 314 : : RPCHelpMan getblockchaininfo();
# 315 : :
# 316 : : static bool rest_chaininfo(const std::any& context, HTTPRequest* req, const std::string& strURIPart)
# 317 : 1 : {
# 318 [ - + ]: 1 : if (!CheckWarmup(req))
# 319 : 0 : return false;
# 320 : 1 : std::string param;
# 321 : 1 : const RetFormat rf = ParseDataFormat(param, strURIPart);
# 322 : :
# 323 : 1 : switch (rf) {
# 324 [ + - ]: 1 : case RetFormat::JSON: {
# 325 : 1 : JSONRPCRequest jsonRequest;
# 326 : 1 : jsonRequest.context = context;
# 327 : 1 : jsonRequest.params = UniValue(UniValue::VARR);
# 328 : 1 : UniValue chainInfoObject = getblockchaininfo().HandleRequest(jsonRequest);
# 329 : 1 : std::string strJSON = chainInfoObject.write() + "\n";
# 330 : 1 : req->WriteHeader("Content-Type", "application/json");
# 331 : 1 : req->WriteReply(HTTP_OK, strJSON);
# 332 : 1 : return true;
# 333 : 0 : }
# 334 [ - + ]: 0 : default: {
# 335 : 0 : return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: json)");
# 336 : 0 : }
# 337 : 1 : }
# 338 : 1 : }
# 339 : :
# 340 : : static bool rest_mempool_info(const std::any& context, HTTPRequest* req, const std::string& strURIPart)
# 341 : 1 : {
# 342 [ - + ]: 1 : if (!CheckWarmup(req))
# 343 : 0 : return false;
# 344 : 1 : const CTxMemPool* mempool = GetMemPool(context, req);
# 345 [ - + ]: 1 : if (!mempool) return false;
# 346 : 1 : std::string param;
# 347 : 1 : const RetFormat rf = ParseDataFormat(param, strURIPart);
# 348 : :
# 349 : 1 : switch (rf) {
# 350 [ + - ]: 1 : case RetFormat::JSON: {
# 351 : 1 : UniValue mempoolInfoObject = MempoolInfoToJSON(*mempool);
# 352 : :
# 353 : 1 : std::string strJSON = mempoolInfoObject.write() + "\n";
# 354 : 1 : req->WriteHeader("Content-Type", "application/json");
# 355 : 1 : req->WriteReply(HTTP_OK, strJSON);
# 356 : 1 : return true;
# 357 : 0 : }
# 358 [ - + ]: 0 : default: {
# 359 : 0 : return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: json)");
# 360 : 0 : }
# 361 : 1 : }
# 362 : 1 : }
# 363 : :
# 364 : : static bool rest_mempool_contents(const std::any& context, HTTPRequest* req, const std::string& strURIPart)
# 365 : 1 : {
# 366 [ - + ]: 1 : if (!CheckWarmup(req)) return false;
# 367 : 1 : const CTxMemPool* mempool = GetMemPool(context, req);
# 368 [ - + ]: 1 : if (!mempool) return false;
# 369 : 1 : std::string param;
# 370 : 1 : const RetFormat rf = ParseDataFormat(param, strURIPart);
# 371 : :
# 372 : 1 : switch (rf) {
# 373 [ + - ]: 1 : case RetFormat::JSON: {
# 374 : 1 : UniValue mempoolObject = MempoolToJSON(*mempool, true);
# 375 : :
# 376 : 1 : std::string strJSON = mempoolObject.write() + "\n";
# 377 : 1 : req->WriteHeader("Content-Type", "application/json");
# 378 : 1 : req->WriteReply(HTTP_OK, strJSON);
# 379 : 1 : return true;
# 380 : 0 : }
# 381 [ - + ]: 0 : default: {
# 382 : 0 : return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: json)");
# 383 : 0 : }
# 384 : 1 : }
# 385 : 1 : }
# 386 : :
# 387 : : static bool rest_tx(const std::any& context, HTTPRequest* req, const std::string& strURIPart)
# 388 : 3 : {
# 389 [ - + ]: 3 : if (!CheckWarmup(req))
# 390 : 0 : return false;
# 391 : 3 : std::string hashStr;
# 392 : 3 : const RetFormat rf = ParseDataFormat(hashStr, strURIPart);
# 393 : :
# 394 : 3 : uint256 hash;
# 395 [ - + ]: 3 : if (!ParseHashStr(hashStr, hash))
# 396 : 0 : return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
# 397 : :
# 398 [ - + ]: 3 : if (g_txindex) {
# 399 : 0 : g_txindex->BlockUntilSyncedToCurrentChain();
# 400 : 0 : }
# 401 : :
# 402 : 3 : const NodeContext* const node = GetNodeContext(context, req);
# 403 [ - + ]: 3 : if (!node) return false;
# 404 : 3 : uint256 hashBlock = uint256();
# 405 : 3 : const CTransactionRef tx = GetTransaction(/* block_index */ nullptr, node->mempool.get(), hash, Params().GetConsensus(), hashBlock);
# 406 [ - + ]: 3 : if (!tx) {
# 407 : 0 : return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found");
# 408 : 0 : }
# 409 : :
# 410 : 3 : switch (rf) {
# 411 [ - + ]: 0 : case RetFormat::BINARY: {
# 412 : 0 : CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION | RPCSerializationFlags());
# 413 : 0 : ssTx << tx;
# 414 : :
# 415 : 0 : std::string binaryTx = ssTx.str();
# 416 : 0 : req->WriteHeader("Content-Type", "application/octet-stream");
# 417 : 0 : req->WriteReply(HTTP_OK, binaryTx);
# 418 : 0 : return true;
# 419 : 0 : }
# 420 : :
# 421 [ + + ]: 1 : case RetFormat::HEX: {
# 422 : 1 : CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION | RPCSerializationFlags());
# 423 : 1 : ssTx << tx;
# 424 : :
# 425 : 1 : std::string strHex = HexStr(ssTx) + "\n";
# 426 : 1 : req->WriteHeader("Content-Type", "text/plain");
# 427 : 1 : req->WriteReply(HTTP_OK, strHex);
# 428 : 1 : return true;
# 429 : 0 : }
# 430 : :
# 431 [ + + ]: 2 : case RetFormat::JSON: {
# 432 : 2 : UniValue objTx(UniValue::VOBJ);
# 433 : 2 : TxToUniv(*tx, hashBlock, objTx);
# 434 : 2 : std::string strJSON = objTx.write() + "\n";
# 435 : 2 : req->WriteHeader("Content-Type", "application/json");
# 436 : 2 : req->WriteReply(HTTP_OK, strJSON);
# 437 : 2 : return true;
# 438 : 0 : }
# 439 : :
# 440 [ - + ]: 0 : default: {
# 441 : 0 : return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
# 442 : 0 : }
# 443 : 3 : }
# 444 : 3 : }
# 445 : :
# 446 : : static bool rest_getutxos(const std::any& context, HTTPRequest* req, const std::string& strURIPart)
# 447 : 15 : {
# 448 [ - + ]: 15 : if (!CheckWarmup(req))
# 449 : 0 : return false;
# 450 : 15 : std::string param;
# 451 : 15 : const RetFormat rf = ParseDataFormat(param, strURIPart);
# 452 : :
# 453 : 15 : std::vector<std::string> uriParts;
# 454 [ + + ]: 15 : if (param.length() > 1)
# 455 : 12 : {
# 456 : 12 : std::string strUriParams = param.substr(1);
# 457 : 12 : boost::split(uriParts, strUriParams, boost::is_any_of("/"));
# 458 : 12 : }
# 459 : :
# 460 : : // throw exception in case of an empty request
# 461 : 15 : std::string strRequestMutable = req->ReadBody();
# 462 [ + + ][ - + ]: 15 : if (strRequestMutable.length() == 0 && uriParts.size() == 0)
# 463 : 0 : return RESTERR(req, HTTP_BAD_REQUEST, "Error: empty request");
# 464 : :
# 465 : 15 : bool fInputParsed = false;
# 466 : 15 : bool fCheckMemPool = false;
# 467 : 15 : std::vector<COutPoint> vOutPoints;
# 468 : :
# 469 : : // parse/deserialize input
# 470 : : // input-format = output-format, rest/getutxos/bin requires binary input, gives binary output, ...
# 471 : :
# 472 [ + + ]: 15 : if (uriParts.size() > 0)
# 473 : 12 : {
# 474 : : //inputs is sent over URI scheme (/rest/getutxos/checkmempool/txid1-n/txid2-n/...)
# 475 [ + + ]: 12 : if (uriParts[0] == "checkmempool") fCheckMemPool = true;
# 476 : :
# 477 [ + + ][ + + ]: 57 : for (size_t i = (fCheckMemPool) ? 1 : 0; i < uriParts.size(); i++)
# 478 : 45 : {
# 479 : 45 : uint256 txid;
# 480 : 45 : int32_t nOutput;
# 481 : 45 : std::string strTxid = uriParts[i].substr(0, uriParts[i].find('-'));
# 482 : 45 : std::string strOutput = uriParts[i].substr(uriParts[i].find('-')+1);
# 483 : :
# 484 [ - + ][ - + ]: 45 : if (!ParseInt32(strOutput, &nOutput) || !IsHex(strTxid))
# 485 : 0 : return RESTERR(req, HTTP_BAD_REQUEST, "Parse error");
# 486 : :
# 487 : 45 : txid.SetHex(strTxid);
# 488 : 45 : vOutPoints.push_back(COutPoint(txid, (uint32_t)nOutput));
# 489 : 45 : }
# 490 : :
# 491 [ + + ]: 12 : if (vOutPoints.size() > 0)
# 492 : 11 : fInputParsed = true;
# 493 : 1 : else
# 494 : 1 : return RESTERR(req, HTTP_BAD_REQUEST, "Error: empty request");
# 495 : 14 : }
# 496 : :
# 497 : 14 : switch (rf) {
# 498 [ - + ]: 0 : case RetFormat::HEX: {
# 499 : : // convert hex to bin, continue then with bin part
# 500 : 0 : std::vector<unsigned char> strRequestV = ParseHex(strRequestMutable);
# 501 : 0 : strRequestMutable.assign(strRequestV.begin(), strRequestV.end());
# 502 : 0 : }
# 503 : :
# 504 [ + + ]: 2 : case RetFormat::BINARY: {
# 505 : 2 : try {
# 506 : : //deserialize only if user sent a request
# 507 [ + - ]: 2 : if (strRequestMutable.size() > 0)
# 508 : 2 : {
# 509 [ - + ]: 2 : if (fInputParsed) //don't allow sending input over URI and HTTP RAW DATA
# 510 : 0 : return RESTERR(req, HTTP_BAD_REQUEST, "Combination of URI scheme inputs and raw post data is not allowed");
# 511 : :
# 512 : 2 : CDataStream oss(SER_NETWORK, PROTOCOL_VERSION);
# 513 : 2 : oss << strRequestMutable;
# 514 : 2 : oss >> fCheckMemPool;
# 515 : 2 : oss >> vOutPoints;
# 516 : 2 : }
# 517 : 2 : } catch (const std::ios_base::failure&) {
# 518 : : // abort in case of unreadable binary data
# 519 : 1 : return RESTERR(req, HTTP_BAD_REQUEST, "Parse error");
# 520 : 1 : }
# 521 : 1 : break;
# 522 : 1 : }
# 523 : :
# 524 [ + + ]: 12 : case RetFormat::JSON: {
# 525 [ + + ]: 12 : if (!fInputParsed)
# 526 : 1 : return RESTERR(req, HTTP_BAD_REQUEST, "Error: empty request");
# 527 : 11 : break;
# 528 : 11 : }
# 529 [ - + ]: 11 : default: {
# 530 : 0 : return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
# 531 : 12 : }
# 532 : 12 : }
# 533 : :
# 534 : : // limit max outpoints
# 535 [ + + ]: 12 : if (vOutPoints.size() > MAX_GETUTXOS_OUTPOINTS)
# 536 : 1 : return RESTERR(req, HTTP_BAD_REQUEST, strprintf("Error: max outpoints exceeded (max: %d, tried: %d)", MAX_GETUTXOS_OUTPOINTS, vOutPoints.size()));
# 537 : :
# 538 : : // check spentness and form a bitmap (as well as a JSON capable human-readable string representation)
# 539 : 11 : std::vector<unsigned char> bitmap;
# 540 : 11 : std::vector<CCoin> outs;
# 541 : 11 : std::string bitmapStringRepresentation;
# 542 : 11 : std::vector<bool> hits;
# 543 : 11 : bitmap.resize((vOutPoints.size() + 7) / 8);
# 544 : 11 : ChainstateManager& chainman = EnsureAnyChainman(context);
# 545 : 11 : {
# 546 : 11 : auto process_utxos = [&vOutPoints, &outs, &hits](const CCoinsView& view, const CTxMemPool& mempool) {
# 547 [ + + ]: 26 : for (const COutPoint& vOutPoint : vOutPoints) {
# 548 : 26 : Coin coin;
# 549 [ + + ][ + + ]: 26 : bool hit = !mempool.isSpent(vOutPoint) && view.GetCoin(vOutPoint, coin);
# 550 : 26 : hits.push_back(hit);
# 551 [ + + ]: 26 : if (hit) outs.emplace_back(std::move(coin));
# 552 : 26 : }
# 553 : 11 : };
# 554 : :
# 555 [ + + ]: 11 : if (fCheckMemPool) {
# 556 : 5 : const CTxMemPool* mempool = GetMemPool(context, req);
# 557 [ - + ]: 5 : if (!mempool) return false;
# 558 : : // use db+mempool as cache backend in case user likes to query mempool
# 559 : 5 : LOCK2(cs_main, mempool->cs);
# 560 : 5 : CCoinsViewCache& viewChain = chainman.ActiveChainstate().CoinsTip();
# 561 : 5 : CCoinsViewMemPool viewMempool(&viewChain, *mempool);
# 562 : 5 : process_utxos(viewMempool, *mempool);
# 563 : 6 : } else {
# 564 : 6 : LOCK(cs_main); // no need to lock mempool!
# 565 : 6 : process_utxos(chainman.ActiveChainstate().CoinsTip(), CTxMemPool());
# 566 : 6 : }
# 567 : :
# 568 [ + + ]: 37 : for (size_t i = 0; i < hits.size(); ++i) {
# 569 : 26 : const bool hit = hits[i];
# 570 [ + + ]: 26 : bitmapStringRepresentation.append(hit ? "1" : "0"); // form a binary string representation (human-readable for json output)
# 571 : 26 : bitmap[i / 8] |= ((uint8_t)hit) << (i % 8);
# 572 : 26 : }
# 573 : 11 : }
# 574 : :
# 575 : 11 : switch (rf) {
# 576 [ + + ]: 1 : case RetFormat::BINARY: {
# 577 : : // serialize data
# 578 : : // use exact same output as mentioned in Bip64
# 579 : 1 : CDataStream ssGetUTXOResponse(SER_NETWORK, PROTOCOL_VERSION);
# 580 : 1 : ssGetUTXOResponse << chainman.ActiveChain().Height() << chainman.ActiveChain().Tip()->GetBlockHash() << bitmap << outs;
# 581 : 1 : std::string ssGetUTXOResponseString = ssGetUTXOResponse.str();
# 582 : :
# 583 : 1 : req->WriteHeader("Content-Type", "application/octet-stream");
# 584 : 1 : req->WriteReply(HTTP_OK, ssGetUTXOResponseString);
# 585 : 1 : return true;
# 586 : 0 : }
# 587 : :
# 588 [ - + ]: 0 : case RetFormat::HEX: {
# 589 : 0 : CDataStream ssGetUTXOResponse(SER_NETWORK, PROTOCOL_VERSION);
# 590 : 0 : ssGetUTXOResponse << chainman.ActiveChain().Height() << chainman.ActiveChain().Tip()->GetBlockHash() << bitmap << outs;
# 591 : 0 : std::string strHex = HexStr(ssGetUTXOResponse) + "\n";
# 592 : :
# 593 : 0 : req->WriteHeader("Content-Type", "text/plain");
# 594 : 0 : req->WriteReply(HTTP_OK, strHex);
# 595 : 0 : return true;
# 596 : 0 : }
# 597 : :
# 598 [ + + ]: 10 : case RetFormat::JSON: {
# 599 : 10 : UniValue objGetUTXOResponse(UniValue::VOBJ);
# 600 : :
# 601 : : // pack in some essentials
# 602 : : // use more or less the same output as mentioned in Bip64
# 603 : 10 : objGetUTXOResponse.pushKV("chainHeight", chainman.ActiveChain().Height());
# 604 : 10 : objGetUTXOResponse.pushKV("chaintipHash", chainman.ActiveChain().Tip()->GetBlockHash().GetHex());
# 605 : 10 : objGetUTXOResponse.pushKV("bitmap", bitmapStringRepresentation);
# 606 : :
# 607 : 10 : UniValue utxos(UniValue::VARR);
# 608 [ + + ]: 10 : for (const CCoin& coin : outs) {
# 609 : 8 : UniValue utxo(UniValue::VOBJ);
# 610 : 8 : utxo.pushKV("height", (int32_t)coin.nHeight);
# 611 : 8 : utxo.pushKV("value", ValueFromAmount(coin.out.nValue));
# 612 : :
# 613 : : // include the script in a json output
# 614 : 8 : UniValue o(UniValue::VOBJ);
# 615 : 8 : ScriptPubKeyToUniv(coin.out.scriptPubKey, o, true);
# 616 : 8 : utxo.pushKV("scriptPubKey", o);
# 617 : 8 : utxos.push_back(utxo);
# 618 : 8 : }
# 619 : 10 : objGetUTXOResponse.pushKV("utxos", utxos);
# 620 : :
# 621 : : // return json string
# 622 : 10 : std::string strJSON = objGetUTXOResponse.write() + "\n";
# 623 : 10 : req->WriteHeader("Content-Type", "application/json");
# 624 : 10 : req->WriteReply(HTTP_OK, strJSON);
# 625 : 10 : return true;
# 626 : 0 : }
# 627 [ - + ]: 0 : default: {
# 628 : 0 : return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
# 629 : 0 : }
# 630 : 11 : }
# 631 : 11 : }
# 632 : :
# 633 : : static bool rest_blockhash_by_height(const std::any& context, HTTPRequest* req,
# 634 : : const std::string& str_uri_part)
# 635 : 7 : {
# 636 [ - + ]: 7 : if (!CheckWarmup(req)) return false;
# 637 : 7 : std::string height_str;
# 638 : 7 : const RetFormat rf = ParseDataFormat(height_str, str_uri_part);
# 639 : :
# 640 : 7 : int32_t blockheight = -1; // Initialization done only to prevent valgrind false positive, see https://github.com/bitcoin/bitcoin/pull/18785
# 641 [ + + ][ + + ]: 7 : if (!ParseInt32(height_str, &blockheight) || blockheight < 0) {
# 642 : 3 : return RESTERR(req, HTTP_BAD_REQUEST, "Invalid height: " + SanitizeString(height_str));
# 643 : 3 : }
# 644 : :
# 645 : 4 : CBlockIndex* pblockindex = nullptr;
# 646 : 4 : {
# 647 : 4 : ChainstateManager& chainman = EnsureAnyChainman(context);
# 648 : 4 : LOCK(cs_main);
# 649 : 4 : const CChain& active_chain = chainman.ActiveChain();
# 650 [ + + ]: 4 : if (blockheight > active_chain.Height()) {
# 651 : 1 : return RESTERR(req, HTTP_NOT_FOUND, "Block height out of range");
# 652 : 1 : }
# 653 : 3 : pblockindex = active_chain[blockheight];
# 654 : 3 : }
# 655 : 3 : switch (rf) {
# 656 [ + + ]: 1 : case RetFormat::BINARY: {
# 657 : 1 : CDataStream ss_blockhash(SER_NETWORK, PROTOCOL_VERSION);
# 658 : 1 : ss_blockhash << pblockindex->GetBlockHash();
# 659 : 1 : req->WriteHeader("Content-Type", "application/octet-stream");
# 660 : 1 : req->WriteReply(HTTP_OK, ss_blockhash.str());
# 661 : 1 : return true;
# 662 : 0 : }
# 663 [ + + ]: 1 : case RetFormat::HEX: {
# 664 : 1 : req->WriteHeader("Content-Type", "text/plain");
# 665 : 1 : req->WriteReply(HTTP_OK, pblockindex->GetBlockHash().GetHex() + "\n");
# 666 : 1 : return true;
# 667 : 0 : }
# 668 [ + + ]: 1 : case RetFormat::JSON: {
# 669 : 1 : req->WriteHeader("Content-Type", "application/json");
# 670 : 1 : UniValue resp = UniValue(UniValue::VOBJ);
# 671 : 1 : resp.pushKV("blockhash", pblockindex->GetBlockHash().GetHex());
# 672 : 1 : req->WriteReply(HTTP_OK, resp.write() + "\n");
# 673 : 1 : return true;
# 674 : 0 : }
# 675 [ - + ]: 0 : default: {
# 676 : 0 : return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")");
# 677 : 0 : }
# 678 : 3 : }
# 679 : 3 : }
# 680 : :
# 681 : : static const struct {
# 682 : : const char* prefix;
# 683 : : bool (*handler)(const std::any& context, HTTPRequest* req, const std::string& strReq);
# 684 : : } uri_prefixes[] = {
# 685 : : {"/rest/tx/", rest_tx},
# 686 : : {"/rest/block/notxdetails/", rest_block_notxdetails},
# 687 : : {"/rest/block/", rest_block_extended},
# 688 : : {"/rest/chaininfo", rest_chaininfo},
# 689 : : {"/rest/mempool/info", rest_mempool_info},
# 690 : : {"/rest/mempool/contents", rest_mempool_contents},
# 691 : : {"/rest/headers/", rest_headers},
# 692 : : {"/rest/getutxos", rest_getutxos},
# 693 : : {"/rest/blockhashbyheight/", rest_blockhash_by_height},
# 694 : : };
# 695 : :
# 696 : : void StartREST(const std::any& context)
# 697 : 1 : {
# 698 [ + + ]: 9 : for (const auto& up : uri_prefixes) {
# 699 : 41 : auto handler = [context, up](HTTPRequest* req, const std::string& prefix) { return up.handler(context, req, prefix); };
# 700 : 9 : RegisterHTTPHandler(up.prefix, false, handler);
# 701 : 9 : }
# 702 : 1 : }
# 703 : :
# 704 : : void InterruptREST()
# 705 : 663 : {
# 706 : 663 : }
# 707 : :
# 708 : : void StopREST()
# 709 : 663 : {
# 710 [ + + ]: 5967 : for (const auto& up : uri_prefixes) {
# 711 : 5967 : UnregisterHTTPHandler(up.prefix, false);
# 712 : 5967 : }
# 713 : 663 : }
|