Bitcoin ABC 0.32.4
P2P Digital Currency
eviction.cpp
Go to the documentation of this file.
1// Copyright (c) 2022 The Bitcoin Core developers
2// Distributed under the MIT software license, see the accompanying
3// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5#include <node/eviction.h>
6
7#include <algorithm>
8#include <array>
9#include <chrono>
10#include <cstdint>
11#include <functional>
12#include <map>
13#include <vector>
14
16 const NodeEvictionCandidate &b) {
18}
19
21 const NodeEvictionCandidate &b) {
22 return a.m_connected > b.m_connected;
23}
24
26 const NodeEvictionCandidate &b) {
27 return a.nKeyedNetGroup < b.nKeyedNetGroup;
28}
29
31 const NodeEvictionCandidate &b) {
32 // There is a fall-through here because it is common for a node to have many
33 // peers which have not yet relayed a block.
36 }
37
39 return b.fRelevantServices;
40 }
41
42 return a.m_connected > b.m_connected;
43}
44
46 const NodeEvictionCandidate &b) {
47 // There is a fall-through here because it is common for a node to have more
48 // than a few peers that have not yet relayed txn.
49 if (a.m_last_tx_time != b.m_last_tx_time) {
50 return a.m_last_tx_time < b.m_last_tx_time;
51 }
52
53 if (a.m_relay_txs != b.m_relay_txs) {
54 return b.m_relay_txs;
55 }
56
57 if (a.fBloomFilter != b.fBloomFilter) {
58 return a.fBloomFilter;
59 }
60
61 return a.m_connected > b.m_connected;
62}
63
65 const NodeEvictionCandidate &b) {
66 // There is a fall-through here because it is common for a node to have more
67 // than a few peers that have not yet relayed proofs. This fallback is also
68 // used in the case avalanche is not enabled.
71 }
72
73 return a.m_connected > b.m_connected;
74}
75
76// Pick out the potential block-relay only peers, and sort them by last block
77// time.
79 const NodeEvictionCandidate &b) {
80 if (a.m_relay_txs != b.m_relay_txs) {
81 return a.m_relay_txs;
82 }
83
86 }
87
89 return b.fRelevantServices;
90 }
91
92 return a.m_connected > b.m_connected;
93}
94
96 const NodeEvictionCandidate &b) {
97 // Equality can happen if the nodes have no score or it has not been
98 // computed yet.
101 }
102
103 return a.m_connected > b.m_connected;
104}
105
117 const bool m_is_local;
119 CompareNodeNetworkTime(bool is_local, Network network)
120 : m_is_local(is_local), m_network(network) {}
122 const NodeEvictionCandidate &b) const {
123 if (m_is_local && a.m_is_local != b.m_is_local) {
124 return b.m_is_local;
125 }
126 if ((a.m_network == m_network) != (b.m_network == m_network)) {
127 return b.m_network == m_network;
128 }
129 return a.m_connected > b.m_connected;
130 };
131};
132
135template <typename T, typename Comparator>
137 std::vector<T> &elements, Comparator comparator, size_t k,
138 std::function<bool(const NodeEvictionCandidate &)> predicate =
139 [](const NodeEvictionCandidate &n) { return true; }) {
140 std::sort(elements.begin(), elements.end(), comparator);
141 size_t eraseSize = std::min(k, elements.size());
142 elements.erase(
143 std::remove_if(elements.end() - eraseSize, elements.end(), predicate),
144 elements.end());
145}
146
148 std::vector<NodeEvictionCandidate> &eviction_candidates) {
149 eviction_candidates.erase(
150 std::remove_if(
151 eviction_candidates.begin(), eviction_candidates.end(),
152 [](NodeEvictionCandidate const &n) { return n.m_noban; }),
153 eviction_candidates.end());
154}
155
157 std::vector<NodeEvictionCandidate> &eviction_candidates) {
158 eviction_candidates.erase(
159 std::remove_if(eviction_candidates.begin(), eviction_candidates.end(),
160 [](NodeEvictionCandidate const &n) {
161 return n.m_conn_type != ConnectionType::INBOUND;
162 }),
163 eviction_candidates.end());
164}
165
167 std::vector<NodeEvictionCandidate> &eviction_candidates) {
168 // Protect the half of the remaining nodes which have been connected the
169 // longest. This replicates the non-eviction implicit behavior, and
170 // precludes attacks that start later.
171 // To promote the diversity of our peer connections, reserve up to half of
172 // these protected spots for Tor/onion, localhost and I2P peers, even if
173 // they're not the longest uptime overall. This helps protect these
174 // higher-latency peers that tend to be otherwise disadvantaged under our
175 // eviction criteria.
176 const size_t initial_size = eviction_candidates.size();
177 const size_t total_protect_size{initial_size / 2};
178
179 // Disadvantaged networks to protect: I2P, localhost and Tor/onion. In case
180 // of equal counts, earlier array members have first opportunity to recover
181 // unused slots from the previous iteration.
182 struct Net {
183 bool is_local;
184 Network id;
185 size_t count;
186 };
187 std::array<Net, 3> networks{{{false, NET_I2P, 0},
188 {/* localhost */ true, NET_MAX, 0},
189 {false, NET_ONION, 0}}};
190
191 // Count and store the number of eviction candidates per network.
192 for (Net &n : networks) {
193 n.count = std::count_if(
194 eviction_candidates.cbegin(), eviction_candidates.cend(),
195 [&n](const NodeEvictionCandidate &c) {
196 return n.is_local ? c.m_is_local : c.m_network == n.id;
197 });
198 }
199 // Sort `networks` by ascending candidate count, to give networks having
200 // fewer candidates the first opportunity to recover unused protected slots
201 // from the previous iteration.
202 std::stable_sort(networks.begin(), networks.end(),
203 [](Net a, Net b) { return a.count < b.count; });
204
205 // Protect up to 25% of the eviction candidates by disadvantaged network.
206 const size_t max_protect_by_network{total_protect_size / 2};
207 size_t num_protected{0};
208
209 while (num_protected < max_protect_by_network) {
210 // Count the number of disadvantaged networks from which we have peers
211 // to protect.
212 auto num_networks = std::count_if(networks.begin(), networks.end(),
213 [](const Net &n) { return n.count; });
214 if (num_networks == 0) {
215 break;
216 }
217 const size_t disadvantaged_to_protect{max_protect_by_network -
218 num_protected};
219 const size_t protect_per_network{std::max(
220 disadvantaged_to_protect / num_networks, static_cast<size_t>(1))};
221
222 // Early exit flag if there are no remaining candidates by disadvantaged
223 // network.
224 bool protected_at_least_one{false};
225
226 for (Net &n : networks) {
227 if (n.count == 0) {
228 continue;
229 }
230 const size_t before = eviction_candidates.size();
232 eviction_candidates, CompareNodeNetworkTime(n.is_local, n.id),
233 protect_per_network, [&n](const NodeEvictionCandidate &c) {
234 return n.is_local ? c.m_is_local : c.m_network == n.id;
235 });
236 const size_t after = eviction_candidates.size();
237 if (before > after) {
238 protected_at_least_one = true;
239 const size_t delta{before - after};
240 num_protected += delta;
241 if (num_protected >= max_protect_by_network) {
242 break;
243 }
244 n.count -= delta;
245 }
246 }
247 if (!protected_at_least_one) {
248 break;
249 }
250 }
251
252 // Calculate how many we removed, and update our total number of peers that
253 // we want to protect based on uptime accordingly.
254 assert(num_protected == initial_size - eviction_candidates.size());
255 const size_t remaining_to_protect{total_protect_size - num_protected};
257 remaining_to_protect);
258}
259
260[[nodiscard]] std::optional<NodeId>
261SelectNodeToEvict(std::vector<NodeEvictionCandidate> &&vEvictionCandidates) {
262 // Protect connections with certain characteristics
263
264 ProtectNoBanConnections(vEvictionCandidates);
265
266 ProtectOutboundConnections(vEvictionCandidates);
267
268 // Deterministically select 4 peers to protect by netgroup.
269 // An attacker cannot predict which netgroups will be protected
270 EraseLastKElements(vEvictionCandidates, CompareNetGroupKeyed, 4);
271 // Protect the 8 nodes with the lowest minimum ping time.
272 // An attacker cannot manipulate this metric without physically moving nodes
273 // closer to the target.
274 EraseLastKElements(vEvictionCandidates, ReverseCompareNodeMinPingTime, 8);
275 // Protect 4 nodes that most recently sent us novel transactions accepted
276 // into our mempool. An attacker cannot manipulate this metric without
277 // performing useful work.
278 EraseLastKElements(vEvictionCandidates, CompareNodeTXTime, 4);
279 // Protect 4 nodes that most recently sent us novel proofs accepted
280 // into our proof pool. An attacker cannot manipulate this metric without
281 // performing useful work.
282 // TODO this filter must happen before the last tx time once avalanche is
283 // enabled for pre-consensus.
284 EraseLastKElements(vEvictionCandidates, CompareNodeProofTime, 4);
285 // Protect up to 8 non-tx-relay peers that have sent us novel blocks.
287 [](const NodeEvictionCandidate &n) {
288 return !n.m_relay_txs && n.fRelevantServices;
289 });
290
291 // Protect 4 nodes that most recently sent us novel blocks.
292 // An attacker cannot manipulate this metric without performing useful work.
293 EraseLastKElements(vEvictionCandidates, CompareNodeBlockTime, 4);
294
295 // Protect up to 128 nodes that have the highest avalanche availability
296 // score.
297 EraseLastKElements(vEvictionCandidates, CompareNodeAvailabilityScore, 128,
298 [](NodeEvictionCandidate const &n) {
299 return n.availabilityScore > 0.;
300 });
301
302 // Protect some of the remaining eviction candidates by ratios of desirable
303 // or disadvantaged characteristics.
304 ProtectEvictionCandidatesByRatio(vEvictionCandidates);
305
306 if (vEvictionCandidates.empty()) {
307 return std::nullopt;
308 }
309
310 // If any remaining peers are preferred for eviction consider only them.
311 // This happens after the other preferences since if a peer is really the
312 // best by other criteria (esp relaying blocks)
313 // then we probably don't want to evict it no matter what.
314 if (std::any_of(
315 vEvictionCandidates.begin(), vEvictionCandidates.end(),
316 [](NodeEvictionCandidate const &n) { return n.prefer_evict; })) {
317 vEvictionCandidates.erase(
318 std::remove_if(
319 vEvictionCandidates.begin(), vEvictionCandidates.end(),
320 [](NodeEvictionCandidate const &n) { return !n.prefer_evict; }),
321 vEvictionCandidates.end());
322 }
323
324 // Identify the network group with the most connections and youngest member.
325 // (vEvictionCandidates is already sorted by reverse connect time)
326 uint64_t naMostConnections;
327 unsigned int nMostConnections = 0;
328 std::chrono::seconds nMostConnectionsTime{0};
329 std::map<uint64_t, std::vector<NodeEvictionCandidate>> mapNetGroupNodes;
330 for (const NodeEvictionCandidate &node : vEvictionCandidates) {
331 std::vector<NodeEvictionCandidate> &group =
332 mapNetGroupNodes[node.nKeyedNetGroup];
333 group.push_back(node);
334 const auto grouptime{group[0].m_connected};
335 size_t group_size = group.size();
336 if (group_size > nMostConnections ||
337 (group_size == nMostConnections &&
338 grouptime > nMostConnectionsTime)) {
339 nMostConnections = group_size;
340 nMostConnectionsTime = grouptime;
341 naMostConnections = node.nKeyedNetGroup;
342 }
343 }
344
345 // Reduce to the network group with the most connections
346 vEvictionCandidates = std::move(mapNetGroupNodes[naMostConnections]);
347
348 // Disconnect from the network group with the most connections
349 return vEvictionCandidates.front().id;
350}
static bool CompareNodeBlockTime(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b)
Definition: eviction.cpp:30
static void EraseLastKElements(std::vector< T > &elements, Comparator comparator, size_t k, std::function< bool(const NodeEvictionCandidate &)> predicate=[](const NodeEvictionCandidate &n) { return true;})
Sort an array by the specified comparator, then erase the last K elements where predicate is true.
Definition: eviction.cpp:136
static bool CompareNodeBlockRelayOnlyTime(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b)
Definition: eviction.cpp:78
void ProtectNoBanConnections(std::vector< NodeEvictionCandidate > &eviction_candidates)
Definition: eviction.cpp:147
static bool ReverseCompareNodeTimeConnected(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b)
Definition: eviction.cpp:20
static bool CompareNodeAvailabilityScore(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b)
Definition: eviction.cpp:95
static bool CompareNetGroupKeyed(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b)
Definition: eviction.cpp:25
void ProtectEvictionCandidatesByRatio(std::vector< NodeEvictionCandidate > &eviction_candidates)
Protect desirable or disadvantaged inbound peers from eviction by ratio.
Definition: eviction.cpp:166
std::optional< NodeId > SelectNodeToEvict(std::vector< NodeEvictionCandidate > &&vEvictionCandidates)
Select an inbound peer to evict after filtering out (protecting) peers having distinct,...
Definition: eviction.cpp:261
static bool CompareNodeTXTime(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b)
Definition: eviction.cpp:45
static bool CompareNodeProofTime(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b)
Definition: eviction.cpp:64
void ProtectOutboundConnections(std::vector< NodeEvictionCandidate > &eviction_candidates)
Definition: eviction.cpp:156
static bool ReverseCompareNodeMinPingTime(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b)
Definition: eviction.cpp:15
static unsigned char elements[DATACOUNT][DATALEN]
Definition: tests_impl.h:36
Definition: init.h:31
Network
A network type.
Definition: netaddress.h:44
@ NET_I2P
I2P.
Definition: netaddress.h:59
@ NET_MAX
Dummy value to indicate the number of NET_* constants.
Definition: netaddress.h:69
@ NET_ONION
TOR (v2 or v3)
Definition: netaddress.h:56
Sort eviction candidates by network/localhost and connection uptime.
Definition: eviction.cpp:116
CompareNodeNetworkTime(bool is_local, Network network)
Definition: eviction.cpp:119
const Network m_network
Definition: eviction.cpp:118
bool operator()(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b) const
Definition: eviction.cpp:121
std::chrono::seconds m_last_tx_time
Definition: eviction.h:24
double availabilityScore
Definition: eviction.h:34
std::chrono::seconds m_connected
Definition: eviction.h:20
std::chrono::seconds m_last_block_time
Definition: eviction.h:22
std::chrono::microseconds m_min_ping_time
Definition: eviction.h:21
std::chrono::seconds m_last_proof_time
Definition: eviction.h:23
uint64_t nKeyedNetGroup
Definition: eviction.h:28
static int count
Definition: tests.c:31
assert(!tx.IsCoinBase())