Skip to content

Commit bde3db4

Browse files
committed
Merge bitcoin#26415: rpc,rest,zmq: faster getblock, NotifyBlock and rest_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
2 parents bef9917 + e710cef commit bde3db4

9 files changed

+65
-28
lines changed

src/init.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1454,9 +1454,9 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
14541454

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

14621462
if (g_zmq_notification_interface) {

src/node/blockstorage.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1088,6 +1088,12 @@ bool BlockManager::ReadBlockFromDisk(CBlock& block, const CBlockIndex& index) co
10881088
bool BlockManager::ReadRawBlockFromDisk(std::vector<uint8_t>& block, const FlatFilePos& pos) const
10891089
{
10901090
FlatFilePos hpos = pos;
1091+
// If nPos is less than 8 the pos is null and we don't have the block data
1092+
// Return early to prevent undefined behavior of unsigned int underflow
1093+
if (hpos.nPos < 8) {
1094+
LogError("%s: OpenBlockFile failed for %s\n", __func__, pos.ToString());
1095+
return false;
1096+
}
10911097
hpos.nPos -= 8; // Seek back 8 bytes for meta header
10921098
AutoFile filein{OpenBlockFile(hpos, true)};
10931099
if (filein.IsNull()) {

src/rest.cpp

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <chain.h>
1414
#include <chainparams.h>
1515
#include <core_io.h>
16+
#include <flatfile.h>
1617
#include <httpserver.h>
1718
#include <index/blockfilterindex.h>
1819
#include <index/txindex.h>
@@ -34,7 +35,7 @@
3435
#include <validation.h>
3536

3637
#include <any>
37-
#include <string>
38+
#include <vector>
3839

3940
#include <univalue.h>
4041

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

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

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

320323
switch (rf) {
321324
case RESTResponseFormat::BINARY: {
322-
DataStream ssBlock;
323-
ssBlock << TX_WITH_WITNESS(block);
324-
std::string binaryBlock = ssBlock.str();
325+
const std::string binaryBlock{block_data.begin(), block_data.end()};
325326
req->WriteHeader("Content-Type", "application/octet-stream");
326327
req->WriteReply(HTTP_OK, binaryBlock);
327328
return true;
328329
}
329330

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

339338
case RESTResponseFormat::JSON: {
339+
CBlock block{};
340+
DataStream block_stream{block_data};
341+
block_stream >> TX_WITH_WITNESS(block);
340342
UniValue objBlock = blockToJSON(chainman.m_blockman, block, *tip, *pblockindex, tx_verbosity);
341343
std::string strJSON = objBlock.write() + "\n";
342344
req->WriteHeader("Content-Type", "application/json");

src/rpc/blockchain.cpp

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <core_io.h>
1818
#include <deploymentinfo.h>
1919
#include <deploymentstatus.h>
20+
#include <flatfile.h>
2021
#include <hash.h>
2122
#include <index/blockfilterindex.h>
2223
#include <index/coinstatsindex.h>
@@ -595,6 +596,28 @@ static CBlock GetBlockChecked(BlockManager& blockman, const CBlockIndex& blockin
595596
return block;
596597
}
597598

599+
static std::vector<uint8_t> GetRawBlockChecked(BlockManager& blockman, const CBlockIndex& blockindex)
600+
{
601+
std::vector<uint8_t> data{};
602+
FlatFilePos pos{};
603+
{
604+
LOCK(cs_main);
605+
if (blockman.IsBlockPruned(blockindex)) {
606+
throw JSONRPCError(RPC_MISC_ERROR, "Block not available (pruned data)");
607+
}
608+
pos = blockindex.GetBlockPos();
609+
}
610+
611+
if (!blockman.ReadRawBlockFromDisk(data, pos)) {
612+
// Block not found on disk. This could be because we have the block
613+
// header in our index but not yet have the block or did not accept the
614+
// block. Or if the block was pruned right after we released the lock above.
615+
throw JSONRPCError(RPC_MISC_ERROR, "Block not found on disk");
616+
}
617+
618+
return data;
619+
}
620+
598621
static CBlockUndo GetUndoChecked(BlockManager& blockman, const CBlockIndex& blockindex)
599622
{
600623
CBlockUndo blockUndo;
@@ -735,15 +758,16 @@ static RPCHelpMan getblock()
735758
}
736759
}
737760

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

740763
if (verbosity <= 0) {
741-
DataStream ssBlock;
742-
ssBlock << TX_WITH_WITNESS(block);
743-
std::string strHex = HexStr(ssBlock);
744-
return strHex;
764+
return HexStr(block_data);
745765
}
746766

767+
DataStream block_stream{block_data};
768+
CBlock block{};
769+
block_stream >> TX_WITH_WITNESS(block);
770+
747771
TxVerbosity tx_verbosity;
748772
if (verbosity == 1) {
749773
tx_verbosity = TxVerbosity::SHOW_TXID;

src/zmq/zmqnotificationinterface.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ std::list<const CZMQAbstractNotifier*> CZMQNotificationInterface::GetActiveNotif
4141
return result;
4242
}
4343

44-
std::unique_ptr<CZMQNotificationInterface> CZMQNotificationInterface::Create(std::function<bool(CBlock&, const CBlockIndex&)> get_block_by_index)
44+
std::unique_ptr<CZMQNotificationInterface> CZMQNotificationInterface::Create(std::function<bool(std::vector<uint8_t>&, const CBlockIndex&)> get_block_by_index)
4545
{
4646
std::map<std::string, CZMQNotifierFactory> factories;
4747
factories["pubhashblock"] = CZMQAbstractNotifier::Create<CZMQPublishHashBlockNotifier>;

src/zmq/zmqnotificationinterface.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <functional>
1313
#include <list>
1414
#include <memory>
15+
#include <vector>
1516

1617
class CBlock;
1718
class CBlockIndex;
@@ -25,7 +26,7 @@ class CZMQNotificationInterface final : public CValidationInterface
2526

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

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

3031
protected:
3132
bool Initialize();

src/zmq/zmqpublishnotifier.cpp

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -243,16 +243,13 @@ bool CZMQPublishRawBlockNotifier::NotifyBlock(const CBlockIndex *pindex)
243243
{
244244
LogPrint(BCLog::ZMQ, "Publish rawblock %s to %s\n", pindex->GetBlockHash().GetHex(), this->address);
245245

246-
DataStream ss;
247-
CBlock block;
246+
std::vector<uint8_t> block{};
248247
if (!m_get_block_by_index(block, *pindex)) {
249248
zmqError("Can't read block from disk");
250249
return false;
251250
}
252251

253-
ss << TX_WITH_WITNESS(block);
254-
255-
return SendZmqMessage(MSG_RAWBLOCK, &(*ss.begin()), ss.size());
252+
return SendZmqMessage(MSG_RAWBLOCK, block.data(), block.size());
256253
}
257254

258255
bool CZMQPublishRawTransactionNotifier::NotifyTransaction(const CTransaction &transaction)

src/zmq/zmqpublishnotifier.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
#include <cstddef>
1111
#include <cstdint>
1212
#include <functional>
13+
#include <vector>
1314

14-
class CBlock;
1515
class CBlockIndex;
1616
class CTransaction;
1717

@@ -49,10 +49,10 @@ class CZMQPublishHashTransactionNotifier : public CZMQAbstractPublishNotifier
4949
class CZMQPublishRawBlockNotifier : public CZMQAbstractPublishNotifier
5050
{
5151
private:
52-
const std::function<bool(CBlock&, const CBlockIndex&)> m_get_block_by_index;
52+
const std::function<bool(std::vector<uint8_t>&, const CBlockIndex&)> m_get_block_by_index;
5353

5454
public:
55-
CZMQPublishRawBlockNotifier(std::function<bool(CBlock&, const CBlockIndex&)> get_block_by_index)
55+
CZMQPublishRawBlockNotifier(std::function<bool(std::vector<uint8_t>&, const CBlockIndex&)> get_block_by_index)
5656
: m_get_block_by_index{std::move(get_block_by_index)} {}
5757
bool NotifyBlock(const CBlockIndex *pindex) override;
5858
};

test/functional/interface_zmq.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"""Test the ZMQ notification interface."""
66
import struct
77
from time import sleep
8+
from io import BytesIO
89

910
from test_framework.address import (
1011
ADDRESS_BCRT1_P2WSH_OP_TRUE,
@@ -17,6 +18,7 @@
1718
)
1819
from test_framework.test_framework import BitcoinTestFramework
1920
from test_framework.messages import (
21+
CBlock,
2022
hash256,
2123
tx_from_hex,
2224
)
@@ -201,8 +203,13 @@ def test_basic(self):
201203
assert_equal(tx.hash, txid.hex())
202204

203205
# Should receive the generated raw block.
204-
block = rawblock.receive()
205-
assert_equal(genhashes[x], hash256_reversed(block[:80]).hex())
206+
hex = rawblock.receive()
207+
block = CBlock()
208+
block.deserialize(BytesIO(hex))
209+
assert block.is_valid()
210+
assert_equal(block.vtx[0].hash, tx.hash)
211+
assert_equal(len(block.vtx), 1)
212+
assert_equal(genhashes[x], hash256_reversed(hex[:80]).hex())
206213

207214
# Should receive the generated block hash.
208215
hash = hashblock.receive().hex()

0 commit comments

Comments
 (0)