rest: allow reading partial block data from storage

It will allow fetching specific transactions using an external index,
following https://github.com/bitcoin/bitcoin/pull/32541#issuecomment-3267485313.

Co-authored-by: Hodlinator <172445034+hodlinator@users.noreply.github.com>
Co-authored-by: Lőrinc <pap.lorinc@gmail.com>
This commit is contained in:
Roman Zeyde
2025-12-06 15:08:57 +01:00
parent 4e2af1c065
commit 07135290c1
4 changed files with 106 additions and 16 deletions

View File

@@ -379,10 +379,17 @@ static bool rest_spent_txouts(const std::any& context, HTTPRequest* req, const s
}
}
/**
* This handler is used by multiple HTTP endpoints:
* - `/block/` via `rest_block_extended()`
* - `/block/notxdetails/` via `rest_block_notxdetails()`
* - `/blockpart/` via `rest_block_part()` (doesn't support JSON response, so `tx_verbosity` is unset)
*/
static bool rest_block(const std::any& context,
HTTPRequest* req,
const std::string& uri_part,
TxVerbosity tx_verbosity)
std::optional<TxVerbosity> tx_verbosity,
std::optional<std::pair<size_t, size_t>> block_part = std::nullopt)
{
if (!CheckWarmup(req))
return false;
@@ -416,12 +423,14 @@ static bool rest_block(const std::any& context,
pos = pblockindex->GetBlockPos();
}
const auto block_data{chainman.m_blockman.ReadRawBlock(pos)};
const auto block_data{chainman.m_blockman.ReadRawBlock(pos, block_part)};
if (!block_data) {
switch (block_data.error()) {
case node::ReadRawError::IO: return RESTERR(req, HTTP_INTERNAL_SERVER_ERROR, "I/O error reading " + hashStr);
case node::ReadRawError::BadPartRange: break; // can happen only when reading a block part
}
case node::ReadRawError::BadPartRange:
assert(block_part);
return RESTERR(req, HTTP_BAD_REQUEST, strprintf("Bad block part offset/size %d/%d for %s", block_part->first, block_part->second, hashStr));
} // no default case, so the compiler can warn about missing cases
assert(false);
}
@@ -440,14 +449,17 @@ static bool rest_block(const std::any& context,
}
case RESTResponseFormat::JSON: {
CBlock block{};
DataStream block_stream{*block_data};
block_stream >> TX_WITH_WITNESS(block);
UniValue objBlock = blockToJSON(chainman.m_blockman, block, *tip, *pblockindex, tx_verbosity, chainman.GetConsensus().powLimit);
std::string strJSON = objBlock.write() + "\n";
req->WriteHeader("Content-Type", "application/json");
req->WriteReply(HTTP_OK, strJSON);
return true;
if (tx_verbosity) {
CBlock block{};
DataStream block_stream{*block_data};
block_stream >> TX_WITH_WITNESS(block);
UniValue objBlock = blockToJSON(chainman.m_blockman, block, *tip, *pblockindex, *tx_verbosity, chainman.GetConsensus().powLimit);
std::string strJSON = objBlock.write() + "\n";
req->WriteHeader("Content-Type", "application/json");
req->WriteReply(HTTP_OK, strJSON);
return true;
}
return RESTERR(req, HTTP_BAD_REQUEST, "JSON output is not supported for this request type");
}
default: {
@@ -466,6 +478,25 @@ static bool rest_block_notxdetails(const std::any& context, HTTPRequest* req, co
return rest_block(context, req, uri_part, TxVerbosity::SHOW_TXID);
}
static bool rest_block_part(const std::any& context, HTTPRequest* req, const std::string& uri_part)
{
try {
if (const auto opt_offset{ToIntegral<size_t>(req->GetQueryParameter("offset").value_or(""))}) {
if (const auto opt_size{ToIntegral<size_t>(req->GetQueryParameter("size").value_or(""))}) {
return rest_block(context, req, uri_part,
/*tx_verbosity=*/std::nullopt,
/*block_part=*/{{*opt_offset, *opt_size}});
} else {
return RESTERR(req, HTTP_BAD_REQUEST, "Block part size missing or invalid");
}
} else {
return RESTERR(req, HTTP_BAD_REQUEST, "Block part offset missing or invalid");
}
} catch (const std::runtime_error& e) {
return RESTERR(req, HTTP_BAD_REQUEST, e.what());
}
}
static bool rest_filter_header(const std::any& context, HTTPRequest* req, const std::string& uri_part)
{
if (!CheckWarmup(req)) return false;
@@ -1114,6 +1145,7 @@ static const struct {
{"/rest/tx/", rest_tx},
{"/rest/block/notxdetails/", rest_block_notxdetails},
{"/rest/block/", rest_block_extended},
{"/rest/blockpart/", rest_block_part},
{"/rest/blockfilter/", rest_block_filter},
{"/rest/blockfilterheaders/", rest_filter_header},
{"/rest/chaininfo", rest_chaininfo},