|
3 | 3 | // Distributed under the MIT software license, see the accompanying
|
4 | 4 | // file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
5 | 5 |
|
| 6 | +#include <blockfilter.h> |
6 | 7 | #include <chain.h>
|
7 | 8 | #include <chainparams.h>
|
8 | 9 | #include <core_io.h>
|
9 | 10 | #include <httpserver.h>
|
| 11 | +#include <index/blockfilterindex.h> |
10 | 12 | #include <index/txindex.h>
|
11 | 13 | #include <node/blockstorage.h>
|
12 | 14 | #include <node/context.h>
|
|
31 | 33 | #include <univalue.h>
|
32 | 34 |
|
33 | 35 | static const size_t MAX_GETUTXOS_OUTPOINTS = 15; //allow a max of 15 outpoints to be queried at once
|
| 36 | +static constexpr unsigned int MAX_REST_HEADERS_RESULTS = 2000; |
34 | 37 |
|
35 | 38 | enum class RetFormat {
|
36 | 39 | UNDEF,
|
@@ -191,8 +194,8 @@ static bool rest_headers(const std::any& context,
|
191 | 194 | return RESTERR(req, HTTP_BAD_REQUEST, "No header count specified. Use /rest/headers/<count>/<hash>.<ext>.");
|
192 | 195 |
|
193 | 196 | const auto parsed_count{ToIntegral<size_t>(path[0])};
|
194 |
| - if (!parsed_count.has_value() || *parsed_count < 1 || *parsed_count > 2000) { |
195 |
| - return RESTERR(req, HTTP_BAD_REQUEST, "Header count out of range: " + path[0]); |
| 197 | + if (!parsed_count.has_value() || *parsed_count < 1 || *parsed_count > MAX_REST_HEADERS_RESULTS) { |
| 198 | + return RESTERR(req, HTTP_BAD_REQUEST, strprintf("Header count out of acceptable range (1-%u): %s", MAX_REST_HEADERS_RESULTS, path[0])); |
196 | 199 | }
|
197 | 200 |
|
198 | 201 | std::string hashStr = path[1];
|
@@ -255,7 +258,7 @@ static bool rest_headers(const std::any& context,
|
255 | 258 | return true;
|
256 | 259 | }
|
257 | 260 | default: {
|
258 |
| - return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: .bin, .hex, .json)"); |
| 261 | + return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")"); |
259 | 262 | }
|
260 | 263 | }
|
261 | 264 | }
|
@@ -338,6 +341,210 @@ static bool rest_block_notxdetails(const std::any& context, HTTPRequest* req, co
|
338 | 341 | return rest_block(context, req, strURIPart, TxVerbosity::SHOW_TXID);
|
339 | 342 | }
|
340 | 343 |
|
| 344 | + |
| 345 | +static bool rest_filter_header(const std::any& context, HTTPRequest* req, const std::string& strURIPart) |
| 346 | +{ |
| 347 | + if (!CheckWarmup(req)) |
| 348 | + return false; |
| 349 | + std::string param; |
| 350 | + const RetFormat rf = ParseDataFormat(param, strURIPart); |
| 351 | + |
| 352 | + std::vector<std::string> uri_parts; |
| 353 | + boost::split(uri_parts, param, boost::is_any_of("/")); |
| 354 | + if (uri_parts.size() != 3) { |
| 355 | + return RESTERR(req, HTTP_BAD_REQUEST, "Invalid URI format. Expected /rest/blockfilterheaders/<filtertype>/<count>/<blockhash>"); |
| 356 | + } |
| 357 | + |
| 358 | + uint256 block_hash; |
| 359 | + if (!ParseHashStr(uri_parts[2], block_hash)) { |
| 360 | + return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + uri_parts[2]); |
| 361 | + } |
| 362 | + |
| 363 | + BlockFilterType filtertype; |
| 364 | + if (!BlockFilterTypeByName(uri_parts[0], filtertype)) { |
| 365 | + return RESTERR(req, HTTP_BAD_REQUEST, "Unknown filtertype " + uri_parts[0]); |
| 366 | + } |
| 367 | + |
| 368 | + BlockFilterIndex* index = GetBlockFilterIndex(filtertype); |
| 369 | + if (!index) { |
| 370 | + return RESTERR(req, HTTP_BAD_REQUEST, "Index is not enabled for filtertype " + uri_parts[0]); |
| 371 | + } |
| 372 | + |
| 373 | + const auto parsed_count{ToIntegral<size_t>(uri_parts[1])}; |
| 374 | + if (!parsed_count.has_value() || *parsed_count < 1 || *parsed_count > MAX_REST_HEADERS_RESULTS) { |
| 375 | + return RESTERR(req, HTTP_BAD_REQUEST, strprintf("Header count out of acceptable range (1-%u): %s", MAX_REST_HEADERS_RESULTS, uri_parts[1])); |
| 376 | + } |
| 377 | + |
| 378 | + std::vector<const CBlockIndex *> headers; |
| 379 | + headers.reserve(*parsed_count); |
| 380 | + { |
| 381 | + ChainstateManager* maybe_chainman = GetChainman(context, req); |
| 382 | + if (!maybe_chainman) return false; |
| 383 | + ChainstateManager& chainman = *maybe_chainman; |
| 384 | + LOCK(cs_main); |
| 385 | + CChain& active_chain = chainman.ActiveChain(); |
| 386 | + const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(block_hash); |
| 387 | + while (pindex != nullptr && active_chain.Contains(pindex)) { |
| 388 | + headers.push_back(pindex); |
| 389 | + if (headers.size() == *parsed_count) |
| 390 | + break; |
| 391 | + pindex = active_chain.Next(pindex); |
| 392 | + } |
| 393 | + } |
| 394 | + |
| 395 | + bool index_ready = index->BlockUntilSyncedToCurrentChain(); |
| 396 | + |
| 397 | + std::vector<uint256> filter_headers; |
| 398 | + filter_headers.reserve(*parsed_count); |
| 399 | + for (const CBlockIndex *pindex : headers) { |
| 400 | + uint256 filter_header; |
| 401 | + if (!index->LookupFilterHeader(pindex, filter_header)) { |
| 402 | + std::string errmsg = "Filter not found."; |
| 403 | + |
| 404 | + if (!index_ready) { |
| 405 | + errmsg += " Block filters are still in the process of being indexed."; |
| 406 | + } else { |
| 407 | + errmsg += " This error is unexpected and indicates index corruption."; |
| 408 | + } |
| 409 | + |
| 410 | + return RESTERR(req, HTTP_NOT_FOUND, errmsg); |
| 411 | + } |
| 412 | + filter_headers.push_back(filter_header); |
| 413 | + } |
| 414 | + |
| 415 | + switch (rf) { |
| 416 | + case RetFormat::BINARY: { |
| 417 | + CDataStream ssHeader(SER_NETWORK, PROTOCOL_VERSION | RPCSerializationFlags()); |
| 418 | + for (const uint256& header : filter_headers) { |
| 419 | + ssHeader << header; |
| 420 | + } |
| 421 | + |
| 422 | + std::string binaryHeader = ssHeader.str(); |
| 423 | + req->WriteHeader("Content-Type", "application/octet-stream"); |
| 424 | + req->WriteReply(HTTP_OK, binaryHeader); |
| 425 | + return true; |
| 426 | + } |
| 427 | + case RetFormat::HEX: { |
| 428 | + CDataStream ssHeader(SER_NETWORK, PROTOCOL_VERSION | RPCSerializationFlags()); |
| 429 | + for (const uint256& header : filter_headers) { |
| 430 | + ssHeader << header; |
| 431 | + } |
| 432 | + |
| 433 | + std::string strHex = HexStr(ssHeader) + "\n"; |
| 434 | + req->WriteHeader("Content-Type", "text/plain"); |
| 435 | + req->WriteReply(HTTP_OK, strHex); |
| 436 | + return true; |
| 437 | + } |
| 438 | + case RetFormat::JSON: { |
| 439 | + UniValue jsonHeaders(UniValue::VARR); |
| 440 | + for (const uint256& header : filter_headers) { |
| 441 | + jsonHeaders.push_back(header.GetHex()); |
| 442 | + } |
| 443 | + |
| 444 | + std::string strJSON = jsonHeaders.write() + "\n"; |
| 445 | + req->WriteHeader("Content-Type", "application/json"); |
| 446 | + req->WriteReply(HTTP_OK, strJSON); |
| 447 | + return true; |
| 448 | + } |
| 449 | + default: { |
| 450 | + return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")"); |
| 451 | + } |
| 452 | + } |
| 453 | +} |
| 454 | + |
| 455 | +static bool rest_block_filter(const std::any& context, HTTPRequest* req, const std::string& strURIPart) |
| 456 | +{ |
| 457 | + if (!CheckWarmup(req)) |
| 458 | + return false; |
| 459 | + std::string param; |
| 460 | + const RetFormat rf = ParseDataFormat(param, strURIPart); |
| 461 | + |
| 462 | + //request is sent over URI scheme /rest/blockfilter/filtertype/blockhash |
| 463 | + std::vector<std::string> uri_parts; |
| 464 | + boost::split(uri_parts, param, boost::is_any_of("/")); |
| 465 | + if (uri_parts.size() != 2) { |
| 466 | + return RESTERR(req, HTTP_BAD_REQUEST, "Invalid URI format. Expected /rest/blockfilter/<filtertype>/<blockhash>"); |
| 467 | + } |
| 468 | + |
| 469 | + uint256 block_hash; |
| 470 | + if (!ParseHashStr(uri_parts[1], block_hash)) { |
| 471 | + return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + uri_parts[1]); |
| 472 | + } |
| 473 | + |
| 474 | + BlockFilterType filtertype; |
| 475 | + if (!BlockFilterTypeByName(uri_parts[0], filtertype)) { |
| 476 | + return RESTERR(req, HTTP_BAD_REQUEST, "Unknown filtertype " + uri_parts[0]); |
| 477 | + } |
| 478 | + |
| 479 | + BlockFilterIndex* index = GetBlockFilterIndex(filtertype); |
| 480 | + if (!index) { |
| 481 | + return RESTERR(req, HTTP_BAD_REQUEST, "Index is not enabled for filtertype " + uri_parts[0]); |
| 482 | + } |
| 483 | + |
| 484 | + const CBlockIndex* block_index; |
| 485 | + bool block_was_connected; |
| 486 | + { |
| 487 | + ChainstateManager* maybe_chainman = GetChainman(context, req); |
| 488 | + if (!maybe_chainman) return false; |
| 489 | + ChainstateManager& chainman = *maybe_chainman; |
| 490 | + LOCK(cs_main); |
| 491 | + block_index = chainman.m_blockman.LookupBlockIndex(block_hash); |
| 492 | + if (!block_index) { |
| 493 | + return RESTERR(req, HTTP_NOT_FOUND, uri_parts[1] + " not found"); |
| 494 | + } |
| 495 | + block_was_connected = block_index->IsValid(BLOCK_VALID_SCRIPTS); |
| 496 | + } |
| 497 | + |
| 498 | + bool index_ready = index->BlockUntilSyncedToCurrentChain(); |
| 499 | + |
| 500 | + BlockFilter filter; |
| 501 | + if (!index->LookupFilter(block_index, filter)) { |
| 502 | + std::string errmsg = "Filter not found."; |
| 503 | + |
| 504 | + if (!block_was_connected) { |
| 505 | + errmsg += " Block was not connected to active chain."; |
| 506 | + } else if (!index_ready) { |
| 507 | + errmsg += " Block filters are still in the process of being indexed."; |
| 508 | + } else { |
| 509 | + errmsg += " This error is unexpected and indicates index corruption."; |
| 510 | + } |
| 511 | + |
| 512 | + return RESTERR(req, HTTP_NOT_FOUND, errmsg); |
| 513 | + } |
| 514 | + |
| 515 | + switch (rf) { |
| 516 | + case RetFormat::BINARY: { |
| 517 | + CDataStream ssResp(SER_NETWORK, PROTOCOL_VERSION | RPCSerializationFlags()); |
| 518 | + ssResp << filter; |
| 519 | + |
| 520 | + std::string binaryResp = ssResp.str(); |
| 521 | + req->WriteHeader("Content-Type", "application/octet-stream"); |
| 522 | + req->WriteReply(HTTP_OK, binaryResp); |
| 523 | + return true; |
| 524 | + } |
| 525 | + case RetFormat::HEX: { |
| 526 | + CDataStream ssResp(SER_NETWORK, PROTOCOL_VERSION | RPCSerializationFlags()); |
| 527 | + ssResp << filter; |
| 528 | + |
| 529 | + std::string strHex = HexStr(ssResp) + "\n"; |
| 530 | + req->WriteHeader("Content-Type", "text/plain"); |
| 531 | + req->WriteReply(HTTP_OK, strHex); |
| 532 | + return true; |
| 533 | + } |
| 534 | + case RetFormat::JSON: { |
| 535 | + UniValue ret(UniValue::VOBJ); |
| 536 | + ret.pushKV("filter", HexStr(filter.GetEncodedFilter())); |
| 537 | + std::string strJSON = ret.write() + "\n"; |
| 538 | + req->WriteHeader("Content-Type", "application/json"); |
| 539 | + req->WriteReply(HTTP_OK, strJSON); |
| 540 | + return true; |
| 541 | + } |
| 542 | + default: { |
| 543 | + return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")"); |
| 544 | + } |
| 545 | + } |
| 546 | +} |
| 547 | + |
341 | 548 | // A bit of a hack - dependency on a function defined in rpc/blockchain.cpp
|
342 | 549 | RPCHelpMan getblockchaininfo();
|
343 | 550 |
|
@@ -718,6 +925,8 @@ static const struct {
|
718 | 925 | {"/rest/tx/", rest_tx},
|
719 | 926 | {"/rest/block/notxdetails/", rest_block_notxdetails},
|
720 | 927 | {"/rest/block/", rest_block_extended},
|
| 928 | + {"/rest/blockfilter/", rest_block_filter}, |
| 929 | + {"/rest/blockfilterheaders/", rest_filter_header}, |
721 | 930 | {"/rest/chaininfo", rest_chaininfo},
|
722 | 931 | {"/rest/mempool/info", rest_mempool_info},
|
723 | 932 | {"/rest/mempool/contents", rest_mempool_contents},
|
|
0 commit comments