| 959 | } |
| 960 | |
| 961 | void ProtectEvictionCandidatesByRatio(std::vector<NodeEvictionCandidate>& eviction_candidates) |
| 962 | { |
| 963 | // Protect the half of the remaining nodes which have been connected the longest. |
| 964 | // This replicates the non-eviction implicit behavior, and precludes attacks that start later. |
| 965 | // To favorise the diversity of our peer connections, reserve up to half of these protected |
| 966 | // spots for Tor/onion, localhost, I2P, and CJDNS peers, even if they're not longest uptime |
| 967 | // overall. This helps protect these higher-latency peers that tend to be otherwise |
| 968 | // disadvantaged under our eviction criteria. |
| 969 | const size_t initial_size = eviction_candidates.size(); |
| 970 | const size_t total_protect_size{initial_size / 2}; |
| 971 | |
| 972 | // Disadvantaged networks to protect. In the case of equal counts, earlier array members |
| 973 | // have the first opportunity to recover unused slots from the previous iteration. |
| 974 | struct Net { bool is_local; Network id; size_t count; }; |
| 975 | std::array<Net, 4> networks{ |
| 976 | {{false, NET_CJDNS, 0}, {false, NET_I2P, 0}, {/*localhost=*/true, NET_MAX, 0}, {false, NET_ONION, 0}}}; |
| 977 | |
| 978 | // Count and store the number of eviction candidates per network. |
| 979 | for (Net& n : networks) { |
| 980 | n.count = std::count_if(eviction_candidates.cbegin(), eviction_candidates.cend(), |
| 981 | [&n](const NodeEvictionCandidate& c) { |
| 982 | return n.is_local ? c.m_is_local : c.m_network == n.id; |
| 983 | }); |
| 984 | } |
| 985 | // Sort `networks` by ascending candidate count, to give networks having fewer candidates |
| 986 | // the first opportunity to recover unused protected slots from the previous iteration. |
| 987 | std::stable_sort(networks.begin(), networks.end(), [](Net a, Net b) { return a.count < b.count; }); |
| 988 | |
| 989 | // Protect up to 25% of the eviction candidates by disadvantaged network. |
| 990 | const size_t max_protect_by_network{total_protect_size / 2}; |
| 991 | size_t num_protected{0}; |
| 992 | |
| 993 | while (num_protected < max_protect_by_network) { |
| 994 | // Count the number of disadvantaged networks from which we have peers to protect. |
| 995 | auto num_networks = std::count_if(networks.begin(), networks.end(), [](const Net& n) { return n.count; }); |
| 996 | if (num_networks == 0) { |
| 997 | break; |
| 998 | } |
| 999 | const size_t disadvantaged_to_protect{max_protect_by_network - num_protected}; |
| 1000 | const size_t protect_per_network{std::max(disadvantaged_to_protect / num_networks, static_cast<size_t>(1))}; |
| 1001 | // Early exit flag if there are no remaining candidates by disadvantaged network. |
| 1002 | bool protected_at_least_one{false}; |
| 1003 | |
| 1004 | for (Net& n : networks) { |
| 1005 | if (n.count == 0) continue; |
| 1006 | const size_t before = eviction_candidates.size(); |
| 1007 | EraseLastKElements(eviction_candidates, CompareNodeNetworkTime(n.is_local, n.id), |
| 1008 | protect_per_network, [&n](const NodeEvictionCandidate& c) { |
| 1009 | return n.is_local ? c.m_is_local : c.m_network == n.id; |
| 1010 | }); |
| 1011 | const size_t after = eviction_candidates.size(); |
| 1012 | if (before > after) { |
| 1013 | protected_at_least_one = true; |
| 1014 | const size_t delta{before - after}; |
| 1015 | num_protected += delta; |
| 1016 | if (num_protected >= max_protect_by_network) { |
| 1017 | break; |
| 1018 | } |