Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F14362753
D12716.id36876.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
11 KB
Subscribers
None
D12716.id36876.diff
View Options
diff --git a/doc/release-notes.md b/doc/release-notes.md
--- a/doc/release-notes.md
+++ b/doc/release-notes.md
@@ -5,3 +5,6 @@
<https://download.bitcoinabc.org/0.26.8/>
This release includes the following features and fixes:
+ - Add a new RPC `getblockfrompeer` which permits requesting a specific block from
+ a specific peer manually. The intended use is acquisition of stale chaintips
+ for fork monitoring and research purposes.
diff --git a/src/net_processing.h b/src/net_processing.h
--- a/src/net_processing.h
+++ b/src/net_processing.h
@@ -56,6 +56,18 @@
bool ignore_incoming_txs);
virtual ~PeerManager() {}
+ /**
+ * Attempt to manually fetch block from a given peer. We must already have
+ * the header.
+ *
+ * @param[in] config The global config
+ * @param[in] id The peer id
+ * @param[in] pindex The block index
+ * @returns Whether a request was successfully made
+ */
+ virtual bool FetchBlock(const Config &config, NodeId id,
+ const CBlockIndex &block_index) = 0;
+
/** Begin running background tasks, should only be called once */
virtual void StartScheduledTasks(CScheduler &scheduler) = 0;
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -494,6 +494,8 @@
/** Implement PeerManager */
void StartScheduledTasks(CScheduler &scheduler) override;
void CheckForStaleTipAndEvictPeers() override;
+ bool FetchBlock(const Config &config, NodeId id,
+ const CBlockIndex &block_index) override;
bool GetNodeStateStats(NodeId nodeid,
CNodeStateStats &stats) const override;
bool IgnoresIncomingTxs() override { return m_ignore_incoming_txs; }
@@ -2059,6 +2061,48 @@
STALE_RELAY_AGE_LIMIT);
}
+bool PeerManagerImpl::FetchBlock(const Config &config, NodeId id,
+ const CBlockIndex &block_index) {
+ if (fImporting || fReindex) {
+ return false;
+ }
+
+ LOCK(cs_main);
+ // Ensure this peer exists and hasn't been disconnected
+ CNodeState *state = State(id);
+ if (state == nullptr) {
+ return false;
+ }
+ // Mark block as in-flight unless it already is (for this peer).
+ // If a block was already in-flight for a different peer, its BLOCKTXN
+ // response will be dropped.
+ if (!BlockRequested(config, id, block_index)) {
+ return false;
+ }
+
+ // Construct message to request the block
+ const BlockHash &hash{block_index.GetBlockHash()};
+ std::vector<CInv> invs{CInv(MSG_BLOCK, hash)};
+
+ // Send block request message to the peer
+ bool success = m_connman.ForNode(id, [this, &invs](CNode *node) {
+ const CNetMsgMaker msgMaker(node->GetCommonVersion());
+ this->m_connman.PushMessage(node,
+ msgMaker.Make(NetMsgType::GETDATA, invs));
+ return true;
+ });
+
+ if (success) {
+ LogPrint(BCLog::NET, "Requesting block %s from peer=%d\n",
+ hash.ToString(), id);
+ } else {
+ RemoveBlockRequest(hash);
+ LogPrint(BCLog::NET, "Failed to request block %s from peer=%d\n",
+ hash.ToString(), id);
+ }
+ return success;
+}
+
std::unique_ptr<PeerManager>
PeerManager::make(const CChainParams &chainparams, CConnman &connman,
AddrMan &addrman, BanMan *banman, ChainstateManager &chainman,
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -19,6 +19,8 @@
#include <hash.h>
#include <index/blockfilterindex.h>
#include <index/coinstatsindex.h>
+#include <net.h>
+#include <net_processing.h>
#include <node/blockstorage.h>
#include <node/coinstats.h>
#include <node/context.h>
@@ -875,6 +877,69 @@
};
}
+static RPCHelpMan getblockfrompeer() {
+ return RPCHelpMan{
+ "getblockfrompeer",
+ "\nAttempt to fetch block from a given peer.\n"
+ "\nWe must have the header for this block, e.g. using submitheader.\n"
+ "Subsequent calls for the same block and a new peer will cause the "
+ "response from the previous peer to be ignored.\n"
+ "\nReturns an empty JSON object if the request was successfully "
+ "scheduled.",
+ {
+ {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
+ "The block hash"},
+ {"nodeid", RPCArg::Type::NUM, RPCArg::Optional::NO,
+ "The node ID (see getpeerinfo for node IDs)"},
+ },
+ RPCResult{RPCResult::Type::OBJ,
+ "",
+ "",
+ {{RPCResult::Type::STR, "warnings", /*optional=*/true,
+ "any warnings"}}},
+ RPCExamples{HelpExampleCli("getblockfrompeer",
+ "\"00000000c937983704a73af28acdec37b049d214a"
+ "dbda81d7e2a3dd146f6ed09\" 0") +
+ HelpExampleRpc("getblockfrompeer",
+ "\"00000000c937983704a73af28acdec37b049d214a"
+ "dbda81d7e2a3dd146f6ed09\" 0")},
+ [&](const RPCHelpMan &self, const Config &config,
+ const JSONRPCRequest &request) -> UniValue {
+ const NodeContext &node = EnsureAnyNodeContext(request.context);
+ ChainstateManager &chainman = EnsureChainman(node);
+ PeerManager &peerman = EnsurePeerman(node);
+ CConnman &connman = EnsureConnman(node);
+
+ const BlockHash hash{ParseHashV(request.params[0], "hash")};
+ const NodeId nodeid{request.params[1].get_int64()};
+
+ // Check that the peer with nodeid exists
+ if (!connman.ForNode(nodeid, [](CNode *node) { return true; })) {
+ throw JSONRPCError(
+ RPC_MISC_ERROR,
+ strprintf("Peer nodeid %d does not exist", nodeid));
+ }
+
+ const CBlockIndex *const index = WITH_LOCK(
+ cs_main, return chainman.m_blockman.LookupBlockIndex(hash););
+
+ if (!index) {
+ throw JSONRPCError(RPC_MISC_ERROR, "Block header missing");
+ }
+
+ UniValue result = UniValue::VOBJ;
+
+ if (index->nStatus.hasData()) {
+ result.pushKV("warnings", "Block already downloaded");
+ } else if (!peerman.FetchBlock(config, nodeid, *index)) {
+ throw JSONRPCError(RPC_MISC_ERROR,
+ "Failed to fetch block from peer");
+ }
+ return result;
+ },
+ };
+}
+
static RPCHelpMan getblockhash() {
return RPCHelpMan{
"getblockhash",
@@ -3309,6 +3374,7 @@
// ------------------ ----------------------
{ "blockchain", getbestblockhash, },
{ "blockchain", getblock, },
+ { "blockchain", getblockfrompeer, },
{ "blockchain", getblockchaininfo, },
{ "blockchain", getblockcount, },
{ "blockchain", getblockhash, },
diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp
--- a/src/rpc/client.cpp
+++ b/src/rpc/client.cpp
@@ -49,6 +49,7 @@
{"getbalance", 1, "minconf"},
{"getbalance", 2, "include_watchonly"},
{"getbalance", 3, "avoid_reuse"},
+ {"getblockfrompeer", 1, "nodeid"},
{"getblockhash", 0, "height"},
{"waitforblockheight", 0, "height"},
{"waitforblockheight", 1, "timeout"},
diff --git a/src/validation.cpp b/src/validation.cpp
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -4335,6 +4335,7 @@
// This requires some new chain data structure to efficiently look up if a
// block is in a chain leading to a candidate for best tip, despite not
// being such a candidate itself.
+ // Note that this would break the getblockfrompeer RPC
// If we didn't ask for it:
if (!fRequested) {
diff --git a/test/functional/rpc_getblockfrompeer.py b/test/functional/rpc_getblockfrompeer.py
new file mode 100755
--- /dev/null
+++ b/test/functional/rpc_getblockfrompeer.py
@@ -0,0 +1,91 @@
+#!/usr/bin/env python3
+# Copyright (c) 2020 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+"""Test the getblockfrompeer RPC."""
+
+from test_framework.authproxy import JSONRPCException
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import assert_equal, assert_raises_rpc_error
+
+
+class GetBlockFromPeerTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.num_nodes = 2
+ self.extra_args = [[], ["-noparkdeepreorg"]]
+
+ def setup_network(self):
+ self.setup_nodes()
+
+ def check_for_block(self, hash):
+ try:
+ self.nodes[0].getblock(hash)
+ return True
+ except JSONRPCException:
+ return False
+
+ def run_test(self):
+ self.log.info("Mine 4 blocks on Node 0")
+ self.generate(self.nodes[0], 4, sync_fun=self.no_op)
+ assert_equal(self.nodes[0].getblockcount(), 204)
+
+ self.log.info("Mine competing 3 blocks on Node 1")
+ self.generate(self.nodes[1], 3, sync_fun=self.no_op)
+ assert_equal(self.nodes[1].getblockcount(), 203)
+ short_tip = self.nodes[1].getbestblockhash()
+
+ self.log.info("Connect nodes to sync headers")
+ self.connect_nodes(0, 1)
+ self.sync_blocks()
+
+ self.log.info(
+ "Node 0 should only have the header for node 1's block 3")
+ x = next(
+ filter(
+ lambda x: x['hash'] == short_tip,
+ self.nodes[0].getchaintips()))
+ assert_equal(x['status'], "headers-only")
+ assert_raises_rpc_error(-1,
+ "Block not found on disk",
+ self.nodes[0].getblock,
+ short_tip)
+
+ self.log.info("Fetch block from node 1")
+ peers = self.nodes[0].getpeerinfo()
+ assert_equal(len(peers), 1)
+ peer_0_peer_1_id = peers[0]["id"]
+
+ self.log.info("Arguments must be sensible")
+ assert_raises_rpc_error(-8,
+ "hash must be of length 64 (not 4, for '1234')",
+ self.nodes[0].getblockfrompeer,
+ "1234",
+ 0)
+
+ self.log.info("We must already have the header")
+ assert_raises_rpc_error(-1,
+ "Block header missing",
+ self.nodes[0].getblockfrompeer,
+ "00" * 32,
+ 0)
+
+ self.log.info("Non-existent peer generates error")
+ assert_raises_rpc_error(-1,
+ f"Peer nodeid {peer_0_peer_1_id + 1} does not exist",
+ self.nodes[0].getblockfrompeer,
+ short_tip,
+ peer_0_peer_1_id + 1)
+
+ self.log.info("Successful fetch")
+ result = self.nodes[0].getblockfrompeer(short_tip, peer_0_peer_1_id)
+ self.wait_until(lambda: self.check_for_block(short_tip), timeout=1)
+ assert "warnings" not in result
+
+ self.log.info("Don't fetch blocks we already have")
+ result = self.nodes[0].getblockfrompeer(short_tip, peer_0_peer_1_id)
+ assert "warnings" in result
+ assert_equal(result["warnings"], "Block already downloaded")
+
+
+if __name__ == '__main__':
+ GetBlockFromPeerTest().main()
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Mon, May 12, 01:45 (1 h, 52 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5777063
Default Alt Text
D12716.id36876.diff (11 KB)
Attached To
D12716: rpc: getblockfrompeer
Event Timeline
Log In to Comment