Skip to content

Commit

Permalink
RPC: getblockfrompeer, introduce fetch from "any" peer functionality
Browse files Browse the repository at this point in the history
If no 'peer_id' is provided, 'getblockfrompeer' will pick a random
full node to fetch the block from.
  • Loading branch information
furszy committed Jun 18, 2023
1 parent 911a8f3 commit 94e6eea
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 6 deletions.
16 changes: 16 additions & 0 deletions src/net.h
Expand Up @@ -800,6 +800,22 @@ class CConnman
}
};

/**
* Loop over nodes until the provided 'func(node)' return false.
* @param[in] func the stop function.
* @param[in] random_access whether nodes should be accessed in-order or shuffled first.
*/
void ForEachNodeContinueIf(const std::function<bool(CNode*)>& func, bool random_access = false)
{
LOCK(m_nodes_mutex);
std::vector<CNode*> nodes = m_nodes;
if (random_access) Shuffle(nodes.begin(), nodes.end(), FastRandomContext());
for (auto&& node : nodes) {
if (NodeFullyConnected(node))
if (!func(node)) break;
}
};

// Addrman functions
/**
* Return all or many randomly selected addresses, optionally by network.
Expand Down
19 changes: 17 additions & 2 deletions src/net_processing.cpp
Expand Up @@ -514,7 +514,7 @@ class PeerManagerImpl final : public PeerManager
/** Implement PeerManager */
void StartScheduledTasks(CScheduler& scheduler) override;
void CheckForStaleTipAndEvictPeers() override;
util::Result<NodeId> FetchBlock(NodeId peer_id, const CBlockIndex& block_index) override
util::Result<NodeId> FetchBlock(std::optional<NodeId> op_peer_id, const CBlockIndex& block_index) override
EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
bool GetNodeStateStats(NodeId nodeid, CNodeStateStats& stats) const override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
bool IgnoresIncomingTxs() override { return m_ignore_incoming_txs; }
Expand Down Expand Up @@ -1776,7 +1776,7 @@ bool PeerManagerImpl::BlockRequestAllowed(const CBlockIndex* pindex)
(GetBlockProofEquivalentTime(*m_chainman.m_best_header, *pindex, *m_chainman.m_best_header, m_chainparams.GetConsensus()) < STALE_RELAY_AGE_LIMIT);
}

util::Result<NodeId> PeerManagerImpl::FetchBlock(NodeId peer_id, const CBlockIndex& block_index)
util::Result<NodeId> PeerManagerImpl::FetchBlock(std::optional<NodeId> op_peer_id, const CBlockIndex& block_index)
{
if (m_chainman.m_blockman.LoadingBlocks()) return util::ErrorUntranslated("Loading blocks ...");

Expand All @@ -1788,6 +1788,21 @@ util::Result<NodeId> PeerManagerImpl::FetchBlock(NodeId peer_id, const CBlockInd
allow_limited_peers = active_chain.m_chainman.ActiveHeight() - block_index.nHeight < static_cast<int>(NODE_NETWORK_LIMITED_MIN_BLOCKS);
}

NodeId peer_id = -1;
if (op_peer_id) peer_id = *op_peer_id;
else {
// If no peer was provided, pick one at random
m_connman.ForEachNodeContinueIf([this, &peer_id, allow_limited_peers](CNode* node) {
std::shared_ptr<Peer> peer = GetPeerRef(node->GetId());
if (!CanServeBlocks(*peer) || !CanServeWitnesses(*peer)) return true; // keep searching
if (IsLimitedPeer(*peer) && !allow_limited_peers) return true; // keep searching

peer_id = node->GetId();
return false;
}, /*random_access=*/ true);
if (peer_id == -1) return util::ErrorUntranslated("No available peers to fetch the block from");
}

// Ensure this peer exists and hasn't been disconnected
PeerRef peer = GetPeerRef(peer_id);
if (peer == nullptr) return util::ErrorUntranslated("Peer does not exist");
Expand Down
4 changes: 2 additions & 2 deletions src/net_processing.h
Expand Up @@ -52,11 +52,11 @@ class PeerManager : public CValidationInterface, public NetEventsInterface
/**
* Attempt to manually fetch block from a given peer. We must already have the header.
*
* @param[in] peer_id The peer id
* @param[in] op_peer_id The peer id (std::nullopt is equivalent to "any peer")
* @param[in] block_index The blockindex
* @returns the peer id if a request was successfully made, otherwise an error message
*/
virtual util::Result<NodeId> FetchBlock(NodeId peer_id, const CBlockIndex& block_index) = 0;
virtual util::Result<NodeId> FetchBlock(std::optional<NodeId> op_peer_id, const CBlockIndex& block_index) = 0;

/** Begin running background tasks, should only be called once */
virtual void StartScheduledTasks(CScheduler& scheduler) = 0;
Expand Down
5 changes: 3 additions & 2 deletions src/rpc/blockchain.cpp
Expand Up @@ -435,7 +435,8 @@ static RPCHelpMan getblockfrompeer()
"Returns the peer id we are requesting the block from if the request was successfully scheduled.",
{
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The block hash to try to fetch"},
{"peer_id", RPCArg::Type::NUM, RPCArg::Optional::NO, "The peer to fetch it from (see getpeerinfo for peer IDs)"},
{"peer_id", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "The peer to fetch it from (see getpeerinfo for peer IDs). "
"If omitted, the node will fetch the block from any available peer."},
},
RPCResult{
RPCResult::Type::OBJ, "", "",
Expand All @@ -453,7 +454,7 @@ static RPCHelpMan getblockfrompeer()
PeerManager& peerman = EnsurePeerman(node);

const uint256& block_hash{ParseHashV(request.params[0], "blockhash")};
const NodeId peer_id{request.params[1].getInt<int64_t>()};
const std::optional<NodeId> peer_id = request.params[1].isNull() ? std::nullopt : std::make_optional(request.params[1].getInt<int64_t>());

const CBlockIndex* const index = WITH_LOCK(cs_main, return chainman.m_blockman.LookupBlockIndex(block_hash););

Expand Down
18 changes: 18 additions & 0 deletions test/functional/rpc_getblockfrompeer.py
Expand Up @@ -42,6 +42,9 @@ def check_for_block(self, node, hash):
except JSONRPCException:
return False

def get_peer_id(self, peer_info, peer_num):
return next(peer for peer in peer_info if "testnode{}".format(peer_num) in peer['subver'])["id"]

def run_test(self):
self.log.info("Mine 4 blocks on Node 0")
self.generate(self.nodes[0], 4, sync_fun=self.no_op)
Expand Down Expand Up @@ -164,7 +167,22 @@ def run_test(self):
pruned_block_10 = self.nodes[0].getblockhash(10)
assert_raises_rpc_error(-1, "Cannot fetch old block from a limited peer", pruned_node.getblockfrompeer, pruned_block_10, limited_peer_id)

#######################################
# Test fetching block from "any" peer #
#######################################

self.log.info("Fetch block from \"any\" peer")
# Disconnect only connection that can provide the block
self.disconnect_nodes(0, 2)
# Try to fetch the block from "any" peer. Which must fail as the only available peer is a NETWORK_LIMITED peer
assert_raises_rpc_error(-1, "No available peers to fetch the block from", pruned_node.getblockfrompeer, pruned_block_10)

# Now connect the full node and re-try to fetch the block from "any" peer. This time fetching must work.
self.connect_nodes(0, 2)
peer_id_expected = self.get_peer_id(pruned_node.getpeerinfo(), peer_num=0)
peer_id = pruned_node.getblockfrompeer(pruned_block_10)["peer_id"]
assert_equal(peer_id_expected, peer_id)
self.wait_until(lambda: self.check_for_block(node=2, hash=pruned_block_10), timeout=1)


if __name__ == '__main__':
Expand Down

0 comments on commit 94e6eea

Please sign in to comment.