| 176 | } |
| 177 | |
| 178 | [[nodiscard]] std::optional<NodeId> SelectNodeToEvict(std::vector<NodeEvictionCandidate>&& vEvictionCandidates) |
| 179 | { |
| 180 | // Protect connections with certain characteristics |
| 181 | |
| 182 | ProtectNoBanConnections(vEvictionCandidates); |
| 183 | |
| 184 | ProtectOutboundConnections(vEvictionCandidates); |
| 185 | |
| 186 | // Deterministically select 4 peers to protect by netgroup. |
| 187 | // An attacker cannot predict which netgroups will be protected |
| 188 | EraseLastKElements(vEvictionCandidates, CompareNetGroupKeyed, 4); |
| 189 | // Protect the 8 nodes with the lowest minimum ping time. |
| 190 | // An attacker cannot manipulate this metric without physically moving nodes closer to the target. |
| 191 | EraseLastKElements(vEvictionCandidates, ReverseCompareNodeMinPingTime, 8); |
| 192 | // Protect 4 nodes that most recently sent us novel transactions accepted into our mempool. |
| 193 | // An attacker cannot manipulate this metric without performing useful work. |
| 194 | EraseLastKElements(vEvictionCandidates, CompareNodeTXTime, 4); |
| 195 | // Protect up to 8 non-tx-relay peers that have sent us novel blocks. |
| 196 | EraseLastKElements(vEvictionCandidates, CompareNodeBlockRelayOnlyTime, 8, |
| 197 | [](const NodeEvictionCandidate& n) { return !n.m_relay_txs && n.fRelevantServices; }); |
| 198 | |
| 199 | // Protect 4 nodes that most recently sent us novel blocks. |
| 200 | // An attacker cannot manipulate this metric without performing useful work. |
| 201 | EraseLastKElements(vEvictionCandidates, CompareNodeBlockTime, 4); |
| 202 | |
| 203 | // Protect some of the remaining eviction candidates by ratios of desirable |
| 204 | // or disadvantaged characteristics. |
| 205 | ProtectEvictionCandidatesByRatio(vEvictionCandidates); |
| 206 | |
| 207 | if (vEvictionCandidates.empty()) return std::nullopt; |
| 208 | |
| 209 | // If any remaining peers are preferred for eviction consider only them. |
| 210 | // This happens after the other preferences since if a peer is really the best by other criteria (esp relaying blocks) |
| 211 | // then we probably don't want to evict it no matter what. |
| 212 | if (std::any_of(vEvictionCandidates.begin(),vEvictionCandidates.end(),[](NodeEvictionCandidate const &n){return n.prefer_evict;})) { |
| 213 | vEvictionCandidates.erase(std::remove_if(vEvictionCandidates.begin(),vEvictionCandidates.end(), |
| 214 | [](NodeEvictionCandidate const &n){return !n.prefer_evict;}),vEvictionCandidates.end()); |
| 215 | } |
| 216 | |
| 217 | // Identify the network group with the most connections and youngest member. |
| 218 | // (vEvictionCandidates is already sorted by reverse connect time) |
| 219 | uint64_t naMostConnections; |
| 220 | unsigned int nMostConnections = 0; |
| 221 | NodeClock::time_point nMostConnectionsTime{NodeClock::epoch}; |
| 222 | std::map<uint64_t, std::vector<NodeEvictionCandidate> > mapNetGroupNodes; |
| 223 | for (const NodeEvictionCandidate &node : vEvictionCandidates) { |
| 224 | std::vector<NodeEvictionCandidate> &group = mapNetGroupNodes[node.nKeyedNetGroup]; |
| 225 | group.push_back(node); |
| 226 | const auto grouptime{group[0].m_connected}; |
| 227 | |
| 228 | if (group.size() > nMostConnections || (group.size() == nMostConnections && grouptime > nMostConnectionsTime)) { |
| 229 | nMostConnections = group.size(); |
| 230 | nMostConnectionsTime = grouptime; |
| 231 | naMostConnections = node.nKeyedNetGroup; |
| 232 | } |
| 233 | } |
| 234 | |
| 235 | // Reduce to the network group with the most connections |