Skip to content

Commit f126cbd

Browse files
committed
Extract ProtectEvictionCandidatesByRatio from SelectNodeToEvict
to allow deterministic unit testing of the ratio-based peer eviction protection logic, which protects peers having longer connection times and those connected via higher-latency networks. Add documentation.
1 parent a9d1b40 commit f126cbd

File tree

2 files changed

+49
-16
lines changed

2 files changed

+49
-16
lines changed

src/net.cpp

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -879,6 +879,26 @@ static void EraseLastKElements(std::vector<T> &elements, Comparator comparator,
879879
elements.erase(elements.end() - eraseSize, elements.end());
880880
}
881881

882+
void ProtectEvictionCandidatesByRatio(std::vector<NodeEvictionCandidate>& vEvictionCandidates)
883+
{
884+
// Protect the half of the remaining nodes which have been connected the longest.
885+
// This replicates the non-eviction implicit behavior, and precludes attacks that start later.
886+
// Reserve half of these protected spots for localhost peers, even if
887+
// they're not longest-uptime overall. This helps protect tor peers, which
888+
// tend to be otherwise disadvantaged under our eviction criteria.
889+
size_t initial_size = vEvictionCandidates.size();
890+
size_t total_protect_size = initial_size / 2;
891+
892+
// Pick out up to 1/4 peers that are localhost, sorted by longest uptime.
893+
std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), CompareLocalHostTimeConnected);
894+
size_t local_erase_size = total_protect_size / 2;
895+
vEvictionCandidates.erase(std::remove_if(vEvictionCandidates.end() - local_erase_size, vEvictionCandidates.end(), [](NodeEvictionCandidate const &n) { return n.m_is_local; }), vEvictionCandidates.end());
896+
// Calculate how many we removed, and update our total number of peers that
897+
// we want to protect based on uptime accordingly.
898+
total_protect_size -= initial_size - vEvictionCandidates.size();
899+
EraseLastKElements(vEvictionCandidates, ReverseCompareNodeTimeConnected, total_protect_size);
900+
}
901+
882902
[[nodiscard]] std::optional<NodeId> SelectNodeToEvict(std::vector<NodeEvictionCandidate>&& vEvictionCandidates)
883903
{
884904
// Protect connections with certain characteristics
@@ -901,22 +921,9 @@ static void EraseLastKElements(std::vector<T> &elements, Comparator comparator,
901921
// An attacker cannot manipulate this metric without performing useful work.
902922
EraseLastKElements(vEvictionCandidates, CompareNodeBlockTime, 4);
903923

904-
// Protect the half of the remaining nodes which have been connected the longest.
905-
// This replicates the non-eviction implicit behavior, and precludes attacks that start later.
906-
// Reserve half of these protected spots for localhost peers, even if
907-
// they're not longest-uptime overall. This helps protect tor peers, which
908-
// tend to be otherwise disadvantaged under our eviction criteria.
909-
size_t initial_size = vEvictionCandidates.size();
910-
size_t total_protect_size = initial_size / 2;
911-
912-
// Pick out up to 1/4 peers that are localhost, sorted by longest uptime.
913-
std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), CompareLocalHostTimeConnected);
914-
size_t local_erase_size = total_protect_size / 2;
915-
vEvictionCandidates.erase(std::remove_if(vEvictionCandidates.end() - local_erase_size, vEvictionCandidates.end(), [](NodeEvictionCandidate const &n) { return n.m_is_local; }), vEvictionCandidates.end());
916-
// Calculate how many we removed, and update our total number of peers that
917-
// we want to protect based on uptime accordingly.
918-
total_protect_size -= initial_size - vEvictionCandidates.size();
919-
EraseLastKElements(vEvictionCandidates, ReverseCompareNodeTimeConnected, total_protect_size);
924+
// Protect some of the remaining eviction candidates by ratios of desirable
925+
// or disadvantaged characteristics.
926+
ProtectEvictionCandidatesByRatio(vEvictionCandidates);
920927

921928
if (vEvictionCandidates.empty()) return std::nullopt;
922929

src/net.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1283,6 +1283,32 @@ struct NodeEvictionCandidate
12831283
bool m_is_local;
12841284
};
12851285

1286+
/**
1287+
* Select an inbound peer to evict after filtering out (protecting) peers having
1288+
* distinct, difficult-to-forge characteristics. The protection logic picks out
1289+
* fixed numbers of desirable peers per various criteria, followed by ratios of
1290+
* desirable or disadvantaged peers. If any eviction candidates remain, the
1291+
* selection logic chooses a peer to evict.
1292+
*/
12861293
[[nodiscard]] std::optional<NodeId> SelectNodeToEvict(std::vector<NodeEvictionCandidate>&& vEvictionCandidates);
12871294

1295+
/** Protect desirable or disadvantaged inbound peers from eviction by ratio.
1296+
*
1297+
* This function protects half of the peers which have been connected the
1298+
* longest, to replicate the non-eviction implicit behavior and preclude attacks
1299+
* that start later.
1300+
*
1301+
* Half of these protected spots (1/4 of the total) are reserved for localhost
1302+
* peers, if any, sorted by longest uptime, even if they're not longest uptime
1303+
* overall.
1304+
*
1305+
* This helps protect onion peers, which tend to be otherwise disadvantaged
1306+
* under our eviction criteria for their higher min ping times relative to IPv4
1307+
* and IPv6 peers, and favorise the diversity of peer connections.
1308+
*
1309+
* This function was extracted from SelectNodeToEvict() to be able to test the
1310+
* ratio-based protection logic deterministically.
1311+
*/
1312+
void ProtectEvictionCandidatesByRatio(std::vector<NodeEvictionCandidate>& vEvictionCandidates);
1313+
12881314
#endif // BITCOIN_NET_H

0 commit comments

Comments
 (0)