Skip to content

Commit 6c07c2c

Browse files
MarcoFalkePastaPastaPasta
authored andcommitted
Merge bitcoin#19858: Periodically make block-relay connections and sync headers
b3a515c Clarify comments around outbound peer eviction (Suhas Daftuar) daffaf0 Periodically make block-relay connections and sync headers (Suhas Daftuar) 3cc8a7a Use conn_type to identify block-relay peers, rather than m_tx_relay == nullptr (Suhas Daftuar) 91d6195 Simplify and clarify extra outbound peer counting (Suhas Daftuar) Pull request description: To make eclipse attacks more difficult, regularly initiate outbound connections and stay connected long enough to sync headers and potentially learn of new blocks. If we learn a new block, rotate out an existing block-relay peer in favor of the new peer. This augments the existing outbound peer rotation that exists -- currently we make new full-relay connections when our tip is stale, which we disconnect after waiting a small time to see if we learn a new block. As block-relay connections use minimal bandwidth, we can make these connections regularly and not just when our tip is stale. Like feeler connections, these connections are not aggressive; whenever our timer fires (once every 5 minutes on average), we'll try to initiate a new block-relay connection as described, but if we fail to connect we just wait for our timer to fire again before repeating with a new peer. ACKs for top commit: ariard: Code Review ACK b3a515c, only change since last time is dropping a useless `cs_main` taking. I manually tested a previous version of the PR, and not substantial change has been introduced since then which would alter behavior IMO. jonatack: Tested ACK b3a515c over several weeks, though this change and behavior could benefit from test coverage and other follow-ups (refactoring, etc.) described in the review feedback. I did not verify the behavior of `m_start_extra_block_relay_peers` only being enabled after initial chain sync. Since my last review, one unneeded `cs_main` lock was removed. Tree-SHA512: 75fc6f8e8003e88e93f86b845caf2d30b8b9c0dbb0a6b8aabe4e24ea4f6327351f736a068a3b2720a8a581b789942a3a47f921e2afdb47e88bc50d078aa37b6f
1 parent 5e23f35 commit 6c07c2c

File tree

6 files changed

+127
-20
lines changed

6 files changed

+127
-20
lines changed

src/init.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ void PrepareShutdown(NodeContext& node)
248248
// using the other before destroying them.
249249
if (node.peerman) UnregisterValidationInterface(node.peerman.get());
250250
// Follow the lock order requirements:
251-
// * CheckForStaleTipAndEvictPeers locks cs_main before indirectly calling GetExtraOutboundCount
251+
// * CheckForStaleTipAndEvictPeers locks cs_main before indirectly calling GetExtraFullOutboundCount
252252
// which locks cs_vNodes.
253253
// * ProcessMessage locks cs_main and g_cs_orphans before indirectly calling ForEachNode which
254254
// locks cs_vNodes.

src/net.cpp

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2235,22 +2235,36 @@ void CConnman::SetTryNewOutboundPeer(bool flag)
22352235
// Also exclude peers that haven't finished initial connection handshake yet
22362236
// (so that we don't decide we're over our desired connection limit, and then
22372237
// evict some peer that has finished the handshake)
2238-
int CConnman::GetExtraOutboundCount()
2238+
int CConnman::GetExtraFullOutboundCount()
22392239
{
2240-
int nOutbound = 0;
2240+
int full_outbound_peers = 0;
22412241
{
22422242
LOCK(cs_vNodes);
22432243
for (const CNode* pnode : vNodes) {
22442244
// don't count outbound masternodes
22452245
if (pnode->m_masternode_connection) {
22462246
continue;
22472247
}
2248-
if (pnode->fSuccessfullyConnected && !pnode->fDisconnect && pnode->IsOutboundOrBlockRelayConn() && !pnode->m_masternode_probe_connection) {
2249-
++nOutbound;
2248+
if (pnode->fSuccessfullyConnected && !pnode->fDisconnect && pnode->IsFullOutboundConn() && !pnode->m_masternode_probe_connection) {
2249+
++full_outbound_peers;
22502250
}
22512251
}
22522252
}
2253-
return std::max(nOutbound - m_max_outbound_full_relay - m_max_outbound_block_relay, 0);
2253+
return std::max(full_outbound_peers - m_max_outbound_full_relay, 0);
2254+
}
2255+
2256+
int CConnman::GetExtraBlockRelayCount()
2257+
{
2258+
int block_relay_peers = 0;
2259+
{
2260+
LOCK(cs_vNodes);
2261+
for (const CNode* pnode : vNodes) {
2262+
if (pnode->fSuccessfullyConnected && !pnode->fDisconnect && pnode->IsBlockOnlyConn()) {
2263+
++block_relay_peers;
2264+
}
2265+
}
2266+
}
2267+
return std::max(block_relay_peers - m_max_outbound_block_relay, 0);
22542268
}
22552269

22562270
void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
@@ -2282,6 +2296,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
22822296

22832297
// Minimum time before next feeler connection (in microseconds).
22842298
int64_t nNextFeeler = PoissonNextSend(nStart*1000*1000, FEELER_INTERVAL);
2299+
int64_t nNextExtraBlockRelay = PoissonNextSend(nStart*1000*1000, EXTRA_BLOCK_RELAY_ONLY_PEER_INTERVAL);
22852300
while (!interruptNet)
22862301
{
22872302
ProcessAddrFetch();
@@ -2365,8 +2380,9 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
23652380
// until we hit our block-relay-only peer limit.
23662381
// GetTryNewOutboundPeer() gets set when a stale tip is detected, so we
23672382
// try opening an additional OUTBOUND_FULL_RELAY connection. If none of
2368-
// these conditions are met, check the nNextFeeler timer to decide if
2369-
// we should open a FEELER.
2383+
// these conditions are met, check to see if it's time to try an extra
2384+
// block-relay-only peer (to confirm our tip is current, see below) or the nNextFeeler
2385+
// timer to decide if we should open a FEELER.
23702386

23712387
if (!m_anchors.empty() && (nOutboundBlockRelay < m_max_outbound_block_relay)) {
23722388
conn_type = ConnectionType::BLOCK_RELAY;
@@ -2377,6 +2393,30 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
23772393
conn_type = ConnectionType::BLOCK_RELAY;
23782394
} else if (GetTryNewOutboundPeer()) {
23792395
// OUTBOUND_FULL_RELAY
2396+
} else if (nTime > nNextExtraBlockRelay && m_start_extra_block_relay_peers) {
2397+
// Periodically connect to a peer (using regular outbound selection
2398+
// methodology from addrman) and stay connected long enough to sync
2399+
// headers, but not much else.
2400+
//
2401+
// Then disconnect the peer, if we haven't learned anything new.
2402+
//
2403+
// The idea is to make eclipse attacks very difficult to pull off,
2404+
// because every few minutes we're finding a new peer to learn headers
2405+
// from.
2406+
//
2407+
// This is similar to the logic for trying extra outbound (full-relay)
2408+
// peers, except:
2409+
// - we do this all the time on a poisson timer, rather than just when
2410+
// our tip is stale
2411+
// - we potentially disconnect our next-youngest block-relay-only peer, if our
2412+
// newest block-relay-only peer delivers a block more recently.
2413+
// See the eviction logic in net_processing.cpp.
2414+
//
2415+
// Because we can promote these connections to block-relay-only
2416+
// connections, they do not get their own ConnectionType enum
2417+
// (similar to how we deal with extra outbound peers).
2418+
nNextExtraBlockRelay = PoissonNextSend(nTime, EXTRA_BLOCK_RELAY_ONLY_PEER_INTERVAL);
2419+
conn_type = ConnectionType::BLOCK_RELAY;
23802420
} else if (nTime > nNextFeeler) {
23812421
nNextFeeler = PoissonNextSend(nTime, FEELER_INTERVAL);
23822422
conn_type = ConnectionType::FEELER;

src/net.h

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ static const int WARNING_INTERVAL = 10 * 60;
6565
static const int FEELER_INTERVAL = 120;
6666
/** The maximum number of entries in an 'inv' protocol message */
6767
static const unsigned int MAX_INV_SZ = 50000;
68+
/** Run the extra block-relay-only connection loop once every 5 minutes. **/
69+
static const int EXTRA_BLOCK_RELAY_ONLY_PEER_INTERVAL = 300;
6870
/** The maximum number of addresses from our addrman to return in response to a getaddr message. */
6971
static constexpr size_t MAX_ADDR_TO_SEND = 1000;
7072
/** Maximum length of incoming protocol messages (no message over 3 MiB is currently acceptable). */
@@ -488,13 +490,20 @@ friend class CNode;
488490
void SetTryNewOutboundPeer(bool flag);
489491
bool GetTryNewOutboundPeer();
490492

493+
void StartExtraBlockRelayPeers() {
494+
LogPrint(BCLog::NET, "net: enabling extra block-relay-only peers\n");
495+
m_start_extra_block_relay_peers = true;
496+
}
497+
491498
// Return the number of outbound peers we have in excess of our target (eg,
492499
// if we previously called SetTryNewOutboundPeer(true), and have since set
493500
// to false, we may have extra peers that we wish to disconnect). This may
494501
// return a value less than (num_outbound_connections - num_outbound_slots)
495502
// in cases where some outbound connections are not yet fully connected, or
496503
// not yet fully disconnected.
497-
int GetExtraOutboundCount();
504+
int GetExtraFullOutboundCount();
505+
// Count the number of block-relay-only peers we have over our limit.
506+
int GetExtraBlockRelayCount();
498507

499508
bool AddNode(const std::string& node);
500509
bool RemoveAddedNode(const std::string& node);
@@ -829,6 +838,12 @@ friend class CNode;
829838
* This takes the place of a feeler connection */
830839
std::atomic_bool m_try_another_outbound_peer;
831840

841+
/** flag for initiating extra block-relay-only peer connections.
842+
* this should only be enabled after initial chain sync has occurred,
843+
* as these connections are intended to be short-lived and low-bandwidth.
844+
*/
845+
std::atomic_bool m_start_extra_block_relay_peers{false};
846+
832847
std::atomic<int64_t> m_next_send_inv_to_incoming{0};
833848

834849
/**

src/net_processing.cpp

Lines changed: 57 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3097,7 +3097,7 @@ void PeerManagerImpl::ProcessMessage(
30973097
LogPrintf("New outbound peer connected: version: %d, blocks=%d, peer=%d%s (%s)\n",
30983098
pfrom.nVersion.load(), pfrom.nStartingHeight,
30993099
pfrom.GetId(), (fLogIPs ? strprintf(", peeraddr=%s", pfrom.addr.ToString()) : ""),
3100-
pfrom.RelayAddrsWithConn()? "full-relay" : "block-relay");
3100+
pfrom.IsBlockOnlyConn()? "block-relay" : "full-relay");
31013101
}
31023102

31033103
if (!pfrom.m_masternode_probe_connection) {
@@ -4607,11 +4607,54 @@ void PeerManagerImpl::ConsiderEviction(CNode& pto, int64_t time_in_seconds)
46074607

46084608
void PeerManagerImpl::EvictExtraOutboundPeers(int64_t time_in_seconds)
46094609
{
4610-
// Check whether we have too many outbound peers
4611-
int extra_peers = m_connman.GetExtraOutboundCount();
4612-
if (extra_peers > 0) {
4613-
// If we have more outbound peers than we target, disconnect one.
4614-
// Pick the outbound peer that least recently announced
4610+
// If we have any extra block-relay-only peers, disconnect the youngest unless
4611+
// it's given us a block -- in which case, compare with the second-youngest, and
4612+
// out of those two, disconnect the peer who least recently gave us a block.
4613+
// The youngest block-relay-only peer would be the extra peer we connected
4614+
// to temporarily in order to sync our tip; see net.cpp.
4615+
// Note that we use higher nodeid as a measure for most recent connection.
4616+
if (m_connman.GetExtraBlockRelayCount() > 0) {
4617+
std::pair<NodeId, int64_t> youngest_peer{-1, 0}, next_youngest_peer{-1, 0};
4618+
4619+
m_connman.ForEachNode([&](CNode* pnode) {
4620+
if (!pnode->IsBlockOnlyConn() || pnode->fDisconnect) return;
4621+
if (pnode->GetId() > youngest_peer.first) {
4622+
next_youngest_peer = youngest_peer;
4623+
youngest_peer.first = pnode->GetId();
4624+
youngest_peer.second = pnode->nLastBlockTime;
4625+
}
4626+
});
4627+
NodeId to_disconnect = youngest_peer.first;
4628+
if (youngest_peer.second > next_youngest_peer.second) {
4629+
// Our newest block-relay-only peer gave us a block more recently;
4630+
// disconnect our second youngest.
4631+
to_disconnect = next_youngest_peer.first;
4632+
}
4633+
m_connman.ForNode(to_disconnect, [&](CNode* pnode) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
4634+
AssertLockHeld(::cs_main);
4635+
// Make sure we're not getting a block right now, and that
4636+
// we've been connected long enough for this eviction to happen
4637+
// at all.
4638+
// Note that we only request blocks from a peer if we learn of a
4639+
// valid headers chain with at least as much work as our tip.
4640+
CNodeState *node_state = State(pnode->GetId());
4641+
if (node_state == nullptr ||
4642+
(time_in_seconds - pnode->nTimeConnected >= MINIMUM_CONNECT_TIME && node_state->nBlocksInFlight == 0)) {
4643+
pnode->fDisconnect = true;
4644+
LogPrint(BCLog::NET, "disconnecting extra block-relay-only peer=%d (last block received at time %d)\n", pnode->GetId(), pnode->nLastBlockTime);
4645+
return true;
4646+
} else {
4647+
LogPrint(BCLog::NET, "keeping block-relay-only peer=%d chosen for eviction (connect time: %d, blocks_in_flight: %d)\n",
4648+
pnode->GetId(), pnode->nTimeConnected, node_state->nBlocksInFlight);
4649+
}
4650+
return false;
4651+
});
4652+
}
4653+
4654+
// Check whether we have too many OUTBOUND_FULL_RELAY peers
4655+
if (m_connman.GetExtraFullOutboundCount() > 0) {
4656+
// If we have more OUTBOUND_FULL_RELAY peers than we target, disconnect one.
4657+
// Pick the OUTBOUND_FULL_RELAY peer that least recently announced
46154658
// us a new block, with ties broken by choosing the more recent
46164659
// connection (higher node id)
46174660
NodeId worst_peer = -1;
@@ -4622,14 +4665,13 @@ void PeerManagerImpl::EvictExtraOutboundPeers(int64_t time_in_seconds)
46224665

46234666
// Don't disconnect masternodes just because they were slow in block announcement
46244667
if (pnode->m_masternode_connection) return;
4625-
// Ignore non-outbound peers, or nodes marked for disconnect already
4626-
if (!pnode->IsOutboundOrBlockRelayConn() || pnode->fDisconnect) return;
4668+
// Only consider OUTBOUND_FULL_RELAY peers that are not already
4669+
// marked for disconnection
4670+
if (!pnode->IsFullOutboundConn() || pnode->fDisconnect) return;
46274671
CNodeState *state = State(pnode->GetId());
46284672
if (state == nullptr) return; // shouldn't be possible, but just in case
46294673
// Don't evict our protected peers
46304674
if (state->m_chain_sync.m_protect) return;
4631-
// Don't evict our block-relay-only peers.
4632-
if (!pnode->RelayAddrsWithConn()) return;
46334675
if (state->m_last_block_announcement < oldest_block_announcement || (state->m_last_block_announcement == oldest_block_announcement && pnode->GetId() > worst_peer)) {
46344676
worst_peer = pnode->GetId();
46354677
oldest_block_announcement = state->m_last_block_announcement;
@@ -4685,6 +4727,11 @@ void PeerManagerImpl::CheckForStaleTipAndEvictPeers()
46854727
}
46864728
m_stale_tip_check_time = time_in_seconds + STALE_CHECK_INTERVAL;
46874729
}
4730+
4731+
if (!m_initial_sync_finished && CanDirectFetch(m_chainparams.GetConsensus())) {
4732+
m_connman.StartExtraBlockRelayPeers();
4733+
m_initial_sync_finished = true;
4734+
}
46884735
}
46894736

46904737
namespace {

src/net_processing.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ class PeerManager : public CValidationInterface, public NetEventsInterface
7575
int64_t nTimeReceived, const std::atomic<bool>& interruptMsgProc) = 0;
7676

7777
virtual bool IsBanned(NodeId pnode) = 0;
78+
79+
/** Whether we've completed initial sync yet, for determining when to turn
80+
* on extra block-relay-only peers. */
81+
bool m_initial_sync_finished{false};
82+
7883
};
7984

8085
#endif // BITCOIN_NET_PROCESSING_H

src/test/fuzz/connman.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ FUZZ_TARGET_INIT(connman, initialize_connman)
117117
});
118118
}
119119
(void)connman.GetAddedNodeInfo();
120-
(void)connman.GetExtraOutboundCount();
120+
(void)connman.GetExtraFullOutboundCount();
121121
(void)connman.GetLocalServices();
122122
(void)connman.GetMaxOutboundTarget();
123123
(void)connman.GetMaxOutboundTimeframe();

0 commit comments

Comments
 (0)