Skip to content

Commit 770e795

Browse files
instagibbspatricklodder
authored andcommitted
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 5a0ba4d commit 770e795

3 files changed

Lines changed: 87 additions & 34 deletions

File tree

src/net.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,9 @@ class CNodeStats
252252
int nVersion;
253253
std::string cleanSubVer;
254254
bool fInbound;
255+
// We requested high bandwidth connection to peer
255256
bool m_bip152_highbandwidth_to;
257+
// Peer requested high bandwidth connection
256258
bool m_bip152_highbandwidth_from;
257259
int m_starting_height;
258260
uint64_t nSendBytes;

src/net_processing.cpp

Lines changed: 83 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,9 @@ class PeerManagerImpl final : public PeerManager
483483
/** Have we requested this block from a peer */
484484
bool IsBlockRequested(const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
485485

486+
/** Have we requested this block from an outbound peer */
487+
bool IsBlockRequestedFromOutbound(const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
488+
486489
/** Remove this block from our tracked requested blocks. Called if:
487490
* - the block has been received from a peer
488491
* - the request for the block has timed out
@@ -788,6 +791,17 @@ bool PeerManagerImpl::IsBlockRequested(const uint256& hash)
788791
return mapBlocksInFlight.count(hash);
789792
}
790793

794+
bool PeerManagerImpl::IsBlockRequestedFromOutbound(const uint256& hash)
795+
{
796+
for (auto range = mapBlocksInFlight.equal_range(hash); range.first != range.second; range.first++) {
797+
auto& nodeblocksinflight = range.first->second;
798+
CNodeState& nodestate = *Assert(State(nodeblocksinflight.first));
799+
if (!nodestate.m_is_inbound) return true;
800+
}
801+
802+
return false;
803+
}
804+
791805
void PeerManagerImpl::RemoveBlockRequest(const uint256& hash, std::optional<NodeId> from_peer)
792806
{
793807
auto range = mapBlocksInFlight.equal_range(hash);
@@ -796,31 +810,30 @@ void PeerManagerImpl::RemoveBlockRequest(const uint256& hash, std::optional<Node
796810
return;
797811
}
798812

799-
// Currently we don't request more than one peer for same block
800-
Assume(mapBlocksInFlight.count(hash) == 1);
813+
// We should not have requested too many of this block
814+
Assume(mapBlocksInFlight.count(hash) <= MAX_CMPCTBLOCKS_INFLIGHT_PER_BLOCK);
801815

802816
while (range.first != range.second) {
803-
auto [node_id, list_it] = range.first->second;
817+
auto& nodeblocksinflight = range.first->second;
804818

805-
if (from_peer && *from_peer != node_id) {
819+
if (from_peer && *from_peer != nodeblocksinflight.first) {
806820
range.first++;
807821
continue;
808822
}
809823

810-
CNodeState *state = State(node_id);
811-
assert(state != nullptr);
824+
CNodeState& state = *Assert(State(nodeblocksinflight.first));
812825

813-
if (state->vBlocksInFlight.begin() == list_it) {
826+
if (state.vBlocksInFlight.begin() == nodeblocksinflight.second) {
814827
// First block on the queue was received, update the start download time for the next one
815-
state->m_downloading_since = std::max(state->m_downloading_since, GetTime<std::chrono::microseconds>());
828+
state.m_downloading_since = std::max(state.m_downloading_since, GetTime<std::chrono::microseconds>());
816829
}
817-
state->vBlocksInFlight.erase(list_it);
830+
state.vBlocksInFlight.erase(nodeblocksinflight.second);
818831

819-
if (state->vBlocksInFlight.empty()) {
832+
if (state.vBlocksInFlight.empty()) {
820833
// Last validated block on the queue for this peer was received.
821834
m_peers_downloading_from--;
822835
}
823-
state->m_stalling_since = 0us;
836+
state.m_stalling_since = 0us;
824837

825838
range.first = mapBlocksInFlight.erase(range.first);
826839
}
@@ -833,6 +846,8 @@ bool PeerManagerImpl::BlockRequested(NodeId nodeid, const CBlockIndex& block, st
833846
CNodeState *state = State(nodeid);
834847
assert(state != nullptr);
835848

849+
Assume(mapBlocksInFlight.count(hash) <= MAX_CMPCTBLOCKS_INFLIGHT_PER_BLOCK);
850+
836851
// Short-circuit most stuff in case it is from the same node
837852
for (auto range = mapBlocksInFlight.equal_range(hash); range.first != range.second; range.first++) {
838853
if (range.first->second.first == nodeid) {
@@ -843,8 +858,8 @@ bool PeerManagerImpl::BlockRequested(NodeId nodeid, const CBlockIndex& block, st
843858
}
844859
}
845860

846-
// Make sure it's not listed somewhere already.
847-
RemoveBlockRequest(hash, std::nullopt);
861+
// Make sure it's not being fetched already from same peer.
862+
RemoveBlockRequest(hash, nodeid);
848863

849864
std::list<QueuedBlock>::iterator it = state->vBlocksInFlight.insert(state->vBlocksInFlight.end(),
850865
{&block, std::unique_ptr<PartiallyDownloadedBlock>(pit ? new PartiallyDownloadedBlock(&m_mempool) : nullptr)});
@@ -3464,20 +3479,23 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
34643479
return;
34653480

34663481
auto range_flight = mapBlocksInFlight.equal_range(pindex->GetBlockHash());
3467-
bool fAlreadyInFlight = range_flight.first != range_flight.second;
3468-
bool in_flight_same_peer{false};
3482+
size_t already_in_flight = std::distance(range_flight.first, range_flight.second);
3483+
bool requested_block_from_this_peer{false};
3484+
3485+
// Multimap ensures ordering of outstanding requests. It's either empty or first in line.
3486+
bool first_in_flight = already_in_flight == 0 || (range_flight.first->second.first == pfrom.GetId());
34693487

34703488
while (range_flight.first != range_flight.second) {
34713489
if (range_flight.first->second.first == pfrom.GetId()) {
3472-
in_flight_same_peer = true;
3490+
requested_block_from_this_peer = true;
34733491
break;
34743492
}
34753493
range_flight.first++;
34763494
}
34773495

34783496
if (pindex->nChainWork <= m_chainman.ActiveChain().Tip()->nChainWork || // We know something better
34793497
pindex->nTx != 0) { // We had this block at some point, but pruned it
3480-
if (in_flight_same_peer) {
3498+
if (requested_block_from_this_peer) {
34813499
// We requested this block for some reason, but our mempool will probably be useless
34823500
// so we just grab the block via normal getdata
34833501
std::vector<CInv> vInv(1);
@@ -3488,7 +3506,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
34883506
}
34893507

34903508
// If we're not close to tip yet, give up and let parallel block fetch work its magic
3491-
if (!fAlreadyInFlight && !CanDirectFetch()) {
3509+
if (!already_in_flight && !CanDirectFetch()) {
34923510
return;
34933511
}
34943512

@@ -3501,8 +3519,8 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
35013519
// We want to be a bit conservative just to be extra careful about DoS
35023520
// possibilities in compact block processing...
35033521
if (pindex->nHeight <= m_chainman.ActiveChain().Height() + 2) {
3504-
if ((!fAlreadyInFlight && nodestate->vBlocksInFlight.size() < MAX_BLOCKS_IN_TRANSIT_PER_PEER) ||
3505-
in_flight_same_peer) {
3522+
if ((already_in_flight < MAX_CMPCTBLOCKS_INFLIGHT_PER_BLOCK && nodestate->vBlocksInFlight.size() < MAX_BLOCKS_IN_TRANSIT_PER_PEER) ||
3523+
requested_block_from_this_peer) {
35063524
std::list<QueuedBlock>::iterator* queuedBlockIt = nullptr;
35073525
if (!BlockRequested(pfrom.GetId(), *pindex, &queuedBlockIt)) {
35083526
if (!(*queuedBlockIt)->partialBlock)
@@ -3521,11 +3539,16 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
35213539
Misbehaving(pfrom.GetId(), 100, "invalid compact block");
35223540
return;
35233541
} else if (status == READ_STATUS_FAILED) {
3524-
// Duplicate txindexes, the block is now in-flight, so just request it
3525-
std::vector<CInv> vInv(1);
3526-
vInv[0] = CInv(MSG_BLOCK | GetFetchFlags(pfrom), cmpctblock.header.GetHash());
3527-
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETDATA, vInv));
3528-
return;
3542+
if (first_in_flight) {
3543+
// Duplicate txindexes, the block is now in-flight, so just request it
3544+
std::vector<CInv> vInv(1);
3545+
vInv[0] = CInv(MSG_BLOCK | GetFetchFlags(pfrom), cmpctblock.header.GetHash());
3546+
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETDATA, vInv));
3547+
return;
3548+
} else {
3549+
// Give up for this peer and wait for other peer(s)
3550+
RemoveBlockRequest(pindex->GetBlockHash(), pfrom.GetId());
3551+
}
35293552
}
35303553

35313554
BlockTransactionsRequest req;
@@ -3539,9 +3562,24 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
35393562
txn.blockhash = cmpctblock.header.GetHash();
35403563
blockTxnMsg << txn;
35413564
fProcessBLOCKTXN = true;
3542-
} else {
3565+
} else if (first_in_flight) {
3566+
// We will try to round-trip any compact blocks we get on failure,
3567+
// as long as it's first...
35433568
req.blockhash = pindex->GetBlockHash();
35443569
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETBLOCKTXN, req));
3570+
} else if (pfrom.m_bip152_highbandwidth_to &&
3571+
(!pfrom.IsInboundConn() ||
3572+
IsBlockRequestedFromOutbound(cmpctblock.header.GetHash()) ||
3573+
already_in_flight < MAX_CMPCTBLOCKS_INFLIGHT_PER_BLOCK - 1)) {
3574+
// ... or it's a hb relay peer and:
3575+
// - peer is outbound, or
3576+
// - we already have an outbound attempt in flight(so we'll take what we can get), or
3577+
// - it's not the final parallel download slot (which we may reserve for first outbound)
3578+
req.blockhash = pindex->GetBlockHash();
3579+
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETBLOCKTXN, req));
3580+
} else {
3581+
// Give up for this peer and wait for other peer(s)
3582+
RemoveBlockRequest(pindex->GetBlockHash(), pfrom.GetId());
35453583
}
35463584
} else {
35473585
// This block is either already in flight from a different
@@ -3562,7 +3600,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
35623600
}
35633601
}
35643602
} else {
3565-
if (in_flight_same_peer) {
3603+
if (requested_block_from_this_peer) {
35663604
// We requested this block, but its far into the future, so our
35673605
// mempool will probably be useless - request the block normally
35683606
std::vector<CInv> vInv(1);
@@ -3634,18 +3672,23 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
36343672
{
36353673
LOCK(cs_main);
36363674

3637-
bool expected_blocktxn = false;
36383675
auto range_flight = mapBlocksInFlight.equal_range(resp.blockhash);
3676+
size_t already_in_flight = std::distance(range_flight.first, range_flight.second);
3677+
bool requested_block_from_this_peer{false};
3678+
3679+
// Multimap ensures ordering of outstanding requests. It's either empty or first in line.
3680+
bool first_in_flight = already_in_flight == 0 || (range_flight.first->second.first == pfrom.GetId());
3681+
36393682
while (range_flight.first != range_flight.second) {
36403683
auto [node_id, block_it] = range_flight.first->second;
36413684
if (node_id == pfrom.GetId() && block_it->partialBlock) {
3642-
expected_blocktxn = true;
3685+
requested_block_from_this_peer = true;
36433686
break;
36443687
}
36453688
range_flight.first++;
36463689
}
36473690

3648-
if (!expected_blocktxn) {
3691+
if (!requested_block_from_this_peer) {
36493692
LogPrint(BCLog::NET, "Peer %d sent us block transactions for block we weren't expecting\n", pfrom.GetId());
36503693
return;
36513694
}
@@ -3657,10 +3700,16 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
36573700
Misbehaving(pfrom.GetId(), 100, "invalid compact block/non-matching block transactions");
36583701
return;
36593702
} else if (status == READ_STATUS_FAILED) {
3660-
// Might have collided, fall back to getdata now :(
3661-
std::vector<CInv> invs;
3662-
invs.push_back(CInv(MSG_BLOCK | GetFetchFlags(pfrom), resp.blockhash));
3663-
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETDATA, invs));
3703+
if (first_in_flight) {
3704+
// Might have collided, fall back to getdata now :(
3705+
std::vector<CInv> invs;
3706+
invs.push_back(CInv(MSG_BLOCK | GetFetchFlags(pfrom), resp.blockhash));
3707+
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETDATA, invs));
3708+
} else {
3709+
RemoveBlockRequest(resp.blockhash, pfrom.GetId());
3710+
LogPrint(BCLog::NET, "Peer %d sent us a compact block but it failed to reconstruct, waiting on first download to complete\n", pfrom.GetId());
3711+
return;
3712+
}
36643713
} else {
36653714
// Block is either okay, or possibly we received
36663715
// 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;

0 commit comments

Comments
 (0)