Bitcoin ABC 0.30.5
P2P Digital Currency
peermanager_tests.cpp
Go to the documentation of this file.
1// Copyright (c) 2020 The Bitcoin developers
2// Distributed under the MIT software license, see the accompanying
3// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
10#include <avalanche/test/util.h>
11#include <cashaddrenc.h>
12#include <config.h>
14#include <core_io.h>
15#include <key_io.h>
16#include <script/standard.h>
17#include <uint256.h>
18#include <util/fs_helpers.h>
19#include <util/time.h>
20#include <util/translation.h>
21#include <validation.h>
22
23#include <test/util/blockindex.h>
24#include <test/util/random.h>
25#include <test/util/setup_common.h>
26
27#include <boost/test/unit_test.hpp>
28
29#include <limits>
30#include <optional>
31#include <unordered_map>
32
33using namespace avalanche;
34
35namespace avalanche {
36namespace {
37 struct TestPeerManager {
38 static bool nodeBelongToPeer(const PeerManager &pm, NodeId nodeid,
39 PeerId peerid) {
40 return pm.forNode(nodeid, [&](const Node &node) {
41 return node.peerid == peerid;
42 });
43 }
44
45 static bool isNodePending(const PeerManager &pm, NodeId nodeid) {
46 auto &pendingNodesView = pm.pendingNodes.get<by_nodeid>();
47 return pendingNodesView.find(nodeid) != pendingNodesView.end();
48 }
49
50 static PeerId getPeerIdForProofId(PeerManager &pm,
51 const ProofId &proofid) {
52 auto &pview = pm.peers.get<by_proofid>();
53 auto it = pview.find(proofid);
54 return it == pview.end() ? NO_PEER : it->peerid;
55 }
56
57 static PeerId registerAndGetPeerId(PeerManager &pm,
58 const ProofRef &proof) {
59 pm.registerProof(proof);
60 return getPeerIdForProofId(pm, proof->getId());
61 }
62
63 static std::vector<uint32_t> getOrderedScores(const PeerManager &pm) {
64 std::vector<uint32_t> scores;
65
66 auto &peerView = pm.peers.get<by_score>();
67 for (const Peer &peer : peerView) {
68 scores.push_back(peer.getScore());
69 }
70
71 return scores;
72 }
73
74 static void cleanupDanglingProofs(
75 PeerManager &pm,
76 std::unordered_set<ProofRef, SaltedProofHasher> &registeredProofs) {
77 pm.cleanupDanglingProofs(registeredProofs);
78 }
79
80 static void cleanupDanglingProofs(PeerManager &pm) {
81 std::unordered_set<ProofRef, SaltedProofHasher> dummy;
82 pm.cleanupDanglingProofs(dummy);
83 }
84
85 static std::optional<RemoteProof> getRemoteProof(const PeerManager &pm,
86 const ProofId &proofid,
87 NodeId nodeid) {
88 auto it = pm.remoteProofs.find(boost::make_tuple(proofid, nodeid));
89 if (it == pm.remoteProofs.end()) {
90 return std::nullopt;
91 }
92 return std::make_optional(*it);
93 }
94
95 static size_t getPeerCount(const PeerManager &pm) {
96 return pm.peers.size();
97 }
98
99 static std::optional<bool>
100 getRemotePresenceStatus(const PeerManager &pm, const ProofId &proofid) {
101 return pm.getRemotePresenceStatus(proofid);
102 }
103
104 static void clearPeers(PeerManager &pm) {
105 std::vector<PeerId> peerIds;
106 for (auto &peer : pm.peers) {
107 peerIds.push_back(peer.peerid);
108 }
109 for (const PeerId &peerid : peerIds) {
110 pm.removePeer(peerid);
111 }
112 BOOST_CHECK_EQUAL(pm.peers.size(), 0);
113 }
114
115 static void setLocalProof(PeerManager &pm, const ProofRef &proof) {
116 pm.localProof = proof;
117 }
118
119 static bool isFlaky(const PeerManager &pm, const ProofId &proofid) {
120 return pm.isFlaky(proofid);
121 }
122 };
123
124 static void addCoin(Chainstate &chainstate, const COutPoint &outpoint,
125 const CKey &key,
126 const Amount amount = PROOF_DUST_THRESHOLD,
127 uint32_t height = 100, bool is_coinbase = false) {
128 CScript script = GetScriptForDestination(PKHash(key.GetPubKey()));
129
130 LOCK(cs_main);
131 CCoinsViewCache &coins = chainstate.CoinsTip();
132 coins.AddCoin(outpoint,
133 Coin(CTxOut(amount, script), height, is_coinbase), false);
134 }
135
136 static COutPoint createUtxo(Chainstate &chainstate, const CKey &key,
137 const Amount amount = PROOF_DUST_THRESHOLD,
138 uint32_t height = 100,
139 bool is_coinbase = false) {
140 COutPoint outpoint(TxId(GetRandHash()), 0);
141 addCoin(chainstate, outpoint, key, amount, height, is_coinbase);
142 return outpoint;
143 }
144
145 static ProofRef
146 buildProof(const CKey &key,
147 const std::vector<std::tuple<COutPoint, Amount>> &outpoints,
148 const CKey &master = CKey::MakeCompressedKey(),
149 int64_t sequence = 1, uint32_t height = 100,
150 bool is_coinbase = false, int64_t expirationTime = 0,
151 const CScript &payoutScript = UNSPENDABLE_ECREG_PAYOUT_SCRIPT) {
152 ProofBuilder pb(sequence, expirationTime, master, payoutScript);
153 for (const auto &[outpoint, amount] : outpoints) {
154 BOOST_CHECK(pb.addUTXO(outpoint, amount, height, is_coinbase, key));
155 }
156 return pb.build();
157 }
158
159 template <typename... Args>
160 static ProofRef
161 buildProofWithOutpoints(const CKey &key,
162 const std::vector<COutPoint> &outpoints,
163 Amount amount, Args &&...args) {
164 std::vector<std::tuple<COutPoint, Amount>> outpointsWithAmount;
165 std::transform(
166 outpoints.begin(), outpoints.end(),
167 std::back_inserter(outpointsWithAmount),
168 [amount](const auto &o) { return std::make_tuple(o, amount); });
169 return buildProof(key, outpointsWithAmount,
170 std::forward<Args>(args)...);
171 }
172
173 static ProofRef
174 buildProofWithSequence(const CKey &key,
175 const std::vector<COutPoint> &outpoints,
176 int64_t sequence) {
177 return buildProofWithOutpoints(key, outpoints, PROOF_DUST_THRESHOLD,
178 key, sequence);
179 }
180} // namespace
181} // namespace avalanche
182
183namespace {
184struct PeerManagerFixture : public TestChain100Setup {
185 PeerManagerFixture() {
186 gArgs.ForceSetArg("-avaproofstakeutxoconfirmations", "1");
187 }
188 ~PeerManagerFixture() {
189 gArgs.ClearForcedArg("-avaproofstakeutxoconfirmations");
190 }
191};
192} // namespace
193
194namespace {
195struct NoCoolDownFixture : public PeerManagerFixture {
196 NoCoolDownFixture() {
197 gArgs.ForceSetArg("-avalancheconflictingproofcooldown", "0");
198 }
199 ~NoCoolDownFixture() {
200 gArgs.ClearForcedArg("-avalancheconflictingproofcooldown");
201 }
202};
203} // namespace
204
205BOOST_FIXTURE_TEST_SUITE(peermanager_tests, PeerManagerFixture)
206
207BOOST_AUTO_TEST_CASE(select_peer_linear) {
208 // No peers.
211
212 // One peer
213 const std::vector<Slot> oneslot = {{100, 100, 23}};
214
215 // Undershoot
216 BOOST_CHECK_EQUAL(selectPeerImpl(oneslot, 0, 300), NO_PEER);
217 BOOST_CHECK_EQUAL(selectPeerImpl(oneslot, 42, 300), NO_PEER);
218 BOOST_CHECK_EQUAL(selectPeerImpl(oneslot, 99, 300), NO_PEER);
219
220 // Nailed it
221 BOOST_CHECK_EQUAL(selectPeerImpl(oneslot, 100, 300), 23);
222 BOOST_CHECK_EQUAL(selectPeerImpl(oneslot, 142, 300), 23);
223 BOOST_CHECK_EQUAL(selectPeerImpl(oneslot, 199, 300), 23);
224
225 // Overshoot
226 BOOST_CHECK_EQUAL(selectPeerImpl(oneslot, 200, 300), NO_PEER);
227 BOOST_CHECK_EQUAL(selectPeerImpl(oneslot, 242, 300), NO_PEER);
228 BOOST_CHECK_EQUAL(selectPeerImpl(oneslot, 299, 300), NO_PEER);
229
230 // Two peers
231 const std::vector<Slot> twoslots = {{100, 100, 69}, {300, 100, 42}};
232
233 // Undershoot
234 BOOST_CHECK_EQUAL(selectPeerImpl(twoslots, 0, 500), NO_PEER);
235 BOOST_CHECK_EQUAL(selectPeerImpl(twoslots, 42, 500), NO_PEER);
236 BOOST_CHECK_EQUAL(selectPeerImpl(twoslots, 99, 500), NO_PEER);
237
238 // First entry
239 BOOST_CHECK_EQUAL(selectPeerImpl(twoslots, 100, 500), 69);
240 BOOST_CHECK_EQUAL(selectPeerImpl(twoslots, 142, 500), 69);
241 BOOST_CHECK_EQUAL(selectPeerImpl(twoslots, 199, 500), 69);
242
243 // In between
244 BOOST_CHECK_EQUAL(selectPeerImpl(twoslots, 200, 500), NO_PEER);
245 BOOST_CHECK_EQUAL(selectPeerImpl(twoslots, 242, 500), NO_PEER);
246 BOOST_CHECK_EQUAL(selectPeerImpl(twoslots, 299, 500), NO_PEER);
247
248 // Second entry
249 BOOST_CHECK_EQUAL(selectPeerImpl(twoslots, 300, 500), 42);
250 BOOST_CHECK_EQUAL(selectPeerImpl(twoslots, 342, 500), 42);
251 BOOST_CHECK_EQUAL(selectPeerImpl(twoslots, 399, 500), 42);
252
253 // Overshoot
254 BOOST_CHECK_EQUAL(selectPeerImpl(twoslots, 400, 500), NO_PEER);
255 BOOST_CHECK_EQUAL(selectPeerImpl(twoslots, 442, 500), NO_PEER);
256 BOOST_CHECK_EQUAL(selectPeerImpl(twoslots, 499, 500), NO_PEER);
257}
258
259BOOST_AUTO_TEST_CASE(select_peer_dichotomic) {
260 std::vector<Slot> slots;
261
262 // 100 peers of size 1 with 1 empty element apart.
263 uint64_t max = 1;
264 for (int i = 0; i < 100; i++) {
265 slots.emplace_back(max, 1, i);
266 max += 2;
267 }
268
270
271 // Check that we get what we expect.
272 for (int i = 0; i < 100; i++) {
273 BOOST_CHECK_EQUAL(selectPeerImpl(slots, 2 * i, max), NO_PEER);
274 BOOST_CHECK_EQUAL(selectPeerImpl(slots, 2 * i + 1, max), i);
275 }
276
277 BOOST_CHECK_EQUAL(selectPeerImpl(slots, max, max), NO_PEER);
278
279 // Update the slots to be heavily skewed toward the last element.
280 slots[99] = slots[99].withScore(101);
281 max = slots[99].getStop();
282 BOOST_CHECK_EQUAL(max, 300);
283
284 for (int i = 0; i < 100; i++) {
285 BOOST_CHECK_EQUAL(selectPeerImpl(slots, 2 * i, max), NO_PEER);
286 BOOST_CHECK_EQUAL(selectPeerImpl(slots, 2 * i + 1, max), i);
287 }
288
289 BOOST_CHECK_EQUAL(selectPeerImpl(slots, 200, max), 99);
290 BOOST_CHECK_EQUAL(selectPeerImpl(slots, 256, max), 99);
291 BOOST_CHECK_EQUAL(selectPeerImpl(slots, 299, max), 99);
292 BOOST_CHECK_EQUAL(selectPeerImpl(slots, 300, max), NO_PEER);
293
294 // Update the slots to be heavily skewed toward the first element.
295 for (int i = 0; i < 100; i++) {
296 slots[i] = slots[i].withStart(slots[i].getStart() + 100);
297 }
298
299 slots[0] = Slot(1, slots[0].getStop() - 1, slots[0].getPeerId());
300 slots[99] = slots[99].withScore(1);
301 max = slots[99].getStop();
302 BOOST_CHECK_EQUAL(max, 300);
303
305 BOOST_CHECK_EQUAL(selectPeerImpl(slots, 1, max), 0);
306 BOOST_CHECK_EQUAL(selectPeerImpl(slots, 42, max), 0);
307
308 for (int i = 0; i < 100; i++) {
309 BOOST_CHECK_EQUAL(selectPeerImpl(slots, 100 + 2 * i + 1, max), i);
310 BOOST_CHECK_EQUAL(selectPeerImpl(slots, 100 + 2 * i + 2, max), NO_PEER);
311 }
312}
313
314BOOST_AUTO_TEST_CASE(select_peer_random) {
315 for (int c = 0; c < 1000; c++) {
316 size_t size = InsecureRandBits(10) + 1;
317 std::vector<Slot> slots;
318 slots.reserve(size);
319
320 uint64_t max = InsecureRandBits(3);
321 auto next = [&]() {
322 uint64_t r = max;
323 max += InsecureRandBits(3);
324 return r;
325 };
326
327 for (size_t i = 0; i < size; i++) {
328 const uint64_t start = next();
329 const uint32_t score = InsecureRandBits(3);
330 max += score;
331 slots.emplace_back(start, score, i);
332 }
333
334 for (int k = 0; k < 100; k++) {
335 uint64_t s = max > 0 ? InsecureRandRange(max) : 0;
336 auto i = selectPeerImpl(slots, s, max);
337 // /!\ Because of the way we construct the vector, the peer id is
338 // always the index. This might not be the case in practice.
339 BOOST_CHECK(i == NO_PEER || slots[i].contains(s));
340 }
341 }
342}
343
344static void addNodeWithScore(Chainstate &active_chainstate,
346 uint32_t score) {
347 auto proof = buildRandomProof(active_chainstate, score);
348 BOOST_CHECK(pm.registerProof(proof));
349 BOOST_CHECK(pm.addNode(node, proof->getId()));
350};
351
352BOOST_AUTO_TEST_CASE(peer_probabilities) {
353 ChainstateManager &chainman = *Assert(m_node.chainman);
354 // No peers.
357
358 const NodeId node0 = 42, node1 = 69, node2 = 37;
359
360 Chainstate &active_chainstate = chainman.ActiveChainstate();
361 // One peer, we always return it.
362 addNodeWithScore(active_chainstate, pm, node0, MIN_VALID_PROOF_SCORE);
363 BOOST_CHECK_EQUAL(pm.selectNode(), node0);
364
365 // Two peers, verify ratio.
366 addNodeWithScore(active_chainstate, pm, node1, 2 * MIN_VALID_PROOF_SCORE);
367
368 std::unordered_map<PeerId, int> results = {};
369 for (int i = 0; i < 10000; i++) {
370 size_t n = pm.selectNode();
371 BOOST_CHECK(n == node0 || n == node1);
372 results[n]++;
373 }
374
375 BOOST_CHECK(abs(2 * results[0] - results[1]) < 500);
376
377 // Three peers, verify ratio.
378 addNodeWithScore(active_chainstate, pm, node2, MIN_VALID_PROOF_SCORE);
379
380 results.clear();
381 for (int i = 0; i < 10000; i++) {
382 size_t n = pm.selectNode();
383 BOOST_CHECK(n == node0 || n == node1 || n == node2);
384 results[n]++;
385 }
386
387 BOOST_CHECK(abs(results[0] - results[1] + results[2]) < 500);
388}
389
391 ChainstateManager &chainman = *Assert(m_node.chainman);
392 // No peers.
395
396 Chainstate &active_chainstate = chainman.ActiveChainstate();
397 // Add 4 peers.
398 std::array<PeerId, 8> peerids;
399 for (int i = 0; i < 4; i++) {
400 auto p = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
401 peerids[i] = TestPeerManager::registerAndGetPeerId(pm, p);
402 BOOST_CHECK(pm.addNode(InsecureRand32(), p->getId()));
403 }
404
405 BOOST_CHECK_EQUAL(pm.getSlotCount(), 40000);
407
408 for (int i = 0; i < 100; i++) {
409 PeerId p = pm.selectPeer();
410 BOOST_CHECK(p == peerids[0] || p == peerids[1] || p == peerids[2] ||
411 p == peerids[3]);
412 }
413
414 // Remove one peer, it nevers show up now.
415 BOOST_CHECK(pm.removePeer(peerids[2]));
416 BOOST_CHECK_EQUAL(pm.getSlotCount(), 40000);
418
419 // Make sure we compact to never get NO_PEER.
420 BOOST_CHECK_EQUAL(pm.compact(), 10000);
421 BOOST_CHECK(pm.verify());
422 BOOST_CHECK_EQUAL(pm.getSlotCount(), 30000);
424
425 for (int i = 0; i < 100; i++) {
426 PeerId p = pm.selectPeer();
427 BOOST_CHECK(p == peerids[0] || p == peerids[1] || p == peerids[3]);
428 }
429
430 // Add 4 more peers.
431 for (int i = 0; i < 4; i++) {
432 auto p = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
433 peerids[i + 4] = TestPeerManager::registerAndGetPeerId(pm, p);
434 BOOST_CHECK(pm.addNode(InsecureRand32(), p->getId()));
435 }
436
437 BOOST_CHECK_EQUAL(pm.getSlotCount(), 70000);
439
440 BOOST_CHECK(pm.removePeer(peerids[0]));
441 BOOST_CHECK_EQUAL(pm.getSlotCount(), 70000);
443
444 // Removing the last entry do not increase fragmentation.
445 BOOST_CHECK(pm.removePeer(peerids[7]));
446 BOOST_CHECK_EQUAL(pm.getSlotCount(), 60000);
448
449 // Make sure we compact to never get NO_PEER.
450 BOOST_CHECK_EQUAL(pm.compact(), 10000);
451 BOOST_CHECK(pm.verify());
452 BOOST_CHECK_EQUAL(pm.getSlotCount(), 50000);
454
455 for (int i = 0; i < 100; i++) {
456 PeerId p = pm.selectPeer();
457 BOOST_CHECK(p == peerids[1] || p == peerids[3] || p == peerids[4] ||
458 p == peerids[5] || p == peerids[6]);
459 }
460
461 // Removing non existent peers fails.
462 BOOST_CHECK(!pm.removePeer(peerids[0]));
463 BOOST_CHECK(!pm.removePeer(peerids[2]));
464 BOOST_CHECK(!pm.removePeer(peerids[7]));
466}
467
468BOOST_AUTO_TEST_CASE(compact_slots) {
469 ChainstateManager &chainman = *Assert(m_node.chainman);
471
472 // Add 4 peers.
473 std::array<PeerId, 4> peerids;
474 for (int i = 0; i < 4; i++) {
475 auto p = buildRandomProof(chainman.ActiveChainstate(),
477 peerids[i] = TestPeerManager::registerAndGetPeerId(pm, p);
478 BOOST_CHECK(pm.addNode(InsecureRand32(), p->getId()));
479 }
480
481 // Remove all peers.
482 for (auto p : peerids) {
483 pm.removePeer(p);
484 }
485
486 BOOST_CHECK_EQUAL(pm.getSlotCount(), 30000);
488
489 for (int i = 0; i < 100; i++) {
491 }
492
493 BOOST_CHECK_EQUAL(pm.compact(), 30000);
494 BOOST_CHECK(pm.verify());
497}
498
500 ChainstateManager &chainman = *Assert(m_node.chainman);
502
503 Chainstate &active_chainstate = chainman.ActiveChainstate();
504
505 // Create one peer.
506 auto proof =
507 buildRandomProof(active_chainstate, 10000000 * MIN_VALID_PROOF_SCORE);
508 BOOST_CHECK(pm.registerProof(proof));
510
511 // Add 4 nodes.
512 const ProofId &proofid = proof->getId();
513 for (int i = 0; i < 4; i++) {
514 BOOST_CHECK(pm.addNode(i, proofid));
515 }
516
517 for (int i = 0; i < 100; i++) {
518 NodeId n = pm.selectNode();
519 BOOST_CHECK(n >= 0 && n < 4);
520 BOOST_CHECK(pm.updateNextRequestTime(n, Now<SteadyMilliseconds>()));
521 }
522
523 // Remove a node, check that it doesn't show up.
524 BOOST_CHECK(pm.removeNode(2));
525
526 for (int i = 0; i < 100; i++) {
527 NodeId n = pm.selectNode();
528 BOOST_CHECK(n == 0 || n == 1 || n == 3);
529 BOOST_CHECK(pm.updateNextRequestTime(n, Now<SteadyMilliseconds>()));
530 }
531
532 // Push a node's timeout in the future, so that it doesn't show up.
533 BOOST_CHECK(pm.updateNextRequestTime(1, Now<SteadyMilliseconds>() +
534 std::chrono::hours(24)));
535
536 for (int i = 0; i < 100; i++) {
537 NodeId n = pm.selectNode();
538 BOOST_CHECK(n == 0 || n == 3);
539 BOOST_CHECK(pm.updateNextRequestTime(n, Now<SteadyMilliseconds>()));
540 }
541
542 // Move a node from a peer to another. This peer has a very low score such
543 // as chances of being picked are 1 in 10 million.
544 addNodeWithScore(active_chainstate, pm, 3, MIN_VALID_PROOF_SCORE);
545
546 int node3selected = 0;
547 for (int i = 0; i < 100; i++) {
548 NodeId n = pm.selectNode();
549 if (n == 3) {
550 // Selecting this node should be exceedingly unlikely.
551 BOOST_CHECK(node3selected++ < 1);
552 } else {
553 BOOST_CHECK_EQUAL(n, 0);
554 }
555 BOOST_CHECK(pm.updateNextRequestTime(n, Now<SteadyMilliseconds>()));
556 }
557}
558
559BOOST_AUTO_TEST_CASE(node_binding) {
560 ChainstateManager &chainman = *Assert(m_node.chainman);
562
563 Chainstate &active_chainstate = chainman.ActiveChainstate();
564
565 auto proof = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
566 const ProofId &proofid = proof->getId();
567
570
571 // Add a bunch of nodes with no associated peer
572 for (int i = 0; i < 10; i++) {
573 BOOST_CHECK(!pm.addNode(i, proofid));
574 BOOST_CHECK(TestPeerManager::isNodePending(pm, i));
577 }
578
579 // Now create the peer and check all the nodes are bound
580 const PeerId peerid = TestPeerManager::registerAndGetPeerId(pm, proof);
581 BOOST_CHECK_NE(peerid, NO_PEER);
582 for (int i = 0; i < 10; i++) {
583 BOOST_CHECK(!TestPeerManager::isNodePending(pm, i));
584 BOOST_CHECK(TestPeerManager::nodeBelongToPeer(pm, i, peerid));
587 }
588 BOOST_CHECK(pm.verify());
589
590 // Disconnect some nodes
591 for (int i = 0; i < 5; i++) {
592 BOOST_CHECK(pm.removeNode(i));
593 BOOST_CHECK(!TestPeerManager::isNodePending(pm, i));
594 BOOST_CHECK(!TestPeerManager::nodeBelongToPeer(pm, i, peerid));
595 BOOST_CHECK_EQUAL(pm.getNodeCount(), 10 - i - 1);
597 }
598
599 // Add nodes when the peer already exists
600 for (int i = 0; i < 5; i++) {
601 BOOST_CHECK(pm.addNode(i, proofid));
602 BOOST_CHECK(!TestPeerManager::isNodePending(pm, i));
603 BOOST_CHECK(TestPeerManager::nodeBelongToPeer(pm, i, peerid));
604 BOOST_CHECK_EQUAL(pm.getNodeCount(), 5 + i + 1);
606 }
607
608 auto alt_proof = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
609 const ProofId &alt_proofid = alt_proof->getId();
610
611 // Update some nodes from a known proof to an unknown proof
612 for (int i = 0; i < 5; i++) {
613 BOOST_CHECK(!pm.addNode(i, alt_proofid));
614 BOOST_CHECK(TestPeerManager::isNodePending(pm, i));
615 BOOST_CHECK(!TestPeerManager::nodeBelongToPeer(pm, i, peerid));
616 BOOST_CHECK_EQUAL(pm.getNodeCount(), 10 - i - 1);
618 }
619
620 auto alt2_proof =
621 buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
622 const ProofId &alt2_proofid = alt2_proof->getId();
623
624 // Update some nodes from an unknown proof to another unknown proof
625 for (int i = 0; i < 5; i++) {
626 BOOST_CHECK(!pm.addNode(i, alt2_proofid));
627 BOOST_CHECK(TestPeerManager::isNodePending(pm, i));
630 }
631
632 // Update some nodes from an unknown proof to a known proof
633 for (int i = 0; i < 5; i++) {
634 BOOST_CHECK(pm.addNode(i, proofid));
635 BOOST_CHECK(!TestPeerManager::isNodePending(pm, i));
636 BOOST_CHECK(TestPeerManager::nodeBelongToPeer(pm, i, peerid));
637 BOOST_CHECK_EQUAL(pm.getNodeCount(), 5 + i + 1);
638 BOOST_CHECK_EQUAL(pm.getPendingNodeCount(), 5 - i - 1);
639 }
640
641 // Remove the peer, the nodes should be pending again
642 BOOST_CHECK(pm.removePeer(peerid));
643 BOOST_CHECK(!pm.exists(proof->getId()));
644 for (int i = 0; i < 10; i++) {
645 BOOST_CHECK(TestPeerManager::isNodePending(pm, i));
646 BOOST_CHECK(!TestPeerManager::nodeBelongToPeer(pm, i, peerid));
649 }
650 BOOST_CHECK(pm.verify());
651
652 // Remove the remaining pending nodes, check the count drops accordingly
653 for (int i = 0; i < 10; i++) {
654 BOOST_CHECK(pm.removeNode(i));
655 BOOST_CHECK(!TestPeerManager::isNodePending(pm, i));
656 BOOST_CHECK(!TestPeerManager::nodeBelongToPeer(pm, i, peerid));
658 BOOST_CHECK_EQUAL(pm.getPendingNodeCount(), 10 - i - 1);
659 }
660}
661
662BOOST_AUTO_TEST_CASE(node_binding_reorg) {
663 gArgs.ForceSetArg("-avaproofstakeutxoconfirmations", "2");
664 ChainstateManager &chainman = *Assert(m_node.chainman);
665
667
668 auto proof = buildRandomProof(chainman.ActiveChainstate(),
670 const ProofId &proofid = proof->getId();
671
672 PeerId peerid = TestPeerManager::registerAndGetPeerId(pm, proof);
673 BOOST_CHECK_NE(peerid, NO_PEER);
674 BOOST_CHECK(pm.verify());
675
676 // Add nodes to our peer
677 for (int i = 0; i < 10; i++) {
678 BOOST_CHECK(pm.addNode(i, proofid));
679 BOOST_CHECK(!TestPeerManager::isNodePending(pm, i));
680 BOOST_CHECK(TestPeerManager::nodeBelongToPeer(pm, i, peerid));
681 }
682
683 // Make the proof immature by reorging to a shorter chain
684 {
686 chainman.ActiveChainstate().InvalidateBlock(
687 state, WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip()));
689 WITH_LOCK(chainman.GetMutex(), return chainman.ActiveHeight()), 99);
690 }
691
692 pm.updatedBlockTip();
693 BOOST_CHECK(pm.isImmature(proofid));
694 BOOST_CHECK(!pm.isBoundToPeer(proofid));
695 for (int i = 0; i < 10; i++) {
696 BOOST_CHECK(TestPeerManager::isNodePending(pm, i));
697 BOOST_CHECK(!TestPeerManager::nodeBelongToPeer(pm, i, peerid));
698 }
699 BOOST_CHECK(pm.verify());
700
701 // Make the proof great again
702 {
703 // Advance the clock so the newly mined block won't collide with the
704 // other deterministically-generated blocks
705 SetMockTime(GetTime() + 20);
706 mineBlocks(1);
708 BOOST_CHECK(chainman.ActiveChainstate().ActivateBestChain(state));
709 LOCK(chainman.GetMutex());
710 BOOST_CHECK_EQUAL(chainman.ActiveHeight(), 100);
711 }
712
713 pm.updatedBlockTip();
714 BOOST_CHECK(!pm.isImmature(proofid));
715 BOOST_CHECK(pm.isBoundToPeer(proofid));
716 // The peerid has certainly been updated
717 peerid = TestPeerManager::registerAndGetPeerId(pm, proof);
718 BOOST_CHECK_NE(peerid, NO_PEER);
719 for (int i = 0; i < 10; i++) {
720 BOOST_CHECK(!TestPeerManager::isNodePending(pm, i));
721 BOOST_CHECK(TestPeerManager::nodeBelongToPeer(pm, i, peerid));
722 }
723 BOOST_CHECK(pm.verify());
724}
725
726BOOST_AUTO_TEST_CASE(proof_conflict) {
727 auto key = CKey::MakeCompressedKey();
728
729 TxId txid1(GetRandHash());
730 TxId txid2(GetRandHash());
731 BOOST_CHECK(txid1 != txid2);
732
734 const int height = 100;
735
736 ChainstateManager &chainman = *Assert(m_node.chainman);
737 for (uint32_t i = 0; i < 10; i++) {
738 addCoin(chainman.ActiveChainstate(), {txid1, i}, key);
739 addCoin(chainman.ActiveChainstate(), {txid2, i}, key);
740 }
741
743 CKey masterKey = CKey::MakeCompressedKey();
744 const auto getPeerId = [&](const std::vector<COutPoint> &outpoints) {
745 return TestPeerManager::registerAndGetPeerId(
746 pm, buildProofWithOutpoints(key, outpoints, v, masterKey, 0, height,
747 false, 0));
748 };
749
750 // Add one peer.
751 const PeerId peer1 = getPeerId({COutPoint(txid1, 0)});
752 BOOST_CHECK(peer1 != NO_PEER);
753
754 // Same proof, same peer.
755 BOOST_CHECK_EQUAL(getPeerId({COutPoint(txid1, 0)}), peer1);
756
757 // Different txid, different proof.
758 const PeerId peer2 = getPeerId({COutPoint(txid2, 0)});
759 BOOST_CHECK(peer2 != NO_PEER && peer2 != peer1);
760
761 // Different index, different proof.
762 const PeerId peer3 = getPeerId({COutPoint(txid1, 1)});
763 BOOST_CHECK(peer3 != NO_PEER && peer3 != peer1);
764
765 // Empty proof, no peer.
766 BOOST_CHECK_EQUAL(getPeerId({}), NO_PEER);
767
768 // Multiple inputs.
769 const PeerId peer4 = getPeerId({COutPoint(txid1, 2), COutPoint(txid2, 2)});
770 BOOST_CHECK(peer4 != NO_PEER && peer4 != peer1);
771
772 // Duplicated input.
773 {
776 COutPoint o(txid1, 3);
777 BOOST_CHECK(pb.addUTXO(o, v, height, false, key));
779 !pm.registerProof(TestProofBuilder::buildDuplicatedStakes(pb)));
780 }
781
782 // Multiple inputs, collision on first input.
783 BOOST_CHECK_EQUAL(getPeerId({COutPoint(txid1, 0), COutPoint(txid2, 4)}),
784 NO_PEER);
785
786 // Mutliple inputs, collision on second input.
787 BOOST_CHECK_EQUAL(getPeerId({COutPoint(txid1, 4), COutPoint(txid2, 0)}),
788 NO_PEER);
789
790 // Mutliple inputs, collision on both inputs.
791 BOOST_CHECK_EQUAL(getPeerId({COutPoint(txid1, 0), COutPoint(txid2, 2)}),
792 NO_PEER);
793}
794
795BOOST_AUTO_TEST_CASE(immature_proofs) {
796 ChainstateManager &chainman = *Assert(m_node.chainman);
797 gArgs.ForceSetArg("-avaproofstakeutxoconfirmations", "2");
799
800 auto key = CKey::MakeCompressedKey();
801 int immatureHeight = 100;
802
803 auto registerImmature = [&](const ProofRef &proof) {
805 BOOST_CHECK(!pm.registerProof(proof, state));
806 BOOST_CHECK(state.GetResult() == ProofRegistrationResult::IMMATURE);
807 };
808
809 auto checkImmature = [&](const ProofRef &proof, bool expectedImmature) {
810 const ProofId &proofid = proof->getId();
811 BOOST_CHECK(pm.exists(proofid));
812
813 BOOST_CHECK_EQUAL(pm.isImmature(proofid), expectedImmature);
814 BOOST_CHECK_EQUAL(pm.isBoundToPeer(proofid), !expectedImmature);
815
816 bool ret = false;
817 pm.forEachPeer([&](const Peer &peer) {
818 if (proof->getId() == peer.proof->getId()) {
819 ret = true;
820 }
821 });
822 BOOST_CHECK_EQUAL(ret, !expectedImmature);
823 };
824
825 // Track immature proofs so we can test them later
826 std::vector<ProofRef> immatureProofs;
827
828 // Fill up the immature pool to test the size limit
829 for (int64_t i = 1; i <= AVALANCHE_MAX_IMMATURE_PROOFS; i++) {
830 COutPoint outpoint = COutPoint(TxId(GetRandHash()), 0);
831 auto proof = buildProofWithOutpoints(
832 key, {outpoint}, i * PROOF_DUST_THRESHOLD, key, 0, immatureHeight);
833 addCoin(chainman.ActiveChainstate(), outpoint, key,
834 i * PROOF_DUST_THRESHOLD, immatureHeight);
835 registerImmature(proof);
836 checkImmature(proof, true);
837 immatureProofs.push_back(proof);
838 }
839
840 // More immature proofs evict lower scoring proofs
841 for (auto i = 0; i < 100; i++) {
842 COutPoint outpoint = COutPoint(TxId(GetRandHash()), 0);
843 auto proof =
844 buildProofWithOutpoints(key, {outpoint}, 200 * PROOF_DUST_THRESHOLD,
845 key, 0, immatureHeight);
846 addCoin(chainman.ActiveChainstate(), outpoint, key,
847 200 * PROOF_DUST_THRESHOLD, immatureHeight);
848 registerImmature(proof);
849 checkImmature(proof, true);
850 immatureProofs.push_back(proof);
851 BOOST_CHECK(!pm.exists(immatureProofs.front()->getId()));
852 immatureProofs.erase(immatureProofs.begin());
853 }
854
855 // Replacement when the pool is full still works
856 {
857 const COutPoint &outpoint =
858 immatureProofs.front()->getStakes()[0].getStake().getUTXO();
859 auto proof =
860 buildProofWithOutpoints(key, {outpoint}, 101 * PROOF_DUST_THRESHOLD,
861 key, 1, immatureHeight);
862 registerImmature(proof);
863 checkImmature(proof, true);
864 immatureProofs.push_back(proof);
865 BOOST_CHECK(!pm.exists(immatureProofs.front()->getId()));
866 immatureProofs.erase(immatureProofs.begin());
867 }
868
869 // Mine a block to increase the chain height, turning all immature proofs to
870 // mature
871 mineBlocks(1);
872 pm.updatedBlockTip();
873 for (const auto &proof : immatureProofs) {
874 checkImmature(proof, false);
875 }
876}
877
878BOOST_AUTO_TEST_CASE(dangling_node) {
879 ChainstateManager &chainman = *Assert(m_node.chainman);
881
882 Chainstate &active_chainstate = chainman.ActiveChainstate();
883
884 auto proof = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
885 PeerId peerid = TestPeerManager::registerAndGetPeerId(pm, proof);
886 BOOST_CHECK_NE(peerid, NO_PEER);
887
888 const SteadyMilliseconds theFuture(Now<SteadyMilliseconds>() +
889 std::chrono::hours(24));
890
891 // Add nodes to this peer and update their request time far in the future
892 for (int i = 0; i < 10; i++) {
893 BOOST_CHECK(pm.addNode(i, proof->getId()));
894 BOOST_CHECK(pm.updateNextRequestTime(i, theFuture));
895 }
896
897 // Remove the peer
898 BOOST_CHECK(pm.removePeer(peerid));
899
900 // Check the nodes are still there
901 for (int i = 0; i < 10; i++) {
902 BOOST_CHECK(pm.forNode(i, [](const Node &n) { return true; }));
903 }
904
905 // Build a new one
906 proof = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
907 peerid = TestPeerManager::registerAndGetPeerId(pm, proof);
908 BOOST_CHECK_NE(peerid, NO_PEER);
909
910 // Update the nodes with the new proof
911 for (int i = 0; i < 10; i++) {
912 BOOST_CHECK(pm.addNode(i, proof->getId()));
914 i, [&](const Node &n) { return n.nextRequestTime == theFuture; }));
915 }
916
917 // Remove the peer
918 BOOST_CHECK(pm.removePeer(peerid));
919
920 // Disconnect the nodes
921 for (int i = 0; i < 10; i++) {
922 BOOST_CHECK(pm.removeNode(i));
923 }
924}
925
926BOOST_AUTO_TEST_CASE(proof_accessors) {
927 ChainstateManager &chainman = *Assert(m_node.chainman);
929
930 constexpr int numProofs = 10;
931
932 std::vector<ProofRef> proofs;
933 proofs.reserve(numProofs);
934 for (int i = 0; i < numProofs; i++) {
935 proofs.push_back(buildRandomProof(chainman.ActiveChainstate(),
937 }
938
939 for (int i = 0; i < numProofs; i++) {
940 BOOST_CHECK(pm.registerProof(proofs[i]));
941
942 {
944 // Fail to add an existing proof
945 BOOST_CHECK(!pm.registerProof(proofs[i], state));
946 BOOST_CHECK(state.GetResult() ==
947 ProofRegistrationResult::ALREADY_REGISTERED);
948 }
949
950 for (int added = 0; added <= i; added++) {
951 auto proof = pm.getProof(proofs[added]->getId());
952 BOOST_CHECK(proof != nullptr);
953
954 const ProofId &proofid = proof->getId();
955 BOOST_CHECK_EQUAL(proofid, proofs[added]->getId());
956 }
957 }
958
959 // No stake, copied from proof_tests.cpp
960 const std::string badProofHex(
961 "96527eae083f1f24625f049d9e54bb9a21023beefdde700a6bc02036335b4df141c8b"
962 "c67bb05a971f5ac2745fd683797dde3002321023beefdde700a6bc02036335b4df141"
963 "c8bc67bb05a971f5ac2745fd683797dde3ac135da984db510334abe41134e3d4ef09a"
964 "d006b1152be8bc413182bf6f947eac1f8580fe265a382195aa2d73935cabf86d90a8f"
965 "666d0a62385ae24732eca51575");
967 auto badProof = RCUPtr<Proof>::make();
968 BOOST_CHECK(Proof::FromHex(*badProof, badProofHex, error));
969
971 BOOST_CHECK(!pm.registerProof(badProof, state));
972 BOOST_CHECK(state.GetResult() == ProofRegistrationResult::INVALID);
973}
974
975BOOST_FIXTURE_TEST_CASE(conflicting_proof_rescan, NoCoolDownFixture) {
976 ChainstateManager &chainman = *Assert(m_node.chainman);
978
979 const CKey key = CKey::MakeCompressedKey();
980
981 Chainstate &active_chainstate = chainman.ActiveChainstate();
982
983 const COutPoint conflictingOutpoint = createUtxo(active_chainstate, key);
984 const COutPoint outpointToSend = createUtxo(active_chainstate, key);
985
986 ProofRef proofToInvalidate =
987 buildProofWithSequence(key, {conflictingOutpoint, outpointToSend}, 20);
988 BOOST_CHECK(pm.registerProof(proofToInvalidate));
989
990 ProofRef conflictingProof =
991 buildProofWithSequence(key, {conflictingOutpoint}, 10);
993 BOOST_CHECK(!pm.registerProof(conflictingProof, state));
994 BOOST_CHECK(state.GetResult() == ProofRegistrationResult::CONFLICTING);
995 BOOST_CHECK(pm.isInConflictingPool(conflictingProof->getId()));
996
997 {
998 LOCK(cs_main);
999 CCoinsViewCache &coins = active_chainstate.CoinsTip();
1000 // Make proofToInvalidate invalid
1001 coins.SpendCoin(outpointToSend);
1002 }
1003
1004 pm.updatedBlockTip();
1005
1006 BOOST_CHECK(!pm.exists(proofToInvalidate->getId()));
1007
1008 BOOST_CHECK(!pm.isInConflictingPool(conflictingProof->getId()));
1009 BOOST_CHECK(pm.isBoundToPeer(conflictingProof->getId()));
1010}
1011
1012BOOST_FIXTURE_TEST_CASE(conflicting_proof_selection, NoCoolDownFixture) {
1013 const CKey key = CKey::MakeCompressedKey();
1014
1015 const Amount amount(PROOF_DUST_THRESHOLD);
1016 const uint32_t height = 100;
1017 const bool is_coinbase = false;
1018
1019 ChainstateManager &chainman = *Assert(m_node.chainman);
1020 Chainstate &active_chainstate = chainman.ActiveChainstate();
1021
1022 // This will be the conflicting UTXO for all the following proofs
1023 auto conflictingOutpoint = createUtxo(active_chainstate, key, amount);
1024
1025 auto proof_base = buildProofWithSequence(key, {conflictingOutpoint}, 10);
1026
1027 ConflictingProofComparator comparator;
1028 auto checkPreferred = [&](const ProofRef &candidate,
1029 const ProofRef &reference, bool expectAccepted) {
1030 BOOST_CHECK_EQUAL(comparator(candidate, reference), expectAccepted);
1031 BOOST_CHECK_EQUAL(comparator(reference, candidate), !expectAccepted);
1032
1034 BOOST_CHECK(pm.registerProof(reference));
1035 BOOST_CHECK(pm.isBoundToPeer(reference->getId()));
1036
1038 BOOST_CHECK_EQUAL(pm.registerProof(candidate, state), expectAccepted);
1039 BOOST_CHECK_EQUAL(state.IsValid(), expectAccepted);
1040 BOOST_CHECK_EQUAL(state.GetResult() ==
1041 ProofRegistrationResult::CONFLICTING,
1042 !expectAccepted);
1043
1044 BOOST_CHECK_EQUAL(pm.isBoundToPeer(candidate->getId()), expectAccepted);
1046 !expectAccepted);
1047
1048 BOOST_CHECK_EQUAL(pm.isBoundToPeer(reference->getId()),
1049 !expectAccepted);
1050 BOOST_CHECK_EQUAL(pm.isInConflictingPool(reference->getId()),
1051 expectAccepted);
1052 };
1053
1054 // Same master key, lower sequence number
1055 checkPreferred(buildProofWithSequence(key, {conflictingOutpoint}, 9),
1056 proof_base, false);
1057 // Same master key, higher sequence number
1058 checkPreferred(buildProofWithSequence(key, {conflictingOutpoint}, 11),
1059 proof_base, true);
1060
1061 auto buildProofFromAmounts = [&](const CKey &master,
1062 std::vector<Amount> &&amounts) {
1063 std::vector<std::tuple<COutPoint, Amount>> outpointsWithAmount{
1064 {conflictingOutpoint, amount}};
1065 std::transform(amounts.begin(), amounts.end(),
1066 std::back_inserter(outpointsWithAmount),
1067 [&key, &active_chainstate](const Amount amount) {
1068 return std::make_tuple(
1069 createUtxo(active_chainstate, key, amount),
1070 amount);
1071 });
1072 return buildProof(key, outpointsWithAmount, master, 0, height,
1073 is_coinbase, 0);
1074 };
1075
1076 auto proof_multiUtxo = buildProofFromAmounts(
1078
1079 // Test for both the same master and a different one. The sequence number
1080 // is the same for all these tests.
1081 for (const CKey &k : {key, CKey::MakeCompressedKey()}) {
1082 // Low amount
1083 checkPreferred(buildProofFromAmounts(
1085 proof_multiUtxo, false);
1086 // High amount
1087 checkPreferred(buildProofFromAmounts(k, {2 * PROOF_DUST_THRESHOLD,
1089 proof_multiUtxo, true);
1090 // Same amount, low stake count
1091 checkPreferred(buildProofFromAmounts(k, {4 * PROOF_DUST_THRESHOLD}),
1092 proof_multiUtxo, true);
1093 // Same amount, high stake count
1094 checkPreferred(buildProofFromAmounts(k, {2 * PROOF_DUST_THRESHOLD,
1097 proof_multiUtxo, false);
1098 // Same amount, same stake count, selection is done on proof id
1099 auto proofSimilar = buildProofFromAmounts(
1101 checkPreferred(proofSimilar, proof_multiUtxo,
1102 proofSimilar->getId() < proof_multiUtxo->getId());
1103 }
1104}
1105
1106BOOST_AUTO_TEST_CASE(conflicting_immature_proofs) {
1107 ChainstateManager &chainman = *Assert(m_node.chainman);
1108 gArgs.ForceSetArg("-avaproofstakeutxoconfirmations", "2");
1110
1111 const CKey key = CKey::MakeCompressedKey();
1112
1113 Chainstate &active_chainstate = chainman.ActiveChainstate();
1114
1115 const COutPoint conflictingOutpoint = createUtxo(active_chainstate, key);
1116 const COutPoint matureOutpoint =
1117 createUtxo(active_chainstate, key, PROOF_DUST_THRESHOLD, 99);
1118
1119 auto immature10 = buildProofWithSequence(key, {conflictingOutpoint}, 10);
1120 auto immature20 =
1121 buildProofWithSequence(key, {conflictingOutpoint, matureOutpoint}, 20);
1122
1123 BOOST_CHECK(!pm.registerProof(immature10));
1124 BOOST_CHECK(pm.isImmature(immature10->getId()));
1125
1126 BOOST_CHECK(!pm.registerProof(immature20));
1127 BOOST_CHECK(pm.isImmature(immature20->getId()));
1128 BOOST_CHECK(!pm.exists(immature10->getId()));
1129
1130 // Build and register a valid proof that will conflict with the immature one
1131 auto proof30 = buildProofWithOutpoints(key, {matureOutpoint},
1132 PROOF_DUST_THRESHOLD, key, 30, 99);
1133 BOOST_CHECK(pm.registerProof(proof30));
1134 BOOST_CHECK(pm.isBoundToPeer(proof30->getId()));
1135
1136 // Reorg to a shorter chain to make proof30 immature
1137 {
1139 active_chainstate.InvalidateBlock(
1140 state, WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip()));
1142 WITH_LOCK(chainman.GetMutex(), return chainman.ActiveHeight()), 99);
1143 }
1144
1145 // Check that a rescan will also select the preferred immature proof, in
1146 // this case proof30 will replace immature20.
1147 pm.updatedBlockTip();
1148
1149 BOOST_CHECK(!pm.isBoundToPeer(proof30->getId()));
1150 BOOST_CHECK(pm.isImmature(proof30->getId()));
1151 BOOST_CHECK(!pm.exists(immature20->getId()));
1152}
1153
1154BOOST_FIXTURE_TEST_CASE(preferred_conflicting_proof, NoCoolDownFixture) {
1155 ChainstateManager &chainman = *Assert(m_node.chainman);
1157
1158 const CKey key = CKey::MakeCompressedKey();
1159 const COutPoint conflictingOutpoint =
1160 createUtxo(chainman.ActiveChainstate(), key);
1161
1162 auto proofSeq10 = buildProofWithSequence(key, {conflictingOutpoint}, 10);
1163 auto proofSeq20 = buildProofWithSequence(key, {conflictingOutpoint}, 20);
1164 auto proofSeq30 = buildProofWithSequence(key, {conflictingOutpoint}, 30);
1165
1166 BOOST_CHECK(pm.registerProof(proofSeq30));
1167 BOOST_CHECK(pm.isBoundToPeer(proofSeq30->getId()));
1168 BOOST_CHECK(!pm.isInConflictingPool(proofSeq30->getId()));
1169
1170 // proofSeq10 is a worst candidate than proofSeq30, so it goes to the
1171 // conflicting pool.
1172 BOOST_CHECK(!pm.registerProof(proofSeq10));
1173 BOOST_CHECK(pm.isBoundToPeer(proofSeq30->getId()));
1174 BOOST_CHECK(!pm.isBoundToPeer(proofSeq10->getId()));
1175 BOOST_CHECK(pm.isInConflictingPool(proofSeq10->getId()));
1176
1177 // proofSeq20 is a worst candidate than proofSeq30 but a better one than
1178 // proogSeq10, so it replaces it in the conflicting pool and proofSeq10 is
1179 // evicted.
1180 BOOST_CHECK(!pm.registerProof(proofSeq20));
1181 BOOST_CHECK(pm.isBoundToPeer(proofSeq30->getId()));
1182 BOOST_CHECK(!pm.isBoundToPeer(proofSeq20->getId()));
1183 BOOST_CHECK(pm.isInConflictingPool(proofSeq20->getId()));
1184 BOOST_CHECK(!pm.exists(proofSeq10->getId()));
1185}
1186
1187BOOST_FIXTURE_TEST_CASE(update_next_conflict_time, NoCoolDownFixture) {
1188 ChainstateManager &chainman = *Assert(m_node.chainman);
1190
1191 auto now = GetTime<std::chrono::seconds>();
1192 SetMockTime(now.count());
1193
1194 // Updating the time of an unknown peer should fail
1195 for (size_t i = 0; i < 10; i++) {
1197 PeerId(GetRand<int>(1000)), now));
1198 }
1199
1200 auto proof =
1202 PeerId peerid = TestPeerManager::registerAndGetPeerId(pm, proof);
1203
1204 auto checkNextPossibleConflictTime = [&](std::chrono::seconds expected) {
1205 BOOST_CHECK(pm.forPeer(proof->getId(), [&](const Peer &p) {
1206 return p.nextPossibleConflictTime == expected;
1207 }));
1208 };
1209
1210 checkNextPossibleConflictTime(now);
1211
1212 // Move the time in the past is not possible
1214 peerid, now - std::chrono::seconds{1}));
1215 checkNextPossibleConflictTime(now);
1216
1218 peerid, now + std::chrono::seconds{1}));
1219 checkNextPossibleConflictTime(now + std::chrono::seconds{1});
1220}
1221
1222BOOST_FIXTURE_TEST_CASE(register_force_accept, NoCoolDownFixture) {
1223 ChainstateManager &chainman = *Assert(m_node.chainman);
1225
1226 const CKey key = CKey::MakeCompressedKey();
1227
1228 const COutPoint conflictingOutpoint =
1229 createUtxo(chainman.ActiveChainstate(), key);
1230
1231 auto proofSeq10 = buildProofWithSequence(key, {conflictingOutpoint}, 10);
1232 auto proofSeq20 = buildProofWithSequence(key, {conflictingOutpoint}, 20);
1233 auto proofSeq30 = buildProofWithSequence(key, {conflictingOutpoint}, 30);
1234
1235 BOOST_CHECK(pm.registerProof(proofSeq30));
1236 BOOST_CHECK(pm.isBoundToPeer(proofSeq30->getId()));
1237 BOOST_CHECK(!pm.isInConflictingPool(proofSeq30->getId()));
1238
1239 // proofSeq20 is a worst candidate than proofSeq30, so it goes to the
1240 // conflicting pool.
1241 BOOST_CHECK(!pm.registerProof(proofSeq20));
1242 BOOST_CHECK(pm.isBoundToPeer(proofSeq30->getId()));
1243 BOOST_CHECK(pm.isInConflictingPool(proofSeq20->getId()));
1244
1245 // We can force the acceptance of proofSeq20
1246 using RegistrationMode = avalanche::PeerManager::RegistrationMode;
1247 BOOST_CHECK(pm.registerProof(proofSeq20, RegistrationMode::FORCE_ACCEPT));
1248 BOOST_CHECK(pm.isBoundToPeer(proofSeq20->getId()));
1249 BOOST_CHECK(pm.isInConflictingPool(proofSeq30->getId()));
1250
1251 // We can also force the acceptance of a proof which is not already in the
1252 // conflicting pool.
1253 BOOST_CHECK(!pm.registerProof(proofSeq10));
1254 BOOST_CHECK(!pm.exists(proofSeq10->getId()));
1255
1256 BOOST_CHECK(pm.registerProof(proofSeq10, RegistrationMode::FORCE_ACCEPT));
1257 BOOST_CHECK(pm.isBoundToPeer(proofSeq10->getId()));
1258 BOOST_CHECK(!pm.exists(proofSeq20->getId()));
1259 BOOST_CHECK(pm.isInConflictingPool(proofSeq30->getId()));
1260
1261 // Attempting to register again fails, and has no impact on the pools
1262 for (size_t i = 0; i < 10; i++) {
1263 BOOST_CHECK(!pm.registerProof(proofSeq10));
1265 !pm.registerProof(proofSeq10, RegistrationMode::FORCE_ACCEPT));
1266
1267 BOOST_CHECK(pm.isBoundToPeer(proofSeq10->getId()));
1268 BOOST_CHECK(!pm.exists(proofSeq20->getId()));
1269 BOOST_CHECK(pm.isInConflictingPool(proofSeq30->getId()));
1270 }
1271
1272 // Revert between proofSeq10 and proofSeq30 a few times
1273 for (size_t i = 0; i < 10; i++) {
1275 pm.registerProof(proofSeq30, RegistrationMode::FORCE_ACCEPT));
1276
1277 BOOST_CHECK(pm.isBoundToPeer(proofSeq30->getId()));
1278 BOOST_CHECK(pm.isInConflictingPool(proofSeq10->getId()));
1279
1281 pm.registerProof(proofSeq10, RegistrationMode::FORCE_ACCEPT));
1282
1283 BOOST_CHECK(pm.isBoundToPeer(proofSeq10->getId()));
1284 BOOST_CHECK(pm.isInConflictingPool(proofSeq30->getId()));
1285 }
1286}
1287
1288BOOST_FIXTURE_TEST_CASE(evicted_proof, NoCoolDownFixture) {
1289 ChainstateManager &chainman = *Assert(m_node.chainman);
1291
1292 const CKey key = CKey::MakeCompressedKey();
1293
1294 const COutPoint conflictingOutpoint =
1295 createUtxo(chainman.ActiveChainstate(), key);
1296
1297 auto proofSeq10 = buildProofWithSequence(key, {conflictingOutpoint}, 10);
1298 auto proofSeq20 = buildProofWithSequence(key, {conflictingOutpoint}, 20);
1299 auto proofSeq30 = buildProofWithSequence(key, {conflictingOutpoint}, 30);
1300
1301 {
1303 BOOST_CHECK(pm.registerProof(proofSeq30, state));
1304 BOOST_CHECK(state.IsValid());
1305 }
1306
1307 {
1309 BOOST_CHECK(!pm.registerProof(proofSeq20, state));
1310 BOOST_CHECK(state.GetResult() == ProofRegistrationResult::CONFLICTING);
1311 }
1312
1313 {
1315 BOOST_CHECK(!pm.registerProof(proofSeq10, state));
1316 BOOST_CHECK(state.GetResult() == ProofRegistrationResult::REJECTED);
1317 }
1318}
1319
1320BOOST_AUTO_TEST_CASE(conflicting_proof_cooldown) {
1321 ChainstateManager &chainman = *Assert(m_node.chainman);
1323
1324 const CKey key = CKey::MakeCompressedKey();
1325
1326 const COutPoint conflictingOutpoint =
1327 createUtxo(chainman.ActiveChainstate(), key);
1328
1329 auto proofSeq20 = buildProofWithSequence(key, {conflictingOutpoint}, 20);
1330 auto proofSeq30 = buildProofWithSequence(key, {conflictingOutpoint}, 30);
1331 auto proofSeq40 = buildProofWithSequence(key, {conflictingOutpoint}, 40);
1332
1333 int64_t conflictingProofCooldown = 100;
1334 gArgs.ForceSetArg("-avalancheconflictingproofcooldown",
1335 strprintf("%d", conflictingProofCooldown));
1336
1337 int64_t now = GetTime();
1338
1339 auto increaseMockTime = [&](int64_t s) {
1340 now += s;
1341 SetMockTime(now);
1342 };
1343 increaseMockTime(0);
1344
1345 BOOST_CHECK(pm.registerProof(proofSeq30));
1346 BOOST_CHECK(pm.isBoundToPeer(proofSeq30->getId()));
1347
1348 auto checkRegistrationFailure = [&](const ProofRef &proof,
1349 ProofRegistrationResult reason) {
1351 BOOST_CHECK(!pm.registerProof(proof, state));
1352 BOOST_CHECK(state.GetResult() == reason);
1353 };
1354
1355 // Registering a conflicting proof will fail due to the conflicting proof
1356 // cooldown
1357 checkRegistrationFailure(proofSeq20,
1358 ProofRegistrationResult::COOLDOWN_NOT_ELAPSED);
1359 BOOST_CHECK(!pm.exists(proofSeq20->getId()));
1360
1361 // The cooldown applies as well if the proof is the favorite
1362 checkRegistrationFailure(proofSeq40,
1363 ProofRegistrationResult::COOLDOWN_NOT_ELAPSED);
1364 BOOST_CHECK(!pm.exists(proofSeq40->getId()));
1365
1366 // Elapse the cooldown
1367 increaseMockTime(conflictingProofCooldown);
1368
1369 // The proof will now be added to conflicting pool
1370 checkRegistrationFailure(proofSeq20, ProofRegistrationResult::CONFLICTING);
1371 BOOST_CHECK(pm.isInConflictingPool(proofSeq20->getId()));
1372
1373 // But no other
1374 checkRegistrationFailure(proofSeq40,
1375 ProofRegistrationResult::COOLDOWN_NOT_ELAPSED);
1376 BOOST_CHECK(!pm.exists(proofSeq40->getId()));
1377 BOOST_CHECK(pm.isInConflictingPool(proofSeq20->getId()));
1378
1379 // Elapse the cooldown
1380 increaseMockTime(conflictingProofCooldown);
1381
1382 // The proof will now be accepted to replace proofSeq30, proofSeq30 will
1383 // move to the conflicting pool, and proofSeq20 will be evicted.
1384 BOOST_CHECK(pm.registerProof(proofSeq40));
1385 BOOST_CHECK(pm.isBoundToPeer(proofSeq40->getId()));
1386 BOOST_CHECK(pm.isInConflictingPool(proofSeq30->getId()));
1387 BOOST_CHECK(!pm.exists(proofSeq20->getId()));
1388
1389 gArgs.ClearForcedArg("-avalancheconflictingproofcooldown");
1390}
1391
1392BOOST_FIXTURE_TEST_CASE(reject_proof, NoCoolDownFixture) {
1393 ChainstateManager &chainman = *Assert(m_node.chainman);
1394 gArgs.ForceSetArg("-avaproofstakeutxoconfirmations", "2");
1396
1397 const CKey key = CKey::MakeCompressedKey();
1398
1399 Chainstate &active_chainstate = chainman.ActiveChainstate();
1400
1401 const COutPoint conflictingOutpoint =
1402 createUtxo(active_chainstate, key, PROOF_DUST_THRESHOLD, 99);
1403 const COutPoint immatureOutpoint = createUtxo(active_chainstate, key);
1404
1405 // The good, the bad and the ugly
1406 auto proofSeq10 = buildProofWithOutpoints(
1407 key, {conflictingOutpoint}, PROOF_DUST_THRESHOLD, key, 10, 99);
1408 auto proofSeq20 = buildProofWithOutpoints(
1409 key, {conflictingOutpoint}, PROOF_DUST_THRESHOLD, key, 20, 99);
1410 auto immature30 = buildProofWithSequence(
1411 key, {conflictingOutpoint, immatureOutpoint}, 30);
1412
1413 BOOST_CHECK(pm.registerProof(proofSeq20));
1414 BOOST_CHECK(!pm.registerProof(proofSeq10));
1415 BOOST_CHECK(!pm.registerProof(immature30));
1416
1417 BOOST_CHECK(pm.isBoundToPeer(proofSeq20->getId()));
1418 BOOST_CHECK(pm.isInConflictingPool(proofSeq10->getId()));
1419 BOOST_CHECK(pm.isImmature(immature30->getId()));
1420
1421 // Rejecting a proof that doesn't exist should fail
1422 for (size_t i = 0; i < 10; i++) {
1429 }
1430
1431 auto checkRejectDefault = [&](const ProofId &proofid) {
1432 BOOST_CHECK(pm.exists(proofid));
1433 const bool isImmature = pm.isImmature(proofid);
1436 BOOST_CHECK(!pm.isBoundToPeer(proofid));
1437 BOOST_CHECK_EQUAL(pm.exists(proofid), !isImmature);
1438 };
1439
1440 auto checkRejectInvalidate = [&](const ProofId &proofid) {
1441 BOOST_CHECK(pm.exists(proofid));
1444 };
1445
1446 // Reject from the immature pool
1447 checkRejectDefault(immature30->getId());
1448 BOOST_CHECK(!pm.registerProof(immature30));
1449 BOOST_CHECK(pm.isImmature(immature30->getId()));
1450 checkRejectInvalidate(immature30->getId());
1451
1452 // Reject from the conflicting pool
1453 checkRejectDefault(proofSeq10->getId());
1454 checkRejectInvalidate(proofSeq10->getId());
1455
1456 // Add again a proof to the conflicting pool
1457 BOOST_CHECK(!pm.registerProof(proofSeq10));
1458 BOOST_CHECK(pm.isInConflictingPool(proofSeq10->getId()));
1459
1460 // Reject from the valid pool, default mode
1461 checkRejectDefault(proofSeq20->getId());
1462
1463 // The conflicting proof should be promoted to a peer
1464 BOOST_CHECK(!pm.isInConflictingPool(proofSeq10->getId()));
1465 BOOST_CHECK(pm.isBoundToPeer(proofSeq10->getId()));
1466
1467 // Reject from the valid pool, invalidate mode
1468 checkRejectInvalidate(proofSeq10->getId());
1469
1470 // The conflicting proof should also be promoted to a peer
1471 BOOST_CHECK(!pm.isInConflictingPool(proofSeq20->getId()));
1472 BOOST_CHECK(pm.isBoundToPeer(proofSeq20->getId()));
1473}
1474
1475BOOST_AUTO_TEST_CASE(should_request_more_nodes) {
1476 ChainstateManager &chainman = *Assert(m_node.chainman);
1478
1479 // Set mock time so that proof registration time is predictable and
1480 // testable.
1482
1483 auto proof =
1485 BOOST_CHECK(pm.registerProof(proof));
1486 // Not dangling yet, the proof will remain active for some time before it
1487 // turns dangling if no node is connecting in the meantime.
1488 BOOST_CHECK(!pm.isDangling(proof->getId()));
1489
1490 // We have no nodes, so select node will fail and flag that we need more
1491 // nodes
1494
1495 for (size_t i = 0; i < 10; i++) {
1496 // The flag will not trigger again until we fail to select nodes again
1498 }
1499
1500 // Add a few nodes.
1501 const ProofId &proofid = proof->getId();
1502 for (size_t i = 0; i < 10; i++) {
1503 BOOST_CHECK(pm.addNode(i, proofid));
1504 }
1505
1506 BOOST_CHECK(!pm.isDangling(proof->getId()));
1507
1508 auto cooldownTimepoint = Now<SteadyMilliseconds>() + 10s;
1509
1510 // All the nodes can be selected once
1511 for (size_t i = 0; i < 10; i++) {
1512 NodeId selectedId = pm.selectNode();
1513 BOOST_CHECK_NE(selectedId, NO_NODE);
1514 BOOST_CHECK(pm.updateNextRequestTime(selectedId, cooldownTimepoint));
1516 }
1517
1518 // All the nodes have been requested, next select will fail and the flag
1519 // should trigger
1522
1523 for (size_t i = 0; i < 10; i++) {
1524 // The flag will not trigger again until we fail to select nodes again
1526 }
1527
1528 // Make it possible to request a node again
1529 BOOST_CHECK(pm.updateNextRequestTime(0, Now<SteadyMilliseconds>()));
1530 BOOST_CHECK_NE(pm.selectNode(), NO_NODE);
1532
1533 // Add another proof with no node attached
1534 auto proof2 =
1536 BOOST_CHECK(pm.registerProof(proof2));
1537 BOOST_CHECK(!pm.isDangling(proof2->getId()));
1538 TestPeerManager::cleanupDanglingProofs(pm);
1539 BOOST_CHECK(!pm.isDangling(proof2->getId()));
1541
1542 // After some time the proof will be considered dangling and more nodes will
1543 // be requested.
1544 SetMockTime(GetTime() + 15 * 60);
1545 TestPeerManager::cleanupDanglingProofs(pm);
1546 BOOST_CHECK(pm.isDangling(proof2->getId()));
1548
1549 for (size_t i = 0; i < 10; i++) {
1550 BOOST_CHECK(pm.isDangling(proof2->getId()));
1551 // The flag will not trigger again until the condition is met again
1553 }
1554
1555 // Attempt to register the dangling proof again. This should fail but
1556 // trigger a request for more nodes.
1558 BOOST_CHECK(!pm.registerProof(proof2, state));
1559 BOOST_CHECK(state.GetResult() == ProofRegistrationResult::DANGLING);
1560 BOOST_CHECK(pm.isDangling(proof2->getId()));
1562
1563 for (size_t i = 0; i < 10; i++) {
1564 BOOST_CHECK(pm.isDangling(proof2->getId()));
1565 // The flag will not trigger again until the condition is met again
1567 }
1568
1569 // Attach a node to that proof
1570 BOOST_CHECK(!pm.addNode(11, proof2->getId()));
1571 BOOST_CHECK(pm.registerProof(proof2));
1572 SetMockTime(GetTime() + 15 * 60);
1573 TestPeerManager::cleanupDanglingProofs(pm);
1574 BOOST_CHECK(!pm.isDangling(proof2->getId()));
1576
1577 // Disconnect the node, the proof is dangling again
1578 BOOST_CHECK(pm.removeNode(11));
1579 TestPeerManager::cleanupDanglingProofs(pm);
1580 BOOST_CHECK(pm.isDangling(proof2->getId()));
1582
1583 // Invalidating the proof, removes the proof from the dangling pool but not
1584 // a simple rejection.
1587 BOOST_CHECK(pm.isDangling(proof2->getId()));
1590 BOOST_CHECK(!pm.isDangling(proof2->getId()));
1591}
1592
1593BOOST_AUTO_TEST_CASE(score_ordering) {
1594 ChainstateManager &chainman = *Assert(m_node.chainman);
1596
1597 std::vector<uint32_t> expectedScores(10);
1598 // Expect the peers to be ordered by descending score
1599 std::generate(expectedScores.rbegin(), expectedScores.rend(),
1600 [n = 1]() mutable { return n++ * MIN_VALID_PROOF_SCORE; });
1601
1602 std::vector<ProofRef> proofs;
1603 proofs.reserve(expectedScores.size());
1604 for (uint32_t score : expectedScores) {
1605 proofs.push_back(buildRandomProof(chainman.ActiveChainstate(), score));
1606 }
1607
1608 // Shuffle the proofs so they are registered in a random score order
1609 Shuffle(proofs.begin(), proofs.end(), FastRandomContext());
1610 for (auto &proof : proofs) {
1611 BOOST_CHECK(pm.registerProof(proof));
1612 }
1613
1614 auto peersScores = TestPeerManager::getOrderedScores(pm);
1615 BOOST_CHECK_EQUAL_COLLECTIONS(peersScores.begin(), peersScores.end(),
1616 expectedScores.begin(), expectedScores.end());
1617}
1618
1619BOOST_FIXTURE_TEST_CASE(known_score_tracking, NoCoolDownFixture) {
1620 ChainstateManager &chainman = *Assert(m_node.chainman);
1621 gArgs.ForceSetArg("-avaproofstakeutxoconfirmations", "2");
1623
1624 const CKey key = CKey::MakeCompressedKey();
1625
1626 const Amount amount1(PROOF_DUST_THRESHOLD);
1627 const Amount amount2(2 * PROOF_DUST_THRESHOLD);
1628
1629 Chainstate &active_chainstate = chainman.ActiveChainstate();
1630
1631 const COutPoint peer1ConflictingOutput =
1632 createUtxo(active_chainstate, key, amount1, 99);
1633 const COutPoint peer1SecondaryOutpoint =
1634 createUtxo(active_chainstate, key, amount2, 99);
1635
1636 auto peer1Proof1 = buildProof(
1637 key,
1638 {{peer1ConflictingOutput, amount1}, {peer1SecondaryOutpoint, amount2}},
1639 key, 10, 99);
1640 auto peer1Proof2 =
1641 buildProof(key, {{peer1ConflictingOutput, amount1}}, key, 20, 99);
1642
1643 // Create a proof with an immature UTXO, so the proof will be immature
1644 auto peer1Proof3 =
1645 buildProof(key,
1646 {{peer1ConflictingOutput, amount1},
1647 {createUtxo(active_chainstate, key, amount1), amount1}},
1648 key, 30);
1649
1650 const uint32_t peer1Score1 = Proof::amountToScore(amount1 + amount2);
1651 const uint32_t peer1Score2 = Proof::amountToScore(amount1);
1652
1653 // Add first peer and check that we have its score tracked
1655 BOOST_CHECK(pm.registerProof(peer1Proof2));
1656 BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), peer1Score2);
1657
1658 // Ensure failing to add conflicting proofs doesn't affect the score, the
1659 // first proof stays bound and counted
1660 BOOST_CHECK(!pm.registerProof(peer1Proof1));
1661 BOOST_CHECK(!pm.registerProof(peer1Proof3));
1662
1663 BOOST_CHECK(pm.isBoundToPeer(peer1Proof2->getId()));
1664 BOOST_CHECK(pm.isInConflictingPool(peer1Proof1->getId()));
1665 BOOST_CHECK(pm.isImmature(peer1Proof3->getId()));
1666
1667 BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), peer1Score2);
1668
1669 auto checkRejectDefault = [&](const ProofId &proofid) {
1670 BOOST_CHECK(pm.exists(proofid));
1671 const bool isImmature = pm.isImmature(proofid);
1674 BOOST_CHECK(!pm.isBoundToPeer(proofid));
1675 BOOST_CHECK_EQUAL(pm.exists(proofid), !isImmature);
1676 };
1677
1678 auto checkRejectInvalidate = [&](const ProofId &proofid) {
1679 BOOST_CHECK(pm.exists(proofid));
1682 };
1683
1684 // Reject from the immature pool doesn't affect tracked score
1685 checkRejectDefault(peer1Proof3->getId());
1686 BOOST_CHECK(!pm.registerProof(peer1Proof3));
1687 BOOST_CHECK(pm.isImmature(peer1Proof3->getId()));
1688 BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), peer1Score2);
1689 checkRejectInvalidate(peer1Proof3->getId());
1690 BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), peer1Score2);
1691
1692 // Reject from the conflicting pool
1693 checkRejectDefault(peer1Proof1->getId());
1694 checkRejectInvalidate(peer1Proof1->getId());
1695
1696 // Add again a proof to the conflicting pool
1697 BOOST_CHECK(!pm.registerProof(peer1Proof1));
1698 BOOST_CHECK(pm.isInConflictingPool(peer1Proof1->getId()));
1699 BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), peer1Score2);
1700
1701 // Reject from the valid pool, default mode
1702 // Now the score should change as the new peer is promoted
1703 checkRejectDefault(peer1Proof2->getId());
1704 BOOST_CHECK(!pm.isInConflictingPool(peer1Proof1->getId()));
1705 BOOST_CHECK(pm.isBoundToPeer(peer1Proof1->getId()));
1706 BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), peer1Score1);
1707
1708 // Reject from the valid pool, invalidate mode
1709 // Now the score should change as the old peer is re-promoted
1710 checkRejectInvalidate(peer1Proof1->getId());
1711
1712 // The conflicting proof should also be promoted to a peer
1713 BOOST_CHECK(!pm.isInConflictingPool(peer1Proof2->getId()));
1714 BOOST_CHECK(pm.isBoundToPeer(peer1Proof2->getId()));
1715 BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), peer1Score2);
1716
1717 // Now add another peer and check that combined scores are correct
1718 uint32_t peer2Score = 1 * MIN_VALID_PROOF_SCORE;
1719 auto peer2Proof1 = buildRandomProof(active_chainstate, peer2Score, 99);
1720 PeerId peerid2 = TestPeerManager::registerAndGetPeerId(pm, peer2Proof1);
1721 BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), peer1Score2 + peer2Score);
1722
1723 // Trying to remove non-existent peer doesn't affect score
1724 BOOST_CHECK(!pm.removePeer(1234));
1725 BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), peer1Score2 + peer2Score);
1726
1727 // Removing new peer removes its score
1728 BOOST_CHECK(pm.removePeer(peerid2));
1729 BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), peer1Score2);
1730 PeerId peerid1 =
1731 TestPeerManager::getPeerIdForProofId(pm, peer1Proof2->getId());
1732 BOOST_CHECK(pm.removePeer(peerid1));
1734}
1735
1736BOOST_AUTO_TEST_CASE(connected_score_tracking) {
1737 ChainstateManager &chainman = *Assert(m_node.chainman);
1739
1740 const auto checkScores = [&pm](uint32_t known, uint32_t connected) {
1743 };
1744
1745 // Start out with 0s
1746 checkScores(0, 0);
1747
1748 Chainstate &active_chainstate = chainman.ActiveChainstate();
1749
1750 // Create one peer without a node. Its score should be registered but not
1751 // connected
1752 uint32_t score1 = 10000000 * MIN_VALID_PROOF_SCORE;
1753 auto proof1 = buildRandomProof(active_chainstate, score1);
1754 PeerId peerid1 = TestPeerManager::registerAndGetPeerId(pm, proof1);
1755 checkScores(score1, 0);
1756
1757 // Add nodes. We now have a connected score, but it doesn't matter how many
1758 // nodes we add the score is the same
1759 const ProofId &proofid1 = proof1->getId();
1760 const uint8_t nodesToAdd = 10;
1761 for (int i = 0; i < nodesToAdd; i++) {
1762 BOOST_CHECK(pm.addNode(i, proofid1));
1763 checkScores(score1, score1);
1764 }
1765
1766 // Remove all but 1 node and ensure the score doesn't change
1767 for (int i = 0; i < nodesToAdd - 1; i++) {
1768 BOOST_CHECK(pm.removeNode(i));
1769 checkScores(score1, score1);
1770 }
1771
1772 // Removing the last node should remove the score from the connected count
1773 BOOST_CHECK(pm.removeNode(nodesToAdd - 1));
1774 checkScores(score1, 0);
1775
1776 // Add 2 nodes to peer and create peer2. Without a node peer2 has no
1777 // connected score but after adding a node it does.
1778 BOOST_CHECK(pm.addNode(0, proofid1));
1779 BOOST_CHECK(pm.addNode(1, proofid1));
1780 checkScores(score1, score1);
1781
1782 uint32_t score2 = 1 * MIN_VALID_PROOF_SCORE;
1783 auto proof2 = buildRandomProof(active_chainstate, score2);
1784 PeerId peerid2 = TestPeerManager::registerAndGetPeerId(pm, proof2);
1785 checkScores(score1 + score2, score1);
1786 BOOST_CHECK(pm.addNode(2, proof2->getId()));
1787 checkScores(score1 + score2, score1 + score2);
1788
1789 // The first peer has two nodes left. Remove one and nothing happens, remove
1790 // the other and its score is no longer in the connected counter..
1791 BOOST_CHECK(pm.removeNode(0));
1792 checkScores(score1 + score2, score1 + score2);
1793 BOOST_CHECK(pm.removeNode(1));
1794 checkScores(score1 + score2, score2);
1795
1796 // Removing a peer with no allocated score has no affect.
1797 BOOST_CHECK(pm.removePeer(peerid1));
1798 checkScores(score2, score2);
1799
1800 // Remove the second peer's node removes its allocated score.
1801 BOOST_CHECK(pm.removeNode(2));
1802 checkScores(score2, 0);
1803
1804 // Removing the second peer takes us back to 0.
1805 BOOST_CHECK(pm.removePeer(peerid2));
1806 checkScores(0, 0);
1807
1808 // Add 2 peers with nodes and remove them without removing the nodes first.
1809 // Both score counters should be reduced by each peer's score when it's
1810 // removed.
1811 peerid1 = TestPeerManager::registerAndGetPeerId(pm, proof1);
1812 checkScores(score1, 0);
1813 peerid2 = TestPeerManager::registerAndGetPeerId(pm, proof2);
1814 checkScores(score1 + score2, 0);
1815 BOOST_CHECK(pm.addNode(0, proof1->getId()));
1816 checkScores(score1 + score2, score1);
1817 BOOST_CHECK(pm.addNode(1, proof2->getId()));
1818 checkScores(score1 + score2, score1 + score2);
1819
1820 BOOST_CHECK(pm.removePeer(peerid2));
1821 checkScores(score1, score1);
1822
1823 BOOST_CHECK(pm.removePeer(peerid1));
1824 checkScores(0, 0);
1825}
1826
1827BOOST_FIXTURE_TEST_CASE(proof_radix_tree, NoCoolDownFixture) {
1828 ChainstateManager &chainman = *Assert(m_node.chainman);
1830
1831 struct ProofComparatorById {
1832 bool operator()(const ProofRef &lhs, const ProofRef &rhs) const {
1833 return lhs->getId() < rhs->getId();
1834 };
1835 };
1836 using ProofSetById = std::set<ProofRef, ProofComparatorById>;
1837 // Maintain a list of the expected proofs through this test
1838 ProofSetById expectedProofs;
1839
1840 auto matchExpectedContent = [&](const auto &tree) {
1841 auto it = expectedProofs.begin();
1842 return tree.forEachLeaf([&](auto pLeaf) {
1843 return it != expectedProofs.end() &&
1844 pLeaf->getId() == (*it++)->getId();
1845 });
1846 };
1847
1849 const int64_t sequence = 10;
1850
1851 Chainstate &active_chainstate = chainman.ActiveChainstate();
1852
1853 // Add some initial proofs
1854 for (size_t i = 0; i < 10; i++) {
1855 auto outpoint = createUtxo(active_chainstate, key);
1856 auto proof = buildProofWithSequence(key, {{outpoint}}, sequence);
1857 BOOST_CHECK(pm.registerProof(proof));
1858 expectedProofs.insert(std::move(proof));
1859 }
1860
1861 const auto &treeRef = pm.getShareableProofsSnapshot();
1862 BOOST_CHECK(matchExpectedContent(treeRef));
1863
1864 // Create a copy
1865 auto tree = pm.getShareableProofsSnapshot();
1866
1867 // Adding more proofs doesn't change the tree...
1868 ProofSetById addedProofs;
1869 std::vector<COutPoint> outpointsToSpend;
1870 for (size_t i = 0; i < 10; i++) {
1871 auto outpoint = createUtxo(active_chainstate, key);
1872 auto proof = buildProofWithSequence(key, {{outpoint}}, sequence);
1873 BOOST_CHECK(pm.registerProof(proof));
1874 addedProofs.insert(std::move(proof));
1875 outpointsToSpend.push_back(std::move(outpoint));
1876 }
1877
1878 BOOST_CHECK(matchExpectedContent(tree));
1879
1880 // ...until we get a new copy
1881 tree = pm.getShareableProofsSnapshot();
1882 expectedProofs.insert(addedProofs.begin(), addedProofs.end());
1883 BOOST_CHECK(matchExpectedContent(tree));
1884
1885 // Spend some coins to make the associated proofs invalid
1886 {
1887 LOCK(cs_main);
1888 CCoinsViewCache &coins = active_chainstate.CoinsTip();
1889 for (const auto &outpoint : outpointsToSpend) {
1890 coins.SpendCoin(outpoint);
1891 }
1892 }
1893
1894 pm.updatedBlockTip();
1895
1896 // This doesn't change the tree...
1897 BOOST_CHECK(matchExpectedContent(tree));
1898
1899 // ...until we get a new copy
1900 tree = pm.getShareableProofsSnapshot();
1901 for (const auto &proof : addedProofs) {
1902 BOOST_CHECK_EQUAL(expectedProofs.erase(proof), 1);
1903 }
1904 BOOST_CHECK(matchExpectedContent(tree));
1905
1906 // Add some more proof for which we will create conflicts
1907 std::vector<ProofRef> conflictingProofs;
1908 std::vector<COutPoint> conflictingOutpoints;
1909 for (size_t i = 0; i < 10; i++) {
1910 auto outpoint = createUtxo(active_chainstate, key);
1911 auto proof = buildProofWithSequence(key, {{outpoint}}, sequence);
1912 BOOST_CHECK(pm.registerProof(proof));
1913 conflictingProofs.push_back(std::move(proof));
1914 conflictingOutpoints.push_back(std::move(outpoint));
1915 }
1916
1917 tree = pm.getShareableProofsSnapshot();
1918 expectedProofs.insert(conflictingProofs.begin(), conflictingProofs.end());
1919 BOOST_CHECK(matchExpectedContent(tree));
1920
1921 // Build a bunch of conflicting proofs, half better, half worst
1922 for (size_t i = 0; i < 10; i += 2) {
1923 // The worst proof is not added to the expected set
1924 BOOST_CHECK(!pm.registerProof(buildProofWithSequence(
1925 key, {{conflictingOutpoints[i]}}, sequence - 1)));
1926
1927 // But the better proof should replace its conflicting one
1928 auto replacementProof = buildProofWithSequence(
1929 key, {{conflictingOutpoints[i + 1]}}, sequence + 1);
1930 BOOST_CHECK(pm.registerProof(replacementProof));
1931 BOOST_CHECK_EQUAL(expectedProofs.erase(conflictingProofs[i + 1]), 1);
1932 BOOST_CHECK(expectedProofs.insert(replacementProof).second);
1933 }
1934
1935 tree = pm.getShareableProofsSnapshot();
1936 BOOST_CHECK(matchExpectedContent(tree));
1937
1938 // Check for consistency
1939 pm.verify();
1940}
1941
1942BOOST_AUTO_TEST_CASE(received_avaproofs) {
1943 ChainstateManager &chainman = *Assert(m_node.chainman);
1945
1946 auto addNode = [&](NodeId nodeid) {
1947 auto proof = buildRandomProof(chainman.ActiveChainstate(),
1949 BOOST_CHECK(pm.registerProof(proof));
1950 BOOST_CHECK(pm.addNode(nodeid, proof->getId()));
1951 };
1952
1953 for (NodeId nodeid = 0; nodeid < 10; nodeid++) {
1954 // Node doesn't exist
1955 BOOST_CHECK(!pm.latchAvaproofsSent(nodeid));
1956
1957 addNode(nodeid);
1958 BOOST_CHECK(pm.latchAvaproofsSent(nodeid));
1959
1960 // The flag is already set
1961 BOOST_CHECK(!pm.latchAvaproofsSent(nodeid));
1962 }
1963}
1964
1965BOOST_FIXTURE_TEST_CASE(cleanup_dangling_proof, NoCoolDownFixture) {
1966 ChainstateManager &chainman = *Assert(m_node.chainman);
1967
1969
1970 const auto now = GetTime<std::chrono::seconds>();
1971 auto mocktime = now;
1972
1973 auto elapseTime = [&](std::chrono::seconds seconds) {
1974 mocktime += seconds;
1975 SetMockTime(mocktime.count());
1976 };
1977 elapseTime(0s);
1978
1979 const CKey key = CKey::MakeCompressedKey();
1980
1981 const size_t numProofs = 10;
1982
1983 std::vector<COutPoint> outpoints(numProofs);
1984 std::vector<ProofRef> proofs(numProofs);
1985 std::vector<ProofRef> conflictingProofs(numProofs);
1986 for (size_t i = 0; i < numProofs; i++) {
1987 outpoints[i] = createUtxo(chainman.ActiveChainstate(), key);
1988 proofs[i] = buildProofWithSequence(key, {outpoints[i]}, 2);
1989 conflictingProofs[i] = buildProofWithSequence(key, {outpoints[i]}, 1);
1990
1991 BOOST_CHECK(pm.registerProof(proofs[i]));
1992 BOOST_CHECK(pm.isBoundToPeer(proofs[i]->getId()));
1993
1994 BOOST_CHECK(!pm.registerProof(conflictingProofs[i]));
1995 BOOST_CHECK(pm.isInConflictingPool(conflictingProofs[i]->getId()));
1996
1997 if (i % 2) {
1998 // Odd indexes get a node attached to them
1999 BOOST_CHECK(pm.addNode(i, proofs[i]->getId()));
2000 }
2001 BOOST_CHECK_EQUAL(pm.forPeer(proofs[i]->getId(),
2002 [&](const avalanche::Peer &peer) {
2003 return peer.node_count;
2004 }),
2005 i % 2);
2006
2007 elapseTime(1s);
2008 }
2009
2010 // No proof expired yet
2011 TestPeerManager::cleanupDanglingProofs(pm);
2012 for (size_t i = 0; i < numProofs; i++) {
2013 BOOST_CHECK(pm.isBoundToPeer(proofs[i]->getId()));
2014 BOOST_CHECK(pm.isInConflictingPool(conflictingProofs[i]->getId()));
2015 }
2016
2017 // Elapse the dangling timeout
2019 TestPeerManager::cleanupDanglingProofs(pm);
2020 for (size_t i = 0; i < numProofs; i++) {
2021 const bool hasNodeAttached = i % 2;
2022
2023 // Only the peers with no nodes attached are getting discarded
2024 BOOST_CHECK_EQUAL(pm.isBoundToPeer(proofs[i]->getId()),
2025 hasNodeAttached);
2026 BOOST_CHECK_EQUAL(!pm.exists(proofs[i]->getId()), !hasNodeAttached);
2027
2028 // The proofs conflicting with the discarded ones are pulled back
2029 BOOST_CHECK_EQUAL(pm.isInConflictingPool(conflictingProofs[i]->getId()),
2030 hasNodeAttached);
2031 BOOST_CHECK_EQUAL(pm.isBoundToPeer(conflictingProofs[i]->getId()),
2032 !hasNodeAttached);
2033 }
2034
2035 // Attach a node to the first conflicting proof, which has been promoted
2036 BOOST_CHECK(pm.addNode(42, conflictingProofs[0]->getId()));
2038 conflictingProofs[0]->getId(),
2039 [&](const avalanche::Peer &peer) { return peer.node_count == 1; }));
2040
2041 // Elapse the dangling timeout again
2043 TestPeerManager::cleanupDanglingProofs(pm);
2044 for (size_t i = 0; i < numProofs; i++) {
2045 const bool hasNodeAttached = i % 2;
2046
2047 // The initial peers with a node attached are still there
2048 BOOST_CHECK_EQUAL(pm.isBoundToPeer(proofs[i]->getId()),
2049 hasNodeAttached);
2050 BOOST_CHECK_EQUAL(!pm.exists(proofs[i]->getId()), !hasNodeAttached);
2051
2052 // This time the previouly promoted conflicting proofs are evicted
2053 // because they have no node attached, except the index 0.
2054 BOOST_CHECK_EQUAL(pm.exists(conflictingProofs[i]->getId()),
2055 hasNodeAttached || i == 0);
2056 BOOST_CHECK_EQUAL(pm.isInConflictingPool(conflictingProofs[i]->getId()),
2057 hasNodeAttached);
2058 BOOST_CHECK_EQUAL(pm.isBoundToPeer(conflictingProofs[i]->getId()),
2059 i == 0);
2060 }
2061
2062 // Disconnect all the nodes
2063 for (size_t i = 1; i < numProofs; i += 2) {
2064 BOOST_CHECK(pm.removeNode(i));
2066 pm.forPeer(proofs[i]->getId(), [&](const avalanche::Peer &peer) {
2067 return peer.node_count == 0;
2068 }));
2069 }
2070 BOOST_CHECK(pm.removeNode(42));
2072 conflictingProofs[0]->getId(),
2073 [&](const avalanche::Peer &peer) { return peer.node_count == 0; }));
2074
2075 TestPeerManager::cleanupDanglingProofs(pm);
2076 for (size_t i = 0; i < numProofs; i++) {
2077 const bool hadNodeAttached = i % 2;
2078
2079 // All initially valid proofs have now been discarded
2080 BOOST_CHECK(!pm.exists(proofs[i]->getId()));
2081
2082 // The remaining conflicting proofs are promoted
2083 BOOST_CHECK_EQUAL(!pm.exists(conflictingProofs[i]->getId()),
2084 !hadNodeAttached);
2085 BOOST_CHECK(!pm.isInConflictingPool(conflictingProofs[i]->getId()));
2086 BOOST_CHECK_EQUAL(pm.isBoundToPeer(conflictingProofs[i]->getId()),
2087 hadNodeAttached);
2088 }
2089
2090 // Elapse the timeout for the newly promoted conflicting proofs
2092
2093 // All other proofs have now been discarded
2094 TestPeerManager::cleanupDanglingProofs(pm);
2095
2096 for (size_t i = 0; i < numProofs; i++) {
2097 // All proofs have finally been discarded
2098 BOOST_CHECK(!pm.exists(proofs[i]->getId()));
2099 BOOST_CHECK(!pm.exists(conflictingProofs[i]->getId()));
2100 }
2101}
2102
2103BOOST_AUTO_TEST_CASE(register_proof_missing_utxo) {
2104 ChainstateManager &chainman = *Assert(m_node.chainman);
2106
2108 auto proof = buildProofWithOutpoints(key, {{TxId(GetRandHash()), 0}},
2110
2112 BOOST_CHECK(!pm.registerProof(proof, state));
2113 BOOST_CHECK(state.GetResult() == ProofRegistrationResult::MISSING_UTXO);
2114}
2115
2116BOOST_FIXTURE_TEST_CASE(proof_expiry, NoCoolDownFixture) {
2117 ChainstateManager &chainman = *Assert(m_node.chainman);
2119
2120 const int64_t tipTime =
2121 WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip())
2122 ->GetBlockTime();
2123
2125
2126 auto utxo = createUtxo(chainman.ActiveChainstate(), key);
2127 auto proofToExpire = buildProof(key, {{utxo, PROOF_DUST_THRESHOLD}}, key, 2,
2128 100, false, tipTime + 1);
2129 auto conflictingProof = buildProof(key, {{utxo, PROOF_DUST_THRESHOLD}}, key,
2130 1, 100, false, tipTime + 2);
2131
2132 // Our proofToExpire is not expired yet, so it registers fine
2133 BOOST_CHECK(pm.registerProof(proofToExpire));
2134 BOOST_CHECK(pm.isBoundToPeer(proofToExpire->getId()));
2135
2136 // The conflicting proof has a longer expiration time but a lower sequence
2137 // number, so it is moved to the conflicting pool.
2138 BOOST_CHECK(!pm.registerProof(conflictingProof));
2139 BOOST_CHECK(pm.isInConflictingPool(conflictingProof->getId()));
2140
2141 // Mine blocks until the MTP of the tip moves to the proof expiration
2142 for (int64_t i = 0; i < 6; i++) {
2143 SetMockTime(proofToExpire->getExpirationTime() + i);
2144 CreateAndProcessBlock({}, CScript());
2145 }
2147 WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip())
2148 ->GetMedianTimePast(),
2149 proofToExpire->getExpirationTime());
2150
2151 pm.updatedBlockTip();
2152
2153 // The now expired proof is removed
2154 BOOST_CHECK(!pm.exists(proofToExpire->getId()));
2155
2156 // The conflicting proof has been pulled back to the valid pool
2157 BOOST_CHECK(pm.isBoundToPeer(conflictingProof->getId()));
2158}
2159
2160BOOST_AUTO_TEST_CASE(peer_availability_score) {
2161 ChainstateManager &chainman = *Assert(m_node.chainman);
2163 Chainstate &active_chainstate = chainman.ActiveChainstate();
2164
2165 const std::vector<std::tuple<uint32_t, uint32_t, double>> testCases = {
2166 // {step, tau, decay_factor}
2167 {10, 100, 1. - std::exp(-1. * 10 / 100)},
2168 // Current defaults
2172 };
2173
2174 for (const auto &[step, tau, decayFactor] : testCases) {
2175 // Add a peer
2176 auto proof = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
2177 BOOST_CHECK(pm.registerProof(proof));
2178 auto proofid = proof->getId();
2179
2180 // Add some nodes for this peer
2181 const int numNodesPerPeer = 5;
2182 for (auto nodeid = 0; nodeid < numNodesPerPeer; nodeid++) {
2183 BOOST_CHECK(pm.addNode(nodeid, proofid));
2184 }
2185
2186 auto getNodeAvailabilityScore = [&](double avgScore,
2187 NodeId nodeid) -> double {
2188 // Spread scores over a range of values such that their average is
2189 // the provided value.
2190 return (nodeid - numNodesPerPeer / 2) * 2 + avgScore;
2191 };
2192
2193 auto getAvailabilityScore = [&]() {
2194 double score{0.0};
2195 pm.forPeer(proofid, [&](auto &peer) {
2196 score = peer.availabilityScore;
2197 return true;
2198 });
2199 return score;
2200 };
2201
2202 double previousScore = getAvailabilityScore();
2203 BOOST_CHECK_SMALL(previousScore, 1e-6);
2204
2205 // Check the statistics follow an exponential response for 1 to 10 tau
2206 for (size_t i = 1; i <= 10; i++) {
2207 for (uint32_t j = 0; j < tau; j += step) {
2208 // Nodes respond to polls > 50% of the time (positive score)
2209 pm.updateAvailabilityScores(decayFactor, [&](auto nodeid) {
2210 return getNodeAvailabilityScore(1.0, nodeid);
2211 });
2212
2213 // Expect a monotonic rise
2214 double currentScore = getAvailabilityScore();
2215 BOOST_CHECK_GE(currentScore, previousScore);
2216 previousScore = currentScore;
2217 }
2218
2219 // We expect (1 - e^-i) * numNodesPerPeer after i * tau. The
2220 // tolerance is expressed as a percentage, and we add a (large)
2221 // 0.1% margin to account for floating point errors.
2222 BOOST_CHECK_CLOSE(previousScore,
2223 -1 * std::expm1(-1. * i) * numNodesPerPeer,
2224 100.1 / tau);
2225 }
2226
2227 // After 10 tau we should be very close to 100% (about 99.995%)
2228 BOOST_CHECK_CLOSE(previousScore, numNodesPerPeer, 0.01);
2229
2230 // Make the proof invalid
2233 BOOST_CHECK(!pm.isBoundToPeer(proofid));
2234 BOOST_CHECK(!pm.exists(proofid));
2235
2236 // Re-register the proof
2237 BOOST_CHECK(pm.registerProof(proof));
2238 pm.forPeer(proofid, [&](auto &peer) {
2239 int nodeCount = 0;
2240 pm.forEachNode(peer, [&](const auto &node) { nodeCount++; });
2241 BOOST_CHECK_EQUAL(nodeCount, numNodesPerPeer);
2242 return true;
2243 });
2244
2245 // Peer score should have reset even though nodes are still connected
2246 previousScore = getAvailabilityScore();
2247 BOOST_CHECK_SMALL(previousScore, 1e-6);
2248
2249 // Bring the score back up to where we were
2250 for (size_t i = 1; i <= 10; i++) {
2251 for (uint32_t j = 0; j < tau; j += step) {
2252 pm.updateAvailabilityScores(decayFactor, [&](auto nodeid) {
2253 return getNodeAvailabilityScore(1.0, nodeid);
2254 });
2255 }
2256 }
2257 previousScore = getAvailabilityScore();
2258 BOOST_CHECK_CLOSE(previousScore, numNodesPerPeer, 0.01);
2259
2260 for (size_t i = 1; i <= 3; i++) {
2261 for (uint32_t j = 0; j < tau; j += step) {
2262 // Nodes only respond to polls 50% of the time (0 score)
2263 pm.updateAvailabilityScores(decayFactor, [&](auto nodeid) {
2264 return getNodeAvailabilityScore(0.0, nodeid);
2265 });
2266
2267 // Expect a monotonic fall
2268 double currentScore = getAvailabilityScore();
2269 BOOST_CHECK_LE(currentScore, previousScore);
2270 previousScore = currentScore;
2271 }
2272
2273 // There is a slight error in the expected value because we did not
2274 // start the decay at exactly 100%, but the 0.1% margin is at least
2275 // an order of magnitude larger than the expected error so it
2276 // doesn't matter.
2277 BOOST_CHECK_CLOSE(previousScore,
2278 (1. + std::expm1(-1. * i)) * numNodesPerPeer,
2279 100.1 / tau);
2280 }
2281
2282 // After 3 more tau we should be under 5%
2283 BOOST_CHECK_LT(previousScore, .05 * numNodesPerPeer);
2284
2285 for (size_t i = 1; i <= 100; i++) {
2286 // Nodes respond to polls < 50% of the time (negative score)
2287 pm.updateAvailabilityScores(decayFactor, [&](auto nodeid) {
2288 return getNodeAvailabilityScore(-10.0, nodeid);
2289 });
2290
2291 // It's still a monotonic fall, and the score should turn negative.
2292 double currentScore = getAvailabilityScore();
2293 BOOST_CHECK_LE(currentScore, previousScore);
2294 BOOST_CHECK_LE(currentScore, 0.);
2295 previousScore = currentScore;
2296 }
2297 }
2298}
2299
2300BOOST_AUTO_TEST_CASE(select_staking_reward_winner) {
2301 ChainstateManager &chainman = *Assert(m_node.chainman);
2303 Chainstate &active_chainstate = chainman.ActiveChainstate();
2304
2305 auto buildProofWithAmountAndPayout = [&](Amount amount,
2306 const CScript &payoutScript) {
2307 const CKey key = CKey::MakeCompressedKey();
2308 COutPoint utxo = createUtxo(active_chainstate, key, amount);
2309 return buildProof(key, {{std::move(utxo), amount}},
2310 /*master=*/CKey::MakeCompressedKey(), /*sequence=*/1,
2311 /*height=*/100, /*is_coinbase=*/false,
2312 /*expirationTime=*/0, payoutScript);
2313 };
2314
2315 std::vector<std::pair<ProofId, CScript>> winners;
2316 // Null pprev
2317 BOOST_CHECK(!pm.selectStakingRewardWinner(nullptr, winners));
2318
2319 CBlockIndex prevBlock;
2320
2321 auto now = GetTime<std::chrono::seconds>();
2322 SetMockTime(now);
2323 prevBlock.nTime = now.count();
2324
2325 BlockHash prevHash{uint256::ONE};
2326 prevBlock.phashBlock = &prevHash;
2327 // No peer
2328 BOOST_CHECK(!pm.selectStakingRewardWinner(&prevBlock, winners));
2329
2330 // Let's build a list of payout addresses, and register a proofs for each
2331 // address
2332 size_t numProofs = 8;
2333 std::vector<ProofRef> proofs;
2334 proofs.reserve(numProofs);
2335 for (size_t i = 0; i < numProofs; i++) {
2336 const CKey key = CKey::MakeCompressedKey();
2337 CScript payoutScript = GetScriptForRawPubKey(key.GetPubKey());
2338
2339 auto proof =
2340 buildProofWithAmountAndPayout(PROOF_DUST_THRESHOLD, payoutScript);
2341 PeerId peerid = TestPeerManager::registerAndGetPeerId(pm, proof);
2342 BOOST_CHECK_NE(peerid, NO_PEER);
2343
2344 // Finalize the proof
2345 BOOST_CHECK(pm.setFinalized(peerid));
2346
2347 proofs.emplace_back(std::move(proof));
2348 }
2349
2350 // Make sure the proofs have been registered before the prev block was found
2351 // and before 6x the peer replacement cooldown.
2352 now += 6 * avalanche::Peer::DANGLING_TIMEOUT + 1s;
2353 SetMockTime(now);
2354 prevBlock.nTime = now.count();
2355
2356 // At this stage we have a set of peers out of which none has any node
2357 // attached, so they're all considered flaky. Note that we have no remote
2358 // proofs status yet.
2359 BOOST_CHECK(pm.selectStakingRewardWinner(&prevBlock, winners));
2360 BOOST_CHECK_LE(winners.size(), numProofs);
2361
2362 // Let's add a node for each peer
2363 for (size_t i = 0; i < numProofs; i++) {
2364 BOOST_CHECK(TestPeerManager::isFlaky(pm, proofs[i]->getId()));
2365 BOOST_CHECK(pm.selectStakingRewardWinner(&prevBlock, winners));
2366 BOOST_CHECK_LE(winners.size(), numProofs);
2367
2368 BOOST_CHECK(pm.addNode(NodeId(i), proofs[i]->getId()));
2369
2370 BOOST_CHECK(!TestPeerManager::isFlaky(pm, proofs[i]->getId()));
2371 BOOST_CHECK(pm.selectStakingRewardWinner(&prevBlock, winners));
2372 BOOST_CHECK_LE(winners.size(), numProofs - i);
2373 }
2374
2375 // Now we have a single winner
2376 BOOST_CHECK(pm.selectStakingRewardWinner(&prevBlock, winners));
2377 BOOST_CHECK_LE(winners.size(), 1);
2378
2379 // All proofs have the same amount, so the same probability to get picked.
2380 // Let's compute how many loop iterations we need to have a low false
2381 // negative rate when checking for this. Target false positive rate is
2382 // 10ppm (aka 1/100000).
2383 const size_t loop_iters =
2384 size_t(-1.0 * std::log(100000.0) /
2385 std::log((double(numProofs) - 1) / numProofs)) +
2386 1;
2387 BOOST_CHECK_GT(loop_iters, numProofs);
2388 std::unordered_map<std::string, size_t> winningCounts;
2389 for (size_t i = 0; i < loop_iters; i++) {
2390 BlockHash randomHash = BlockHash(GetRandHash());
2391 prevBlock.phashBlock = &randomHash;
2392 BOOST_CHECK(pm.selectStakingRewardWinner(&prevBlock, winners));
2393 winningCounts[FormatScript(winners[0].second)]++;
2394 }
2395 BOOST_CHECK_EQUAL(winningCounts.size(), numProofs);
2396
2397 prevBlock.phashBlock = &prevHash;
2398
2399 // Ensure all nodes have all the proofs
2400 for (size_t i = 0; i < numProofs; i++) {
2401 for (size_t j = 0; j < numProofs; j++) {
2403 pm.saveRemoteProof(proofs[j]->getId(), NodeId(i), true));
2404 }
2405 }
2406
2407 // Make all the proofs flaky. This loop needs to be updated if the threshold
2408 // or the number of proofs change, so assert the test precondition.
2409 BOOST_CHECK_GT(3. / numProofs, 0.3);
2410 for (size_t i = 0; i < numProofs; i++) {
2411 const NodeId nodeid = NodeId(i);
2412
2414 proofs[(i - 1 + numProofs) % numProofs]->getId(), nodeid, false));
2416 proofs[(i + numProofs) % numProofs]->getId(), nodeid, false));
2418 proofs[(i + 1 + numProofs) % numProofs]->getId(), nodeid, false));
2419 }
2420
2421 // Now all the proofs are flaky
2422 BOOST_CHECK(pm.selectStakingRewardWinner(&prevBlock, winners));
2423 for (const auto &proof : proofs) {
2424 BOOST_CHECK(TestPeerManager::isFlaky(pm, proof->getId()));
2425 }
2426 BOOST_CHECK_EQUAL(winners.size(), numProofs);
2427
2428 // Revert flakyness for all proofs
2429 for (const auto &proof : proofs) {
2430 for (NodeId nodeid = 0; nodeid < NodeId(numProofs); nodeid++) {
2431 BOOST_CHECK(pm.saveRemoteProof(proof->getId(), nodeid, true));
2432 }
2433 }
2434
2435 BOOST_CHECK(pm.selectStakingRewardWinner(&prevBlock, winners));
2436 BOOST_CHECK_EQUAL(winners.size(), 1);
2437
2438 // Increase the list from 1 to 4 winners by making them flaky
2439 for (size_t numWinner = 1; numWinner < 4; numWinner++) {
2440 // Who is the last possible winner ?
2441 CScript lastWinner = winners[numWinner - 1].second;
2442
2443 // Make the last winner flaky, the other proofs untouched
2444 ProofId winnerProofId = ProofId(uint256::ZERO);
2445 for (const auto &proof : proofs) {
2446 if (proof->getPayoutScript() == lastWinner) {
2447 winnerProofId = proof->getId();
2448 break;
2449 }
2450 }
2451 BOOST_CHECK_NE(winnerProofId, ProofId(uint256::ZERO));
2452
2453 for (NodeId nodeid = 0; nodeid < NodeId(numProofs); nodeid++) {
2454 BOOST_CHECK(pm.saveRemoteProof(winnerProofId, nodeid, false));
2455 }
2456 BOOST_CHECK(TestPeerManager::isFlaky(pm, winnerProofId));
2457
2458 // There should be now exactly numWinner + 1 winners
2459 BOOST_CHECK(pm.selectStakingRewardWinner(&prevBlock, winners));
2460 BOOST_CHECK_EQUAL(winners.size(), numWinner + 1);
2461 }
2462
2463 // One more time and the nodes will be missing too many proofs, so they are
2464 // no longer considered for flakyness evaluation and we're back to a single
2465 // winner.
2466 CScript lastWinner = winners[3].second;
2467
2468 ProofId winnerProofId = ProofId(uint256::ZERO);
2469 for (const auto &proof : proofs) {
2470 if (proof->getPayoutScript() == lastWinner) {
2471 winnerProofId = proof->getId();
2472 break;
2473 }
2474 }
2475 BOOST_CHECK_NE(winnerProofId, ProofId(uint256::ZERO));
2476
2477 for (NodeId nodeid = 0; nodeid < NodeId(numProofs); nodeid++) {
2478 BOOST_CHECK(pm.saveRemoteProof(winnerProofId, nodeid, false));
2479 }
2480
2481 // We're back to exactly 1 winner
2482 BOOST_CHECK(pm.selectStakingRewardWinner(&prevBlock, winners));
2483 BOOST_CHECK_EQUAL(winners.size(), 1);
2484
2485 // Remove all proofs
2486 for (auto &proof : proofs) {
2489 }
2490 // No more winner
2491 prevBlock.phashBlock = &prevHash;
2492 BOOST_CHECK(!pm.selectStakingRewardWinner(&prevBlock, winners));
2493
2494 {
2495 // Add back a single proof
2496 const CKey key = CKey::MakeCompressedKey();
2497 CScript payoutScript = GetScriptForRawPubKey(key.GetPubKey());
2498
2499 auto proof =
2500 buildProofWithAmountAndPayout(PROOF_DUST_THRESHOLD, payoutScript);
2501 PeerId peerid = TestPeerManager::registerAndGetPeerId(pm, proof);
2502 BOOST_CHECK_NE(peerid, NO_PEER);
2503
2504 // The single proof should always be selected, but:
2505 // 1. The proof is not finalized, and has been registered after the last
2506 // block was mined.
2507 BOOST_CHECK(!pm.selectStakingRewardWinner(&prevBlock, winners));
2508
2509 // 2. The proof has has been registered after the last block was mined.
2510 BOOST_CHECK(pm.setFinalized(peerid));
2511 BOOST_CHECK(!pm.selectStakingRewardWinner(&prevBlock, winners));
2512
2513 // 3. The proof has been registered 60min from the previous block time,
2514 // but the previous block time is in the future.
2515 now += 50min + 1s;
2516 SetMockTime(now);
2517 prevBlock.nTime = (now + 10min).count();
2518 BOOST_CHECK(!pm.selectStakingRewardWinner(&prevBlock, winners));
2519
2520 // 4. The proof has been registered 60min from now, but only 50min from
2521 // the previous block time.
2522 now += 10min;
2523 SetMockTime(now);
2524 prevBlock.nTime = (now - 10min).count();
2525 BOOST_CHECK(!pm.selectStakingRewardWinner(&prevBlock, winners));
2526
2527 // 5. Now the proof has it all
2528 prevBlock.nTime = now.count();
2529 BOOST_CHECK(pm.selectStakingRewardWinner(&prevBlock, winners));
2530 // With a single proof, it's easy to determine the winner
2531 BOOST_CHECK_EQUAL(FormatScript(winners[0].second),
2532 FormatScript(payoutScript));
2533
2534 // Remove the proof
2537 }
2538
2539 {
2540 BOOST_CHECK_EQUAL(TestPeerManager::getPeerCount(pm), 0);
2541
2542 proofs.clear();
2543 for (size_t i = 0; i < 4; i++) {
2544 // Add 4 proofs, registered at a 30 minutes interval
2545 SetMockTime(now + i * 30min);
2546
2547 const CKey key = CKey::MakeCompressedKey();
2548 CScript payoutScript = GetScriptForRawPubKey(key.GetPubKey());
2549
2550 auto proof = buildProofWithAmountAndPayout(PROOF_DUST_THRESHOLD,
2551 payoutScript);
2552 PeerId peerid = TestPeerManager::registerAndGetPeerId(pm, proof);
2553 BOOST_CHECK_NE(peerid, NO_PEER);
2554 BOOST_CHECK(pm.forPeer(proof->getId(), [&](const Peer &peer) {
2555 return peer.registration_time == now + i * 30min;
2556 }));
2557
2558 BOOST_CHECK(pm.addNode(NodeId(i), proof->getId()));
2559
2560 BOOST_CHECK(pm.setFinalized(peerid));
2561
2562 proofs.push_back(proof);
2563 }
2564
2565 // No proof has been registered before the previous block time
2566 SetMockTime(now);
2567 prevBlock.nTime = now.count();
2568 BOOST_CHECK(!pm.selectStakingRewardWinner(&prevBlock, winners));
2569
2570 // 1 proof has been registered > 30min from the previous block time, but
2571 // none > 60 minutes from the previous block time
2572 // => we have no winner.
2573 now += 30min + 1s;
2574 SetMockTime(now);
2575 prevBlock.nTime = now.count();
2576 BOOST_CHECK(!pm.selectStakingRewardWinner(&prevBlock, winners));
2577
2578 auto checkRegistrationTime =
2579 [&](const std::pair<ProofId, CScript> &winner) {
2580 pm.forEachPeer([&](const Peer &peer) {
2581 if (peer.proof->getPayoutScript() == winner.second) {
2582 BOOST_CHECK_LT(peer.registration_time.count(),
2583 (now - 60min).count());
2584 }
2585 return true;
2586 });
2587 };
2588
2589 // 1 proof has been registered > 60min but < 90min from the previous
2590 // block time and 1 more has been registered > 30 minutes
2591 // => we have a winner and one acceptable substitute.
2592 now += 30min;
2593 SetMockTime(now);
2594 prevBlock.nTime = now.count();
2595 BOOST_CHECK(pm.selectStakingRewardWinner(&prevBlock, winners));
2596 BOOST_CHECK_EQUAL(winners.size(), 2);
2597 checkRegistrationTime(winners[0]);
2598
2599 // 1 proof has been registered > 60min but < 90min from the
2600 // previous block time, 1 has been registered > 90 minutes and 1 more
2601 // has been registered > 30 minutes
2602 // => we have 1 winner and up to 2 acceptable substitutes.
2603 now += 30min;
2604 SetMockTime(now);
2605 prevBlock.nTime = now.count();
2606 BOOST_CHECK(pm.selectStakingRewardWinner(&prevBlock, winners));
2607 BOOST_CHECK_LE(winners.size(), 3);
2608 checkRegistrationTime(winners[0]);
2609
2610 // 1 proofs has been registered > 60min but < 90min from the
2611 // previous block time, 2 has been registered > 90 minutes and 1 more
2612 // has been registered > 30 minutes
2613 // => we have 1 winner, and up to 2 substitutes.
2614 now += 30min;
2615 SetMockTime(now);
2616 prevBlock.nTime = now.count();
2617 BOOST_CHECK(pm.selectStakingRewardWinner(&prevBlock, winners));
2618 BOOST_CHECK_LE(winners.size(), 3);
2619 checkRegistrationTime(winners[0]);
2620
2621 // 1 proof has been registered > 60min but < 90min from the
2622 // previous block time and 3 more has been registered > 90 minutes
2623 // => we have 1 winner, and up to 1 substitute.
2624 now += 30min;
2625 SetMockTime(now);
2626 prevBlock.nTime = now.count();
2627 BOOST_CHECK(pm.selectStakingRewardWinner(&prevBlock, winners));
2628 BOOST_CHECK_LE(winners.size(), 2);
2629 checkRegistrationTime(winners[0]);
2630
2631 // All proofs has been registered > 90min from the previous block time
2632 // => we have 1 winner, and no substitute.
2633 now += 30min;
2634 SetMockTime(now);
2635 prevBlock.nTime = now.count();
2636 BOOST_CHECK(pm.selectStakingRewardWinner(&prevBlock, winners));
2637 BOOST_CHECK_EQUAL(winners.size(), 1);
2638 checkRegistrationTime(winners[0]);
2639 }
2640}
2641
2643 ChainstateManager &chainman = *Assert(m_node.chainman);
2645
2646 auto mockTime = GetTime<std::chrono::seconds>();
2647 SetMockTime(mockTime);
2648
2653
2654 auto checkRemoteProof =
2655 [&](const ProofId &proofid, const NodeId nodeid,
2656 const bool expectedPresent,
2657 const std::chrono::seconds &expectedlastUpdate) {
2658 BOOST_CHECK(pm.isRemoteProof(proofid));
2659 auto remoteProof =
2660 TestPeerManager::getRemoteProof(pm, proofid, nodeid);
2661 BOOST_CHECK(remoteProof.has_value());
2662 BOOST_CHECK_EQUAL(remoteProof->proofid, proofid);
2663 BOOST_CHECK_EQUAL(remoteProof->nodeid, nodeid);
2664 BOOST_CHECK_EQUAL(remoteProof->present, expectedPresent);
2665 BOOST_CHECK_EQUAL(remoteProof->lastUpdate.count(),
2666 expectedlastUpdate.count());
2667 };
2668
2669 checkRemoteProof(ProofId(uint256::ZERO), 0, true, mockTime);
2670 checkRemoteProof(ProofId(uint256::ONE), 0, false, mockTime);
2671 checkRemoteProof(ProofId(uint256::ZERO), 1, true, mockTime);
2672 checkRemoteProof(ProofId(uint256::ONE), 1, false, mockTime);
2673
2674 mockTime += 1s;
2675 SetMockTime(mockTime);
2676
2677 // Reverse the state
2682
2683 checkRemoteProof(ProofId(uint256::ZERO), 0, false, mockTime);
2684 checkRemoteProof(ProofId(uint256::ONE), 0, true, mockTime);
2685 checkRemoteProof(ProofId(uint256::ZERO), 1, false, mockTime);
2686 checkRemoteProof(ProofId(uint256::ONE), 1, true, mockTime);
2687
2688 Chainstate &active_chainstate = chainman.ActiveChainstate();
2689
2690 // Actually register the nodes
2691 auto proof0 = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
2692 BOOST_CHECK(pm.registerProof(proof0));
2693 BOOST_CHECK(pm.addNode(0, proof0->getId()));
2694 auto proof1 = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
2695 BOOST_CHECK(pm.registerProof(proof1));
2696 BOOST_CHECK(pm.addNode(1, proof1->getId()));
2697
2698 // Removing the node removes all the associated remote proofs
2699 BOOST_CHECK(pm.removeNode(0));
2701 !TestPeerManager::getRemoteProof(pm, ProofId(uint256::ZERO), 0));
2702 BOOST_CHECK(!TestPeerManager::getRemoteProof(pm, ProofId(uint256::ONE), 0));
2703 // Other nodes are left untouched
2704 checkRemoteProof(ProofId(uint256::ZERO), 1, false, mockTime);
2705 checkRemoteProof(ProofId(uint256::ONE), 1, true, mockTime);
2706
2707 BOOST_CHECK(pm.removeNode(1));
2709 !TestPeerManager::getRemoteProof(pm, ProofId(uint256::ZERO), 0));
2710 BOOST_CHECK(!TestPeerManager::getRemoteProof(pm, ProofId(uint256::ONE), 0));
2712 !TestPeerManager::getRemoteProof(pm, ProofId(uint256::ZERO), 1));
2713 BOOST_CHECK(!TestPeerManager::getRemoteProof(pm, ProofId(uint256::ONE), 1));
2714
2715 for (size_t i = 0; i < avalanche::PeerManager::MAX_REMOTE_PROOFS; i++) {
2716 mockTime += 1s;
2717 SetMockTime(mockTime);
2718
2719 const ProofId proofid{uint256(i)};
2720
2721 BOOST_CHECK(pm.saveRemoteProof(proofid, 0, true));
2722 checkRemoteProof(proofid, 0, true, mockTime);
2723 }
2724
2725 // The last updated proof is still there
2726 checkRemoteProof(ProofId(uint256::ZERO), 0, true,
2727 mockTime -
2729
2730 // If we add one more it gets evicted
2731 mockTime += 1s;
2732 SetMockTime(mockTime);
2733
2734 ProofId proofid{
2736
2737 BOOST_CHECK(pm.saveRemoteProof(proofid, 0, true));
2738 checkRemoteProof(proofid, 0, true, mockTime);
2739 // Proof id 0 has been evicted
2741 !TestPeerManager::getRemoteProof(pm, ProofId(uint256::ZERO), 0));
2742
2743 // Proof id 1 is still there
2744 BOOST_CHECK(TestPeerManager::getRemoteProof(pm, ProofId(uint256::ONE), 0));
2745
2746 // Add MAX_REMOTE_PROOFS / 2 + 1 proofs to our node to bump the limit
2747 // Note that we already have proofs from the beginning of the test.
2748 std::vector<ProofRef> proofs;
2749 for (size_t i = 0; i < avalanche::PeerManager::MAX_REMOTE_PROOFS / 2 - 1;
2750 i++) {
2751 auto proof = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
2752 BOOST_CHECK(pm.registerProof(proof));
2753 proofs.push_back(proof);
2754 }
2755 BOOST_CHECK_EQUAL(TestPeerManager::getPeerCount(pm),
2757
2758 // We can now add one more without eviction
2759 mockTime += 1s;
2760 SetMockTime(mockTime);
2761
2762 proofid = ProofId{
2764
2765 BOOST_CHECK(pm.saveRemoteProof(proofid, 0, true));
2766 checkRemoteProof(proofid, 0, true, mockTime);
2767 // Proof id 1 is still there
2768 BOOST_CHECK(TestPeerManager::getRemoteProof(pm, ProofId(uint256::ONE), 0));
2769
2770 // Shrink our proofs to MAX_REMOTE_PROOFS / 2 - 1
2775
2776 BOOST_CHECK_EQUAL(TestPeerManager::getPeerCount(pm),
2778
2779 // Upon update the first proof got evicted
2780 proofid = ProofId{
2782 BOOST_CHECK(pm.saveRemoteProof(proofid, 0, true));
2783 // Proof id 1 is evicted
2784 BOOST_CHECK(!TestPeerManager::getRemoteProof(pm, ProofId(uint256::ONE), 0));
2785 // So is proof id 2
2786 BOOST_CHECK(!TestPeerManager::getRemoteProof(pm, ProofId(uint256(2)), 0));
2787 // But proof id 3 is still here
2788 BOOST_CHECK(TestPeerManager::getRemoteProof(pm, ProofId(uint256(3)), 0));
2789}
2790
2791BOOST_AUTO_TEST_CASE(get_remote_status) {
2792 ChainstateManager &chainman = *Assert(m_node.chainman);
2794 Chainstate &active_chainstate = chainman.ActiveChainstate();
2795
2796 auto mockTime = GetTime<std::chrono::seconds>();
2797 SetMockTime(mockTime);
2798
2799 // No remote proof yet
2801 !TestPeerManager::getRemotePresenceStatus(pm, ProofId(uint256::ZERO))
2802 .has_value());
2803
2804 // 6/12 (50%) of the stakes
2805 for (NodeId nodeid = 0; nodeid < 12; nodeid++) {
2806 auto proof = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
2807 BOOST_CHECK(pm.registerProof(proof));
2808 BOOST_CHECK(pm.addNode(nodeid, proof->getId()));
2810 nodeid % 2 == 0));
2811 }
2812
2814 !TestPeerManager::getRemotePresenceStatus(pm, ProofId(uint256::ZERO))
2815 .has_value());
2816
2817 // 7/12 (~58%) of the stakes
2818 for (NodeId nodeid = 0; nodeid < 5; nodeid++) {
2819 BOOST_CHECK(pm.saveRemoteProof(ProofId(uint256::ZERO), nodeid, false));
2820 }
2821 for (NodeId nodeid = 5; nodeid < 12; nodeid++) {
2823 }
2825 TestPeerManager::getRemotePresenceStatus(pm, ProofId(uint256::ZERO))
2826 .value());
2827
2828 // Add our local proof so we have 7/13 (~54% < 55%)
2829 auto localProof =
2830 buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
2831 TestPeerManager::setLocalProof(pm, localProof);
2832 BOOST_CHECK(pm.registerProof(localProof));
2834 !TestPeerManager::getRemotePresenceStatus(pm, ProofId(uint256::ZERO))
2835 .has_value());
2836
2837 // Remove the local proof to revert back to 7/12 (~58%)
2838 pm.rejectProof(localProof->getId());
2839 TestPeerManager::setLocalProof(pm, ProofRef());
2841 TestPeerManager::getRemotePresenceStatus(pm, ProofId(uint256::ZERO))
2842 .value());
2843
2844 // 5/12 (~42%) of the stakes
2845 for (NodeId nodeid = 0; nodeid < 5; nodeid++) {
2847 }
2848 for (NodeId nodeid = 5; nodeid < 12; nodeid++) {
2849 BOOST_CHECK(pm.saveRemoteProof(ProofId(uint256::ZERO), nodeid, false));
2850 }
2852 !TestPeerManager::getRemotePresenceStatus(pm, ProofId(uint256::ZERO))
2853 .value());
2854
2855 // Most nodes agree but not enough of the stakes
2856 auto bigProof =
2857 buildRandomProof(active_chainstate, 100 * MIN_VALID_PROOF_SCORE);
2858 BOOST_CHECK(pm.registerProof(bigProof));
2859 // Update the node's proof
2860 BOOST_CHECK(pm.addNode(0, bigProof->getId()));
2861
2862 // 7/12 (~58%) of the remotes, but < 10% of the stakes => absent
2863 for (NodeId nodeid = 0; nodeid < 5; nodeid++) {
2864 BOOST_CHECK(pm.saveRemoteProof(ProofId(uint256::ZERO), nodeid, false));
2865 }
2866 for (NodeId nodeid = 5; nodeid < 12; nodeid++) {
2868 }
2870 !TestPeerManager::getRemotePresenceStatus(pm, ProofId(uint256::ZERO))
2871 .value());
2872
2873 // 5/12 (42%) of the remotes, but > 90% of the stakes => present
2874 for (NodeId nodeid = 0; nodeid < 5; nodeid++) {
2876 }
2877 for (NodeId nodeid = 5; nodeid < 12; nodeid++) {
2878 BOOST_CHECK(pm.saveRemoteProof(ProofId(uint256::ZERO), nodeid, false));
2879 }
2881 TestPeerManager::getRemotePresenceStatus(pm, ProofId(uint256::ZERO))
2882 .value());
2883
2884 TestPeerManager::clearPeers(pm);
2885
2886 // Peer 1 has 1 node (id 0)
2887 auto proof1 = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
2888 BOOST_CHECK(pm.registerProof(proof1));
2889 BOOST_CHECK(pm.addNode(0, proof1->getId()));
2890
2891 // Peer 2 has 5 nodes (ids 1 to 5)
2892 auto proof2 = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
2893 BOOST_CHECK(pm.registerProof(proof2));
2894 for (NodeId nodeid = 1; nodeid < 6; nodeid++) {
2895 BOOST_CHECK(pm.addNode(nodeid, proof2->getId()));
2896 }
2897
2898 // Node 0 is missing proofid 0, nodes 1 to 5 have it
2900 for (NodeId nodeid = 1; nodeid < 6; nodeid++) {
2902 }
2903
2904 // At this stage we have 5/6 nodes with the proof, but since all the nodes
2905 // advertising the proof are from the same peer, we only 1/2 peers, i.e. 50%
2906 // of the stakes.
2908 !TestPeerManager::getRemotePresenceStatus(pm, ProofId(uint256::ZERO))
2909 .has_value());
2910}
2911
2912BOOST_AUTO_TEST_CASE(dangling_with_remotes) {
2913 ChainstateManager &chainman = *Assert(m_node.chainman);
2915 Chainstate &active_chainstate = chainman.ActiveChainstate();
2916
2917 auto mockTime = GetTime<std::chrono::seconds>();
2918 SetMockTime(mockTime);
2919
2920 // Add a few proofs with no node attached
2921 std::vector<ProofRef> proofs;
2922 for (size_t i = 0; i < 10; i++) {
2923 auto proof = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
2924 BOOST_CHECK(pm.registerProof(proof));
2925 proofs.push_back(proof);
2926 }
2927
2928 // The proofs are recent enough, the cleanup won't make them dangling
2929 TestPeerManager::cleanupDanglingProofs(pm);
2930 for (const auto &proof : proofs) {
2931 BOOST_CHECK(pm.isBoundToPeer(proof->getId()));
2932 BOOST_CHECK(!pm.isDangling(proof->getId()));
2933 }
2934
2935 // Elapse enough time so we get the proofs dangling
2936 mockTime += avalanche::Peer::DANGLING_TIMEOUT + 1s;
2937 SetMockTime(mockTime);
2938
2939 // The proofs are now dangling
2940 TestPeerManager::cleanupDanglingProofs(pm);
2941 for (const auto &proof : proofs) {
2942 BOOST_CHECK(!pm.isBoundToPeer(proof->getId()));
2943 BOOST_CHECK(pm.isDangling(proof->getId()));
2944 }
2945
2946 // Add some remotes having this proof
2947 for (NodeId nodeid = 0; nodeid < 10; nodeid++) {
2948 auto localProof =
2949 buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
2950 BOOST_CHECK(pm.registerProof(localProof));
2951 BOOST_CHECK(pm.addNode(nodeid, localProof->getId()));
2952
2953 for (const auto &proof : proofs) {
2954 BOOST_CHECK(pm.saveRemoteProof(proof->getId(), nodeid, true));
2955 }
2956 }
2957
2958 // The proofs are all present according to the remote status
2959 for (const auto &proof : proofs) {
2960 BOOST_CHECK(TestPeerManager::getRemotePresenceStatus(pm, proof->getId())
2961 .value());
2962 }
2963
2964 // The proofs should be added back as a peer
2965 std::unordered_set<ProofRef, SaltedProofHasher> registeredProofs;
2966 TestPeerManager::cleanupDanglingProofs(pm, registeredProofs);
2967 for (const auto &proof : proofs) {
2968 BOOST_CHECK(pm.isBoundToPeer(proof->getId()));
2969 BOOST_CHECK(!pm.isDangling(proof->getId()));
2970 BOOST_CHECK_EQUAL(registeredProofs.count(proof), 1);
2971 }
2972 BOOST_CHECK_EQUAL(proofs.size(), registeredProofs.size());
2973
2974 // Remove the proofs from the remotes
2975 for (NodeId nodeid = 0; nodeid < 10; nodeid++) {
2976 for (const auto &proof : proofs) {
2977 BOOST_CHECK(pm.saveRemoteProof(proof->getId(), nodeid, false));
2978 }
2979 }
2980
2981 // The proofs are now all absent according to the remotes
2982 for (const auto &proof : proofs) {
2984 !TestPeerManager::getRemotePresenceStatus(pm, proof->getId())
2985 .value());
2986 }
2987
2988 // The proofs are not dangling yet as they have been registered recently
2989 TestPeerManager::cleanupDanglingProofs(pm, registeredProofs);
2990 BOOST_CHECK(registeredProofs.empty());
2991 for (const auto &proof : proofs) {
2992 BOOST_CHECK(pm.isBoundToPeer(proof->getId()));
2993 BOOST_CHECK(!pm.isDangling(proof->getId()));
2994 }
2995
2996 // Wait some time then run the cleanup again, the proofs will be dangling
2997 mockTime += avalanche::Peer::DANGLING_TIMEOUT + 1s;
2998 SetMockTime(mockTime);
2999
3000 TestPeerManager::cleanupDanglingProofs(pm, registeredProofs);
3001 BOOST_CHECK(registeredProofs.empty());
3002 for (const auto &proof : proofs) {
3003 BOOST_CHECK(!pm.isBoundToPeer(proof->getId()));
3004 BOOST_CHECK(pm.isDangling(proof->getId()));
3005 }
3006
3007 // Pull them back one more time
3008 for (NodeId nodeid = 0; nodeid < 10; nodeid++) {
3009 for (const auto &proof : proofs) {
3010 BOOST_CHECK(pm.saveRemoteProof(proof->getId(), nodeid, true));
3011 }
3012 }
3013
3014 TestPeerManager::cleanupDanglingProofs(pm, registeredProofs);
3015 for (const auto &proof : proofs) {
3016 BOOST_CHECK(pm.isBoundToPeer(proof->getId()));
3017 BOOST_CHECK(!pm.isDangling(proof->getId()));
3018 BOOST_CHECK_EQUAL(registeredProofs.count(proof), 1);
3019 }
3020 BOOST_CHECK_EQUAL(proofs.size(), registeredProofs.size());
3021}
3022
3023BOOST_AUTO_TEST_CASE(avapeers_dump) {
3024 ChainstateManager &chainman = *Assert(m_node.chainman);
3026 Chainstate &active_chainstate = chainman.ActiveChainstate();
3027
3028 auto mockTime = GetTime<std::chrono::seconds>();
3029 SetMockTime(mockTime);
3030
3031 std::vector<ProofRef> proofs;
3032 for (size_t i = 0; i < 10; i++) {
3033 SetMockTime(mockTime + std::chrono::seconds{i});
3034
3035 auto proof = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
3036 // Registration time is mockTime + i
3037 BOOST_CHECK(pm.registerProof(proof));
3038
3039 auto peerid = TestPeerManager::getPeerIdForProofId(pm, proof->getId());
3040
3041 // Next conflict time is mockTime + 100 + i
3043 peerid, mockTime + std::chrono::seconds{100 + i}));
3044
3045 // The 5 first proofs are finalized
3046 if (i < 5) {
3047 BOOST_CHECK(pm.setFinalized(peerid));
3048 }
3049
3050 proofs.push_back(proof);
3051 }
3052
3053 BOOST_CHECK_EQUAL(TestPeerManager::getPeerCount(pm), 10);
3054
3055 const fs::path testDumpPath = "test_avapeers_dump.dat";
3056 BOOST_CHECK(pm.dumpPeersToFile(testDumpPath));
3057
3058 TestPeerManager::clearPeers(pm);
3059
3060 std::unordered_set<ProofRef, SaltedProofHasher> registeredProofs;
3061 BOOST_CHECK(pm.loadPeersFromFile(testDumpPath, registeredProofs));
3062 BOOST_CHECK_EQUAL(registeredProofs.size(), 10);
3063
3064 auto findProofIndex = [&proofs](const ProofId &proofid) {
3065 for (size_t i = 0; i < proofs.size(); i++) {
3066 if (proofs[i]->getId() == proofid) {
3067 return i;
3068 }
3069 }
3070
3071 // ProofId not found
3072 BOOST_CHECK(false);
3073 return size_t{0};
3074 };
3075
3076 for (const auto &proof : registeredProofs) {
3077 const ProofId &proofid = proof->getId();
3078 size_t i = findProofIndex(proofid);
3079 BOOST_CHECK(pm.forPeer(proofid, [&](auto &peer) {
3080 BOOST_CHECK_EQUAL(peer.hasFinalized, i < 5);
3081 BOOST_CHECK_EQUAL(peer.registration_time.count(),
3082 (mockTime + std::chrono::seconds{i}).count());
3084 peer.nextPossibleConflictTime.count(),
3085 (mockTime + std::chrono::seconds{100 + i}).count());
3086 return true;
3087 }));
3088 }
3089
3090 // No peer: create an empty file but generate no error
3091 TestPeerManager::clearPeers(pm);
3092 BOOST_CHECK(pm.dumpPeersToFile("test_empty_avapeers.dat"));
3093 // We can also load an empty file
3095 pm.loadPeersFromFile("test_empty_avapeers.dat", registeredProofs));
3096 BOOST_CHECK(registeredProofs.empty());
3097 BOOST_CHECK_EQUAL(TestPeerManager::getPeerCount(pm), 0);
3098
3099 // If the file exists, it is overrwritten
3100 BOOST_CHECK(pm.dumpPeersToFile("test_empty_avapeers.dat"));
3101
3102 // It fails to load if the file does not exist and the registeredProofs is
3103 // cleared
3104 registeredProofs.insert(proofs[0]);
3105 BOOST_CHECK(!registeredProofs.empty());
3106 BOOST_CHECK(!pm.loadPeersFromFile("I_dont_exist.dat", registeredProofs));
3107 BOOST_CHECK(registeredProofs.empty());
3108
3109 {
3110 // Change the version
3111 FILE *f = fsbridge::fopen("test_bad_version_avapeers.dat", "wb");
3112 BOOST_CHECK(f);
3114 file << static_cast<uint64_t>(-1); // Version
3115 file << uint64_t{0}; // Number of peers
3116 BOOST_CHECK(FileCommit(file.Get()));
3117 file.fclose();
3118
3119 // Check loading fails and the registeredProofs is cleared
3120 registeredProofs.insert(proofs[0]);
3121 BOOST_CHECK(!registeredProofs.empty());
3122 BOOST_CHECK(!pm.loadPeersFromFile("test_bad_version_avapeers.dat",
3123 registeredProofs));
3124 BOOST_CHECK(registeredProofs.empty());
3125 }
3126
3127 {
3128 // Wrong format, will cause a deserialization error
3129 FILE *f = fsbridge::fopen("test_ill_formed_avapeers.dat", "wb");
3130 BOOST_CHECK(f);
3131 const uint64_t now = GetTime();
3133 file << static_cast<uint64_t>(1); // Version
3134 file << uint64_t{2}; // Number of peers
3135 // Single peer content!
3136 file << proofs[0];
3137 file << true;
3138 file << now;
3139 file << now + 100;
3140
3141 BOOST_CHECK(FileCommit(file.Get()));
3142 file.fclose();
3143
3144 // Check loading fails and the registeredProofs is fed with our single
3145 // peer
3146 BOOST_CHECK(registeredProofs.empty());
3147 BOOST_CHECK(!pm.loadPeersFromFile("test_ill_formed_avapeers.dat",
3148 registeredProofs));
3149 BOOST_CHECK_EQUAL(registeredProofs.size(), 1);
3150 BOOST_CHECK_EQUAL((*registeredProofs.begin())->getId(),
3151 proofs[0]->getId());
3152 }
3153}
3154
3155BOOST_AUTO_TEST_CASE(dangling_proof_invalidation) {
3156 ChainstateManager &chainman = *Assert(m_node.chainman);
3158 Chainstate &active_chainstate = chainman.ActiveChainstate();
3159
3160 SetMockTime(GetTime<std::chrono::seconds>());
3161
3163 auto utxo = createUtxo(active_chainstate, key);
3164 auto proof =
3165 buildProof(key, {{utxo, PROOF_DUST_THRESHOLD}}, key, 2, 100, false,
3166 GetTime<std::chrono::seconds>().count() + 1000000);
3167
3168 // Register the proof
3169 BOOST_CHECK(pm.registerProof(proof));
3170 BOOST_CHECK(pm.isBoundToPeer(proof->getId()));
3171 BOOST_CHECK(!pm.isDangling(proof->getId()));
3172
3173 // Elapse the dangling timeout. No nodes are bound, so the proof is now
3174 // dangling.
3175 SetMockTime(GetTime<std::chrono::seconds>() +
3177 TestPeerManager::cleanupDanglingProofs(pm);
3178 BOOST_CHECK(!pm.isBoundToPeer(proof->getId()));
3179 BOOST_CHECK(!pm.exists(proof->getId()));
3180 BOOST_CHECK(pm.isDangling(proof->getId()));
3181
3182 {
3183 LOCK(cs_main);
3184 CCoinsViewCache &coins = active_chainstate.CoinsTip();
3185 // Make proof invalid
3186 coins.SpendCoin(utxo);
3187 }
3188
3189 // Trigger proof validity checks
3190 pm.updatedBlockTip();
3191
3192 // The now invalid proof is removed
3193 BOOST_CHECK(!pm.exists(proof->getId()));
3194 BOOST_CHECK(!pm.isDangling(proof->getId()));
3195
3196 {
3197 LOCK(cs_main);
3198 CCoinsViewCache &coins = active_chainstate.CoinsTip();
3199 // Add the utxo back so we can make the proof valid again
3200 CScript script = GetScriptForDestination(PKHash(key.GetPubKey()));
3201 coins.AddCoin(utxo,
3202 Coin(CTxOut(PROOF_DUST_THRESHOLD, script), 100, false),
3203 false);
3204 }
3205
3206 // Our proof is not expired yet, so it registers fine
3207 BOOST_CHECK(pm.registerProof(proof));
3208 BOOST_CHECK(pm.isBoundToPeer(proof->getId()));
3209 BOOST_CHECK(!pm.isDangling(proof->getId()));
3210
3211 // Elapse the dangling timeout. No nodes are bound, so the proof is now
3212 // dangling.
3213 SetMockTime(GetTime<std::chrono::seconds>() +
3215 TestPeerManager::cleanupDanglingProofs(pm);
3216 BOOST_CHECK(!pm.isBoundToPeer(proof->getId()));
3217 BOOST_CHECK(!pm.exists(proof->getId()));
3218 BOOST_CHECK(pm.isDangling(proof->getId()));
3219
3220 // Mine blocks until the MTP of the tip moves to the proof expiration
3221 for (int64_t i = 0; i < 6; i++) {
3222 SetMockTime(proof->getExpirationTime() + i);
3223 CreateAndProcessBlock({}, CScript());
3224 }
3226 WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip())
3227 ->GetMedianTimePast(),
3228 proof->getExpirationTime());
3229
3230 pm.updatedBlockTip();
3231
3232 // The now expired proof is removed
3233 BOOST_CHECK(!pm.exists(proof->getId()));
3234 BOOST_CHECK(!pm.isDangling(proof->getId()));
3235}
3236
3237BOOST_AUTO_TEST_SUITE_END()
ArgsManager gArgs
Definition: args.cpp:38
static constexpr PeerId NO_PEER
Definition: node.h:16
uint32_t PeerId
Definition: node.h:15
#define Assert(val)
Identity function.
Definition: check.h:84
void ForceSetArg(const std::string &strArg, const std::string &strValue)
Definition: args.cpp:597
void ClearForcedArg(const std::string &strArg)
Remove a forced arg setting, used only in testing.
Definition: args.cpp:648
The block chain is a tree shaped structure starting with the genesis block at the root,...
Definition: blockindex.h:25
const BlockHash * phashBlock
pointer to the hash of the block, if any.
Definition: blockindex.h:29
uint32_t nTime
Definition: blockindex.h:92
CCoinsView that adds a memory cache for transactions to another CCoinsView.
Definition: coins.h:221
void AddCoin(const COutPoint &outpoint, Coin coin, bool possible_overwrite)
Add a coin.
Definition: coins.cpp:104
bool SpendCoin(const COutPoint &outpoint, Coin *moveto=nullptr)
Spend a coin.
Definition: coins.cpp:172
An encapsulated secp256k1 private key.
Definition: key.h:28
static CKey MakeCompressedKey()
Produce a valid compressed key.
Definition: key.cpp:466
CPubKey GetPubKey() const
Compute the public key from a private key.
Definition: key.cpp:210
An output of a transaction.
Definition: transaction.h:128
Chainstate stores and provides an API to update our local knowledge of the current best chain.
Definition: validation.h:699
CCoinsViewCache & CoinsTip() EXCLUSIVE_LOCKS_REQUIRED(
Definition: validation.h:834
bool InvalidateBlock(BlockValidationState &state, CBlockIndex *pindex) EXCLUSIVE_LOCKS_REQUIRED(!m_chainstate_mutex
Mark a block as invalid.
Provides an interface for creating and interacting with one or two chainstates: an IBD chainstate gen...
Definition: validation.h:1219
RecursiveMutex & GetMutex() const LOCK_RETURNED(
Alias for cs_main.
Definition: validation.h:1343
CBlockIndex * ActiveTip() const EXCLUSIVE_LOCKS_REQUIRED(GetMutex())
Definition: validation.h:1435
SnapshotCompletionResult MaybeCompleteSnapshotValidation(std::function< void(bilingual_str)> shutdown_fnc=[](bilingual_str msg) { AbortNode(msg.original, msg);}) EXCLUSIVE_LOCKS_REQUIRED(Chainstate & ActiveChainstate() const
Once the background validation chainstate has reached the height which is the base of the UTXO snapsh...
int ActiveHeight() const EXCLUSIVE_LOCKS_REQUIRED(GetMutex())
Definition: validation.h:1432
A UTXO entry.
Definition: coins.h:28
Fast randomness source.
Definition: random.h:156
static RCUPtr make(Args &&...args)
Construct a new object that is owned by the pointer.
Definition: rcu.h:112
bool IsValid() const
Definition: validation.h:119
Result GetResult() const
Definition: validation.h:122
bool selectStakingRewardWinner(const CBlockIndex *pprev, std::vector< std::pair< ProofId, CScript > > &winners)
Deterministically select a list of payout scripts based on the proof set and the previous block hash.
bool removeNode(NodeId nodeid)
bool setFinalized(PeerId peerid)
Latch on that this peer has a finalized proof.
bool dumpPeersToFile(const fs::path &dumpPath) const
RemoteProofSet remoteProofs
Remember which node sent which proof so we have an image of the proof set of our peers.
Definition: peermanager.h:281
uint64_t getFragmentation() const
Definition: peermanager.h:503
uint32_t getConnectedPeersScore() const
Definition: peermanager.h:442
bool isDangling(const ProofId &proofid) const
bool updateNextRequestTime(NodeId nodeid, SteadyMilliseconds timeout)
std::optional< bool > getRemotePresenceStatus(const ProofId &proofid) const
Get the presence remote status of a proof.
bool shouldRequestMoreNodes()
Returns true if we encountered a lack of node since the last call.
Definition: peermanager.h:331
bool exists(const ProofId &proofid) const
Return true if the (valid) proof exists, but only for non-dangling proofs.
Definition: peermanager.h:406
bool isRemoteProof(const ProofId &proofid) const
size_t getNodeCount() const
Definition: peermanager.h:313
PendingNodeSet pendingNodes
Definition: peermanager.h:225
bool verify() const
Perform consistency check on internal data structures.
bool forNode(NodeId nodeid, Callable &&func) const
Definition: peermanager.h:334
bool forPeer(const ProofId &proofid, Callable &&func) const
Definition: peermanager.h:414
uint32_t getTotalPeersScore() const
Definition: peermanager.h:441
bool latchAvaproofsSent(NodeId nodeid)
Flag that a node did send its compact proofs.
bool addNode(NodeId nodeid, const ProofId &proofid)
Node API.
Definition: peermanager.cpp:31
uint64_t getSlotCount() const
Definition: peermanager.h:502
bool loadPeersFromFile(const fs::path &dumpPath, std::unordered_set< ProofRef, SaltedProofHasher > &registeredProofs)
std::unordered_set< ProofRef, SaltedProofHasher > updatedBlockTip()
Update the peer set when a new block is connected.
const ProofRadixTree & getShareableProofsSnapshot() const
Definition: peermanager.h:521
bool isBoundToPeer(const ProofId &proofid) const
size_t getPendingNodeCount() const
Definition: peermanager.h:314
bool saveRemoteProof(const ProofId &proofid, const NodeId nodeid, const bool present)
uint64_t compact()
Trigger maintenance of internal data structures.
void forEachPeer(Callable &&func) const
Definition: peermanager.h:420
void forEachNode(const Peer &peer, Callable &&func) const
Definition: peermanager.h:340
bool isFlaky(const ProofId &proofid) const
bool removePeer(const PeerId peerid)
Remove an existing peer.
bool isImmature(const ProofId &proofid) const
bool rejectProof(const ProofId &proofid, RejectionMode mode=RejectionMode::DEFAULT)
RegistrationMode
Registration mode.
Definition: peermanager.h:371
static constexpr size_t MAX_REMOTE_PROOFS
Definition: peermanager.h:300
PeerId selectPeer() const
Randomly select a peer to poll.
void updateAvailabilityScores(const double decayFactor, Callable &&getNodeAvailabilityScore)
Definition: peermanager.h:458
bool isInConflictingPool(const ProofId &proofid) const
void cleanupDanglingProofs(std::unordered_set< ProofRef, SaltedProofHasher > &registeredProofs)
ProofRef getProof(const ProofId &proofid) const
bool registerProof(const ProofRef &proof, ProofRegistrationState &registrationState, RegistrationMode mode=RegistrationMode::DEFAULT)
bool updateNextPossibleConflictTime(PeerId peerid, const std::chrono::seconds &nextTime)
Proof and Peer related API.
bool addUTXO(COutPoint utxo, Amount amount, uint32_t height, bool is_coinbase, CKey key)
int64_t getExpirationTime() const
Definition: proof.h:163
const CScript & getPayoutScript() const
Definition: proof.h:166
const ProofId & getId() const
Definition: proof.h:169
uint8_t * begin()
Definition: uint256.h:85
Path class wrapper to block calls to the fs::path(std::string) implicit constructor and the fs::path:...
Definition: fs.h:30
256-bit opaque blob.
Definition: uint256.h:129
static const uint256 ONE
Definition: uint256.h:135
static const uint256 ZERO
Definition: uint256.h:134
static constexpr int CLIENT_VERSION
bitcoind-res.rc includes this file, but it cannot cope with real c++ code.
Definition: clientversion.h:38
static void addCoin(const Amount nValue, const CWallet &wallet, std::vector< std::unique_ptr< CWalletTx > > &wtxs)
std::string FormatScript(const CScript &script)
Definition: core_write.cpp:24
static const uint8_t tau[]
Definition: chacha20.cpp:30
RecursiveMutex cs_main
Mutex to guard access to validation specific variables, such as reading or changing the chainstate.
Definition: cs_main.cpp:7
bool FileCommit(FILE *file)
Ensure file contents are fully committed to disk, using a platform-specific feature analogous to fsyn...
Definition: fs_helpers.cpp:125
bool error(const char *fmt, const Args &...args)
Definition: logging.h:226
static RPCHelpMan generate()
Definition: mining.cpp:293
static constexpr Amount PROOF_DUST_THRESHOLD
Minimum amount per utxo.
Definition: proof.h:40
ProofRegistrationResult
Definition: peermanager.h:145
static constexpr uint32_t AVALANCHE_MAX_IMMATURE_PROOFS
Maximum number of immature proofs the peer manager will accept from the network.
Definition: peermanager.h:44
const CScript UNSPENDABLE_ECREG_PAYOUT_SCRIPT
Definition: util.h:19
ProofRef buildRandomProof(Chainstate &active_chainstate, uint32_t score, int height, const CKey &masterKey)
Definition: util.cpp:20
constexpr uint32_t MIN_VALID_PROOF_SCORE
Definition: util.h:17
PeerId selectPeerImpl(const std::vector< Slot > &slots, const uint64_t slot, const uint64_t max)
Internal methods that are exposed for testing purposes.
RCUPtr< const Proof > ProofRef
Definition: proof.h:185
FILE * fopen(const fs::path &p, const char *mode)
Definition: fs.cpp:30
Definition: init.h:28
NodeContext & m_node
Definition: interfaces.cpp:785
static constexpr NodeId NO_NODE
Special NodeId that represent no node.
Definition: nodeid.h:15
int64_t NodeId
Definition: nodeid.h:10
#define BOOST_CHECK_EQUAL(v1, v2)
Definition: object.cpp:18
#define BOOST_CHECK(expr)
Definition: object.cpp:17
static void addNodeWithScore(Chainstate &active_chainstate, avalanche::PeerManager &pm, NodeId node, uint32_t score)
BOOST_AUTO_TEST_CASE(select_peer_linear)
BOOST_FIXTURE_TEST_CASE(conflicting_proof_rescan, NoCoolDownFixture)
uint256 GetRandHash() noexcept
Definition: random.cpp:659
void Shuffle(I first, I last, R &&rng)
More efficient than using std::shuffle on a FastRandomContext.
Definition: random.h:291
@ SER_DISK
Definition: serialize.h:153
CScript GetScriptForRawPubKey(const CPubKey &pubKey)
Generate a P2PK script for the given pubkey.
Definition: standard.cpp:244
CScript GetScriptForDestination(const CTxDestination &dest)
Generate a Bitcoin scriptPubKey for the given CTxDestination.
Definition: standard.cpp:240
static const double AVALANCHE_STATISTICS_DECAY_FACTOR
Pre-computed decay factor for the avalanche statistics computation.
Definition: statistics.h:18
static constexpr std::chrono::minutes AVALANCHE_STATISTICS_TIME_CONSTANT
Time constant for the avalanche statistics computation.
Definition: statistics.h:13
static constexpr std::chrono::minutes AVALANCHE_STATISTICS_REFRESH_PERIOD
Refresh period for the avalanche statistics computation.
Definition: statistics.h:11
Definition: amount.h:19
A BlockHash is a unqiue identifier for a block.
Definition: blockhash.h:13
bool insert(const RCUPtr< T > &value)
Insert a value into the tree.
Definition: radix.h:112
A TxId is the identifier of a transaction.
Definition: txid.h:14
Compare conflicting proofs.
std::chrono::seconds registration_time
Definition: peermanager.h:93
static constexpr auto DANGLING_TIMEOUT
Consider dropping the peer if no node is attached after this timeout expired.
Definition: peermanager.h:102
ProofRef proof
Definition: peermanager.h:89
Bilingual messages:
Definition: translation.h:17
#define LOCK(cs)
Definition: sync.h:306
#define WITH_LOCK(cs, code)
Run code while locking a mutex.
Definition: sync.h:357
static int count
Definition: tests.c:31
int64_t GetTime()
DEPRECATED Use either ClockType::now() or Now<TimePointType>() if a cast is needed.
Definition: time.cpp:109
void SetMockTime(int64_t nMockTimeIn)
DEPRECATED Use SetMockTime with chrono type.
Definition: time.cpp:89
std::chrono::time_point< std::chrono::steady_clock, std::chrono::milliseconds > SteadyMilliseconds
Definition: time.h:31
#define strprintf
Format arguments and return the string or write to given std::ostream (see tinyformat::format doc for...
Definition: tinyformat.h:1202