Daemon RPC: getblocks.bin block_ids_exclusive req param
What changed, and why it matters
This commit adds a new optional flag to the Monero daemon's binary RPC for fetching blocks. When enabled, the daemon skips sending the one block the client already knows about, reducing bandwidth. The change is backward-compatible (default is the old behavior) and appears to be a routine protocol optimization, not a security fix.
No immediate security action required. Treat as a normal feature/protocol optimization. If deploying, ensure client implementations understand the new optional flag and validate the prev_id of the first returned block as described in the commit message.
Security signals we found
New optional RPC parameter with safe default (false) preserving prior behavior
Added CHECK_AND_ASSERT_MES(total_height > start_height, ...) invariant
Potential for empty response when start_height reaches total_height under exclusive mode
No input validation changes for block_ids list itself
Evidence from the diff
The patch introduces block_ids_exclusive (also referred to as qblock_ids_exclusive) to getblocks.bin / find_blockchain_supplement. When true, the daemon increments start_height past the highest common block in block_ids, returning only blocks after the split point. It also moves top_hash/total_height computation earlier, adds a total_height > start_height assertion, and allows an empty response when the client is already at the chain tip. The RPC minor version is bumped from 16 to 17. The legacy ZMQ daemon handler passes false explicitly, preserving existing behavior.
Changed components
Daemon RPC (getblocks.bin / COMMAND_RPC_GET_BLOCKS_FAST)Blockchain::find_blockchain_supplementcryptonote_core::core::find_blockchain_supplementZMQ daemon_handlerInspect captured patch +29 / −10
diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp
index f27699f..ee40a56 100644
--- a/src/cryptonote_core/blockchain.cpp
+++ b/src/cryptonote_core/blockchain.cpp
@@ -2736,17 +2736,18 @@ bool Blockchain::find_blockchain_supplement(const std::list<crypto::hash>& qbloc
// find split point between ours and foreign blockchain (or start at
// blockchain height <req_start_block>), and return up to max_count FULL
// blocks by reference.
-bool Blockchain::find_blockchain_supplement(const uint64_t req_start_block, const std::list<crypto::hash>& qblock_ids, std::vector<std::pair<std::pair<cryptonote::blobdata, crypto::hash>, std::vector<std::pair<crypto::hash, cryptonote::blobdata> > > >& blocks, uint64_t& total_height, crypto::hash& top_hash, uint64_t& start_height, bool pruned, bool get_miner_tx_hash, size_t max_block_count, size_t max_tx_count) const
+bool Blockchain::find_blockchain_supplement(const uint64_t req_start_block, const std::list<crypto::hash>& qblock_ids, std::vector<std::pair<std::pair<cryptonote::blobdata, crypto::hash>, std::vector<std::pair<crypto::hash, cryptonote::blobdata> > > >& blocks, uint64_t& total_height, crypto::hash& top_hash, uint64_t& start_height, bool pruned, bool get_miner_tx_hash, const bool qblock_ids_exclusive, size_t max_block_count, size_t max_tx_count) const
{
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
+ top_hash = m_db->top_block_hash(&total_height);
+ ++total_height;
+
// if a specific start height has been requested
if(req_start_block > 0)
{
// if requested height is higher than our chain, return false -- we can't help
- top_hash = m_db->top_block_hash(&total_height);
- ++total_height;
if (req_start_block >= total_height)
{
return false;
@@ -2755,15 +2756,30 @@ bool Blockchain::find_blockchain_supplement(const uint64_t req_start_block, cons
}
else
{
+ // find_blockchain_supplement's start_height is the highest block idx included in qblock_ids that's *also* in the main chain
if(!find_blockchain_supplement(qblock_ids, start_height))
{
return false;
}
+ if (qblock_ids_exclusive)
+ {
+ // start from 1 block higher than the first common block (i.e. from the first block the client might not know about)
+ ++start_height;
+
+ // if start_height is now the chain tip, we can return a truthy empty resp
+ if (start_height == total_height)
+ {
+ LOG_PRINT_L3("Returning empty find_blockchain_supplement, start_height: " << start_height);
+ blocks.clear();
+ return true;
+ }
+ }
}
db_rtxn_guard rtxn_guard(m_db);
top_hash = m_db->top_block_hash(&total_height);
++total_height;
+ CHECK_AND_ASSERT_MES(total_height > start_height, false, "chain height expected to be higher than start block");
blocks.reserve(std::min(std::min(max_block_count, (size_t)10000), (size_t)(total_height - start_height)));
CHECK_AND_ASSERT_MES(m_db->get_blocks_from(start_height, 3, max_block_count, max_tx_count, FIND_BLOCKCHAIN_SUPPLEMENT_MAX_SIZE, blocks, pruned, true, get_miner_tx_hash),
false, "Error getting blocks");
diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h
index 03a0d21..bf19b5c 100644
--- a/src/cryptonote_core/blockchain.h
+++ b/src/cryptonote_core/blockchain.h
@@ -504,12 +504,13 @@ namespace cryptonote
* @param top_hash return-by-reference top block hash
* @param start_height return-by-reference the height of the first block returned
* @param pruned whether to return full or pruned tx blobs
+ * @param qblock_ids_exclusive when using qblock_ids, whether or not to include highest common block in response
* @param max_block_count the max number of blocks to get
* @param max_tx_count the max number of txes to get (it can get overshot by the last block's number of txes minus 1)
*
* @return true if a block found in common or req_start_block specified, else false
*/
- bool find_blockchain_supplement(const uint64_t req_start_block, const std::list<crypto::hash>& qblock_ids, std::vector<std::pair<std::pair<cryptonote::blobdata, crypto::hash>, std::vector<std::pair<crypto::hash, cryptonote::blobdata> > > >& blocks, uint64_t& total_height, crypto::hash& top_hash, uint64_t& start_height, bool pruned, bool get_miner_tx_hash, size_t max_block_count, size_t max_tx_count) const;
+ bool find_blockchain_supplement(const uint64_t req_start_block, const std::list<crypto::hash>& qblock_ids, std::vector<std::pair<std::pair<cryptonote::blobdata, crypto::hash>, std::vector<std::pair<crypto::hash, cryptonote::blobdata> > > >& blocks, uint64_t& total_height, crypto::hash& top_hash, uint64_t& start_height, bool pruned, bool get_miner_tx_hash, const bool qblock_ids_exclusive, size_t max_block_count, size_t max_tx_count) const;
/**
* @brief retrieves a set of blocks and their transactions, and possibly other transactions
diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp
index c460068..80d6258 100644
--- a/src/cryptonote_core/cryptonote_core.cpp
+++ b/src/cryptonote_core/cryptonote_core.cpp
@@ -1225,9 +1225,9 @@ namespace cryptonote
return m_blockchain_storage.find_blockchain_supplement(qblock_ids, clip_pruned, resp);
}
//-----------------------------------------------------------------------------------------------
- bool core::find_blockchain_supplement(const uint64_t req_start_block, const std::list<crypto::hash>& qblock_ids, std::vector<std::pair<std::pair<cryptonote::blobdata, crypto::hash>, std::vector<std::pair<crypto::hash, cryptonote::blobdata> > > >& blocks, uint64_t& total_height, crypto::hash& top_hash, uint64_t& start_height, bool pruned, bool get_miner_tx_hash, size_t max_block_count, size_t max_tx_count) const
+ bool core::find_blockchain_supplement(const uint64_t req_start_block, const std::list<crypto::hash>& qblock_ids, std::vector<std::pair<std::pair<cryptonote::blobdata, crypto::hash>, std::vector<std::pair<crypto::hash, cryptonote::blobdata> > > >& blocks, uint64_t& total_height, crypto::hash& top_hash, uint64_t& start_height, bool pruned, bool get_miner_tx_hash, const bool qblock_ids_exclusive, size_t max_block_count, size_t max_tx_count) const
{
- return m_blockchain_storage.find_blockchain_supplement(req_start_block, qblock_ids, blocks, total_height, top_hash, start_height, pruned, get_miner_tx_hash, max_block_count, max_tx_count);
+ return m_blockchain_storage.find_blockchain_supplement(req_start_block, qblock_ids, blocks, total_height, top_hash, start_height, pruned, get_miner_tx_hash, qblock_ids_exclusive, max_block_count, max_tx_count);
}
//-----------------------------------------------------------------------------------------------
bool core::get_outs(const COMMAND_RPC_GET_OUTPUTS_BIN::request& req, COMMAND_RPC_GET_OUTPUTS_BIN::response& res) const
diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h
index 763b7e1..77d4170 100644
--- a/src/cryptonote_core/cryptonote_core.h
+++ b/src/cryptonote_core/cryptonote_core.h
@@ -593,7 +593,7 @@ namespace cryptonote
*
* @note see Blockchain::find_blockchain_supplement(const uint64_t, const std::list<crypto::hash>&, std::vector<std::pair<cryptonote::blobdata, std::vector<transaction> > >&, uint64_t&, uint64_t&, size_t) const
*/
- bool find_blockchain_supplement(const uint64_t req_start_block, const std::list<crypto::hash>& qblock_ids, std::vector<std::pair<std::pair<cryptonote::blobdata, crypto::hash>, std::vector<std::pair<crypto::hash, cryptonote::blobdata> > > >& blocks, uint64_t& total_height, crypto::hash& top_hash, uint64_t& start_height, bool pruned, bool get_miner_tx_hash, size_t max_block_count, size_t max_tx_count) const;
+ bool find_blockchain_supplement(const uint64_t req_start_block, const std::list<crypto::hash>& qblock_ids, std::vector<std::pair<std::pair<cryptonote::blobdata, crypto::hash>, std::vector<std::pair<crypto::hash, cryptonote::blobdata> > > >& blocks, uint64_t& total_height, crypto::hash& top_hash, uint64_t& start_height, bool pruned, bool get_miner_tx_hash, const bool qblock_ids_exclusive, size_t max_block_count, size_t max_tx_count) const;
/**
* @copydoc Blockchain::get_tx_outputs_gindexs
diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp
index 016a0ce..6737567 100644
--- a/src/rpc/core_rpc_server.cpp
+++ b/src/rpc/core_rpc_server.cpp
@@ -755,7 +755,7 @@ namespace cryptonote
}
std::vector<std::pair<std::pair<cryptonote::blobdata, crypto::hash>, std::vector<std::pair<crypto::hash, cryptonote::blobdata> > > > bs;
- if(!m_core.find_blockchain_supplement(req.start_height, req.block_ids, bs, res.current_height, res.top_block_hash, res.start_height, req.prune, !req.no_miner_tx, max_blocks, COMMAND_RPC_GET_BLOCKS_FAST_MAX_TX_COUNT))
+ if(!m_core.find_blockchain_supplement(req.start_height, req.block_ids, bs, res.current_height, res.top_block_hash, res.start_height, req.prune, !req.no_miner_tx, req.block_ids_exclusive, max_blocks, COMMAND_RPC_GET_BLOCKS_FAST_MAX_TX_COUNT))
{
res.status = "Failed";
add_host_fail(ctx);
diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h
index 1350294..5764614 100644
--- a/src/rpc/core_rpc_server_commands_defs.h
+++ b/src/rpc/core_rpc_server_commands_defs.h
@@ -101,7 +101,7 @@ inline const std::string get_rpc_status(const bool trusted_daemon, const std::st
// advance which version they will stop working with
// Don't go over 32767 for any of these
#define CORE_RPC_VERSION_MAJOR 3
-#define CORE_RPC_VERSION_MINOR 16
+#define CORE_RPC_VERSION_MINOR 17
#define MAKE_CORE_RPC_VERSION(major,minor) (((major)<<16)|(minor))
#define CORE_RPC_VERSION MAKE_CORE_RPC_VERSION(CORE_RPC_VERSION_MAJOR, CORE_RPC_VERSION_MINOR)
@@ -189,6 +189,7 @@ inline const std::string get_rpc_status(const bool trusted_daemon, const std::st
uint64_t start_height;
bool prune;
bool no_miner_tx;
+ bool block_ids_exclusive;
uint64_t pool_info_since;
uint64_t max_block_count;
@@ -199,6 +200,7 @@ inline const std::string get_rpc_status(const bool trusted_daemon, const std::st
KV_SERIALIZE(start_height)
KV_SERIALIZE(prune)
KV_SERIALIZE_OPT(no_miner_tx, false)
+ KV_SERIALIZE_OPT(block_ids_exclusive, false)
KV_SERIALIZE_OPT(pool_info_since, (uint64_t)0)
KV_SERIALIZE_OPT(max_block_count, (uint64_t)0)
END_KV_SERIALIZE_MAP()
diff --git a/src/rpc/daemon_handler.cpp b/src/rpc/daemon_handler.cpp
index 6df7134..dc2811f 100644
--- a/src/rpc/daemon_handler.cpp
+++ b/src/rpc/daemon_handler.cpp
@@ -128,7 +128,7 @@ namespace rpc
{
std::vector<std::pair<std::pair<blobdata, crypto::hash>, std::vector<std::pair<crypto::hash, blobdata> > > > blocks;
- if(!m_core.find_blockchain_supplement(req.start_height, req.block_ids, blocks, res.current_height, res.top_block_hash, res.start_height, req.prune, true, COMMAND_RPC_GET_BLOCKS_FAST_MAX_BLOCK_COUNT, COMMAND_RPC_GET_BLOCKS_FAST_MAX_TX_COUNT))
+ if(!m_core.find_blockchain_supplement(req.start_height, req.block_ids, blocks, res.current_height, res.top_block_hash, res.start_height, req.prune, true, false, COMMAND_RPC_GET_BLOCKS_FAST_MAX_BLOCK_COUNT, COMMAND_RPC_GET_BLOCKS_FAST_MAX_TX_COUNT))
{
res.status = Message::STATUS_FAILED;
res.error_details = "core::find_blockchain_supplement() returned false";
Why this scored 24/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.