Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions src/net_processing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1430,6 +1430,33 @@ bool static AlreadyHaveBlock(const uint256& block_hash) EXCLUSIVE_LOCKS_REQUIRED
return LookupBlockIndex(block_hash) != nullptr;
}

bool FetchBlock(const NodeId nodeid, const CBlockIndex* pindex, CConnman& connman, CTxMemPool& mempool) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
{
PeerRef peer = GetPeerRef(nodeid);
if (peer == nullptr) return false;
uint32_t nFetchFlags = 0;
CNodeState* state = State(nodeid);
if (state == nullptr) {
return false;
}
const int node_sync_height = state->pindexBestKnownBlock ? state->pindexBestKnownBlock->nHeight : -1;
if (pindex->nHeight > node_sync_height) {
return false;
}
if (state->fHaveWitness) {
nFetchFlags |= MSG_WITNESS_FLAG;
}
std::vector<CInv> vInv(1);
vInv[0] = CInv(MSG_BLOCK | nFetchFlags, pindex->GetBlockHash());
bool success = connman.ForNode(nodeid, [&connman, vInv](CNode* pnode) {
const CNetMsgMaker msgMaker(pnode->GetCommonVersion());
connman.PushMessage(pnode, msgMaker.Make(NetMsgType::GETDATA, vInv));
return true;
});
MarkBlockAsInFlight(mempool, nodeid, pindex->GetBlockHash(), pindex);
return success;
}

void RelayTransaction(const uint256& txid, const uint256& wtxid, const CConnman& connman)
{
connman.ForEachNode([&txid, &wtxid](CNode* pnode) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
Expand Down
5 changes: 5 additions & 0 deletions src/net_processing.h
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,11 @@ struct CNodeStateStats {
std::vector<int> vHeightInFlight;
};

/** Attempt to manually fetch block from a given node.
* A refactor of in flight block tracking could remove the need for passing CTxMemPool in
*/
bool FetchBlock(const NodeId nodeid, const CBlockIndex* pindex, CConnman& connman, CTxMemPool& mempool);

/** Get statistics from node state */
bool GetNodeStateStats(NodeId nodeid, CNodeStateStats &stats);

Expand Down
45 changes: 45 additions & 0 deletions src/rpc/blockchain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
#include <core_io.h>
#include <hash.h>
#include <index/blockfilterindex.h>
#include <net.h> // For NodeId
#include <net_processing.h>
#include <node/coinstats.h>
#include <node/context.h>
#include <node/utxo_snapshot.h>
Expand Down Expand Up @@ -753,6 +755,48 @@ static RPCHelpMan getmempoolentry()
};
}

static RPCHelpMan getblockfrompeer()
{
return RPCHelpMan{"getblockfrompeer",
"\nAttempts to fetch block from a given peer.\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::NONE, "", ""},
RPCExamples{
HelpExampleCli("getblockfrompeer", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\" 0")
+ HelpExampleRpc("getblockfrompeer", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\" 0")
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
uint256 hash(ParseHashV(request.params[0], "hash"));

const CBlockIndex* pblockindex;
{
LOCK(cs_main);
pblockindex = LookupBlockIndex(hash);
}

if (!pblockindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
}

const UniValue &id_arg = request.params[1];
const NodeId nodeid = (NodeId) id_arg.get_int64();
const NodeContext& node_context = EnsureNodeContext(request.context);

CTxMemPool& mempool = EnsureMemPool(request.context);
LOCK(mempool.cs);

if (!FetchBlock(nodeid, pblockindex, *node_context.connman, mempool)) {
throw JSONRPCError(RPC_MISC_ERROR, "Failed to fetch block from peer");
}
return NullUniValue;
},
};
}

static RPCHelpMan getblockhash()
{
return RPCHelpMan{"getblockhash",
Expand Down Expand Up @@ -2472,6 +2516,7 @@ static const CRPCCommand commands[] =
{ "blockchain", "getbestblockhash", &getbestblockhash, {} },
{ "blockchain", "getblockcount", &getblockcount, {} },
{ "blockchain", "getblock", &getblock, {"blockhash","verbosity|verbose"} },
{ "blockchain", "getblockfrompeer", &getblockfrompeer, {"blockhash","nodeid"} },
{ "blockchain", "getblockhash", &getblockhash, {"height"} },
{ "blockchain", "getblockheader", &getblockheader, {"blockhash","verbose"} },
{ "blockchain", "getchaintips", &getchaintips, {} },
Expand Down
1 change: 1 addition & 0 deletions src/rpc/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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" },
Expand Down
58 changes: 58 additions & 0 deletions test/functional/rpc_getblockfrompeer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/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.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
)

import time


class GetBlockFromPeerTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 2

def setup_network(self):
self.setup_nodes()

def run_test(self):
self.log.info("Mine 4 blocks on Node 0")
self.nodes[0].generatetoaddress(4, self.nodes[0].get_deterministic_priv_key().address)
assert_equal(self.nodes[0].getblockcount(), 204)

self.log.info("Mine competing 3 blocks on Node 1")
self.nodes[1].generatetoaddress(3, self.nodes[1].get_deterministic_priv_key().address)
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.nodes[0:2])

self.log.info("Node 0 should only have the header for node 1's block 3")
short_tip_synced = False
for x in self.nodes[0].getchaintips():
if x['hash'] == short_tip:
assert_equal(x['status'], "headers-only")
short_tip_synced = True
assert short_tip_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.nodes[0].getblockfrompeer(short_tip, peer_0_peer_1_id)
time.sleep(1)
self.nodes[0].getblock(short_tip)

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)

if __name__ == '__main__':
GetBlockFromPeerTest().main()
1 change: 1 addition & 0 deletions test/functional/test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@
'wallet_txn_clone.py --mineblock',
'feature_notifications.py',
'rpc_getblockfilter.py',
'rpc_getblockfrompeer.py',
'rpc_invalidateblock.py',
'feature_rbf.py',
'mempool_packages.py',
Expand Down