Something like this may work (untested, feel free to ignore):
<details>
<summary>[patch] EraseLastKElements() with predicate</summary>
diff --git i/src/net.cpp w/src/net.cpp
index ddf33c38a..64900b8b5 100644
--- i/src/net.cpp
+++ w/src/net.cpp
@@ -41,12 +41,13 @@
// with Ubuntu 16.04 LTS and Debian 8 libminiupnpc-dev packages.
static_assert(MINIUPNPC_API_VERSION >= 10, "miniUPnPc API version >= 10 assumed");
#endif
#include <algorithm>
#include <cstdint>
+#include <functional> /* std::function */
#include <unordered_map>
#include <math.h>
/** Maximum number of block-relay-only anchor connections */
static constexpr size_t MAX_BLOCK_RELAY_ONLY_ANCHORS = 2;
@@ -893,38 +894,46 @@ static bool CompareNodeBlockRelayOnlyTime(const NodeEvictionCandidate &a, const
if (a.fRelayTxes != b.fRelayTxes) return a.fRelayTxes;
if (a.nLastBlockTime != b.nLastBlockTime) return a.nLastBlockTime < b.nLastBlockTime;
if (a.fRelevantServices != b.fRelevantServices) return b.fRelevantServices;
return a.nTimeConnected > b.nTimeConnected;
}
-//! Sort an array by the specified comparator, then erase the last K elements.
-template<typename T, typename Comparator>
-static void EraseLastKElements(std::vector<T> &elements, Comparator comparator, size_t k)
+//! Sort an array by the specified comparator, then erase each one of the last K elements if
+//! `pred(element)` returns true.
+template <typename T, typename Comparator>
+static void EraseLastKElements(
+ std::vector<T>& elements,
+ Comparator comparator,
+ size_t k,
+ std::function<bool(const T&)> pred = [](const T&) { return true; })
{
std::sort(elements.begin(), elements.end(), comparator);
size_t eraseSize = std::min(k, elements.size());
- elements.erase(elements.end() - eraseSize, elements.end());
+ elements.erase(std::remove_if(elements.end() - eraseSize, elements.end(), pred),
+ elements.end());
}
[[nodiscard]] Optional<NodeId> SelectNodeToEvict(std::vector<NodeEvictionCandidate>&& vEvictionCandidates)
{
// Protect connections with certain characteristics
+ std::function<bool(const NodeEvictionCandidate&)> pred;
+
// Deterministically select 4 peers to protect by netgroup.
// An attacker cannot predict which netgroups will be protected
EraseLastKElements(vEvictionCandidates, CompareNetGroupKeyed, 4);
// Protect the 8 nodes with the lowest minimum ping time.
// An attacker cannot manipulate this metric without physically moving nodes closer to the target.
EraseLastKElements(vEvictionCandidates, ReverseCompareNodeMinPingTime, 8);
// Protect 4 nodes that most recently sent us novel transactions accepted into our mempool.
// An attacker cannot manipulate this metric without performing useful work.
EraseLastKElements(vEvictionCandidates, CompareNodeTXTime, 4);
// Protect up to 8 non-tx-relay peers that have sent us novel blocks.
- std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), CompareNodeBlockRelayOnlyTime);
- size_t erase_size = std::min(size_t(8), vEvictionCandidates.size());
- vEvictionCandidates.erase(std::remove_if(vEvictionCandidates.end() - erase_size, vEvictionCandidates.end(), [](NodeEvictionCandidate const &n) { return !n.fRelayTxes && n.fRelevantServices; }), vEvictionCandidates.end());
+ const size_t erase_size = std::min(size_t(8), vEvictionCandidates.size());
+ pred = [](const NodeEvictionCandidate& n) { return !n.fRelayTxes && n.fRelevantServices; };
+ EraseLastKElements(vEvictionCandidates, CompareNodeBlockRelayOnlyTime, erase_size, pred);
// Protect 4 nodes that most recently sent us novel blocks.
// An attacker cannot manipulate this metric without performing useful work.
EraseLastKElements(vEvictionCandidates, CompareNodeBlockTime, 4);
// Protect the half of the remaining nodes which have been connected the longest.
@@ -933,22 +942,22 @@ static void EraseLastKElements(std::vector<T> &elements, Comparator comparator,
// they're not longest-uptime overall. This helps protect tor peers, which
// tend to be otherwise disadvantaged under our eviction criteria.
const size_t initial_size = vEvictionCandidates.size();
size_t total_protect_size = initial_size / 2;
// Pick out up to 1/4 peers that are connected via our tor onion service, sorted by longest uptime.
- std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), CompareOnionTimeConnected);
const size_t local_erase_size = total_protect_size / 2;
- vEvictionCandidates.erase(std::remove_if(vEvictionCandidates.end() - local_erase_size, vEvictionCandidates.end(), [](NodeEvictionCandidate const &n) { return n.m_is_onion; }), vEvictionCandidates.end());
+ pred = [](const NodeEvictionCandidate& n) { return n.m_is_onion; };
+ EraseLastKElements(vEvictionCandidates, CompareOnionTimeConnected, local_erase_size, pred);
// If no onion peers were removed, extend the same protection to localhost peers, as manually
// configured hidden services not using -bind will not be detected as inbound onion connections.
if (initial_size == vEvictionCandidates.size()) {
// Pick out up to 1/4 peers that are localhost, sorted by longest uptime.
- std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), CompareLocalHostTimeConnected);
- vEvictionCandidates.erase(std::remove_if(vEvictionCandidates.end() - local_erase_size, vEvictionCandidates.end(), [](NodeEvictionCandidate const &n) { return n.m_is_local; }), vEvictionCandidates.end());
+ pred = [](const NodeEvictionCandidate& n) { return n.m_is_local; };
+ EraseLastKElements(vEvictionCandidates, CompareLocalHostTimeConnected, local_erase_size, pred);
}
// 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);
@@ -956,14 +965,15 @@ static void EraseLastKElements(std::vector<T> &elements, Comparator comparator,
if (vEvictionCandidates.empty()) return nullopt;
// If any remaining peers are preferred for eviction consider only them.
// This happens after the other preferences since if a peer is really the best by other criteria (esp relaying blocks)
// then we probably don't want to evict it no matter what.
if (std::any_of(vEvictionCandidates.begin(),vEvictionCandidates.end(),[](NodeEvictionCandidate const &n){return n.prefer_evict;})) {
- vEvictionCandidates.erase(std::remove_if(vEvictionCandidates.begin(),vEvictionCandidates.end(),
- [](NodeEvictionCandidate const &n){return !n.prefer_evict;}),vEvictionCandidates.end());
+ pred = [](const NodeEvictionCandidate& n) { return !n.prefer_evict; };
+ EraseLastKElements(vEvictionCandidates, ReverseCompareNodeTimeConnected,
+ vEvictionCandidates.size(), pred);
}
// Identify the network group with the most connections and youngest member.
// (vEvictionCandidates is already sorted by reverse connect time)
uint64_t naMostConnections;
unsigned int nMostConnections = 0;
</details>