Skip to content

Commit 03423f8

Browse files
committed
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.
1 parent 13f9b20 commit 03423f8

File tree

4 files changed

+90
-38
lines changed

4 files changed

+90
-38
lines changed

src/net.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,9 @@ class CNodeStats
200200
int nVersion;
201201
std::string cleanSubVer;
202202
bool fInbound;
203+
// We requested high bandwidth connection to peer
203204
bool m_bip152_highbandwidth_to;
205+
// Peer requested high bandwidth connection
204206
bool m_bip152_highbandwidth_from;
205207
int m_starting_height;
206208
uint64_t nSendBytes;

src/net_processing.cpp

Lines changed: 85 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -876,6 +876,9 @@ class PeerManagerImpl final : public PeerManager
876876
/** Have we requested this block from a peer */
877877
bool IsBlockRequested(const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
878878

879+
/** Have we requested this block from an outbound peer */
880+
bool IsBlockRequestedFromOutbound(const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
881+
879882
/** Remove this block from our tracked requested blocks. Called if:
880883
* - the block has been received from a peer
881884
* - the request for the block has timed out
@@ -1121,6 +1124,17 @@ bool PeerManagerImpl::IsBlockRequested(const uint256& hash)
11211124
return mapBlocksInFlight.count(hash);
11221125
}
11231126

1127+
bool PeerManagerImpl::IsBlockRequestedFromOutbound(const uint256& hash)
1128+
{
1129+
for (auto range = mapBlocksInFlight.equal_range(hash); range.first != range.second; range.first++) {
1130+
auto [nodeid, block_it] = range.first->second;
1131+
CNodeState& nodestate = *Assert(State(nodeid));
1132+
if (!nodestate.m_is_inbound) return true;
1133+
}
1134+
1135+
return false;
1136+
}
1137+
11241138
void PeerManagerImpl::RemoveBlockRequest(const uint256& hash, std::optional<NodeId> from_peer)
11251139
{
11261140
auto range = mapBlocksInFlight.equal_range(hash);
@@ -1129,8 +1143,8 @@ void PeerManagerImpl::RemoveBlockRequest(const uint256& hash, std::optional<Node
11291143
return;
11301144
}
11311145

1132-
// Currently we don't request more than one peer for same block
1133-
Assume(mapBlocksInFlight.count(hash) == 1);
1146+
// We should not have requested too many of this block
1147+
Assume(mapBlocksInFlight.count(hash) <= MAX_CMPCTBLOCKS_INFLIGHT_PER_BLOCK);
11341148

11351149
while (range.first != range.second) {
11361150
auto [node_id, list_it] = range.first->second;
@@ -1140,20 +1154,19 @@ void PeerManagerImpl::RemoveBlockRequest(const uint256& hash, std::optional<Node
11401154
continue;
11411155
}
11421156

1143-
CNodeState *state = State(node_id);
1144-
assert(state != nullptr);
1157+
CNodeState& state = *Assert(State(node_id));
11451158

1146-
if (state->vBlocksInFlight.begin() == list_it) {
1159+
if (state.vBlocksInFlight.begin() == list_it) {
11471160
// First block on the queue was received, update the start download time for the next one
1148-
state->m_downloading_since = std::max(state->m_downloading_since, GetTime<std::chrono::microseconds>());
1161+
state.m_downloading_since = std::max(state.m_downloading_since, GetTime<std::chrono::microseconds>());
11491162
}
1150-
state->vBlocksInFlight.erase(list_it);
1163+
state.vBlocksInFlight.erase(list_it);
11511164

1152-
if (state->vBlocksInFlight.empty()) {
1165+
if (state.vBlocksInFlight.empty()) {
11531166
// Last validated block on the queue for this peer was received.
11541167
m_peers_downloading_from--;
11551168
}
1156-
state->m_stalling_since = 0us;
1169+
state.m_stalling_since = 0us;
11571170

11581171
range.first = mapBlocksInFlight.erase(range.first);
11591172
}
@@ -1166,6 +1179,8 @@ bool PeerManagerImpl::BlockRequested(NodeId nodeid, const CBlockIndex& block, st
11661179
CNodeState *state = State(nodeid);
11671180
assert(state != nullptr);
11681181

1182+
Assume(mapBlocksInFlight.count(hash) <= MAX_CMPCTBLOCKS_INFLIGHT_PER_BLOCK);
1183+
11691184
// Short-circuit most stuff in case it is from the same node
11701185
for (auto range = mapBlocksInFlight.equal_range(hash); range.first != range.second; range.first++) {
11711186
if (range.first->second.first == nodeid) {
@@ -1176,8 +1191,8 @@ bool PeerManagerImpl::BlockRequested(NodeId nodeid, const CBlockIndex& block, st
11761191
}
11771192
}
11781193

1179-
// Make sure it's not listed somewhere already.
1180-
RemoveBlockRequest(hash, std::nullopt);
1194+
// Make sure it's not being fetched already from same peer.
1195+
RemoveBlockRequest(hash, nodeid);
11811196

11821197
std::list<QueuedBlock>::iterator it = state->vBlocksInFlight.insert(state->vBlocksInFlight.end(),
11831198
{&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
17741789

17751790
LOCK(cs_main);
17761791

1777-
// Mark block as in-flight unless it already is (for this peer).
1778-
// If the peer does not send us a block, vBlocksInFlight remains non-empty,
1779-
// causing us to timeout and disconnect.
1780-
// If a block was already in-flight for a different peer, its BLOCKTXN
1781-
// response will be dropped.
1792+
// Forget about all prior requests
1793+
RemoveBlockRequest(block_index.GetBlockHash(), std::nullopt);
1794+
1795+
// Mark block as in-flight
17821796
if (!BlockRequested(peer_id, block_index)) return "Already requested from this peer";
17831797

17841798
// Construct message to request the block
@@ -4292,20 +4306,23 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
42924306
return;
42934307

42944308
auto range_flight = mapBlocksInFlight.equal_range(pindex->GetBlockHash());
4295-
bool fAlreadyInFlight = range_flight.first != range_flight.second;
4296-
bool in_flight_same_peer{false};
4309+
size_t already_in_flight = std::distance(range_flight.first, range_flight.second);
4310+
bool requested_block_from_this_peer{false};
4311+
4312+
// Multimap ensures ordering of outstanding requests. It's either empty or first in line.
4313+
bool first_in_flight = already_in_flight == 0 || (range_flight.first->second.first == pfrom.GetId());
42974314

42984315
while (range_flight.first != range_flight.second) {
42994316
if (range_flight.first->second.first == pfrom.GetId()) {
4300-
in_flight_same_peer = true;
4317+
requested_block_from_this_peer = true;
43014318
break;
43024319
}
43034320
range_flight.first++;
43044321
}
43054322

43064323
if (pindex->nChainWork <= m_chainman.ActiveChain().Tip()->nChainWork || // We know something better
43074324
pindex->nTx != 0) { // We had this block at some point, but pruned it
4308-
if (in_flight_same_peer) {
4325+
if (requested_block_from_this_peer) {
43094326
// We requested this block for some reason, but our mempool will probably be useless
43104327
// so we just grab the block via normal getdata
43114328
std::vector<CInv> vInv(1);
@@ -4316,15 +4333,15 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
43164333
}
43174334

43184335
// If we're not close to tip yet, give up and let parallel block fetch work its magic
4319-
if (!fAlreadyInFlight && !CanDirectFetch()) {
4336+
if (!already_in_flight && !CanDirectFetch()) {
43204337
return;
43214338
}
43224339

43234340
// We want to be a bit conservative just to be extra careful about DoS
43244341
// possibilities in compact block processing...
43254342
if (pindex->nHeight <= m_chainman.ActiveChain().Height() + 2) {
4326-
if ((!fAlreadyInFlight && nodestate->vBlocksInFlight.size() < MAX_BLOCKS_IN_TRANSIT_PER_PEER) ||
4327-
in_flight_same_peer) {
4343+
if ((already_in_flight < MAX_CMPCTBLOCKS_INFLIGHT_PER_BLOCK && nodestate->vBlocksInFlight.size() < MAX_BLOCKS_IN_TRANSIT_PER_PEER) ||
4344+
requested_block_from_this_peer) {
43284345
std::list<QueuedBlock>::iterator* queuedBlockIt = nullptr;
43294346
if (!BlockRequested(pfrom.GetId(), *pindex, &queuedBlockIt)) {
43304347
if (!(*queuedBlockIt)->partialBlock)
@@ -4343,11 +4360,16 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
43434360
Misbehaving(*peer, 100, "invalid compact block");
43444361
return;
43454362
} else if (status == READ_STATUS_FAILED) {
4346-
// Duplicate txindexes, the block is now in-flight, so just request it
4347-
std::vector<CInv> vInv(1);
4348-
vInv[0] = CInv(MSG_BLOCK | GetFetchFlags(*peer), blockhash);
4349-
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETDATA, vInv));
4350-
return;
4363+
if (first_in_flight) {
4364+
// Duplicate txindexes, the block is now in-flight, so just request it
4365+
std::vector<CInv> vInv(1);
4366+
vInv[0] = CInv(MSG_BLOCK | GetFetchFlags(*peer), blockhash);
4367+
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETDATA, vInv));
4368+
return;
4369+
} else {
4370+
// Give up for this peer and wait for other peer(s)
4371+
RemoveBlockRequest(pindex->GetBlockHash(), pfrom.GetId());
4372+
}
43514373
}
43524374

43534375
BlockTransactionsRequest req;
@@ -4361,9 +4383,24 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
43614383
txn.blockhash = blockhash;
43624384
blockTxnMsg << txn;
43634385
fProcessBLOCKTXN = true;
4364-
} else {
4386+
} else if (first_in_flight) {
4387+
// We will try to round-trip any compact blocks we get on failure,
4388+
// as long as it's first...
4389+
req.blockhash = pindex->GetBlockHash();
4390+
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETBLOCKTXN, req));
4391+
} else if (pfrom.m_bip152_highbandwidth_to &&
4392+
(!pfrom.IsInboundConn() ||
4393+
IsBlockRequestedFromOutbound(blockhash) ||
4394+
already_in_flight < MAX_CMPCTBLOCKS_INFLIGHT_PER_BLOCK - 1)) {
4395+
// ... or it's a hb relay peer and:
4396+
// - peer is outbound, or
4397+
// - we already have an outbound attempt in flight(so we'll take what we can get), or
4398+
// - it's not the final parallel download slot (which we may reserve for first outbound)
43654399
req.blockhash = pindex->GetBlockHash();
43664400
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETBLOCKTXN, req));
4401+
} else {
4402+
// Give up for this peer and wait for other peer(s)
4403+
RemoveBlockRequest(pindex->GetBlockHash(), pfrom.GetId());
43674404
}
43684405
} else {
43694406
// This block is either already in flight from a different
@@ -4384,7 +4421,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
43844421
}
43854422
}
43864423
} else {
4387-
if (in_flight_same_peer) {
4424+
if (requested_block_from_this_peer) {
43884425
// We requested this block, but its far into the future, so our
43894426
// mempool will probably be useless - request the block normally
43904427
std::vector<CInv> vInv(1);
@@ -4456,18 +4493,23 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
44564493
{
44574494
LOCK(cs_main);
44584495

4459-
bool expected_blocktxn = false;
44604496
auto range_flight = mapBlocksInFlight.equal_range(resp.blockhash);
4497+
size_t already_in_flight = std::distance(range_flight.first, range_flight.second);
4498+
bool requested_block_from_this_peer{false};
4499+
4500+
// Multimap ensures ordering of outstanding requests. It's either empty or first in line.
4501+
bool first_in_flight = already_in_flight == 0 || (range_flight.first->second.first == pfrom.GetId());
4502+
44614503
while (range_flight.first != range_flight.second) {
44624504
auto [node_id, block_it] = range_flight.first->second;
44634505
if (node_id == pfrom.GetId() && block_it->partialBlock) {
4464-
expected_blocktxn = true;
4506+
requested_block_from_this_peer = true;
44654507
break;
44664508
}
44674509
range_flight.first++;
44684510
}
44694511

4470-
if (!expected_blocktxn) {
4512+
if (!requested_block_from_this_peer) {
44714513
LogPrint(BCLog::NET, "Peer %d sent us block transactions for block we weren't expecting\n", pfrom.GetId());
44724514
return;
44734515
}
@@ -4479,10 +4521,16 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
44794521
Misbehaving(*peer, 100, "invalid compact block/non-matching block transactions");
44804522
return;
44814523
} else if (status == READ_STATUS_FAILED) {
4482-
// Might have collided, fall back to getdata now :(
4483-
std::vector<CInv> invs;
4484-
invs.push_back(CInv(MSG_BLOCK | GetFetchFlags(*peer), resp.blockhash));
4485-
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETDATA, invs));
4524+
if (first_in_flight) {
4525+
// Might have collided, fall back to getdata now :(
4526+
std::vector<CInv> invs;
4527+
invs.push_back(CInv(MSG_BLOCK | GetFetchFlags(*peer), resp.blockhash));
4528+
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETDATA, invs));
4529+
} else {
4530+
RemoveBlockRequest(resp.blockhash, pfrom.GetId());
4531+
LogPrint(BCLog::NET, "Peer %d sent us a compact block but it failed to reconstruct, waiting on first download to complete\n", pfrom.GetId());
4532+
return;
4533+
}
44864534
} else {
44874535
// Block is either okay, or possibly we received
44884536
// READ_STATUS_CHECKBLOCK_FAILED.

src/net_processing.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ static const bool DEFAULT_PEERBLOOMFILTERS = false;
2222
static const bool DEFAULT_PEERBLOCKFILTERS = false;
2323
/** Threshold for marking a node to be discouraged, e.g. disconnected and added to the discouragement filter. */
2424
static const int DISCOURAGEMENT_THRESHOLD{100};
25+
/** Maximum number of outstanding CMPCTBLOCK requests for the same block. */
26+
static const unsigned int MAX_CMPCTBLOCKS_INFLIGHT_PER_BLOCK = 3;
2527

2628
struct CNodeStateStats {
2729
int nSyncHeight = -1;

src/rpc/blockchain.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -428,7 +428,7 @@ static RPCHelpMan getblockfrompeer()
428428
"getblockfrompeer",
429429
"Attempt to fetch block from a given peer.\n\n"
430430
"We must have the header for this block, e.g. using submitheader.\n"
431-
"Subsequent calls for the same block and a new peer will cause the response from the previous peer to be ignored.\n"
431+
"Subsequent calls for the same block may cause the response from the previous peer to be ignored.\n"
432432
"Peers generally ignore requests for a stale block that they never fully verified, or one that is more than a month old.\n"
433433
"When a peer does not respond with a block, we will disconnect.\n"
434434
"Note: The block could be re-pruned as soon as it is received.\n\n"

0 commit comments

Comments
 (0)