Skip to content

Commit caa21f5

Browse files
committed
Protect onion+localhost peers in ProtectEvictionCandidatesByRatio()
Now that we have a reliable way to detect inbound onion peers, this commit updates our existing eviction protection of 1/4 localhost peers to instead protect up to 1/4 onion peers (connected via our tor control service), sorted by longest uptime. Any remaining slots of the 1/4 are then allocated to protect localhost peers, or 2 localhost peers if no slots remain and 2 or more onion peers are protected, sorted by longest uptime. The goal is to avoid penalizing onion peers, due to their higher min ping times relative to IPv4 and IPv6 peers, and improve our diversity of peer connections. Thank you to Gregory Maxwell, Suhas Daftuar, Vasil Dimov and Pieter Wuille for valuable review feedback that shaped the direction.
1 parent 8f1a53e commit caa21f5

File tree

3 files changed

+45
-18
lines changed

3 files changed

+45
-18
lines changed

src/net.cpp

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -840,6 +840,12 @@ static bool CompareLocalHostTimeConnected(const NodeEvictionCandidate &a, const
840840
return a.nTimeConnected > b.nTimeConnected;
841841
}
842842

843+
static bool CompareOnionTimeConnected(const NodeEvictionCandidate& a, const NodeEvictionCandidate& b)
844+
{
845+
if (a.m_is_onion != b.m_is_onion) return b.m_is_onion;
846+
return a.nTimeConnected > b.nTimeConnected;
847+
}
848+
843849
static bool CompareNetGroupKeyed(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b) {
844850
return a.nKeyedNetGroup < b.nKeyedNetGroup;
845851
}
@@ -885,16 +891,31 @@ void ProtectEvictionCandidatesByRatio(std::vector<NodeEvictionCandidate>& vEvict
885891
{
886892
// Protect the half of the remaining nodes which have been connected the longest.
887893
// This replicates the non-eviction implicit behavior, and precludes attacks that start later.
888-
// Reserve half of these protected spots for localhost peers, even if
889-
// they're not longest-uptime overall. This helps protect tor peers, which
890-
// tend to be otherwise disadvantaged under our eviction criteria.
894+
// To favorise the diversity of our peer connections, reserve up to (half + 2) of
895+
// these protected spots for onion and localhost peers, if any, even if they're not
896+
// longest uptime overall. This helps protect tor peers, which tend to be otherwise
897+
// disadvantaged under our eviction criteria.
891898
const size_t initial_size = vEvictionCandidates.size();
892899
size_t total_protect_size = initial_size / 2;
900+
const size_t onion_protect_size = total_protect_size / 2;
893901

894-
// Pick out up to 1/4 peers that are localhost, sorted by longest uptime.
895-
const size_t local_erase_size = total_protect_size / 2;
896-
EraseLastKElements(vEvictionCandidates, CompareLocalHostTimeConnected, local_erase_size,
897-
[](const NodeEvictionCandidate& n) { return n.m_is_local; });
902+
if (onion_protect_size) {
903+
// Pick out up to 1/4 peers connected via our onion service, sorted by longest uptime.
904+
EraseLastKElements(vEvictionCandidates, CompareOnionTimeConnected, onion_protect_size,
905+
[](const NodeEvictionCandidate& n) { return n.m_is_onion; });
906+
}
907+
908+
const size_t localhost_min_protect_size{2};
909+
if (onion_protect_size >= localhost_min_protect_size) {
910+
// Allocate any remaining slots of the 1/4, or minimum 2 additional slots,
911+
// to localhost peers, sorted by longest uptime, as manually configured
912+
// hidden services not using `-bind=addr[:port]=onion` will not be detected
913+
// as inbound onion connections.
914+
const size_t remaining_tor_slots{onion_protect_size - (initial_size - vEvictionCandidates.size())};
915+
const size_t localhost_protect_size{std::max(remaining_tor_slots, localhost_min_protect_size)};
916+
EraseLastKElements(vEvictionCandidates, CompareLocalHostTimeConnected, localhost_protect_size,
917+
[](const NodeEvictionCandidate& n) { return n.m_is_local; });
918+
}
898919

899920
// Calculate how many we removed, and update our total number of peers that
900921
// we want to protect based on uptime accordingly.

src/net.h

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1288,9 +1288,9 @@ struct NodeEvictionCandidate
12881288
/**
12891289
* Select an inbound peer to evict after filtering out (protecting) peers having
12901290
* distinct, difficult-to-forge characteristics. The protection logic picks out
1291-
* fixed numbers of desirable peers per various criteria, followed by ratios of
1292-
* desirable or disadvantaged peers. If any eviction candidates remain, the
1293-
* selection logic chooses a peer to evict.
1291+
* fixed numbers of desirable peers per various criteria, followed by (mostly)
1292+
* ratios of desirable or disadvantaged peers. If any eviction candidates
1293+
* remain, the selection logic chooses a peer to evict.
12941294
*/
12951295
[[nodiscard]] std::optional<NodeId> SelectNodeToEvict(std::vector<NodeEvictionCandidate>&& vEvictionCandidates);
12961296

@@ -1300,9 +1300,13 @@ struct NodeEvictionCandidate
13001300
* longest, to replicate the non-eviction implicit behavior and preclude attacks
13011301
* that start later.
13021302
*
1303-
* Half of these protected spots (1/4 of the total) are reserved for localhost
1304-
* peers, if any, sorted by longest uptime, even if they're not longest uptime
1305-
* overall.
1303+
* Half of these protected spots (1/4 of the total) are reserved for onion peers
1304+
* connected via our tor control service, if any, sorted by longest uptime, even
1305+
* if they're not longest uptime overall. Any remaining slots of the 1/4 are
1306+
* then allocated to protect localhost peers, if any (or up to 2 localhost peers
1307+
* if no slots remain and 2 or more onion peers were protected), sorted by
1308+
* longest uptime, as manually configured hidden services not using
1309+
* `-bind=addr[:port]=onion` will not be detected as inbound onion connections.
13061310
*
13071311
* This helps protect onion peers, which tend to be otherwise disadvantaged
13081312
* under our eviction criteria for their higher min ping times relative to IPv4

src/test/net_peer_eviction_tests.cpp

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ BOOST_AUTO_TEST_CASE(peer_protection_test)
9494
BOOST_CHECK(IsProtected(
9595
num_peers, [](NodeEvictionCandidate& c) {
9696
c.nTimeConnected = c.id;
97-
c.m_is_local = false;
97+
c.m_is_onion = c.m_is_local = false;
9898
},
9999
/* protected_peer_ids */ {0, 1, 2, 3, 4, 5},
100100
/* unprotected_peer_ids */ {6, 7, 8, 9, 10, 11},
@@ -104,7 +104,7 @@ BOOST_AUTO_TEST_CASE(peer_protection_test)
104104
BOOST_CHECK(IsProtected(
105105
num_peers, [num_peers](NodeEvictionCandidate& c) {
106106
c.nTimeConnected = num_peers - c.id;
107-
c.m_is_local = false;
107+
c.m_is_onion = c.m_is_local = false;
108108
},
109109
/* protected_peer_ids */ {6, 7, 8, 9, 10, 11},
110110
/* unprotected_peer_ids */ {0, 1, 2, 3, 4, 5},
@@ -113,20 +113,22 @@ BOOST_AUTO_TEST_CASE(peer_protection_test)
113113
// Test protection of localhost peers...
114114

115115
// Expect 1/4 localhost peers to be protected from eviction,
116-
// independently of other characteristics.
116+
// if no onion peers.
117117
BOOST_CHECK(IsProtected(
118118
num_peers, [](NodeEvictionCandidate& c) {
119+
c.m_is_onion = false;
119120
c.m_is_local = (c.id == 1 || c.id == 9 || c.id == 11);
120121
},
121122
/* protected_peer_ids */ {1, 9, 11},
122123
/* unprotected_peer_ids */ {},
123124
random_context));
124125

125-
// Expect 1/4 localhost peers and 1/4 of the others to be protected
126-
// from eviction, sorted by longest uptime (lowest nTimeConnected).
126+
// Expect 1/4 localhost peers and 1/4 of the other peers to be protected,
127+
// sorted by longest uptime (lowest nTimeConnected), if no onion peers.
127128
BOOST_CHECK(IsProtected(
128129
num_peers, [](NodeEvictionCandidate& c) {
129130
c.nTimeConnected = c.id;
131+
c.m_is_onion = false;
130132
c.m_is_local = (c.id > 6);
131133
},
132134
/* protected_peer_ids */ {0, 1, 2, 7, 8, 9},

0 commit comments

Comments
 (0)