aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGreg Sanders <gsanders87@gmail.com>2023-05-16 15:36:38 -0400
committerGreg Sanders <gsanders87@gmail.com>2023-05-23 13:07:49 -0400
commit03423f8bd12b95a06a4a9d8377e781625dd38aae (patch)
treef8ab6335e73a791de8791c36d917116c7b735e62 /src
parent13f9b20b4cb2f3f26e81184a77e9cf1f626d4f57 (diff)
Support up to 3 parallel compact block txn fetchings
A single outbound slot is required, so if the first two slots are taken by inbound in-flights, the node will reject additional unless they are coming from outbound. This means in the case where a fast sybil peer is attempting to stall out a node, a single high bandwidth outbound peer can mitigate the attack.
Diffstat (limited to 'src')
-rw-r--r--src/net.h2
-rw-r--r--src/net_processing.cpp122
-rw-r--r--src/net_processing.h2
-rw-r--r--src/rpc/blockchain.cpp2
4 files changed, 90 insertions, 38 deletions
diff --git a/src/net.h b/src/net.h
index 908b16f35e..83fe0427d4 100644
--- a/src/net.h
+++ b/src/net.h
@@ -200,7 +200,9 @@ public:
int nVersion;
std::string cleanSubVer;
bool fInbound;
+ // We requested high bandwidth connection to peer
bool m_bip152_highbandwidth_to;
+ // Peer requested high bandwidth connection
bool m_bip152_highbandwidth_from;
int m_starting_height;
uint64_t nSendBytes;
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index 78ff9c8cb9..f08e771f63 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -876,6 +876,9 @@ private:
/** Have we requested this block from a peer */
bool IsBlockRequested(const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
+ /** Have we requested this block from an outbound peer */
+ bool IsBlockRequestedFromOutbound(const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
+
/** Remove this block from our tracked requested blocks. Called if:
* - the block has been received from a peer
* - the request for the block has timed out
@@ -1121,6 +1124,17 @@ bool PeerManagerImpl::IsBlockRequested(const uint256& hash)
return mapBlocksInFlight.count(hash);
}
+bool PeerManagerImpl::IsBlockRequestedFromOutbound(const uint256& hash)
+{
+ for (auto range = mapBlocksInFlight.equal_range(hash); range.first != range.second; range.first++) {
+ auto [nodeid, block_it] = range.first->second;
+ CNodeState& nodestate = *Assert(State(nodeid));
+ if (!nodestate.m_is_inbound) return true;
+ }
+
+ return false;
+}
+
void PeerManagerImpl::RemoveBlockRequest(const uint256& hash, std::optional<NodeId> from_peer)
{
auto range = mapBlocksInFlight.equal_range(hash);
@@ -1129,8 +1143,8 @@ void PeerManagerImpl::RemoveBlockRequest(const uint256& hash, std::optional<Node
return;
}
- // Currently we don't request more than one peer for same block
- Assume(mapBlocksInFlight.count(hash) == 1);
+ // We should not have requested too many of this block
+ Assume(mapBlocksInFlight.count(hash) <= MAX_CMPCTBLOCKS_INFLIGHT_PER_BLOCK);
while (range.first != range.second) {
auto [node_id, list_it] = range.first->second;
@@ -1140,20 +1154,19 @@ void PeerManagerImpl::RemoveBlockRequest(const uint256& hash, std::optional<Node
continue;
}
- CNodeState *state = State(node_id);
- assert(state != nullptr);
+ CNodeState& state = *Assert(State(node_id));
- if (state->vBlocksInFlight.begin() == list_it) {
+ if (state.vBlocksInFlight.begin() == list_it) {
// First block on the queue was received, update the start download time for the next one
- state->m_downloading_since = std::max(state->m_downloading_since, GetTime<std::chrono::microseconds>());
+ state.m_downloading_since = std::max(state.m_downloading_since, GetTime<std::chrono::microseconds>());
}
- state->vBlocksInFlight.erase(list_it);
+ state.vBlocksInFlight.erase(list_it);
- if (state->vBlocksInFlight.empty()) {
+ if (state.vBlocksInFlight.empty()) {
// Last validated block on the queue for this peer was received.
m_peers_downloading_from--;
}
- state->m_stalling_since = 0us;
+ state.m_stalling_since = 0us;
range.first = mapBlocksInFlight.erase(range.first);
}
@@ -1166,6 +1179,8 @@ bool PeerManagerImpl::BlockRequested(NodeId nodeid, const CBlockIndex& block, st
CNodeState *state = State(nodeid);
assert(state != nullptr);
+ Assume(mapBlocksInFlight.count(hash) <= MAX_CMPCTBLOCKS_INFLIGHT_PER_BLOCK);
+
// Short-circuit most stuff in case it is from the same node
for (auto range = mapBlocksInFlight.equal_range(hash); range.first != range.second; range.first++) {
if (range.first->second.first == nodeid) {
@@ -1176,8 +1191,8 @@ bool PeerManagerImpl::BlockRequested(NodeId nodeid, const CBlockIndex& block, st
}
}
- // Make sure it's not listed somewhere already.
- RemoveBlockRequest(hash, std::nullopt);
+ // Make sure it's not being fetched already from same peer.
+ RemoveBlockRequest(hash, nodeid);
std::list<QueuedBlock>::iterator it = state->vBlocksInFlight.insert(state->vBlocksInFlight.end(),
{&block, std::unique_ptr<PartiallyDownloadedBlock>(pit ? new PartiallyDownloadedBlock(&m_mempool) : nullptr)});
@@ -1774,11 +1789,10 @@ std::optional<std::string> PeerManagerImpl::FetchBlock(NodeId peer_id, const CBl
LOCK(cs_main);
- // Mark block as in-flight unless it already is (for this peer).
- // If the peer does not send us a block, vBlocksInFlight remains non-empty,
- // causing us to timeout and disconnect.
- // If a block was already in-flight for a different peer, its BLOCKTXN
- // response will be dropped.
+ // Forget about all prior requests
+ RemoveBlockRequest(block_index.GetBlockHash(), std::nullopt);
+
+ // Mark block as in-flight
if (!BlockRequested(peer_id, block_index)) return "Already requested from this peer";
// Construct message to request the block
@@ -4292,12 +4306,15 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
return;
auto range_flight = mapBlocksInFlight.equal_range(pindex->GetBlockHash());
- bool fAlreadyInFlight = range_flight.first != range_flight.second;
- bool in_flight_same_peer{false};
+ size_t already_in_flight = std::distance(range_flight.first, range_flight.second);
+ bool requested_block_from_this_peer{false};
+
+ // Multimap ensures ordering of outstanding requests. It's either empty or first in line.
+ bool first_in_flight = already_in_flight == 0 || (range_flight.first->second.first == pfrom.GetId());
while (range_flight.first != range_flight.second) {
if (range_flight.first->second.first == pfrom.GetId()) {
- in_flight_same_peer = true;
+ requested_block_from_this_peer = true;
break;
}
range_flight.first++;
@@ -4305,7 +4322,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
if (pindex->nChainWork <= m_chainman.ActiveChain().Tip()->nChainWork || // We know something better
pindex->nTx != 0) { // We had this block at some point, but pruned it
- if (in_flight_same_peer) {
+ if (requested_block_from_this_peer) {
// We requested this block for some reason, but our mempool will probably be useless
// so we just grab the block via normal getdata
std::vector<CInv> vInv(1);
@@ -4316,15 +4333,15 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
}
// If we're not close to tip yet, give up and let parallel block fetch work its magic
- if (!fAlreadyInFlight && !CanDirectFetch()) {
+ if (!already_in_flight && !CanDirectFetch()) {
return;
}
// We want to be a bit conservative just to be extra careful about DoS
// possibilities in compact block processing...
if (pindex->nHeight <= m_chainman.ActiveChain().Height() + 2) {
- if ((!fAlreadyInFlight && nodestate->vBlocksInFlight.size() < MAX_BLOCKS_IN_TRANSIT_PER_PEER) ||
- in_flight_same_peer) {
+ if ((already_in_flight < MAX_CMPCTBLOCKS_INFLIGHT_PER_BLOCK && nodestate->vBlocksInFlight.size() < MAX_BLOCKS_IN_TRANSIT_PER_PEER) ||
+ requested_block_from_this_peer) {
std::list<QueuedBlock>::iterator* queuedBlockIt = nullptr;
if (!BlockRequested(pfrom.GetId(), *pindex, &queuedBlockIt)) {
if (!(*queuedBlockIt)->partialBlock)
@@ -4343,11 +4360,16 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
Misbehaving(*peer, 100, "invalid compact block");
return;
} else if (status == READ_STATUS_FAILED) {
- // Duplicate txindexes, the block is now in-flight, so just request it
- std::vector<CInv> vInv(1);
- vInv[0] = CInv(MSG_BLOCK | GetFetchFlags(*peer), blockhash);
- m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETDATA, vInv));
- return;
+ if (first_in_flight) {
+ // Duplicate txindexes, the block is now in-flight, so just request it
+ std::vector<CInv> vInv(1);
+ vInv[0] = CInv(MSG_BLOCK | GetFetchFlags(*peer), blockhash);
+ m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETDATA, vInv));
+ return;
+ } else {
+ // Give up for this peer and wait for other peer(s)
+ RemoveBlockRequest(pindex->GetBlockHash(), pfrom.GetId());
+ }
}
BlockTransactionsRequest req;
@@ -4361,9 +4383,24 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
txn.blockhash = blockhash;
blockTxnMsg << txn;
fProcessBLOCKTXN = true;
- } else {
+ } else if (first_in_flight) {
+ // We will try to round-trip any compact blocks we get on failure,
+ // as long as it's first...
+ req.blockhash = pindex->GetBlockHash();
+ m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETBLOCKTXN, req));
+ } else if (pfrom.m_bip152_highbandwidth_to &&
+ (!pfrom.IsInboundConn() ||
+ IsBlockRequestedFromOutbound(blockhash) ||
+ already_in_flight < MAX_CMPCTBLOCKS_INFLIGHT_PER_BLOCK - 1)) {
+ // ... or it's a hb relay peer and:
+ // - peer is outbound, or
+ // - we already have an outbound attempt in flight(so we'll take what we can get), or
+ // - it's not the final parallel download slot (which we may reserve for first outbound)
req.blockhash = pindex->GetBlockHash();
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETBLOCKTXN, req));
+ } else {
+ // Give up for this peer and wait for other peer(s)
+ RemoveBlockRequest(pindex->GetBlockHash(), pfrom.GetId());
}
} else {
// This block is either already in flight from a different
@@ -4384,7 +4421,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
}
}
} else {
- if (in_flight_same_peer) {
+ if (requested_block_from_this_peer) {
// We requested this block, but its far into the future, so our
// mempool will probably be useless - request the block normally
std::vector<CInv> vInv(1);
@@ -4456,18 +4493,23 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
{
LOCK(cs_main);
- bool expected_blocktxn = false;
auto range_flight = mapBlocksInFlight.equal_range(resp.blockhash);
+ size_t already_in_flight = std::distance(range_flight.first, range_flight.second);
+ bool requested_block_from_this_peer{false};
+
+ // Multimap ensures ordering of outstanding requests. It's either empty or first in line.
+ bool first_in_flight = already_in_flight == 0 || (range_flight.first->second.first == pfrom.GetId());
+
while (range_flight.first != range_flight.second) {
auto [node_id, block_it] = range_flight.first->second;
if (node_id == pfrom.GetId() && block_it->partialBlock) {
- expected_blocktxn = true;
+ requested_block_from_this_peer = true;
break;
}
range_flight.first++;
}
- if (!expected_blocktxn) {
+ if (!requested_block_from_this_peer) {
LogPrint(BCLog::NET, "Peer %d sent us block transactions for block we weren't expecting\n", pfrom.GetId());
return;
}
@@ -4479,10 +4521,16 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
Misbehaving(*peer, 100, "invalid compact block/non-matching block transactions");
return;
} else if (status == READ_STATUS_FAILED) {
- // Might have collided, fall back to getdata now :(
- std::vector<CInv> invs;
- invs.push_back(CInv(MSG_BLOCK | GetFetchFlags(*peer), resp.blockhash));
- m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETDATA, invs));
+ if (first_in_flight) {
+ // Might have collided, fall back to getdata now :(
+ std::vector<CInv> invs;
+ invs.push_back(CInv(MSG_BLOCK | GetFetchFlags(*peer), resp.blockhash));
+ m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETDATA, invs));
+ } else {
+ RemoveBlockRequest(resp.blockhash, pfrom.GetId());
+ LogPrint(BCLog::NET, "Peer %d sent us a compact block but it failed to reconstruct, waiting on first download to complete\n", pfrom.GetId());
+ return;
+ }
} else {
// Block is either okay, or possibly we received
// READ_STATUS_CHECKBLOCK_FAILED.
diff --git a/src/net_processing.h b/src/net_processing.h
index af9a02139b..deebb24c94 100644
--- a/src/net_processing.h
+++ b/src/net_processing.h
@@ -22,6 +22,8 @@ static const bool DEFAULT_PEERBLOOMFILTERS = false;
static const bool DEFAULT_PEERBLOCKFILTERS = false;
/** Threshold for marking a node to be discouraged, e.g. disconnected and added to the discouragement filter. */
static const int DISCOURAGEMENT_THRESHOLD{100};
+/** Maximum number of outstanding CMPCTBLOCK requests for the same block. */
+static const unsigned int MAX_CMPCTBLOCKS_INFLIGHT_PER_BLOCK = 3;
struct CNodeStateStats {
int nSyncHeight = -1;
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index 72866532d2..4c25dbc345 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -428,7 +428,7 @@ static RPCHelpMan getblockfrompeer()
"getblockfrompeer",
"Attempt to fetch block from a given peer.\n\n"
"We 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"
+ "Subsequent calls for the same block may cause the response from the previous peer to be ignored.\n"
"Peers generally ignore requests for a stale block that they never fully verified, or one that is more than a month old.\n"
"When a peer does not respond with a block, we will disconnect.\n"
"Note: The block could be re-pruned as soon as it is received.\n\n"