0diff --git i/src/net.cpp w/src/net.cpp
1index ddf33c38a..64900b8b5 100644
2--- i/src/net.cpp
3+++ w/src/net.cpp
4@@ -41,12 +41,13 @@
5 // with Ubuntu 16.04 LTS and Debian 8 libminiupnpc-dev packages.
6 static_assert(MINIUPNPC_API_VERSION >= 10, "miniUPnPc API version >= 10 assumed");
7 #endif
8
9 #include <algorithm>
10 #include <cstdint>
11+#include <functional> /* std::function */
12 #include <unordered_map>
13
14 #include <math.h>
15
16 /** Maximum number of block-relay-only anchor connections */
17 static constexpr size_t MAX_BLOCK_RELAY_ONLY_ANCHORS = 2;
18@@ -893,38 +894,46 @@ static bool CompareNodeBlockRelayOnlyTime(const NodeEvictionCandidate &a, const
19 if (a.fRelayTxes != b.fRelayTxes) return a.fRelayTxes;
20 if (a.nLastBlockTime != b.nLastBlockTime) return a.nLastBlockTime < b.nLastBlockTime;
21 if (a.fRelevantServices != b.fRelevantServices) return b.fRelevantServices;
22 return a.nTimeConnected > b.nTimeConnected;
23 }
24
25-//! Sort an array by the specified comparator, then erase the last K elements.
26-template<typename T, typename Comparator>
27-static void EraseLastKElements(std::vector<T> &elements, Comparator comparator, size_t k)
28+//! Sort an array by the specified comparator, then erase each one of the last K elements if
29+//! `pred(element)` returns true.
30+template <typename T, typename Comparator>
31+static void EraseLastKElements(
32+ std::vector<T>& elements,
33+ Comparator comparator,
34+ size_t k,
35+ std::function<bool(const T&)> pred = [](const T&) { return true; })
36 {
37 std::sort(elements.begin(), elements.end(), comparator);
38 size_t eraseSize = std::min(k, elements.size());
39- elements.erase(elements.end() - eraseSize, elements.end());
40+ elements.erase(std::remove_if(elements.end() - eraseSize, elements.end(), pred),
41+ elements.end());
42 }
43
44 [[nodiscard]] Optional<NodeId> SelectNodeToEvict(std::vector<NodeEvictionCandidate>&& vEvictionCandidates)
45 {
46 // Protect connections with certain characteristics
47
48+ std::function<bool(const NodeEvictionCandidate&)> pred;
49+
50 // Deterministically select 4 peers to protect by netgroup.
51 // An attacker cannot predict which netgroups will be protected
52 EraseLastKElements(vEvictionCandidates, CompareNetGroupKeyed, 4);
53 // Protect the 8 nodes with the lowest minimum ping time.
54 // An attacker cannot manipulate this metric without physically moving nodes closer to the target.
55 EraseLastKElements(vEvictionCandidates, ReverseCompareNodeMinPingTime, 8);
56 // Protect 4 nodes that most recently sent us novel transactions accepted into our mempool.
57 // An attacker cannot manipulate this metric without performing useful work.
58 EraseLastKElements(vEvictionCandidates, CompareNodeTXTime, 4);
59 // Protect up to 8 non-tx-relay peers that have sent us novel blocks.
60- std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), CompareNodeBlockRelayOnlyTime);
61- size_t erase_size = std::min(size_t(8), vEvictionCandidates.size());
62- vEvictionCandidates.erase(std::remove_if(vEvictionCandidates.end() - erase_size, vEvictionCandidates.end(), [](NodeEvictionCandidate const &n) { return !n.fRelayTxes && n.fRelevantServices; }), vEvictionCandidates.end());
63+ const size_t erase_size = std::min(size_t(8), vEvictionCandidates.size());
64+ pred = [](const NodeEvictionCandidate& n) { return !n.fRelayTxes && n.fRelevantServices; };
65+ EraseLastKElements(vEvictionCandidates, CompareNodeBlockRelayOnlyTime, erase_size, pred);
66
67 // Protect 4 nodes that most recently sent us novel blocks.
68 // An attacker cannot manipulate this metric without performing useful work.
69 EraseLastKElements(vEvictionCandidates, CompareNodeBlockTime, 4);
70
71 // Protect the half of the remaining nodes which have been connected the longest.
72@@ -933,22 +942,22 @@ static void EraseLastKElements(std::vector<T> &elements, Comparator comparator,
73 // they're not longest-uptime overall. This helps protect tor peers, which
74 // tend to be otherwise disadvantaged under our eviction criteria.
75 const size_t initial_size = vEvictionCandidates.size();
76 size_t total_protect_size = initial_size / 2;
77
78 // Pick out up to 1/4 peers that are connected via our tor onion service, sorted by longest uptime.
79- std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), CompareOnionTimeConnected);
80 const size_t local_erase_size = total_protect_size / 2;
81- vEvictionCandidates.erase(std::remove_if(vEvictionCandidates.end() - local_erase_size, vEvictionCandidates.end(), [](NodeEvictionCandidate const &n) { return n.m_is_onion; }), vEvictionCandidates.end());
82+ pred = [](const NodeEvictionCandidate& n) { return n.m_is_onion; };
83+ EraseLastKElements(vEvictionCandidates, CompareOnionTimeConnected, local_erase_size, pred);
84
85 // If no onion peers were removed, extend the same protection to localhost peers, as manually
86 // configured hidden services not using -bind will not be detected as inbound onion connections.
87 if (initial_size == vEvictionCandidates.size()) {
88 // Pick out up to 1/4 peers that are localhost, sorted by longest uptime.
89- std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), CompareLocalHostTimeConnected);
90- vEvictionCandidates.erase(std::remove_if(vEvictionCandidates.end() - local_erase_size, vEvictionCandidates.end(), [](NodeEvictionCandidate const &n) { return n.m_is_local; }), vEvictionCandidates.end());
91+ pred = [](const NodeEvictionCandidate& n) { return n.m_is_local; };
92+ EraseLastKElements(vEvictionCandidates, CompareLocalHostTimeConnected, local_erase_size, pred);
93 }
94
95 // Calculate how many we removed, and update our total number of peers that
96 // we want to protect based on uptime accordingly.
97 total_protect_size -= initial_size - vEvictionCandidates.size();
98 EraseLastKElements(vEvictionCandidates, ReverseCompareNodeTimeConnected, total_protect_size);
99@@ -956,14 +965,15 @@ static void EraseLastKElements(std::vector<T> &elements, Comparator comparator,
100 if (vEvictionCandidates.empty()) return nullopt;
101
102 // If any remaining peers are preferred for eviction consider only them.
103 // This happens after the other preferences since if a peer is really the best by other criteria (esp relaying blocks)
104 // then we probably don't want to evict it no matter what.
105 if (std::any_of(vEvictionCandidates.begin(),vEvictionCandidates.end(),[](NodeEvictionCandidate const &n){return n.prefer_evict;})) {
106- vEvictionCandidates.erase(std::remove_if(vEvictionCandidates.begin(),vEvictionCandidates.end(),
107- [](NodeEvictionCandidate const &n){return !n.prefer_evict;}),vEvictionCandidates.end());
108+ pred = [](const NodeEvictionCandidate& n) { return !n.prefer_evict; };
109+ EraseLastKElements(vEvictionCandidates, ReverseCompareNodeTimeConnected,
110+ vEvictionCandidates.size(), pred);
111 }
112
113 // Identify the network group with the most connections and youngest member.
114 // (vEvictionCandidates is already sorted by reverse connect time)
115 uint64_t naMostConnections;
116 unsigned int nMostConnections = 0;