Skip to content

Commit daffaf0

Browse files
committed
Periodically make block-relay connections and sync headers
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.
1 parent 3cc8a7a commit daffaf0

File tree

4 files changed

+111
-4
lines changed

4 files changed

+111
-4
lines changed

src/net.cpp

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1841,6 +1841,20 @@ int CConnman::GetExtraFullOutboundCount()
18411841
return std::max(full_outbound_peers - m_max_outbound_full_relay, 0);
18421842
}
18431843

1844+
int CConnman::GetExtraBlockRelayCount()
1845+
{
1846+
int block_relay_peers = 0;
1847+
{
1848+
LOCK(cs_vNodes);
1849+
for (const CNode* pnode : vNodes) {
1850+
if (pnode->fSuccessfullyConnected && !pnode->fDisconnect && pnode->IsBlockOnlyConn()) {
1851+
++block_relay_peers;
1852+
}
1853+
}
1854+
}
1855+
return std::max(block_relay_peers - m_max_outbound_block_relay, 0);
1856+
}
1857+
18441858
void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
18451859
{
18461860
// Connect to specific addresses
@@ -1869,6 +1883,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
18691883

18701884
// Minimum time before next feeler connection (in microseconds).
18711885
int64_t nNextFeeler = PoissonNextSend(nStart*1000*1000, FEELER_INTERVAL);
1886+
int64_t nNextExtraBlockRelay = PoissonNextSend(nStart*1000*1000, EXTRA_BLOCK_RELAY_ONLY_PEER_INTERVAL);
18721887
while (!interruptNet)
18731888
{
18741889
ProcessAddrFetch();
@@ -1941,8 +1956,9 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
19411956
// until we hit our block-relay-only peer limit.
19421957
// GetTryNewOutboundPeer() gets set when a stale tip is detected, so we
19431958
// try opening an additional OUTBOUND_FULL_RELAY connection. If none of
1944-
// these conditions are met, check the nNextFeeler timer to decide if
1945-
// we should open a FEELER.
1959+
// these conditions are met, check to see if it's time to try an extra
1960+
// block-relay-only peer (to confirm our tip is current, see below) or the nNextFeeler
1961+
// timer to decide if we should open a FEELER.
19461962

19471963
if (!m_anchors.empty() && (nOutboundBlockRelay < m_max_outbound_block_relay)) {
19481964
conn_type = ConnectionType::BLOCK_RELAY;
@@ -1953,6 +1969,30 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
19531969
conn_type = ConnectionType::BLOCK_RELAY;
19541970
} else if (GetTryNewOutboundPeer()) {
19551971
// OUTBOUND_FULL_RELAY
1972+
} else if (nTime > nNextExtraBlockRelay && m_start_extra_block_relay_peers) {
1973+
// Periodically connect to a peer (using regular outbound selection
1974+
// methodology from addrman) and stay connected long enough to sync
1975+
// headers, but not much else.
1976+
//
1977+
// Then disconnect the peer, if we haven't learned anything new.
1978+
//
1979+
// The idea is to make eclipse attacks very difficult to pull off,
1980+
// because every few minutes we're finding a new peer to learn headers
1981+
// from.
1982+
//
1983+
// This is similar to the logic for trying extra outbound (full-relay)
1984+
// peers, except:
1985+
// - we do this all the time on a poisson timer, rather than just when
1986+
// our tip is stale
1987+
// - we potentially disconnect our next-youngest block-relay-only peer, if our
1988+
// newest block-relay-only peer delivers a block more recently.
1989+
// See the eviction logic in net_processing.cpp.
1990+
//
1991+
// Because we can promote these connections to block-relay-only
1992+
// connections, they do not get their own ConnectionType enum
1993+
// (similar to how we deal with extra outbound peers).
1994+
nNextExtraBlockRelay = PoissonNextSend(nTime, EXTRA_BLOCK_RELAY_ONLY_PEER_INTERVAL);
1995+
conn_type = ConnectionType::BLOCK_RELAY;
19561996
} else if (nTime > nNextFeeler) {
19571997
nNextFeeler = PoissonNextSend(nTime, FEELER_INTERVAL);
19581998
conn_type = ConnectionType::FEELER;

src/net.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ static const bool DEFAULT_WHITELISTFORCERELAY = false;
4848
static const int TIMEOUT_INTERVAL = 20 * 60;
4949
/** Run the feeler connection loop once every 2 minutes or 120 seconds. **/
5050
static const int FEELER_INTERVAL = 120;
51+
/** Run the extra block-relay-only connection loop once every 5 minutes. **/
52+
static const int EXTRA_BLOCK_RELAY_ONLY_PEER_INTERVAL = 300;
5153
/** The maximum number of addresses from our addrman to return in response to a getaddr message. */
5254
static constexpr size_t MAX_ADDR_TO_SEND = 1000;
5355
/** Maximum length of incoming protocol messages (no message over 4 MB is currently acceptable). */
@@ -330,13 +332,20 @@ class CConnman
330332
void SetTryNewOutboundPeer(bool flag);
331333
bool GetTryNewOutboundPeer();
332334

335+
void StartExtraBlockRelayPeers() {
336+
LogPrint(BCLog::NET, "net: enabling extra block-relay-only peers\n");
337+
m_start_extra_block_relay_peers = true;
338+
}
339+
333340
// Return the number of outbound peers we have in excess of our target (eg,
334341
// if we previously called SetTryNewOutboundPeer(true), and have since set
335342
// to false, we may have extra peers that we wish to disconnect). This may
336343
// return a value less than (num_outbound_connections - num_outbound_slots)
337344
// in cases where some outbound connections are not yet fully connected, or
338345
// not yet fully disconnected.
339346
int GetExtraFullOutboundCount();
347+
// Count the number of block-relay-only peers we have over our limit.
348+
int GetExtraBlockRelayCount();
340349

341350
bool AddNode(const std::string& node);
342351
bool RemoveAddedNode(const std::string& node);
@@ -594,6 +603,12 @@ class CConnman
594603
* This takes the place of a feeler connection */
595604
std::atomic_bool m_try_another_outbound_peer;
596605

606+
/** flag for initiating extra block-relay-only peer connections.
607+
* this should only be enabled after initial chain sync has occurred,
608+
* as these connections are intended to be short-lived and low-bandwidth.
609+
*/
610+
std::atomic_bool m_start_extra_block_relay_peers{false};
611+
597612
std::atomic<int64_t> m_next_send_inv_to_incoming{0};
598613

599614
/**

src/net_processing.cpp

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3909,9 +3909,52 @@ void PeerManager::ConsiderEviction(CNode& pto, int64_t time_in_seconds)
39093909

39103910
void PeerManager::EvictExtraOutboundPeers(int64_t time_in_seconds)
39113911
{
3912+
// If we have any extra block-relay-only peers, disconnect the youngest unless
3913+
// it's given us a block -- in which case, compare with the second-youngest, and
3914+
// out of those two, disconnect the peer who least recently gave us a block.
3915+
// The youngest block-relay-only peer would be the extra peer we connected
3916+
// to temporarily in order to sync our tip; see net.cpp.
3917+
// Note that we use higher nodeid as a measure for most recent connection.
3918+
if (m_connman.GetExtraBlockRelayCount() > 0) {
3919+
std::pair<NodeId, int64_t> youngest_peer{-1, 0}, next_youngest_peer{-1, 0};
3920+
3921+
m_connman.ForEachNode([&](CNode* pnode) {
3922+
if (!pnode->IsBlockOnlyConn() || pnode->fDisconnect) return;
3923+
if (pnode->GetId() > youngest_peer.first) {
3924+
next_youngest_peer = youngest_peer;
3925+
youngest_peer.first = pnode->GetId();
3926+
youngest_peer.second = pnode->nLastBlockTime;
3927+
}
3928+
});
3929+
NodeId to_disconnect = youngest_peer.first;
3930+
if (youngest_peer.second > next_youngest_peer.second) {
3931+
// Our newest block-relay-only peer gave us a block more recently;
3932+
// disconnect our second youngest.
3933+
to_disconnect = next_youngest_peer.first;
3934+
}
3935+
m_connman.ForNode(to_disconnect, [&](CNode* pnode) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
3936+
AssertLockHeld(::cs_main);
3937+
// Make sure we're not getting a block right now, and that
3938+
// we've been connected long enough for this eviction to happen
3939+
// at all.
3940+
// Note that we only request blocks from a peer if we learn of a
3941+
// valid headers chain with at least as much work as our tip.
3942+
CNodeState *node_state = State(pnode->GetId());
3943+
if (node_state == nullptr ||
3944+
(time_in_seconds - pnode->nTimeConnected >= MINIMUM_CONNECT_TIME && node_state->nBlocksInFlight == 0)) {
3945+
pnode->fDisconnect = true;
3946+
LogPrint(BCLog::NET, "disconnecting extra block-relay-only peer=%d (last block received at time %d)\n", pnode->GetId(), pnode->nLastBlockTime);
3947+
return true;
3948+
} else {
3949+
LogPrint(BCLog::NET, "keeping block-relay-only peer=%d chosen for eviction (connect time: %d, blocks_in_flight: %d)\n",
3950+
pnode->GetId(), pnode->nTimeConnected, node_state->nBlocksInFlight);
3951+
}
3952+
return false;
3953+
});
3954+
}
3955+
39123956
// Check whether we have too many outbound peers
3913-
int extra_peers = m_connman.GetExtraFullOutboundCount();
3914-
if (extra_peers > 0) {
3957+
if (m_connman.GetExtraFullOutboundCount() > 0) {
39153958
// If we have more outbound peers than we target, disconnect one.
39163959
// Pick the outbound peer that least recently announced
39173960
// us a new block, with ties broken by choosing the more recent
@@ -3983,6 +4026,11 @@ void PeerManager::CheckForStaleTipAndEvictPeers()
39834026
}
39844027
m_stale_tip_check_time = time_in_seconds + STALE_CHECK_INTERVAL;
39854028
}
4029+
4030+
if (!m_initial_sync_finished && CanDirectFetch(m_chainparams.GetConsensus())) {
4031+
m_connman.StartExtraBlockRelayPeers();
4032+
m_initial_sync_finished = true;
4033+
}
39864034
}
39874035

39884036
namespace {

src/net_processing.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,10 @@ class PeerManager final : public CValidationInterface, public NetEventsInterface
206206
//* Whether this node is running in blocks only mode */
207207
const bool m_ignore_incoming_txs;
208208

209+
/** Whether we've completed initial sync yet, for determining when to turn
210+
* on extra block-relay-only peers. */
211+
bool m_initial_sync_finished{false};
212+
209213
/** Protects m_peer_map */
210214
mutable Mutex m_peer_mutex;
211215
/**

0 commit comments

Comments
 (0)