Skip to content

Commit

Permalink
Merge bitcoin#26415: rpc,rest,zmq: faster getblock, NotifyBlock and r…
Browse files Browse the repository at this point in the history
…est_block by reading raw block

e710cef rest: read raw block in rest_block and deserialize for json (Andrew Toth)
95ce078 rpc: read raw block in getblock and deserialize for verbosity > 0 (Andrew Toth)
0865ab8 test: check more details on zmq raw block response (Andrew Toth)
38265cc zmq: read raw block with ReadRawBlockFromDisk (Andrew Toth)
da338aa blockstorage: check nPos in ReadRawBlockFromDisk before seeking back (Andrew Toth)

Pull request description:

  For the `getblock` endpoint with `verbosity=0`, the  `rest_block` REST endpoint for `bin` and `hex`, and zmq `NotifyBlock` we don't have to deserialize the block since we're just sending the raw data. This PR uses `ReadRawBlockFromDisk` instead of `ReadBlockFromDisk` to serve these requests, and only deserializes for `verbosity > 0` and `json` REST requests. See benchmarks in bitcoin#26684.

  Benchmarked using ApacheBench. Requesting block 750,000 in binary 10k times on a single core (set `-rest=1` in config):
  `ab -n 10000 -c 1 "http://127.0.0.1:8332/rest/block/0000000000000000000592a974b1b9f087cb77628bb4a097d5c2c11b3476a58e.bin"`

  On master, mean time 15ms.
  On this branch, mean time 1ms.

  For RPC
  ```
  echo '{"jsonrpc": "1.0", "id": "curltest", "method": "getblock", "params": ["0000000000000000000592a974b1b9f087cb77628bb4a097d5c2c11b3476a58e", 0]}' > /tmp/data.json
  ab -p /tmp/data.json -n 1000 -c 1 -A user:password "http://127.0.0.1:8332/"
  ```
  On master, mean time 32ms
  On this branch, mean time 13ms

ACKs for top commit:
  achow101:
    re-ACK e710cef

Tree-SHA512: 4cea13c7b563b2139d041b1fdcfdb793c8cc688654ae08db07e7ee6b875c5e582b8185db3ae603abbfb06d2164724f29205774620b48c493726b991999af289e
  • Loading branch information
achow101 committed Mar 12, 2024
2 parents bef9917 + e710cef commit bde3db4
Show file tree
Hide file tree
Showing 9 changed files with 65 additions and 28 deletions.
4 changes: 2 additions & 2 deletions src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1454,9 +1454,9 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)

#if ENABLE_ZMQ
g_zmq_notification_interface = CZMQNotificationInterface::Create(
[&chainman = node.chainman](CBlock& block, const CBlockIndex& index) {
[&chainman = node.chainman](std::vector<uint8_t>& block, const CBlockIndex& index) {
assert(chainman);
return chainman->m_blockman.ReadBlockFromDisk(block, index);
return chainman->m_blockman.ReadRawBlockFromDisk(block, WITH_LOCK(cs_main, return index.GetBlockPos()));
});

if (g_zmq_notification_interface) {
Expand Down
6 changes: 6 additions & 0 deletions src/node/blockstorage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1088,6 +1088,12 @@ bool BlockManager::ReadBlockFromDisk(CBlock& block, const CBlockIndex& index) co
bool BlockManager::ReadRawBlockFromDisk(std::vector<uint8_t>& block, const FlatFilePos& pos) const
{
FlatFilePos hpos = pos;
// If nPos is less than 8 the pos is null and we don't have the block data
// Return early to prevent undefined behavior of unsigned int underflow
if (hpos.nPos < 8) {
LogError("%s: OpenBlockFile failed for %s\n", __func__, pos.ToString());
return false;
}
hpos.nPos -= 8; // Seek back 8 bytes for meta header
AutoFile filein{OpenBlockFile(hpos, true)};
if (filein.IsNull()) {
Expand Down
20 changes: 11 additions & 9 deletions src/rest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <chain.h>
#include <chainparams.h>
#include <core_io.h>
#include <flatfile.h>
#include <httpserver.h>
#include <index/blockfilterindex.h>
#include <index/txindex.h>
Expand All @@ -34,7 +35,7 @@
#include <validation.h>

#include <any>
#include <string>
#include <vector>

#include <univalue.h>

Expand Down Expand Up @@ -295,7 +296,7 @@ static bool rest_block(const std::any& context,
if (!ParseHashStr(hashStr, hash))
return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);

CBlock block;
FlatFilePos pos{};
const CBlockIndex* pblockindex = nullptr;
const CBlockIndex* tip = nullptr;
ChainstateManager* maybe_chainman = GetChainman(context, req);
Expand All @@ -311,32 +312,33 @@ static bool rest_block(const std::any& context,
if (chainman.m_blockman.IsBlockPruned(*pblockindex)) {
return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not available (pruned data)");
}
pos = pblockindex->GetBlockPos();
}

if (!chainman.m_blockman.ReadBlockFromDisk(block, *pblockindex)) {
std::vector<uint8_t> block_data{};
if (!chainman.m_blockman.ReadRawBlockFromDisk(block_data, pos)) {
return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found");
}

switch (rf) {
case RESTResponseFormat::BINARY: {
DataStream ssBlock;
ssBlock << TX_WITH_WITNESS(block);
std::string binaryBlock = ssBlock.str();
const std::string binaryBlock{block_data.begin(), block_data.end()};
req->WriteHeader("Content-Type", "application/octet-stream");
req->WriteReply(HTTP_OK, binaryBlock);
return true;
}

case RESTResponseFormat::HEX: {
DataStream ssBlock;
ssBlock << TX_WITH_WITNESS(block);
std::string strHex = HexStr(ssBlock) + "\n";
const std::string strHex{HexStr(block_data) + "\n"};
req->WriteHeader("Content-Type", "text/plain");
req->WriteReply(HTTP_OK, strHex);
return true;
}

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);
std::string strJSON = objBlock.write() + "\n";
req->WriteHeader("Content-Type", "application/json");
Expand Down
34 changes: 29 additions & 5 deletions src/rpc/blockchain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <core_io.h>
#include <deploymentinfo.h>
#include <deploymentstatus.h>
#include <flatfile.h>
#include <hash.h>
#include <index/blockfilterindex.h>
#include <index/coinstatsindex.h>
Expand Down Expand Up @@ -595,6 +596,28 @@ static CBlock GetBlockChecked(BlockManager& blockman, const CBlockIndex& blockin
return block;
}

static std::vector<uint8_t> GetRawBlockChecked(BlockManager& blockman, const CBlockIndex& blockindex)
{
std::vector<uint8_t> data{};
FlatFilePos pos{};
{
LOCK(cs_main);
if (blockman.IsBlockPruned(blockindex)) {
throw JSONRPCError(RPC_MISC_ERROR, "Block not available (pruned data)");
}
pos = blockindex.GetBlockPos();
}

if (!blockman.ReadRawBlockFromDisk(data, pos)) {
// Block not found on disk. This could be because we have the block
// header in our index but not yet have the block or did not accept the
// block. Or if the block was pruned right after we released the lock above.
throw JSONRPCError(RPC_MISC_ERROR, "Block not found on disk");
}

return data;
}

static CBlockUndo GetUndoChecked(BlockManager& blockman, const CBlockIndex& blockindex)
{
CBlockUndo blockUndo;
Expand Down Expand Up @@ -735,15 +758,16 @@ static RPCHelpMan getblock()
}
}

const CBlock block{GetBlockChecked(chainman.m_blockman, *pblockindex)};
const std::vector<uint8_t> block_data{GetRawBlockChecked(chainman.m_blockman, *pblockindex)};

if (verbosity <= 0) {
DataStream ssBlock;
ssBlock << TX_WITH_WITNESS(block);
std::string strHex = HexStr(ssBlock);
return strHex;
return HexStr(block_data);
}

DataStream block_stream{block_data};
CBlock block{};
block_stream >> TX_WITH_WITNESS(block);

TxVerbosity tx_verbosity;
if (verbosity == 1) {
tx_verbosity = TxVerbosity::SHOW_TXID;
Expand Down
2 changes: 1 addition & 1 deletion src/zmq/zmqnotificationinterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ std::list<const CZMQAbstractNotifier*> CZMQNotificationInterface::GetActiveNotif
return result;
}

std::unique_ptr<CZMQNotificationInterface> CZMQNotificationInterface::Create(std::function<bool(CBlock&, const CBlockIndex&)> get_block_by_index)
std::unique_ptr<CZMQNotificationInterface> CZMQNotificationInterface::Create(std::function<bool(std::vector<uint8_t>&, const CBlockIndex&)> get_block_by_index)
{
std::map<std::string, CZMQNotifierFactory> factories;
factories["pubhashblock"] = CZMQAbstractNotifier::Create<CZMQPublishHashBlockNotifier>;
Expand Down
3 changes: 2 additions & 1 deletion src/zmq/zmqnotificationinterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <functional>
#include <list>
#include <memory>
#include <vector>

class CBlock;
class CBlockIndex;
Expand All @@ -25,7 +26,7 @@ class CZMQNotificationInterface final : public CValidationInterface

std::list<const CZMQAbstractNotifier*> GetActiveNotifiers() const;

static std::unique_ptr<CZMQNotificationInterface> Create(std::function<bool(CBlock&, const CBlockIndex&)> get_block_by_index);
static std::unique_ptr<CZMQNotificationInterface> Create(std::function<bool(std::vector<uint8_t>&, const CBlockIndex&)> get_block_by_index);

protected:
bool Initialize();
Expand Down
7 changes: 2 additions & 5 deletions src/zmq/zmqpublishnotifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -243,16 +243,13 @@ bool CZMQPublishRawBlockNotifier::NotifyBlock(const CBlockIndex *pindex)
{
LogPrint(BCLog::ZMQ, "Publish rawblock %s to %s\n", pindex->GetBlockHash().GetHex(), this->address);

DataStream ss;
CBlock block;
std::vector<uint8_t> block{};
if (!m_get_block_by_index(block, *pindex)) {
zmqError("Can't read block from disk");
return false;
}

ss << TX_WITH_WITNESS(block);

return SendZmqMessage(MSG_RAWBLOCK, &(*ss.begin()), ss.size());
return SendZmqMessage(MSG_RAWBLOCK, block.data(), block.size());
}

bool CZMQPublishRawTransactionNotifier::NotifyTransaction(const CTransaction &transaction)
Expand Down
6 changes: 3 additions & 3 deletions src/zmq/zmqpublishnotifier.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
#include <cstddef>
#include <cstdint>
#include <functional>
#include <vector>

class CBlock;
class CBlockIndex;
class CTransaction;

Expand Down Expand Up @@ -49,10 +49,10 @@ class CZMQPublishHashTransactionNotifier : public CZMQAbstractPublishNotifier
class CZMQPublishRawBlockNotifier : public CZMQAbstractPublishNotifier
{
private:
const std::function<bool(CBlock&, const CBlockIndex&)> m_get_block_by_index;
const std::function<bool(std::vector<uint8_t>&, const CBlockIndex&)> m_get_block_by_index;

public:
CZMQPublishRawBlockNotifier(std::function<bool(CBlock&, const CBlockIndex&)> get_block_by_index)
CZMQPublishRawBlockNotifier(std::function<bool(std::vector<uint8_t>&, const CBlockIndex&)> get_block_by_index)
: m_get_block_by_index{std::move(get_block_by_index)} {}
bool NotifyBlock(const CBlockIndex *pindex) override;
};
Expand Down
11 changes: 9 additions & 2 deletions test/functional/interface_zmq.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"""Test the ZMQ notification interface."""
import struct
from time import sleep
from io import BytesIO

from test_framework.address import (
ADDRESS_BCRT1_P2WSH_OP_TRUE,
Expand All @@ -17,6 +18,7 @@
)
from test_framework.test_framework import BitcoinTestFramework
from test_framework.messages import (
CBlock,
hash256,
tx_from_hex,
)
Expand Down Expand Up @@ -201,8 +203,13 @@ def test_basic(self):
assert_equal(tx.hash, txid.hex())

# Should receive the generated raw block.
block = rawblock.receive()
assert_equal(genhashes[x], hash256_reversed(block[:80]).hex())
hex = rawblock.receive()
block = CBlock()
block.deserialize(BytesIO(hex))
assert block.is_valid()
assert_equal(block.vtx[0].hash, tx.hash)
assert_equal(len(block.vtx), 1)
assert_equal(genhashes[x], hash256_reversed(hex[:80]).hex())

# Should receive the generated block hash.
hash = hashblock.receive().hex()
Expand Down

0 comments on commit bde3db4

Please sign in to comment.