aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/net_processing.cpp36
-rw-r--r--src/net_processing.h10
-rw-r--r--src/rpc/blockchain.cpp55
-rw-r--r--src/rpc/client.cpp1
-rw-r--r--src/test/fuzz/rpc.cpp1
-rw-r--r--src/validation.cpp1
-rwxr-xr-xtest/functional/rpc_getblockfrompeer.py76
-rwxr-xr-xtest/functional/test_runner.py1
8 files changed, 181 insertions, 0 deletions
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index 3f755eb178..866a344dd3 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -312,6 +312,7 @@ public:
/** Implement PeerManager */
void StartScheduledTasks(CScheduler& scheduler) override;
void CheckForStaleTipAndEvictPeers() override;
+ bool FetchBlock(NodeId id, const uint256& hash, const CBlockIndex& index) override;
bool GetNodeStateStats(NodeId nodeid, CNodeStateStats& stats) const override;
bool IgnoresIncomingTxs() override { return m_ignore_incoming_txs; }
void SendPings() override;
@@ -1427,6 +1428,41 @@ bool PeerManagerImpl::BlockRequestAllowed(const CBlockIndex* pindex)
(GetBlockProofEquivalentTime(*pindexBestHeader, *pindex, *pindexBestHeader, m_chainparams.GetConsensus()) < STALE_RELAY_AGE_LIMIT);
}
+bool PeerManagerImpl::FetchBlock(NodeId id, const uint256& hash, const CBlockIndex& 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;
+ // Ignore pre-segwit peers
+ if (!state->fHaveWitness) return false;
+
+ // Mark block as in-flight unless it already is
+ if (!BlockRequested(id, index)) return false;
+
+ // Construct message to request the block
+ std::vector<CInv> invs{CInv(MSG_BLOCK | MSG_WITNESS_FLAG, 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,
CTxMemPool& pool, bool ignore_incoming_txs)
diff --git a/src/net_processing.h b/src/net_processing.h
index 27bc40687a..6c18e8ddfa 100644
--- a/src/net_processing.h
+++ b/src/net_processing.h
@@ -42,6 +42,16 @@ public:
CTxMemPool& pool, bool ignore_incoming_txs);
virtual ~PeerManager() { }
+ /**
+ * Attempt to manually fetch block from a given peer. We must already have the header.
+ *
+ * @param[in] id The peer id
+ * @param[in] hash The block hash
+ * @param[in] pindex The blockindex
+ * @returns Whether a request was successfully made
+ */
+ virtual bool FetchBlock(NodeId id, const uint256& hash, const CBlockIndex& pindex) = 0;
+
/** Begin running background tasks, should only be called once */
virtual void StartScheduledTasks(CScheduler& scheduler) = 0;
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index 6442d30feb..fdbfe55aaf 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -18,6 +18,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>
@@ -755,6 +757,58 @@ static RPCHelpMan getmempoolentry()
};
}
+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"
+ "\nReturns {} if a block-request was successfully scheduled\n",
+ {
+ {"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", "any warnings"}
+ }},
+ RPCExamples{
+ HelpExampleCli("getblockfrompeer", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\" 0")
+ + HelpExampleRpc("getblockfrompeer", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\" 0")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+{
+ const NodeContext& node = EnsureAnyNodeContext(request.context);
+ ChainstateManager& chainman = EnsureChainman(node);
+ PeerManager& peerman = EnsurePeerman(node);
+ CConnman& connman = EnsureConnman(node);
+
+ uint256 hash(ParseHashV(request.params[0], "hash"));
+
+ const NodeId nodeid = static_cast<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 & BLOCK_HAVE_DATA) {
+ result.pushKV("warnings", "Block already downloaded");
+ } else if (!peerman.FetchBlock(nodeid, hash, *index)) {
+ throw JSONRPCError(RPC_MISC_ERROR, "Failed to fetch block from peer");
+ }
+ return result;
+},
+ };
+}
+
static RPCHelpMan getblockhash()
{
return RPCHelpMan{"getblockhash",
@@ -2612,6 +2666,7 @@ static const CRPCCommand commands[] =
{ "blockchain", &getbestblockhash, },
{ "blockchain", &getblockcount, },
{ "blockchain", &getblock, },
+ { "blockchain", &getblockfrompeer, },
{ "blockchain", &getblockhash, },
{ "blockchain", &getblockheader, },
{ "blockchain", &getchaintips, },
diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp
index 90fbd823a4..349efa3bd1 100644
--- a/src/rpc/client.cpp
+++ b/src/rpc/client.cpp
@@ -56,6 +56,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "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/test/fuzz/rpc.cpp b/src/test/fuzz/rpc.cpp
index 251687104e..30518b4d77 100644
--- a/src/test/fuzz/rpc.cpp
+++ b/src/test/fuzz/rpc.cpp
@@ -110,6 +110,7 @@ const std::vector<std::string> RPC_COMMANDS_SAFE_FOR_FUZZING{
"getblockfilter",
"getblockhash",
"getblockheader",
+ "getblockfrompeer", // when no peers are connected, no p2p message is sent
"getblockstats",
"getblocktemplate",
"getchaintips",
diff --git a/src/validation.cpp b/src/validation.cpp
index 6ed5e7a548..33ce3ffcbe 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -3408,6 +3408,7 @@ bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, Block
// 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
// TODO: deal better with return value and error conditions for duplicate
// and unrequested blocks.
diff --git a/test/functional/rpc_getblockfrompeer.py b/test/functional/rpc_getblockfrompeer.py
new file mode 100755
index 0000000000..e841cba70e
--- /dev/null
+++ b/test/functional/rpc_getblockfrompeer.py
@@ -0,0 +1,76 @@
+#!/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
+
+ 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")
+ for x in self.nodes[0].getchaintips():
+ if x['hash'] == short_tip:
+ assert_equal(x['status'], "headers-only")
+ break
+ else:
+ raise AssertionError("short tip not synced")
+ 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(not "warnings" 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()
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index 35cab8f6ec..f92fcdd7aa 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -215,6 +215,7 @@ BASE_SCRIPTS = [
'wallet_txn_clone.py --mineblock',
'feature_notifications.py',
'rpc_getblockfilter.py',
+ 'rpc_getblockfrompeer.py',
'rpc_invalidateblock.py',
'feature_utxo_set_hash.py',
'feature_rbf.py --legacy-wallet',