From 4a2d6eb3da1b6362b9b30356c39e99f60e336e9b Mon Sep 17 00:00:00 2001 From: j-berman Date: Wed, 1 Oct 2025 16:00:55 -0700 Subject: [PATCH] Daemon RPC: getblocks.bin block_ids_exclusive req param When the request includes block_ids, the daemon uses find_blockchain_supplement to identify the highest block hash passed in block_ids that the daemon also knows about, and then serves subsequent blocks contiguous to that block. When block_ids_skip_exclusive is false (default current behavior), the daemon includes the highest block requested in the response, in addition to contiguous blocks after it. When block_ids_skip_exclusive is true (new param), the daemon serves blocks starting from the block 1 higher than the highest known block included in block_ids. Thus, the daemon skips the common block known to the client and daemon. Clients can make sure the daemon is serving expected contiguous blocks to its highest known block by checking the first block's prev_id included in the response, and making sure it is equivalent to the block the client already knows about that was included in block_ids. This avoids the daemon serving 1 extra block it does not need to serve to the client, since the client should already know about that block. bl --- src/cryptonote_core/blockchain.cpp | 22 +++++++++++++++++++--- src/cryptonote_core/blockchain.h | 3 ++- src/cryptonote_core/cryptonote_core.cpp | 4 ++-- src/cryptonote_core/cryptonote_core.h | 2 +- src/rpc/core_rpc_server.cpp | 2 +- src/rpc/core_rpc_server_commands_defs.h | 4 +++- src/rpc/daemon_handler.cpp | 2 +- 7 files changed, 29 insertions(+), 10 deletions(-) diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index f27699f0d1d..ee40a56b9d5 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& qbloc // find split point between ours and foreign blockchain (or start at // blockchain height ), and return up to max_count FULL // blocks by reference. -bool Blockchain::find_blockchain_supplement(const uint64_t req_start_block, const std::list& qblock_ids, std::vector, std::vector > > >& 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& qblock_ids, std::vector, std::vector > > >& 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 03a0d219629..bf19b5cc830 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& qblock_ids, std::vector, std::vector > > >& 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& qblock_ids, std::vector, std::vector > > >& 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 c460068f49d..80d6258c049 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& qblock_ids, std::vector, std::vector > > >& 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& qblock_ids, std::vector, std::vector > > >& 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 763b7e190fb..77d41700df9 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&, std::vector > >&, uint64_t&, uint64_t&, size_t) const */ - bool find_blockchain_supplement(const uint64_t req_start_block, const std::list& qblock_ids, std::vector, std::vector > > >& 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& qblock_ids, std::vector, std::vector > > >& 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 016a0ce1de1..6737567e55a 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -755,7 +755,7 @@ namespace cryptonote } std::vector, std::vector > > > 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 13502947f55..57646148f38 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 6df71340a6a..dc2811fbb8e 100644 --- a/src/rpc/daemon_handler.cpp +++ b/src/rpc/daemon_handler.cpp @@ -128,7 +128,7 @@ namespace rpc { std::vector, std::vector > > > 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";