diff --git a/src/net.h b/src/net.h --- a/src/net.h +++ b/src/net.h @@ -1403,7 +1403,35 @@ double availabilityScore; }; +/** + * Select an inbound peer to evict after filtering out (protecting) peers having + * distinct, difficult-to-forge characteristics. The protection logic picks out + * fixed numbers of desirable peers per various criteria, followed by ratios of + * desirable or disadvantaged peers. If any eviction candidates remain, the + * selection logic chooses a peer to evict. + */ [[nodiscard]] std::optional SelectNodeToEvict(std::vector &&vEvictionCandidates); +/** + * Protect desirable or disadvantaged inbound peers from eviction by ratio. + * + * This function protects half of the peers which have been connected the + * longest, to replicate the non-eviction implicit behavior and preclude attacks + * that start later. + * + * Half of these protected spots (1/4 of the total) are reserved for localhost + * peers, if any, sorted by longest uptime, even if they're not longest uptime + * overall. + * + * This helps protect onion peers, which tend to be otherwise disadvantaged + * under our eviction criteria for their higher min ping times relative to IPv4 + * and IPv6 peers, and favorise the diversity of peer connections. + * + * This function was extracted from SelectNodeToEvict() to be able to test the + * ratio-based protection logic deterministically. + */ +void ProtectEvictionCandidatesByRatio( + std::vector &vEvictionCandidates); + #endif // BITCOIN_NET_H diff --git a/src/net.cpp b/src/net.cpp --- a/src/net.cpp +++ b/src/net.cpp @@ -990,6 +990,29 @@ elements.end()); } +void ProtectEvictionCandidatesByRatio( + std::vector &vEvictionCandidates) { + // Protect the half of the remaining nodes which have been connected the + // longest. This replicates the non-eviction implicit behavior, and + // precludes attacks that start later. + // Reserve half of these protected spots for localhost peers, even if + // they're not longest-uptime overall. This helps protect tor peers, which + // tend to be otherwise disadvantaged under our eviction criteria. + size_t initial_size = vEvictionCandidates.size(); + size_t total_protect_size = initial_size / 2; + + // Pick out up to 1/4 peers that are localhost, sorted by longest uptime. + EraseLastKElementsIf( + vEvictionCandidates, CompareLocalHostTimeConnected, + total_protect_size / 2, + [](NodeEvictionCandidate const &n) { return n.m_is_local; }); + // Calculate how many we removed, and update our total number of peers that + // we want to protect based on uptime accordingly. + total_protect_size -= initial_size - vEvictionCandidates.size(); + EraseLastKElements(vEvictionCandidates, ReverseCompareNodeTimeConnected, + total_protect_size); +} + [[nodiscard]] std::optional SelectNodeToEvict(std::vector &&vEvictionCandidates) { // Protect connections with certain characteristics @@ -1028,25 +1051,9 @@ return n.availabilityScore > 0.; }); - // Protect the half of the remaining nodes which have been connected the - // longest. This replicates the non-eviction implicit behavior, and - // precludes attacks that start later. - // Reserve half of these protected spots for localhost peers, even if - // they're not longest-uptime overall. This helps protect tor peers, which - // tend to be otherwise disadvantaged under our eviction criteria. - size_t initial_size = vEvictionCandidates.size(); - size_t total_protect_size = initial_size / 2; - - // Pick out up to 1/4 peers that are localhost, sorted by longest uptime. - EraseLastKElementsIf( - vEvictionCandidates, CompareLocalHostTimeConnected, - total_protect_size / 2, - [](NodeEvictionCandidate const &n) { return n.m_is_local; }); - // Calculate how many we removed, and update our total number of peers that - // we want to protect based on uptime accordingly. - total_protect_size -= initial_size - vEvictionCandidates.size(); - EraseLastKElements(vEvictionCandidates, ReverseCompareNodeTimeConnected, - total_protect_size); + // Protect some of the remaining eviction candidates by ratios of desirable + // or disadvantaged characteristics. + ProtectEvictionCandidatesByRatio(vEvictionCandidates); if (vEvictionCandidates.empty()) { return std::nullopt;