| 1032 | } |
| 1033 | |
| 1034 | [[nodiscard]] std::optional<NodeId> SelectNodeToEvict(std::vector<NodeEvictionCandidate>&& vEvictionCandidates) |
| 1035 | { |
| 1036 | // Protect connections with certain characteristics |
| 1037 | |
| 1038 | // Deterministically select 4 peers to protect by netgroup. |
| 1039 | // An attacker cannot predict which netgroups will be protected |
| 1040 | EraseLastKElements(vEvictionCandidates, CompareNetGroupKeyed, 4); |
| 1041 | // Protect the 8 nodes with the lowest minimum ping time. |
| 1042 | // An attacker cannot manipulate this metric without physically moving nodes closer to the target. |
| 1043 | EraseLastKElements(vEvictionCandidates, ReverseCompareNodeMinPingTime, 8); |
| 1044 | // Protect 4 nodes that most recently sent us novel transactions accepted into our mempool. |
| 1045 | // An attacker cannot manipulate this metric without performing useful work. |
| 1046 | EraseLastKElements(vEvictionCandidates, CompareNodeTXTime, 4); |
| 1047 | // Protect up to 8 non-tx-relay peers that have sent us novel blocks. |
| 1048 | EraseLastKElements(vEvictionCandidates, CompareNodeBlockRelayOnlyTime, 8, |
| 1049 | [](const NodeEvictionCandidate& n) { return !n.fRelayTxes && n.fRelevantServices; }); |
| 1050 | |
| 1051 | // Protect 4 nodes that most recently sent us novel blocks. |
| 1052 | // An attacker cannot manipulate this metric without performing useful work. |
| 1053 | EraseLastKElements(vEvictionCandidates, CompareNodeBlockTime, 4); |
| 1054 | |
| 1055 | // Protect some of the remaining eviction candidates by ratios of desirable |
| 1056 | // or disadvantaged characteristics. |
| 1057 | ProtectEvictionCandidatesByRatio(vEvictionCandidates); |
| 1058 | |
| 1059 | if (vEvictionCandidates.empty()) return std::nullopt; |
| 1060 | |
| 1061 | // If any remaining peers are preferred for eviction consider only them. |
| 1062 | // This happens after the other preferences since if a peer is really the best by other criteria (esp relaying blocks) |
| 1063 | // then we probably don't want to evict it no matter what. |
| 1064 | if (std::any_of(vEvictionCandidates.begin(),vEvictionCandidates.end(),[](NodeEvictionCandidate const &n){return n.prefer_evict;})) { |
| 1065 | vEvictionCandidates.erase(std::remove_if(vEvictionCandidates.begin(),vEvictionCandidates.end(), |
| 1066 | [](NodeEvictionCandidate const &n){return !n.prefer_evict;}),vEvictionCandidates.end()); |
| 1067 | } |
| 1068 | |
| 1069 | // Identify the network group with the most connections and youngest member. |
| 1070 | // (vEvictionCandidates is already sorted by reverse connect time) |
| 1071 | uint64_t naMostConnections; |
| 1072 | unsigned int nMostConnections = 0; |
| 1073 | std::chrono::seconds nMostConnectionsTime{0}; |
| 1074 | std::map<uint64_t, std::vector<NodeEvictionCandidate> > mapNetGroupNodes; |
| 1075 | for (const NodeEvictionCandidate &node : vEvictionCandidates) { |
| 1076 | std::vector<NodeEvictionCandidate> &group = mapNetGroupNodes[node.nKeyedNetGroup]; |
| 1077 | group.push_back(node); |
| 1078 | const auto grouptime{group[0].m_connected}; |
| 1079 | |
| 1080 | if (group.size() > nMostConnections || (group.size() == nMostConnections && grouptime > nMostConnectionsTime)) { |
| 1081 | nMostConnections = group.size(); |
| 1082 | nMostConnectionsTime = grouptime; |
| 1083 | naMostConnections = node.nKeyedNetGroup; |
| 1084 | } |
| 1085 | } |
| 1086 | |
| 1087 | // Reduce to the network group with the most connections |
| 1088 | vEvictionCandidates = std::move(mapNetGroupNodes[naMostConnections]); |
| 1089 | |
| 1090 | // Disconnect from the network group with the most connections |
| 1091 | return vEvictionCandidates.front().id; |