Bitcoin ABC 0.33.11
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 bool addDanglingProof(PeerManager &pm, const ProofRef &proof) {
86 return pm.danglingProofPool.addProofIfPreferred(proof) ==
87 ProofPool::AddProofStatus::SUCCEED;
88 }
89
90 static std::optional<RemoteProof> getRemoteProof(const PeerManager &pm,
91 const ProofId &proofid,
92 NodeId nodeid) {
93 auto it = pm.remoteProofs.find(boost::make_tuple(proofid, nodeid));
94 if (it == pm.remoteProofs.end()) {
95 return std::nullopt;
96 }
97 return std::make_optional(*it);
98 }
99
100 static size_t getPeerCount(const PeerManager &pm) {
101 return pm.peers.size();
102 }
103
104 static std::optional<bool>
105 getRemotePresenceStatus(const PeerManager &pm, const ProofId &proofid) {
106 return pm.getRemotePresenceStatus(proofid);
107 }
108
109 static void clearPeers(PeerManager &pm) {
110 std::vector<PeerId> peerIds;
111 for (auto &peer : pm.peers) {
112 peerIds.push_back(peer.peerid);
113 }
114 for (const PeerId &peerid : peerIds) {
115 pm.removePeer(peerid);
116 }
117 BOOST_CHECK_EQUAL(pm.peers.size(), 0);
118 }
119
120 static void setLocalProof(PeerManager &pm, const ProofRef &proof) {
121 pm.localProof = proof;
122 }
123
124 static bool isFlaky(const PeerManager &pm, const ProofId &proofid) {
125 return pm.isFlaky(proofid);
126 }
127
128 static PeerId selectPeerFromSlot(const PeerManager &pm, uint64_t slot) {
129 return selectPeerImpl(pm.slots, slot, pm.slotCount);
130 }
131 };
132
133 static void addCoin(Chainstate &chainstate, const COutPoint &outpoint,
134 const CKey &key,
135 const Amount amount = PROOF_DUST_THRESHOLD,
136 uint32_t height = 100, bool is_coinbase = false) {
138
139 LOCK(cs_main);
140 CCoinsViewCache &coins = chainstate.CoinsTip();
141 coins.AddCoin(outpoint,
142 Coin(CTxOut(amount, script), height, is_coinbase), false);
143 }
144
145 static COutPoint createUtxo(Chainstate &chainstate, const CKey &key,
146 const Amount amount = PROOF_DUST_THRESHOLD,
147 uint32_t height = 100,
148 bool is_coinbase = false) {
149 COutPoint outpoint(TxId(GetRandHash()), 0);
150 addCoin(chainstate, outpoint, key, amount, height, is_coinbase);
151 return outpoint;
152 }
153
154 static ProofRef
155 buildProof(const CKey &key,
156 const std::vector<std::tuple<COutPoint, Amount>> &outpoints,
157 const CKey &master = CKey::MakeCompressedKey(),
158 int64_t sequence = 1, uint32_t height = 100,
159 bool is_coinbase = false, int64_t expirationTime = 0,
160 const CScript &payoutScript = UNSPENDABLE_ECREG_PAYOUT_SCRIPT) {
161 ProofBuilder pb(sequence, expirationTime, master, payoutScript);
162 for (const auto &[outpoint, amount] : outpoints) {
163 BOOST_CHECK(pb.addUTXO(outpoint, amount, height, is_coinbase, key));
164 }
165 return pb.build();
166 }
167
168 template <typename... Args>
169 static ProofRef
170 buildProofWithOutpoints(const CKey &key,
171 const std::vector<COutPoint> &outpoints,
172 Amount amount, Args &&...args) {
173 std::vector<std::tuple<COutPoint, Amount>> outpointsWithAmount;
174 std::transform(
175 outpoints.begin(), outpoints.end(),
176 std::back_inserter(outpointsWithAmount),
177 [amount](const auto &o) { return std::make_tuple(o, amount); });
178 return buildProof(key, outpointsWithAmount,
179 std::forward<Args>(args)...);
180 }
181
182 static ProofRef
183 buildProofWithSequence(const CKey &key,
184 const std::vector<COutPoint> &outpoints,
185 int64_t sequence) {
186 return buildProofWithOutpoints(key, outpoints, PROOF_DUST_THRESHOLD,
187 key, sequence);
188 }
189} // namespace
190} // namespace avalanche
191
192namespace {
193struct PeerManagerFixture : public TestChain100Setup {
194 PeerManagerFixture() {
195 gArgs.ForceSetArg("-avaproofstakeutxoconfirmations", "1");
196 }
197 ~PeerManagerFixture() {
198 gArgs.ClearForcedArg("-avaproofstakeutxoconfirmations");
199 }
200};
201} // namespace
202
203namespace {
204struct NoCoolDownFixture : public PeerManagerFixture {
205 NoCoolDownFixture() {
206 gArgs.ForceSetArg("-avalancheconflictingproofcooldown", "0");
207 }
208 ~NoCoolDownFixture() {
209 gArgs.ClearForcedArg("-avalancheconflictingproofcooldown");
210 }
211};
212} // namespace
213
214BOOST_FIXTURE_TEST_SUITE(peermanager_tests, PeerManagerFixture)
215
216BOOST_AUTO_TEST_CASE(select_peer_linear) {
217 // No peers.
220
221 // One peer
222 const std::vector<Slot> oneslot = {{100, 100, 23}};
223
224 // Undershoot
225 BOOST_CHECK_EQUAL(selectPeerImpl(oneslot, 0, 300), NO_PEER);
226 BOOST_CHECK_EQUAL(selectPeerImpl(oneslot, 42, 300), NO_PEER);
227 BOOST_CHECK_EQUAL(selectPeerImpl(oneslot, 99, 300), NO_PEER);
228
229 // Nailed it
230 BOOST_CHECK_EQUAL(selectPeerImpl(oneslot, 100, 300), 23);
231 BOOST_CHECK_EQUAL(selectPeerImpl(oneslot, 142, 300), 23);
232 BOOST_CHECK_EQUAL(selectPeerImpl(oneslot, 199, 300), 23);
233
234 // Overshoot
235 BOOST_CHECK_EQUAL(selectPeerImpl(oneslot, 200, 300), NO_PEER);
236 BOOST_CHECK_EQUAL(selectPeerImpl(oneslot, 242, 300), NO_PEER);
237 BOOST_CHECK_EQUAL(selectPeerImpl(oneslot, 299, 300), NO_PEER);
238
239 // Two peers
240 const std::vector<Slot> twoslots = {{100, 100, 69}, {300, 100, 42}};
241
242 // Undershoot
243 BOOST_CHECK_EQUAL(selectPeerImpl(twoslots, 0, 500), NO_PEER);
244 BOOST_CHECK_EQUAL(selectPeerImpl(twoslots, 42, 500), NO_PEER);
245 BOOST_CHECK_EQUAL(selectPeerImpl(twoslots, 99, 500), NO_PEER);
246
247 // First entry
248 BOOST_CHECK_EQUAL(selectPeerImpl(twoslots, 100, 500), 69);
249 BOOST_CHECK_EQUAL(selectPeerImpl(twoslots, 142, 500), 69);
250 BOOST_CHECK_EQUAL(selectPeerImpl(twoslots, 199, 500), 69);
251
252 // In between
253 BOOST_CHECK_EQUAL(selectPeerImpl(twoslots, 200, 500), NO_PEER);
254 BOOST_CHECK_EQUAL(selectPeerImpl(twoslots, 242, 500), NO_PEER);
255 BOOST_CHECK_EQUAL(selectPeerImpl(twoslots, 299, 500), NO_PEER);
256
257 // Second entry
258 BOOST_CHECK_EQUAL(selectPeerImpl(twoslots, 300, 500), 42);
259 BOOST_CHECK_EQUAL(selectPeerImpl(twoslots, 342, 500), 42);
260 BOOST_CHECK_EQUAL(selectPeerImpl(twoslots, 399, 500), 42);
261
262 // Overshoot
263 BOOST_CHECK_EQUAL(selectPeerImpl(twoslots, 400, 500), NO_PEER);
264 BOOST_CHECK_EQUAL(selectPeerImpl(twoslots, 442, 500), NO_PEER);
265 BOOST_CHECK_EQUAL(selectPeerImpl(twoslots, 499, 500), NO_PEER);
266}
267
268BOOST_AUTO_TEST_CASE(select_peer_dichotomic) {
269 std::vector<Slot> slots;
270
271 // 100 peers of size 1 with 1 empty element apart.
272 uint64_t max = 1;
273 for (int i = 0; i < 100; i++) {
274 slots.emplace_back(max, 1, i);
275 max += 2;
276 }
277
279
280 // Check that we get what we expect.
281 for (int i = 0; i < 100; i++) {
282 BOOST_CHECK_EQUAL(selectPeerImpl(slots, 2 * i, max), NO_PEER);
283 BOOST_CHECK_EQUAL(selectPeerImpl(slots, 2 * i + 1, max), i);
284 }
285
286 BOOST_CHECK_EQUAL(selectPeerImpl(slots, max, max), NO_PEER);
287
288 // Update the slots to be heavily skewed toward the last element.
289 slots[99] = slots[99].withScore(101);
290 max = slots[99].getStop();
291 BOOST_CHECK_EQUAL(max, 300);
292
293 for (int i = 0; i < 100; i++) {
294 BOOST_CHECK_EQUAL(selectPeerImpl(slots, 2 * i, max), NO_PEER);
295 BOOST_CHECK_EQUAL(selectPeerImpl(slots, 2 * i + 1, max), i);
296 }
297
298 BOOST_CHECK_EQUAL(selectPeerImpl(slots, 200, max), 99);
299 BOOST_CHECK_EQUAL(selectPeerImpl(slots, 256, max), 99);
300 BOOST_CHECK_EQUAL(selectPeerImpl(slots, 299, max), 99);
301 BOOST_CHECK_EQUAL(selectPeerImpl(slots, 300, max), NO_PEER);
302
303 // Update the slots to be heavily skewed toward the first element.
304 for (int i = 0; i < 100; i++) {
305 slots[i] = slots[i].withStart(slots[i].getStart() + 100);
306 }
307
308 slots[0] = Slot(1, slots[0].getStop() - 1, slots[0].getPeerId());
309 slots[99] = slots[99].withScore(1);
310 max = slots[99].getStop();
311 BOOST_CHECK_EQUAL(max, 300);
312
314 BOOST_CHECK_EQUAL(selectPeerImpl(slots, 1, max), 0);
315 BOOST_CHECK_EQUAL(selectPeerImpl(slots, 42, max), 0);
316
317 for (int i = 0; i < 100; i++) {
318 BOOST_CHECK_EQUAL(selectPeerImpl(slots, 100 + 2 * i + 1, max), i);
319 BOOST_CHECK_EQUAL(selectPeerImpl(slots, 100 + 2 * i + 2, max), NO_PEER);
320 }
321}
322
323BOOST_AUTO_TEST_CASE(select_peer_random) {
324 for (int c = 0; c < 1000; c++) {
325 size_t size = m_rng.randbits(10) + 1;
326 std::vector<Slot> slots;
327 slots.reserve(size);
328
329 uint64_t max = m_rng.randbits(3);
330 auto next = [&]() {
331 uint64_t r = max;
332 max += m_rng.randbits(3);
333 return r;
334 };
335
336 for (size_t i = 0; i < size; i++) {
337 const uint64_t start = next();
338 const uint32_t score = m_rng.randbits(3);
339 max += score;
340 slots.emplace_back(start, score, i);
341 }
342
343 for (int k = 0; k < 100; k++) {
344 uint64_t s = max > 0 ? m_rng.randrange(max) : 0;
345 auto i = selectPeerImpl(slots, s, max);
346 // /!\ Because of the way we construct the vector, the peer id is
347 // always the index. This might not be the case in practice.
348 BOOST_CHECK(i == NO_PEER || slots[i].contains(s));
349 }
350 }
351}
352
353static void addNodeWithScore(Chainstate &active_chainstate,
355 uint32_t score) {
356 auto proof = buildRandomProof(active_chainstate, score);
357 BOOST_CHECK(pm.registerProof(proof));
360};
361
362BOOST_AUTO_TEST_CASE(peer_probabilities) {
363 ChainstateManager &chainman = *Assert(m_node.chainman);
364 // No peers.
367
368 const NodeId node0 = 42, node1 = 69, node2 = 37;
369
370 Chainstate &active_chainstate = chainman.ActiveChainstate();
371 // One peer, we always return it.
372 addNodeWithScore(active_chainstate, pm, node0, MIN_VALID_PROOF_SCORE);
373 BOOST_CHECK_EQUAL(pm.selectNode(), node0);
374
375 // Two peers, verify ratio.
376 addNodeWithScore(active_chainstate, pm, node1, 2 * MIN_VALID_PROOF_SCORE);
377
378 std::unordered_map<PeerId, int> results = {};
379 for (int i = 0; i < 10000; i++) {
380 size_t n = pm.selectNode();
381 BOOST_CHECK(n == node0 || n == node1);
382 results[n]++;
383 }
384
385 BOOST_CHECK(abs(2 * results[0] - results[1]) < 500);
386
387 // Three peers, verify ratio.
388 addNodeWithScore(active_chainstate, pm, node2, MIN_VALID_PROOF_SCORE);
389
390 results.clear();
391 for (int i = 0; i < 10000; i++) {
392 size_t n = pm.selectNode();
393 BOOST_CHECK(n == node0 || n == node1 || n == node2);
394 results[n]++;
395 }
396
397 BOOST_CHECK(abs(results[0] - results[1] + results[2]) < 500);
398}
399
401 ChainstateManager &chainman = *Assert(m_node.chainman);
402 // No peers.
405
406 Chainstate &active_chainstate = chainman.ActiveChainstate();
407 // Add 4 peers.
408 std::array<PeerId, 8> peerids;
409 for (int i = 0; i < 4; i++) {
410 auto p = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
411 peerids[i] = TestPeerManager::registerAndGetPeerId(pm, p);
412 BOOST_CHECK(pm.addNode(m_rng.rand32(), p->getId(),
414 }
415
416 BOOST_CHECK_EQUAL(pm.getSlotCount(), 40000);
418
419 for (int i = 0; i < 100; i++) {
420 PeerId p = pm.selectPeer();
421 BOOST_CHECK(p == peerids[0] || p == peerids[1] || p == peerids[2] ||
422 p == peerids[3]);
423 }
424
425 // Remove one peer, it nevers show up now.
426 BOOST_CHECK(pm.removePeer(peerids[2]));
427 BOOST_CHECK_EQUAL(pm.getSlotCount(), 40000);
429
430 // Make sure we compact to never get NO_PEER.
431 BOOST_CHECK_EQUAL(pm.compact(), 10000);
432 BOOST_CHECK(pm.verify());
433 BOOST_CHECK_EQUAL(pm.getSlotCount(), 30000);
435
436 for (int i = 0; i < 100; i++) {
437 PeerId p = pm.selectPeer();
438 BOOST_CHECK(p == peerids[0] || p == peerids[1] || p == peerids[3]);
439 }
440
441 // Add 4 more peers.
442 for (int i = 0; i < 4; i++) {
443 auto p = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
444 peerids[i + 4] = TestPeerManager::registerAndGetPeerId(pm, p);
445 BOOST_CHECK(pm.addNode(m_rng.rand32(), p->getId(),
447 }
448
449 BOOST_CHECK_EQUAL(pm.getSlotCount(), 70000);
451
452 BOOST_CHECK(pm.removePeer(peerids[0]));
453 BOOST_CHECK_EQUAL(pm.getSlotCount(), 70000);
455
456 // Removing the last entry do not increase fragmentation.
457 BOOST_CHECK(pm.removePeer(peerids[7]));
458 BOOST_CHECK_EQUAL(pm.getSlotCount(), 60000);
460
461 // Make sure we compact to never get NO_PEER.
462 BOOST_CHECK_EQUAL(pm.compact(), 10000);
463 BOOST_CHECK(pm.verify());
464 BOOST_CHECK_EQUAL(pm.getSlotCount(), 50000);
466
467 for (int i = 0; i < 100; i++) {
468 PeerId p = pm.selectPeer();
469 BOOST_CHECK(p == peerids[1] || p == peerids[3] || p == peerids[4] ||
470 p == peerids[5] || p == peerids[6]);
471 }
472
473 // Removing non existent peers fails.
474 BOOST_CHECK(!pm.removePeer(peerids[0]));
475 BOOST_CHECK(!pm.removePeer(peerids[2]));
476 BOOST_CHECK(!pm.removePeer(peerids[7]));
478}
479
480BOOST_AUTO_TEST_CASE(compact_slots) {
481 ChainstateManager &chainman = *Assert(m_node.chainman);
483
484 // Add 4 peers.
485 std::array<PeerId, 4> peerids;
486 for (int i = 0; i < 4; i++) {
487 auto p = buildRandomProof(chainman.ActiveChainstate(),
489 peerids[i] = TestPeerManager::registerAndGetPeerId(pm, p);
490 BOOST_CHECK(pm.addNode(m_rng.rand32(), p->getId(),
492 }
493
494 // Remove all peers.
495 for (auto p : peerids) {
496 pm.removePeer(p);
497 }
498
499 BOOST_CHECK_EQUAL(pm.getSlotCount(), 30000);
501
502 for (int i = 0; i < 100; i++) {
504 }
505
506 BOOST_CHECK_EQUAL(pm.compact(), 30000);
507 BOOST_CHECK(pm.verify());
510}
511
512BOOST_AUTO_TEST_CASE(compact_slots_non_uniform_scores) {
513 ChainstateManager &chainman = *Assert(m_node.chainman);
515
516 // Add 4 peers with distinct scores
517 const std::array<uint32_t, 4> scores{{10000, 20000, 30000, 40000}};
518 std::array<PeerId, 4> peerids;
519 for (int i = 0; i < 4; i++) {
520 auto p = buildRandomProof(chainman.ActiveChainstate(), scores[i]);
521 peerids[i] = TestPeerManager::registerAndGetPeerId(pm, p);
522 BOOST_CHECK(pm.addNode(m_rng.rand32(), p->getId(),
524 }
525
526 BOOST_CHECK_EQUAL(pm.getSlotCount(), 100000);
528
529 // Remove a peer in a middle slot, leaving a dead slot behind
530 BOOST_CHECK(pm.removePeer(peerids[1]));
531 BOOST_CHECK_EQUAL(pm.getSlotCount(), 100000);
533
534 // Compaction reclaims exactly the fragmented slot space
535 BOOST_CHECK_EQUAL(pm.compact(), 20000);
536 BOOST_CHECK(pm.verify());
537
538 // After compaction, the slot space must cover exactly the sum of the
539 // remaining peers' scores.
540 BOOST_CHECK_EQUAL(pm.getSlotCount(), 80000);
542
543 // Every value of the slot space must select one of the remaining peers
544 // (not NO_PEER), and each remaining peer must be selected over its range.
545 for (uint64_t slot = 0; slot < pm.getSlotCount(); slot++) {
546 PeerId p = TestPeerManager::selectPeerFromSlot(pm, slot);
547 if (slot < 10000) {
548 BOOST_CHECK_EQUAL(p, peerids[0]);
549 } else if (slot < 40000) {
550 BOOST_CHECK_EQUAL(p, peerids[2]);
551 } else {
552 BOOST_CHECK_EQUAL(p, peerids[3]);
553 }
554 }
555}
556
558 ChainstateManager &chainman = *Assert(m_node.chainman);
560
561 Chainstate &active_chainstate = chainman.ActiveChainstate();
562
563 // Create one peer.
564 auto proof =
565 buildRandomProof(active_chainstate, 10000000 * MIN_VALID_PROOF_SCORE);
566 BOOST_CHECK(pm.registerProof(proof));
568
569 // Add 4 nodes.
570 const ProofId &proofid = proof->getId();
571 for (int i = 0; i < 4; i++) {
573 }
574
575 uint64_t round{0};
576 for (int i = 0; i < 100; i++) {
577 NodeId n = pm.selectNode();
578 BOOST_CHECK(n >= 0 && n < 4);
580 n, Now<SteadyMilliseconds>(), round++));
581 }
582
583 // Remove a node, check that it doesn't show up.
584 BOOST_CHECK(pm.removeNode(2));
585
586 for (int i = 0; i < 100; i++) {
587 NodeId n = pm.selectNode();
588 BOOST_CHECK(n == 0 || n == 1 || n == 3);
590 n, Now<SteadyMilliseconds>(), round++));
591 }
592
593 // Push a node's timeout in the future, so that it doesn't show up.
595 1, Now<SteadyMilliseconds>() + std::chrono::hours(24), round++));
596
597 for (int i = 0; i < 100; i++) {
598 NodeId n = pm.selectNode();
599 BOOST_CHECK(n == 0 || n == 3);
601 n, Now<SteadyMilliseconds>(), round++));
602 }
603
604 // Move a node from a peer to another. This peer has a very low score such
605 // as chances of being picked are 1 in 10 million.
606 addNodeWithScore(active_chainstate, pm, 3, MIN_VALID_PROOF_SCORE);
607
608 int node3selected = 0;
609 for (int i = 0; i < 100; i++) {
610 NodeId n = pm.selectNode();
611 if (n == 3) {
612 // Selecting this node should be exceedingly unlikely.
613 BOOST_CHECK(node3selected++ < 1);
614 } else {
615 BOOST_CHECK_EQUAL(n, 0);
616 }
618 n, Now<SteadyMilliseconds>(), round++));
619 }
620
622 for (int i = 0; i < 100; i++) {
623 NodeId n = pm.selectNode();
624
625 round =
626 pm.forNode(n, [&](const Node &node) { return node.last_round; });
627 // [0..range] (upper bound is inclusive)
628 round = rng.randrange(round + 1);
629
630 // Response to old rounds don't update the next request time.
632 !pm.updateNextRequestTimeForResponse(n, Response{round--, 0, {}}));
633 }
634}
635
636BOOST_AUTO_TEST_CASE(node_binding) {
637 ChainstateManager &chainman = *Assert(m_node.chainman);
639
640 Chainstate &active_chainstate = chainman.ActiveChainstate();
641
642 auto proof = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
643 const ProofId &proofid = proof->getId();
644
647
648 // Add a bunch of nodes with no associated peer
649 for (int i = 0; i < 10; i++) {
652 BOOST_CHECK(TestPeerManager::isNodePending(pm, i));
655 }
656
657 // Now create the peer and check all the nodes are bound
658 const PeerId peerid = TestPeerManager::registerAndGetPeerId(pm, proof);
659 BOOST_CHECK_NE(peerid, NO_PEER);
660 for (int i = 0; i < 10; i++) {
661 BOOST_CHECK(!TestPeerManager::isNodePending(pm, i));
662 BOOST_CHECK(TestPeerManager::nodeBelongToPeer(pm, i, peerid));
665 }
666 BOOST_CHECK(pm.verify());
667
668 // Disconnect some nodes
669 for (int i = 0; i < 5; i++) {
670 BOOST_CHECK(pm.removeNode(i));
671 BOOST_CHECK(!TestPeerManager::isNodePending(pm, i));
672 BOOST_CHECK(!TestPeerManager::nodeBelongToPeer(pm, i, peerid));
673 BOOST_CHECK_EQUAL(pm.getNodeCount(), 10 - i - 1);
675 }
676
677 // Add nodes when the peer already exists
678 for (int i = 0; i < 5; i++) {
680 BOOST_CHECK(!TestPeerManager::isNodePending(pm, i));
681 BOOST_CHECK(TestPeerManager::nodeBelongToPeer(pm, i, peerid));
682 BOOST_CHECK_EQUAL(pm.getNodeCount(), 5 + i + 1);
684 }
685
686 auto alt_proof = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
687 const ProofId &alt_proofid = alt_proof->getId();
688
689 // Update some nodes from a known proof to an unknown proof
690 for (int i = 0; i < 5; i++) {
692 !pm.addNode(i, alt_proofid, DEFAULT_AVALANCHE_MAX_ELEMENT_POLL));
693 BOOST_CHECK(TestPeerManager::isNodePending(pm, i));
694 BOOST_CHECK(!TestPeerManager::nodeBelongToPeer(pm, i, peerid));
695 BOOST_CHECK_EQUAL(pm.getNodeCount(), 10 - i - 1);
697 }
698
699 auto alt2_proof =
700 buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
701 const ProofId &alt2_proofid = alt2_proof->getId();
702
703 // Update some nodes from an unknown proof to another unknown proof
704 for (int i = 0; i < 5; i++) {
706 !pm.addNode(i, alt2_proofid, DEFAULT_AVALANCHE_MAX_ELEMENT_POLL));
707 BOOST_CHECK(TestPeerManager::isNodePending(pm, i));
710 }
711
712 // Update some nodes from an unknown proof to a known proof
713 for (int i = 0; i < 5; i++) {
715 BOOST_CHECK(!TestPeerManager::isNodePending(pm, i));
716 BOOST_CHECK(TestPeerManager::nodeBelongToPeer(pm, i, peerid));
717 BOOST_CHECK_EQUAL(pm.getNodeCount(), 5 + i + 1);
718 BOOST_CHECK_EQUAL(pm.getPendingNodeCount(), 5 - i - 1);
719 }
720
721 // Remove the peer, the nodes should be pending again
722 BOOST_CHECK(pm.removePeer(peerid));
723 BOOST_CHECK(!pm.exists(proof->getId()));
724 for (int i = 0; i < 10; i++) {
725 BOOST_CHECK(TestPeerManager::isNodePending(pm, i));
726 BOOST_CHECK(!TestPeerManager::nodeBelongToPeer(pm, i, peerid));
729 }
730 BOOST_CHECK(pm.verify());
731
732 // Remove the remaining pending nodes, check the count drops accordingly
733 for (int i = 0; i < 10; i++) {
734 BOOST_CHECK(pm.removeNode(i));
735 BOOST_CHECK(!TestPeerManager::isNodePending(pm, i));
736 BOOST_CHECK(!TestPeerManager::nodeBelongToPeer(pm, i, peerid));
738 BOOST_CHECK_EQUAL(pm.getPendingNodeCount(), 10 - i - 1);
739 }
740}
741
742BOOST_AUTO_TEST_CASE(node_binding_reorg) {
743 gArgs.ForceSetArg("-avaproofstakeutxoconfirmations", "2");
744 ChainstateManager &chainman = *Assert(m_node.chainman);
745
747
748 auto proof = buildRandomProof(chainman.ActiveChainstate(),
750 const ProofId &proofid = proof->getId();
751
752 PeerId peerid = TestPeerManager::registerAndGetPeerId(pm, proof);
753 BOOST_CHECK_NE(peerid, NO_PEER);
754 BOOST_CHECK(pm.verify());
755
756 // Add nodes to our peer
757 for (int i = 0; i < 10; i++) {
759 BOOST_CHECK(!TestPeerManager::isNodePending(pm, i));
760 BOOST_CHECK(TestPeerManager::nodeBelongToPeer(pm, i, peerid));
761 }
762
763 // Make the proof immature by reorging to a shorter chain
764 {
766 chainman.ActiveChainstate().InvalidateBlock(
767 state, WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip()));
769 WITH_LOCK(chainman.GetMutex(), return chainman.ActiveHeight()), 99);
770 }
771
772 pm.updatedBlockTip();
773 BOOST_CHECK(pm.isImmature(proofid));
774 BOOST_CHECK(!pm.isBoundToPeer(proofid));
775 for (int i = 0; i < 10; i++) {
776 BOOST_CHECK(TestPeerManager::isNodePending(pm, i));
777 BOOST_CHECK(!TestPeerManager::nodeBelongToPeer(pm, i, peerid));
778 }
779 BOOST_CHECK(pm.verify());
780
781 // Make the proof great again
782 {
783 // Advance the clock so the newly mined block won't collide with the
784 // other deterministically-generated blocks
785 SetMockTime(GetTime() + 20);
786 mineBlocks(1);
788 BOOST_CHECK(chainman.ActiveChainstate().ActivateBestChain(state));
789 LOCK(chainman.GetMutex());
790 BOOST_CHECK_EQUAL(chainman.ActiveHeight(), 100);
791 }
792
793 pm.updatedBlockTip();
794 BOOST_CHECK(!pm.isImmature(proofid));
795 BOOST_CHECK(pm.isBoundToPeer(proofid));
796 // The peerid has certainly been updated
797 peerid = TestPeerManager::registerAndGetPeerId(pm, proof);
798 BOOST_CHECK_NE(peerid, NO_PEER);
799 for (int i = 0; i < 10; i++) {
800 BOOST_CHECK(!TestPeerManager::isNodePending(pm, i));
801 BOOST_CHECK(TestPeerManager::nodeBelongToPeer(pm, i, peerid));
802 }
803 BOOST_CHECK(pm.verify());
804}
805
806BOOST_AUTO_TEST_CASE(proof_conflict) {
807 auto key = CKey::MakeCompressedKey();
808
809 TxId txid1(GetRandHash());
810 TxId txid2(GetRandHash());
811 BOOST_CHECK(txid1 != txid2);
812
814 const int height = 100;
815
816 ChainstateManager &chainman = *Assert(m_node.chainman);
817 for (uint32_t i = 0; i < 10; i++) {
818 addCoin(chainman.ActiveChainstate(), {txid1, i}, key);
819 addCoin(chainman.ActiveChainstate(), {txid2, i}, key);
820 }
821
823 CKey masterKey = CKey::MakeCompressedKey();
824 const auto getPeerId = [&](const std::vector<COutPoint> &outpoints) {
825 return TestPeerManager::registerAndGetPeerId(
826 pm, buildProofWithOutpoints(key, outpoints, v, masterKey, 0, height,
827 false, 0));
828 };
829
830 // Add one peer.
831 const PeerId peer1 = getPeerId({COutPoint(txid1, 0)});
832 BOOST_CHECK(peer1 != NO_PEER);
833
834 // Same proof, same peer.
835 BOOST_CHECK_EQUAL(getPeerId({COutPoint(txid1, 0)}), peer1);
836
837 // Different txid, different proof.
838 const PeerId peer2 = getPeerId({COutPoint(txid2, 0)});
839 BOOST_CHECK(peer2 != NO_PEER && peer2 != peer1);
840
841 // Different index, different proof.
842 const PeerId peer3 = getPeerId({COutPoint(txid1, 1)});
843 BOOST_CHECK(peer3 != NO_PEER && peer3 != peer1);
844
845 // Empty proof, no peer.
846 BOOST_CHECK_EQUAL(getPeerId({}), NO_PEER);
847
848 // Multiple inputs.
849 const PeerId peer4 = getPeerId({COutPoint(txid1, 2), COutPoint(txid2, 2)});
850 BOOST_CHECK(peer4 != NO_PEER && peer4 != peer1);
851
852 // Duplicated input.
853 {
856 COutPoint o(txid1, 3);
857 BOOST_CHECK(pb.addUTXO(o, v, height, false, key));
859 !pm.registerProof(TestProofBuilder::buildDuplicatedStakes(pb)));
860 }
861
862 // Multiple inputs, collision on first input.
863 BOOST_CHECK_EQUAL(getPeerId({COutPoint(txid1, 0), COutPoint(txid2, 4)}),
864 NO_PEER);
865
866 // Mutliple inputs, collision on second input.
867 BOOST_CHECK_EQUAL(getPeerId({COutPoint(txid1, 4), COutPoint(txid2, 0)}),
868 NO_PEER);
869
870 // Mutliple inputs, collision on both inputs.
871 BOOST_CHECK_EQUAL(getPeerId({COutPoint(txid1, 0), COutPoint(txid2, 2)}),
872 NO_PEER);
873}
874
875BOOST_AUTO_TEST_CASE(immature_proofs) {
876 ChainstateManager &chainman = *Assert(m_node.chainman);
877 gArgs.ForceSetArg("-avaproofstakeutxoconfirmations", "2");
879
880 auto key = CKey::MakeCompressedKey();
881 int immatureHeight = 100;
882
883 auto registerImmature = [&](const ProofRef &proof) {
885 BOOST_CHECK(!pm.registerProof(proof, state));
886 BOOST_CHECK(state.GetResult() == ProofRegistrationResult::IMMATURE);
887 };
888
889 auto checkImmature = [&](const ProofRef &proof, bool expectedImmature) {
890 const ProofId &proofid = proof->getId();
891 BOOST_CHECK(pm.exists(proofid));
892
893 BOOST_CHECK_EQUAL(pm.isImmature(proofid), expectedImmature);
894 BOOST_CHECK_EQUAL(pm.isBoundToPeer(proofid), !expectedImmature);
895
896 bool ret = false;
897 pm.forEachPeer([&](const Peer &peer) {
898 if (proof->getId() == peer.proof->getId()) {
899 ret = true;
900 }
901 });
902 BOOST_CHECK_EQUAL(ret, !expectedImmature);
903 };
904
905 // Track immature proofs so we can test them later
906 std::vector<ProofRef> immatureProofs;
907
908 // Fill up the immature pool to test the size limit
909 for (int64_t i = 1; i <= AVALANCHE_MAX_IMMATURE_PROOFS; i++) {
910 COutPoint outpoint = COutPoint(TxId(GetRandHash()), 0);
911 auto proof = buildProofWithOutpoints(
912 key, {outpoint}, i * PROOF_DUST_THRESHOLD, key, 0, immatureHeight);
913 addCoin(chainman.ActiveChainstate(), outpoint, key,
914 i * PROOF_DUST_THRESHOLD, immatureHeight);
915 registerImmature(proof);
916 checkImmature(proof, true);
917 immatureProofs.push_back(proof);
918 }
919
920 // More immature proofs evict lower scoring proofs
921 for (auto i = 0; i < 100; i++) {
922 COutPoint outpoint = COutPoint(TxId(GetRandHash()), 0);
923 auto proof =
924 buildProofWithOutpoints(key, {outpoint}, 200 * PROOF_DUST_THRESHOLD,
925 key, 0, immatureHeight);
926 addCoin(chainman.ActiveChainstate(), outpoint, key,
927 200 * PROOF_DUST_THRESHOLD, immatureHeight);
928 registerImmature(proof);
929 checkImmature(proof, true);
930 immatureProofs.push_back(proof);
931 BOOST_CHECK(!pm.exists(immatureProofs.front()->getId()));
932 immatureProofs.erase(immatureProofs.begin());
933 }
934
935 // Replacement when the pool is full still works
936 {
937 const COutPoint &outpoint =
938 immatureProofs.front()->getStakes()[0].getStake().getUTXO();
939 auto proof =
940 buildProofWithOutpoints(key, {outpoint}, 101 * PROOF_DUST_THRESHOLD,
941 key, 1, immatureHeight);
942 registerImmature(proof);
943 checkImmature(proof, true);
944 immatureProofs.push_back(proof);
945 BOOST_CHECK(!pm.exists(immatureProofs.front()->getId()));
946 immatureProofs.erase(immatureProofs.begin());
947 }
948
949 // Mine a block to increase the chain height, turning all immature proofs to
950 // mature
951 mineBlocks(1);
952 pm.updatedBlockTip();
953 for (const auto &proof : immatureProofs) {
954 checkImmature(proof, false);
955 }
956}
957
958BOOST_AUTO_TEST_CASE(dangling_node) {
959 ChainstateManager &chainman = *Assert(m_node.chainman);
961
962 Chainstate &active_chainstate = chainman.ActiveChainstate();
963
964 auto proof = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
965 PeerId peerid = TestPeerManager::registerAndGetPeerId(pm, proof);
966 BOOST_CHECK_NE(peerid, NO_PEER);
967
968 const SteadyMilliseconds theFuture(Now<SteadyMilliseconds>() +
969 std::chrono::hours(24));
970
971 // Add nodes to this peer and update their request time far in the future
972 for (int i = 0; i < 10; i++) {
975 BOOST_CHECK(pm.updateNextRequestTimeForPoll(i, theFuture, i));
976 }
977
978 // Remove the peer
979 BOOST_CHECK(pm.removePeer(peerid));
980
981 // Check the nodes are still there
982 for (int i = 0; i < 10; i++) {
983 BOOST_CHECK(pm.forNode(i, [](const Node &n) { return true; }));
984 }
985
986 // Build a new one
987 proof = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
988 peerid = TestPeerManager::registerAndGetPeerId(pm, proof);
989 BOOST_CHECK_NE(peerid, NO_PEER);
990
991 // Update the nodes with the new proof
992 for (int i = 0; i < 10; i++) {
996 i, [&](const Node &n) { return n.nextRequestTime == theFuture; }));
997 }
998
999 // Remove the peer
1000 BOOST_CHECK(pm.removePeer(peerid));
1001
1002 // Disconnect the nodes
1003 for (int i = 0; i < 10; i++) {
1004 BOOST_CHECK(pm.removeNode(i));
1005 }
1006}
1007
1008BOOST_AUTO_TEST_CASE(proof_accessors) {
1009 ChainstateManager &chainman = *Assert(m_node.chainman);
1011
1012 constexpr int numProofs = 10;
1013
1014 std::vector<ProofRef> proofs;
1015 proofs.reserve(numProofs);
1016 for (int i = 0; i < numProofs; i++) {
1017 proofs.push_back(buildRandomProof(chainman.ActiveChainstate(),
1019 }
1020
1021 for (int i = 0; i < numProofs; i++) {
1022 BOOST_CHECK(pm.registerProof(proofs[i]));
1023
1024 {
1026 // Fail to add an existing proof
1027 BOOST_CHECK(!pm.registerProof(proofs[i], state));
1028 BOOST_CHECK(state.GetResult() ==
1029 ProofRegistrationResult::ALREADY_REGISTERED);
1030 }
1031
1032 for (int added = 0; added <= i; added++) {
1033 auto proof = pm.getProof(proofs[added]->getId());
1034 BOOST_CHECK(proof != nullptr);
1035
1036 const ProofId &proofid = proof->getId();
1037 BOOST_CHECK_EQUAL(proofid, proofs[added]->getId());
1038 }
1039 }
1040
1041 // No stake, copied from proof_tests.cpp
1042 const std::string badProofHex(
1043 "96527eae083f1f24625f049d9e54bb9a21023beefdde700a6bc02036335b4df141c8b"
1044 "c67bb05a971f5ac2745fd683797dde3002321023beefdde700a6bc02036335b4df141"
1045 "c8bc67bb05a971f5ac2745fd683797dde3ac135da984db510334abe41134e3d4ef09a"
1046 "d006b1152be8bc413182bf6f947eac1f8580fe265a382195aa2d73935cabf86d90a8f"
1047 "666d0a62385ae24732eca51575");
1048 bilingual_str error;
1049 auto badProof = RCUPtr<Proof>::make();
1050 BOOST_CHECK(Proof::FromHex(*badProof, badProofHex, error));
1051
1053 BOOST_CHECK(!pm.registerProof(badProof, state));
1054 BOOST_CHECK(state.GetResult() == ProofRegistrationResult::INVALID);
1055}
1056
1057BOOST_FIXTURE_TEST_CASE(conflicting_proof_rescan, NoCoolDownFixture) {
1058 ChainstateManager &chainman = *Assert(m_node.chainman);
1060
1061 const CKey key = CKey::MakeCompressedKey();
1062
1063 Chainstate &active_chainstate = chainman.ActiveChainstate();
1064
1065 const COutPoint conflictingOutpoint = createUtxo(active_chainstate, key);
1066 const COutPoint outpointToSend = createUtxo(active_chainstate, key);
1067
1068 ProofRef proofToInvalidate =
1069 buildProofWithSequence(key, {conflictingOutpoint, outpointToSend}, 20);
1070 BOOST_CHECK(pm.registerProof(proofToInvalidate));
1071
1072 ProofRef conflictingProof =
1073 buildProofWithSequence(key, {conflictingOutpoint}, 10);
1075 BOOST_CHECK(!pm.registerProof(conflictingProof, state));
1076 BOOST_CHECK(state.GetResult() == ProofRegistrationResult::CONFLICTING);
1077 BOOST_CHECK(pm.isInConflictingPool(conflictingProof->getId()));
1078
1079 {
1080 LOCK(cs_main);
1081 CCoinsViewCache &coins = active_chainstate.CoinsTip();
1082 // Make proofToInvalidate invalid
1083 coins.SpendCoin(outpointToSend);
1084 }
1085
1086 pm.updatedBlockTip();
1087
1088 BOOST_CHECK(!pm.exists(proofToInvalidate->getId()));
1089
1090 BOOST_CHECK(!pm.isInConflictingPool(conflictingProof->getId()));
1091 BOOST_CHECK(pm.isBoundToPeer(conflictingProof->getId()));
1092}
1093
1094BOOST_FIXTURE_TEST_CASE(conflicting_proof_selection, NoCoolDownFixture) {
1095 const CKey key = CKey::MakeCompressedKey();
1096
1097 const Amount amount(PROOF_DUST_THRESHOLD);
1098 const uint32_t height = 100;
1099 const bool is_coinbase = false;
1100
1101 ChainstateManager &chainman = *Assert(m_node.chainman);
1102 Chainstate &active_chainstate = chainman.ActiveChainstate();
1103
1104 // This will be the conflicting UTXO for all the following proofs
1105 auto conflictingOutpoint = createUtxo(active_chainstate, key, amount);
1106
1107 auto proof_base = buildProofWithSequence(key, {conflictingOutpoint}, 10);
1108
1109 ConflictingProofComparator comparator;
1110 auto checkPreferred = [&](const ProofRef &candidate,
1111 const ProofRef &reference, bool expectAccepted) {
1112 BOOST_CHECK_EQUAL(comparator(candidate, reference), expectAccepted);
1113 BOOST_CHECK_EQUAL(comparator(reference, candidate), !expectAccepted);
1114
1116 BOOST_CHECK(pm.registerProof(reference));
1117 BOOST_CHECK(pm.isBoundToPeer(reference->getId()));
1118
1120 BOOST_CHECK_EQUAL(pm.registerProof(candidate, state), expectAccepted);
1121 BOOST_CHECK_EQUAL(state.IsValid(), expectAccepted);
1122 BOOST_CHECK_EQUAL(state.GetResult() ==
1123 ProofRegistrationResult::CONFLICTING,
1124 !expectAccepted);
1125
1126 BOOST_CHECK_EQUAL(pm.isBoundToPeer(candidate->getId()), expectAccepted);
1128 !expectAccepted);
1129
1130 BOOST_CHECK_EQUAL(pm.isBoundToPeer(reference->getId()),
1131 !expectAccepted);
1132 BOOST_CHECK_EQUAL(pm.isInConflictingPool(reference->getId()),
1133 expectAccepted);
1134 };
1135
1136 // Same master key, lower sequence number
1137 checkPreferred(buildProofWithSequence(key, {conflictingOutpoint}, 9),
1138 proof_base, false);
1139 // Same master key, higher sequence number
1140 checkPreferred(buildProofWithSequence(key, {conflictingOutpoint}, 11),
1141 proof_base, true);
1142
1143 auto buildProofFromAmounts = [&](const CKey &master,
1144 std::vector<Amount> &&amounts) {
1145 std::vector<std::tuple<COutPoint, Amount>> outpointsWithAmount{
1146 {conflictingOutpoint, amount}};
1147 std::transform(amounts.begin(), amounts.end(),
1148 std::back_inserter(outpointsWithAmount),
1149 [&key, &active_chainstate](const Amount amount) {
1150 return std::make_tuple(
1151 createUtxo(active_chainstate, key, amount),
1152 amount);
1153 });
1154 return buildProof(key, outpointsWithAmount, master, 0, height,
1155 is_coinbase, 0);
1156 };
1157
1158 auto proof_multiUtxo = buildProofFromAmounts(
1160
1161 // Test for both the same master and a different one. The sequence number
1162 // is the same for all these tests.
1163 for (const CKey &k : {key, CKey::MakeCompressedKey()}) {
1164 // Low amount
1165 checkPreferred(buildProofFromAmounts(
1167 proof_multiUtxo, false);
1168 // High amount
1169 checkPreferred(buildProofFromAmounts(k, {2 * PROOF_DUST_THRESHOLD,
1171 proof_multiUtxo, true);
1172 // Same amount, low stake count
1173 checkPreferred(buildProofFromAmounts(k, {4 * PROOF_DUST_THRESHOLD}),
1174 proof_multiUtxo, true);
1175 // Same amount, high stake count
1176 checkPreferred(buildProofFromAmounts(k, {2 * PROOF_DUST_THRESHOLD,
1179 proof_multiUtxo, false);
1180 // Same amount, same stake count, selection is done on proof id
1181 auto proofSimilar = buildProofFromAmounts(
1183 checkPreferred(proofSimilar, proof_multiUtxo,
1184 proofSimilar->getId() < proof_multiUtxo->getId());
1185 }
1186}
1187
1188BOOST_AUTO_TEST_CASE(conflicting_immature_proofs) {
1189 ChainstateManager &chainman = *Assert(m_node.chainman);
1190 gArgs.ForceSetArg("-avaproofstakeutxoconfirmations", "2");
1192
1193 const CKey key = CKey::MakeCompressedKey();
1194
1195 Chainstate &active_chainstate = chainman.ActiveChainstate();
1196
1197 const COutPoint conflictingOutpoint = createUtxo(active_chainstate, key);
1198 const COutPoint matureOutpoint =
1199 createUtxo(active_chainstate, key, PROOF_DUST_THRESHOLD, 99);
1200
1201 auto immature10 = buildProofWithSequence(key, {conflictingOutpoint}, 10);
1202 auto immature20 =
1203 buildProofWithSequence(key, {conflictingOutpoint, matureOutpoint}, 20);
1204
1205 BOOST_CHECK(!pm.registerProof(immature10));
1206 BOOST_CHECK(pm.isImmature(immature10->getId()));
1207
1208 BOOST_CHECK(!pm.registerProof(immature20));
1209 BOOST_CHECK(pm.isImmature(immature20->getId()));
1210 BOOST_CHECK(!pm.exists(immature10->getId()));
1211
1212 // Build and register a valid proof that will conflict with the immature one
1213 auto proof30 = buildProofWithOutpoints(key, {matureOutpoint},
1214 PROOF_DUST_THRESHOLD, key, 30, 99);
1215 BOOST_CHECK(pm.registerProof(proof30));
1216 BOOST_CHECK(pm.isBoundToPeer(proof30->getId()));
1217
1218 // Reorg to a shorter chain to make proof30 immature
1219 {
1221 active_chainstate.InvalidateBlock(
1222 state, WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip()));
1224 WITH_LOCK(chainman.GetMutex(), return chainman.ActiveHeight()), 99);
1225 }
1226
1227 // Check that a rescan will also select the preferred immature proof, in
1228 // this case proof30 will replace immature20.
1229 pm.updatedBlockTip();
1230
1231 BOOST_CHECK(!pm.isBoundToPeer(proof30->getId()));
1232 BOOST_CHECK(pm.isImmature(proof30->getId()));
1233 BOOST_CHECK(!pm.exists(immature20->getId()));
1234}
1235
1236BOOST_FIXTURE_TEST_CASE(preferred_conflicting_proof, NoCoolDownFixture) {
1237 ChainstateManager &chainman = *Assert(m_node.chainman);
1239
1240 const CKey key = CKey::MakeCompressedKey();
1241 const COutPoint conflictingOutpoint =
1242 createUtxo(chainman.ActiveChainstate(), key);
1243
1244 auto proofSeq10 = buildProofWithSequence(key, {conflictingOutpoint}, 10);
1245 auto proofSeq20 = buildProofWithSequence(key, {conflictingOutpoint}, 20);
1246 auto proofSeq30 = buildProofWithSequence(key, {conflictingOutpoint}, 30);
1247
1248 BOOST_CHECK(pm.registerProof(proofSeq30));
1249 BOOST_CHECK(pm.isBoundToPeer(proofSeq30->getId()));
1250 BOOST_CHECK(!pm.isInConflictingPool(proofSeq30->getId()));
1251
1252 // proofSeq10 is a worst candidate than proofSeq30, so it goes to the
1253 // conflicting pool.
1254 BOOST_CHECK(!pm.registerProof(proofSeq10));
1255 BOOST_CHECK(pm.isBoundToPeer(proofSeq30->getId()));
1256 BOOST_CHECK(!pm.isBoundToPeer(proofSeq10->getId()));
1257 BOOST_CHECK(pm.isInConflictingPool(proofSeq10->getId()));
1258
1259 // proofSeq20 is a worst candidate than proofSeq30 but a better one than
1260 // proogSeq10, so it replaces it in the conflicting pool and proofSeq10 is
1261 // evicted.
1262 BOOST_CHECK(!pm.registerProof(proofSeq20));
1263 BOOST_CHECK(pm.isBoundToPeer(proofSeq30->getId()));
1264 BOOST_CHECK(!pm.isBoundToPeer(proofSeq20->getId()));
1265 BOOST_CHECK(pm.isInConflictingPool(proofSeq20->getId()));
1266 BOOST_CHECK(!pm.exists(proofSeq10->getId()));
1267}
1268
1269BOOST_FIXTURE_TEST_CASE(update_next_conflict_time, NoCoolDownFixture) {
1270 ChainstateManager &chainman = *Assert(m_node.chainman);
1272
1273 auto now = GetTime<std::chrono::seconds>();
1274 SetMockTime(now.count());
1275
1276 // Updating the time of an unknown peer should fail
1277 for (size_t i = 0; i < 10; i++) {
1279 PeerId(FastRandomContext().randrange<int>(1000)), now));
1280 }
1281
1282 auto proof =
1284 PeerId peerid = TestPeerManager::registerAndGetPeerId(pm, proof);
1285
1286 auto checkNextPossibleConflictTime = [&](std::chrono::seconds expected) {
1287 BOOST_CHECK(pm.forPeer(proof->getId(), [&](const Peer &p) {
1288 return p.nextPossibleConflictTime == expected;
1289 }));
1290 };
1291
1292 checkNextPossibleConflictTime(now);
1293
1294 // Move the time in the past is not possible
1296 peerid, now - std::chrono::seconds{1}));
1297 checkNextPossibleConflictTime(now);
1298
1300 peerid, now + std::chrono::seconds{1}));
1301 checkNextPossibleConflictTime(now + std::chrono::seconds{1});
1302}
1303
1304BOOST_FIXTURE_TEST_CASE(register_force_accept, NoCoolDownFixture) {
1305 ChainstateManager &chainman = *Assert(m_node.chainman);
1307
1308 const CKey key = CKey::MakeCompressedKey();
1309
1310 const COutPoint conflictingOutpoint =
1311 createUtxo(chainman.ActiveChainstate(), key);
1312
1313 auto proofSeq10 = buildProofWithSequence(key, {conflictingOutpoint}, 10);
1314 auto proofSeq20 = buildProofWithSequence(key, {conflictingOutpoint}, 20);
1315 auto proofSeq30 = buildProofWithSequence(key, {conflictingOutpoint}, 30);
1316
1317 BOOST_CHECK(pm.registerProof(proofSeq30));
1318 BOOST_CHECK(pm.isBoundToPeer(proofSeq30->getId()));
1319 BOOST_CHECK(!pm.isInConflictingPool(proofSeq30->getId()));
1320
1321 // proofSeq20 is a worst candidate than proofSeq30, so it goes to the
1322 // conflicting pool.
1323 BOOST_CHECK(!pm.registerProof(proofSeq20));
1324 BOOST_CHECK(pm.isBoundToPeer(proofSeq30->getId()));
1325 BOOST_CHECK(pm.isInConflictingPool(proofSeq20->getId()));
1326
1327 // We can force the acceptance of proofSeq20
1328 using RegistrationMode = avalanche::PeerManager::RegistrationMode;
1329 BOOST_CHECK(pm.registerProof(proofSeq20, RegistrationMode::FORCE_ACCEPT));
1330 BOOST_CHECK(pm.isBoundToPeer(proofSeq20->getId()));
1331 BOOST_CHECK(pm.isInConflictingPool(proofSeq30->getId()));
1332
1333 // We can also force the acceptance of a proof which is not already in the
1334 // conflicting pool.
1335 BOOST_CHECK(!pm.registerProof(proofSeq10));
1336 BOOST_CHECK(!pm.exists(proofSeq10->getId()));
1337
1338 BOOST_CHECK(pm.registerProof(proofSeq10, RegistrationMode::FORCE_ACCEPT));
1339 BOOST_CHECK(pm.isBoundToPeer(proofSeq10->getId()));
1340 BOOST_CHECK(!pm.exists(proofSeq20->getId()));
1341 BOOST_CHECK(pm.isInConflictingPool(proofSeq30->getId()));
1342
1343 // Attempting to register again fails, and has no impact on the pools
1344 for (size_t i = 0; i < 10; i++) {
1345 BOOST_CHECK(!pm.registerProof(proofSeq10));
1347 !pm.registerProof(proofSeq10, RegistrationMode::FORCE_ACCEPT));
1348
1349 BOOST_CHECK(pm.isBoundToPeer(proofSeq10->getId()));
1350 BOOST_CHECK(!pm.exists(proofSeq20->getId()));
1351 BOOST_CHECK(pm.isInConflictingPool(proofSeq30->getId()));
1352 }
1353
1354 // Revert between proofSeq10 and proofSeq30 a few times
1355 for (size_t i = 0; i < 10; i++) {
1357 pm.registerProof(proofSeq30, RegistrationMode::FORCE_ACCEPT));
1358
1359 BOOST_CHECK(pm.isBoundToPeer(proofSeq30->getId()));
1360 BOOST_CHECK(pm.isInConflictingPool(proofSeq10->getId()));
1361
1363 pm.registerProof(proofSeq10, RegistrationMode::FORCE_ACCEPT));
1364
1365 BOOST_CHECK(pm.isBoundToPeer(proofSeq10->getId()));
1366 BOOST_CHECK(pm.isInConflictingPool(proofSeq30->getId()));
1367 }
1368}
1369
1370BOOST_FIXTURE_TEST_CASE(evicted_proof, NoCoolDownFixture) {
1371 ChainstateManager &chainman = *Assert(m_node.chainman);
1373
1374 const CKey key = CKey::MakeCompressedKey();
1375
1376 const COutPoint conflictingOutpoint =
1377 createUtxo(chainman.ActiveChainstate(), key);
1378
1379 auto proofSeq10 = buildProofWithSequence(key, {conflictingOutpoint}, 10);
1380 auto proofSeq20 = buildProofWithSequence(key, {conflictingOutpoint}, 20);
1381 auto proofSeq30 = buildProofWithSequence(key, {conflictingOutpoint}, 30);
1382
1383 {
1385 BOOST_CHECK(pm.registerProof(proofSeq30, state));
1386 BOOST_CHECK(state.IsValid());
1387 }
1388
1389 {
1391 BOOST_CHECK(!pm.registerProof(proofSeq20, state));
1392 BOOST_CHECK(state.GetResult() == ProofRegistrationResult::CONFLICTING);
1393 }
1394
1395 {
1397 BOOST_CHECK(!pm.registerProof(proofSeq10, state));
1398 BOOST_CHECK(state.GetResult() == ProofRegistrationResult::REJECTED);
1399 }
1400}
1401
1402BOOST_AUTO_TEST_CASE(conflicting_proof_cooldown) {
1403 ChainstateManager &chainman = *Assert(m_node.chainman);
1405
1406 const CKey key = CKey::MakeCompressedKey();
1407
1408 const COutPoint conflictingOutpoint =
1409 createUtxo(chainman.ActiveChainstate(), key);
1410
1411 auto proofSeq20 = buildProofWithSequence(key, {conflictingOutpoint}, 20);
1412 auto proofSeq30 = buildProofWithSequence(key, {conflictingOutpoint}, 30);
1413 auto proofSeq40 = buildProofWithSequence(key, {conflictingOutpoint}, 40);
1414
1415 int64_t conflictingProofCooldown = 100;
1416 gArgs.ForceSetArg("-avalancheconflictingproofcooldown",
1417 strprintf("%d", conflictingProofCooldown));
1418
1419 int64_t now = GetTime();
1420
1421 auto increaseMockTime = [&](int64_t s) {
1422 now += s;
1423 SetMockTime(now);
1424 };
1425 increaseMockTime(0);
1426
1427 BOOST_CHECK(pm.registerProof(proofSeq30));
1428 BOOST_CHECK(pm.isBoundToPeer(proofSeq30->getId()));
1429
1430 auto checkRegistrationFailure = [&](const ProofRef &proof,
1431 ProofRegistrationResult reason) {
1433 BOOST_CHECK(!pm.registerProof(proof, state));
1434 BOOST_CHECK(state.GetResult() == reason);
1435 };
1436
1437 // Registering a conflicting proof will fail due to the conflicting proof
1438 // cooldown
1439 checkRegistrationFailure(proofSeq20,
1440 ProofRegistrationResult::COOLDOWN_NOT_ELAPSED);
1441 BOOST_CHECK(!pm.exists(proofSeq20->getId()));
1442
1443 // The cooldown applies as well if the proof is the favorite
1444 checkRegistrationFailure(proofSeq40,
1445 ProofRegistrationResult::COOLDOWN_NOT_ELAPSED);
1446 BOOST_CHECK(!pm.exists(proofSeq40->getId()));
1447
1448 // Elapse the cooldown
1449 increaseMockTime(conflictingProofCooldown);
1450
1451 // The proof will now be added to conflicting pool
1452 checkRegistrationFailure(proofSeq20, ProofRegistrationResult::CONFLICTING);
1453 BOOST_CHECK(pm.isInConflictingPool(proofSeq20->getId()));
1454
1455 // But no other
1456 checkRegistrationFailure(proofSeq40,
1457 ProofRegistrationResult::COOLDOWN_NOT_ELAPSED);
1458 BOOST_CHECK(!pm.exists(proofSeq40->getId()));
1459 BOOST_CHECK(pm.isInConflictingPool(proofSeq20->getId()));
1460
1461 // Elapse the cooldown
1462 increaseMockTime(conflictingProofCooldown);
1463
1464 // The proof will now be accepted to replace proofSeq30, proofSeq30 will
1465 // move to the conflicting pool, and proofSeq20 will be evicted.
1466 BOOST_CHECK(pm.registerProof(proofSeq40));
1467 BOOST_CHECK(pm.isBoundToPeer(proofSeq40->getId()));
1468 BOOST_CHECK(pm.isInConflictingPool(proofSeq30->getId()));
1469 BOOST_CHECK(!pm.exists(proofSeq20->getId()));
1470
1471 gArgs.ClearForcedArg("-avalancheconflictingproofcooldown");
1472}
1473
1474BOOST_FIXTURE_TEST_CASE(reject_proof, NoCoolDownFixture) {
1475 ChainstateManager &chainman = *Assert(m_node.chainman);
1476 gArgs.ForceSetArg("-avaproofstakeutxoconfirmations", "2");
1478
1479 const CKey key = CKey::MakeCompressedKey();
1480
1481 Chainstate &active_chainstate = chainman.ActiveChainstate();
1482
1483 const COutPoint conflictingOutpoint =
1484 createUtxo(active_chainstate, key, PROOF_DUST_THRESHOLD, 99);
1485 const COutPoint immatureOutpoint = createUtxo(active_chainstate, key);
1486
1487 // The good, the bad and the ugly
1488 auto proofSeq10 = buildProofWithOutpoints(
1489 key, {conflictingOutpoint}, PROOF_DUST_THRESHOLD, key, 10, 99);
1490 auto proofSeq20 = buildProofWithOutpoints(
1491 key, {conflictingOutpoint}, PROOF_DUST_THRESHOLD, key, 20, 99);
1492 auto immature30 = buildProofWithSequence(
1493 key, {conflictingOutpoint, immatureOutpoint}, 30);
1494
1495 BOOST_CHECK(pm.registerProof(proofSeq20));
1496 BOOST_CHECK(!pm.registerProof(proofSeq10));
1497 BOOST_CHECK(!pm.registerProof(immature30));
1498
1499 BOOST_CHECK(pm.isBoundToPeer(proofSeq20->getId()));
1500 BOOST_CHECK(pm.isInConflictingPool(proofSeq10->getId()));
1501 BOOST_CHECK(pm.isImmature(immature30->getId()));
1502
1503 // Rejecting a proof that doesn't exist should fail
1504 for (size_t i = 0; i < 10; i++) {
1511 }
1512
1513 auto checkRejectDefault = [&](const ProofId &proofid) {
1514 BOOST_CHECK(pm.exists(proofid));
1515 const bool isImmature = pm.isImmature(proofid);
1518 BOOST_CHECK(!pm.isBoundToPeer(proofid));
1519 BOOST_CHECK_EQUAL(pm.exists(proofid), !isImmature);
1520 };
1521
1522 auto checkRejectInvalidate = [&](const ProofId &proofid) {
1523 BOOST_CHECK(pm.exists(proofid));
1526 };
1527
1528 // Reject from the immature pool
1529 checkRejectDefault(immature30->getId());
1530 BOOST_CHECK(!pm.registerProof(immature30));
1531 BOOST_CHECK(pm.isImmature(immature30->getId()));
1532 checkRejectInvalidate(immature30->getId());
1533
1534 // Reject from the conflicting pool
1535 checkRejectDefault(proofSeq10->getId());
1536 checkRejectInvalidate(proofSeq10->getId());
1537
1538 // Add again a proof to the conflicting pool
1539 BOOST_CHECK(!pm.registerProof(proofSeq10));
1540 BOOST_CHECK(pm.isInConflictingPool(proofSeq10->getId()));
1541
1542 // Reject from the valid pool, default mode
1543 checkRejectDefault(proofSeq20->getId());
1544
1545 // The conflicting proof should be promoted to a peer
1546 BOOST_CHECK(!pm.isInConflictingPool(proofSeq10->getId()));
1547 BOOST_CHECK(pm.isBoundToPeer(proofSeq10->getId()));
1548
1549 // Reject from the valid pool, invalidate mode
1550 checkRejectInvalidate(proofSeq10->getId());
1551
1552 // The conflicting proof should also be promoted to a peer
1553 BOOST_CHECK(!pm.isInConflictingPool(proofSeq20->getId()));
1554 BOOST_CHECK(pm.isBoundToPeer(proofSeq20->getId()));
1555}
1556
1557BOOST_AUTO_TEST_CASE(should_request_more_nodes) {
1558 ChainstateManager &chainman = *Assert(m_node.chainman);
1560
1561 // Set mock time so that proof registration time is predictable and
1562 // testable.
1564
1565 auto proof =
1567 BOOST_CHECK(pm.registerProof(proof));
1568 // Not dangling yet, the proof will remain active for some time before it
1569 // turns dangling if no node is connecting in the meantime.
1570 BOOST_CHECK(!pm.isDangling(proof->getId()));
1571
1572 // We have no nodes, so select node will fail and flag that we need more
1573 // nodes
1576
1577 for (size_t i = 0; i < 10; i++) {
1578 // The flag will not trigger again until we fail to select nodes again
1580 }
1581
1582 // Add a few nodes.
1583 const ProofId &proofid = proof->getId();
1584 for (size_t i = 0; i < 10; i++) {
1586 }
1587
1588 BOOST_CHECK(!pm.isDangling(proof->getId()));
1589
1590 auto cooldownTimepoint = Now<SteadyMilliseconds>() + 10s;
1591
1592 uint64_t round{0};
1593
1594 // All the nodes can be selected once
1595 for (size_t i = 0; i < 10; i++) {
1596 NodeId selectedId = pm.selectNode();
1597 BOOST_CHECK_NE(selectedId, NO_NODE);
1599 selectedId, cooldownTimepoint, round++));
1601 }
1602
1603 // All the nodes have been requested, next select will fail and the flag
1604 // should trigger
1607
1608 for (size_t i = 0; i < 10; i++) {
1609 // The flag will not trigger again until we fail to select nodes again
1611 }
1612
1613 // Make it possible to request a node again
1615 pm.updateNextRequestTimeForPoll(0, Now<SteadyMilliseconds>(), round++));
1616 BOOST_CHECK_NE(pm.selectNode(), NO_NODE);
1618
1619 // Add another proof with no node attached
1620 auto proof2 =
1622 BOOST_CHECK(pm.registerProof(proof2));
1623 BOOST_CHECK(!pm.isDangling(proof2->getId()));
1624 TestPeerManager::cleanupDanglingProofs(pm);
1625 BOOST_CHECK(!pm.isDangling(proof2->getId()));
1627
1628 // After some time the proof will be considered dangling and more nodes will
1629 // be requested.
1630 SetMockTime(GetTime() + 15 * 60);
1631 TestPeerManager::cleanupDanglingProofs(pm);
1632 BOOST_CHECK(pm.isDangling(proof2->getId()));
1634
1635 for (size_t i = 0; i < 10; i++) {
1636 BOOST_CHECK(pm.isDangling(proof2->getId()));
1637 // The flag will not trigger again until the condition is met again
1639 }
1640
1641 // Attempt to register the dangling proof again. This should fail but
1642 // trigger a request for more nodes.
1644 BOOST_CHECK(!pm.registerProof(proof2, state));
1645 BOOST_CHECK(state.GetResult() == ProofRegistrationResult::DANGLING);
1646 BOOST_CHECK(pm.isDangling(proof2->getId()));
1648
1649 for (size_t i = 0; i < 10; i++) {
1650 BOOST_CHECK(pm.isDangling(proof2->getId()));
1651 // The flag will not trigger again until the condition is met again
1653 }
1654
1655 // Attach a node to that proof
1657 !pm.addNode(11, proof2->getId(), DEFAULT_AVALANCHE_MAX_ELEMENT_POLL));
1658 BOOST_CHECK(pm.registerProof(proof2));
1659 SetMockTime(GetTime() + 15 * 60);
1660 TestPeerManager::cleanupDanglingProofs(pm);
1661 BOOST_CHECK(!pm.isDangling(proof2->getId()));
1663
1664 // Disconnect the node, the proof is dangling again
1665 BOOST_CHECK(pm.removeNode(11));
1666 TestPeerManager::cleanupDanglingProofs(pm);
1667 BOOST_CHECK(pm.isDangling(proof2->getId()));
1669
1670 // Invalidating the proof, removes the proof from the dangling pool but not
1671 // a simple rejection.
1674 BOOST_CHECK(pm.isDangling(proof2->getId()));
1677 BOOST_CHECK(!pm.isDangling(proof2->getId()));
1678}
1679
1680BOOST_AUTO_TEST_CASE(score_ordering) {
1681 ChainstateManager &chainman = *Assert(m_node.chainman);
1683
1684 std::vector<uint32_t> expectedScores(10);
1685 // Expect the peers to be ordered by descending score
1686 std::generate(expectedScores.rbegin(), expectedScores.rend(),
1687 [n = 1]() mutable { return n++ * MIN_VALID_PROOF_SCORE; });
1688
1689 std::vector<ProofRef> proofs;
1690 proofs.reserve(expectedScores.size());
1691 for (uint32_t score : expectedScores) {
1692 proofs.push_back(buildRandomProof(chainman.ActiveChainstate(), score));
1693 }
1694
1695 // Shuffle the proofs so they are registered in a random score order
1696 Shuffle(proofs.begin(), proofs.end(), FastRandomContext());
1697 for (auto &proof : proofs) {
1698 BOOST_CHECK(pm.registerProof(proof));
1699 }
1700
1701 auto peersScores = TestPeerManager::getOrderedScores(pm);
1702 BOOST_CHECK_EQUAL_COLLECTIONS(peersScores.begin(), peersScores.end(),
1703 expectedScores.begin(), expectedScores.end());
1704}
1705
1706BOOST_FIXTURE_TEST_CASE(known_score_tracking, NoCoolDownFixture) {
1707 ChainstateManager &chainman = *Assert(m_node.chainman);
1708 gArgs.ForceSetArg("-avaproofstakeutxoconfirmations", "2");
1710
1711 const CKey key = CKey::MakeCompressedKey();
1712
1713 const Amount amount1(PROOF_DUST_THRESHOLD);
1714 const Amount amount2(2 * PROOF_DUST_THRESHOLD);
1715
1716 Chainstate &active_chainstate = chainman.ActiveChainstate();
1717
1718 const COutPoint peer1ConflictingOutput =
1719 createUtxo(active_chainstate, key, amount1, 99);
1720 const COutPoint peer1SecondaryOutpoint =
1721 createUtxo(active_chainstate, key, amount2, 99);
1722
1723 auto peer1Proof1 = buildProof(
1724 key,
1725 {{peer1ConflictingOutput, amount1}, {peer1SecondaryOutpoint, amount2}},
1726 key, 10, 99);
1727 auto peer1Proof2 =
1728 buildProof(key, {{peer1ConflictingOutput, amount1}}, key, 20, 99);
1729
1730 // Create a proof with an immature UTXO, so the proof will be immature
1731 auto peer1Proof3 =
1732 buildProof(key,
1733 {{peer1ConflictingOutput, amount1},
1734 {createUtxo(active_chainstate, key, amount1), amount1}},
1735 key, 30);
1736
1737 const uint32_t peer1Score1 = Proof::amountToScore(amount1 + amount2);
1738 const uint32_t peer1Score2 = Proof::amountToScore(amount1);
1739
1740 // Add first peer and check that we have its score tracked
1742 BOOST_CHECK(pm.registerProof(peer1Proof2));
1743 BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), peer1Score2);
1744
1745 // Ensure failing to add conflicting proofs doesn't affect the score, the
1746 // first proof stays bound and counted
1747 BOOST_CHECK(!pm.registerProof(peer1Proof1));
1748 BOOST_CHECK(!pm.registerProof(peer1Proof3));
1749
1750 BOOST_CHECK(pm.isBoundToPeer(peer1Proof2->getId()));
1751 BOOST_CHECK(pm.isInConflictingPool(peer1Proof1->getId()));
1752 BOOST_CHECK(pm.isImmature(peer1Proof3->getId()));
1753
1754 BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), peer1Score2);
1755
1756 auto checkRejectDefault = [&](const ProofId &proofid) {
1757 BOOST_CHECK(pm.exists(proofid));
1758 const bool isImmature = pm.isImmature(proofid);
1761 BOOST_CHECK(!pm.isBoundToPeer(proofid));
1762 BOOST_CHECK_EQUAL(pm.exists(proofid), !isImmature);
1763 };
1764
1765 auto checkRejectInvalidate = [&](const ProofId &proofid) {
1766 BOOST_CHECK(pm.exists(proofid));
1769 };
1770
1771 // Reject from the immature pool doesn't affect tracked score
1772 checkRejectDefault(peer1Proof3->getId());
1773 BOOST_CHECK(!pm.registerProof(peer1Proof3));
1774 BOOST_CHECK(pm.isImmature(peer1Proof3->getId()));
1775 BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), peer1Score2);
1776 checkRejectInvalidate(peer1Proof3->getId());
1777 BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), peer1Score2);
1778
1779 // Reject from the conflicting pool
1780 checkRejectDefault(peer1Proof1->getId());
1781 checkRejectInvalidate(peer1Proof1->getId());
1782
1783 // Add again a proof to the conflicting pool
1784 BOOST_CHECK(!pm.registerProof(peer1Proof1));
1785 BOOST_CHECK(pm.isInConflictingPool(peer1Proof1->getId()));
1786 BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), peer1Score2);
1787
1788 // Reject from the valid pool, default mode
1789 // Now the score should change as the new peer is promoted
1790 checkRejectDefault(peer1Proof2->getId());
1791 BOOST_CHECK(!pm.isInConflictingPool(peer1Proof1->getId()));
1792 BOOST_CHECK(pm.isBoundToPeer(peer1Proof1->getId()));
1793 BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), peer1Score1);
1794
1795 // Reject from the valid pool, invalidate mode
1796 // Now the score should change as the old peer is re-promoted
1797 checkRejectInvalidate(peer1Proof1->getId());
1798
1799 // The conflicting proof should also be promoted to a peer
1800 BOOST_CHECK(!pm.isInConflictingPool(peer1Proof2->getId()));
1801 BOOST_CHECK(pm.isBoundToPeer(peer1Proof2->getId()));
1802 BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), peer1Score2);
1803
1804 // Now add another peer and check that combined scores are correct
1805 uint32_t peer2Score = 1 * MIN_VALID_PROOF_SCORE;
1806 auto peer2Proof1 = buildRandomProof(active_chainstate, peer2Score, 99);
1807 PeerId peerid2 = TestPeerManager::registerAndGetPeerId(pm, peer2Proof1);
1808 BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), peer1Score2 + peer2Score);
1809
1810 // Trying to remove non-existent peer doesn't affect score
1811 BOOST_CHECK(!pm.removePeer(1234));
1812 BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), peer1Score2 + peer2Score);
1813
1814 // Removing new peer removes its score
1815 BOOST_CHECK(pm.removePeer(peerid2));
1816 BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), peer1Score2);
1817 PeerId peerid1 =
1818 TestPeerManager::getPeerIdForProofId(pm, peer1Proof2->getId());
1819 BOOST_CHECK(pm.removePeer(peerid1));
1821}
1822
1823BOOST_AUTO_TEST_CASE(connected_score_tracking) {
1824 ChainstateManager &chainman = *Assert(m_node.chainman);
1826
1827 const auto checkScores = [&pm](uint32_t known, uint32_t connected) {
1830 };
1831
1832 // Start out with 0s
1833 checkScores(0, 0);
1834
1835 Chainstate &active_chainstate = chainman.ActiveChainstate();
1836
1837 // Create one peer without a node. Its score should be registered but not
1838 // connected
1839 uint32_t score1 = 10000000 * MIN_VALID_PROOF_SCORE;
1840 auto proof1 = buildRandomProof(active_chainstate, score1);
1841 PeerId peerid1 = TestPeerManager::registerAndGetPeerId(pm, proof1);
1842 checkScores(score1, 0);
1843
1844 // Add nodes. We now have a connected score, but it doesn't matter how many
1845 // nodes we add the score is the same
1846 const ProofId &proofid1 = proof1->getId();
1847 const uint8_t nodesToAdd = 10;
1848 for (int i = 0; i < nodesToAdd; i++) {
1851 checkScores(score1, score1);
1852 }
1853
1854 // Remove all but 1 node and ensure the score doesn't change
1855 for (int i = 0; i < nodesToAdd - 1; i++) {
1856 BOOST_CHECK(pm.removeNode(i));
1857 checkScores(score1, score1);
1858 }
1859
1860 // Removing the last node should remove the score from the connected count
1861 BOOST_CHECK(pm.removeNode(nodesToAdd - 1));
1862 checkScores(score1, 0);
1863
1864 // Add 2 nodes to peer and create peer2. Without a node peer2 has no
1865 // connected score but after adding a node it does.
1868 checkScores(score1, score1);
1869
1870 uint32_t score2 = 1 * MIN_VALID_PROOF_SCORE;
1871 auto proof2 = buildRandomProof(active_chainstate, score2);
1872 PeerId peerid2 = TestPeerManager::registerAndGetPeerId(pm, proof2);
1873 checkScores(score1 + score2, score1);
1875 pm.addNode(2, proof2->getId(), DEFAULT_AVALANCHE_MAX_ELEMENT_POLL));
1876 checkScores(score1 + score2, score1 + score2);
1877
1878 // The first peer has two nodes left. Remove one and nothing happens, remove
1879 // the other and its score is no longer in the connected counter..
1880 BOOST_CHECK(pm.removeNode(0));
1881 checkScores(score1 + score2, score1 + score2);
1882 BOOST_CHECK(pm.removeNode(1));
1883 checkScores(score1 + score2, score2);
1884
1885 // Removing a peer with no allocated score has no affect.
1886 BOOST_CHECK(pm.removePeer(peerid1));
1887 checkScores(score2, score2);
1888
1889 // Remove the second peer's node removes its allocated score.
1890 BOOST_CHECK(pm.removeNode(2));
1891 checkScores(score2, 0);
1892
1893 // Removing the second peer takes us back to 0.
1894 BOOST_CHECK(pm.removePeer(peerid2));
1895 checkScores(0, 0);
1896
1897 // Add 2 peers with nodes and remove them without removing the nodes first.
1898 // Both score counters should be reduced by each peer's score when it's
1899 // removed.
1900 peerid1 = TestPeerManager::registerAndGetPeerId(pm, proof1);
1901 checkScores(score1, 0);
1902 peerid2 = TestPeerManager::registerAndGetPeerId(pm, proof2);
1903 checkScores(score1 + score2, 0);
1905 pm.addNode(0, proof1->getId(), DEFAULT_AVALANCHE_MAX_ELEMENT_POLL));
1906 checkScores(score1 + score2, score1);
1908 pm.addNode(1, proof2->getId(), DEFAULT_AVALANCHE_MAX_ELEMENT_POLL));
1909 checkScores(score1 + score2, score1 + score2);
1910
1911 BOOST_CHECK(pm.removePeer(peerid2));
1912 checkScores(score1, score1);
1913
1914 BOOST_CHECK(pm.removePeer(peerid1));
1915 checkScores(0, 0);
1916}
1917
1918BOOST_FIXTURE_TEST_CASE(proof_radix_tree, NoCoolDownFixture) {
1919 ChainstateManager &chainman = *Assert(m_node.chainman);
1921
1922 struct ProofComparatorById {
1923 bool operator()(const ProofRef &lhs, const ProofRef &rhs) const {
1924 return lhs->getId() < rhs->getId();
1925 };
1926 };
1927 using ProofSetById = std::set<ProofRef, ProofComparatorById>;
1928 // Maintain a list of the expected proofs through this test
1929 ProofSetById expectedProofs;
1930
1931 auto matchExpectedContent = [&](const auto &tree) {
1932 auto it = expectedProofs.begin();
1933 return tree.forEachLeaf([&](auto pLeaf) {
1934 return it != expectedProofs.end() &&
1935 pLeaf->getId() == (*it++)->getId();
1936 });
1937 };
1938
1940 const int64_t sequence = 10;
1941
1942 Chainstate &active_chainstate = chainman.ActiveChainstate();
1943
1944 // Add some initial proofs
1945 for (size_t i = 0; i < 10; i++) {
1946 auto outpoint = createUtxo(active_chainstate, key);
1947 auto proof = buildProofWithSequence(key, {{outpoint}}, sequence);
1948 BOOST_CHECK(pm.registerProof(proof));
1949 expectedProofs.insert(std::move(proof));
1950 }
1951
1952 const auto &treeRef = pm.getShareableProofsSnapshot();
1953 BOOST_CHECK(matchExpectedContent(treeRef));
1954
1955 // Create a copy
1956 auto tree = pm.getShareableProofsSnapshot();
1957
1958 // Adding more proofs doesn't change the tree...
1959 ProofSetById addedProofs;
1960 std::vector<COutPoint> outpointsToSpend;
1961 for (size_t i = 0; i < 10; i++) {
1962 auto outpoint = createUtxo(active_chainstate, key);
1963 auto proof = buildProofWithSequence(key, {{outpoint}}, sequence);
1964 BOOST_CHECK(pm.registerProof(proof));
1965 addedProofs.insert(std::move(proof));
1966 outpointsToSpend.push_back(std::move(outpoint));
1967 }
1968
1969 BOOST_CHECK(matchExpectedContent(tree));
1970
1971 // ...until we get a new copy
1972 tree = pm.getShareableProofsSnapshot();
1973 expectedProofs.insert(addedProofs.begin(), addedProofs.end());
1974 BOOST_CHECK(matchExpectedContent(tree));
1975
1976 // Spend some coins to make the associated proofs invalid
1977 {
1978 LOCK(cs_main);
1979 CCoinsViewCache &coins = active_chainstate.CoinsTip();
1980 for (const auto &outpoint : outpointsToSpend) {
1981 coins.SpendCoin(outpoint);
1982 }
1983 }
1984
1985 pm.updatedBlockTip();
1986
1987 // This doesn't change the tree...
1988 BOOST_CHECK(matchExpectedContent(tree));
1989
1990 // ...until we get a new copy
1991 tree = pm.getShareableProofsSnapshot();
1992 for (const auto &proof : addedProofs) {
1993 BOOST_CHECK_EQUAL(expectedProofs.erase(proof), 1);
1994 }
1995 BOOST_CHECK(matchExpectedContent(tree));
1996
1997 // Add some more proof for which we will create conflicts
1998 std::vector<ProofRef> conflictingProofs;
1999 std::vector<COutPoint> conflictingOutpoints;
2000 for (size_t i = 0; i < 10; i++) {
2001 auto outpoint = createUtxo(active_chainstate, key);
2002 auto proof = buildProofWithSequence(key, {{outpoint}}, sequence);
2003 BOOST_CHECK(pm.registerProof(proof));
2004 conflictingProofs.push_back(std::move(proof));
2005 conflictingOutpoints.push_back(std::move(outpoint));
2006 }
2007
2008 tree = pm.getShareableProofsSnapshot();
2009 expectedProofs.insert(conflictingProofs.begin(), conflictingProofs.end());
2010 BOOST_CHECK(matchExpectedContent(tree));
2011
2012 // Build a bunch of conflicting proofs, half better, half worst
2013 for (size_t i = 0; i < 10; i += 2) {
2014 // The worst proof is not added to the expected set
2015 BOOST_CHECK(!pm.registerProof(buildProofWithSequence(
2016 key, {{conflictingOutpoints[i]}}, sequence - 1)));
2017
2018 // But the better proof should replace its conflicting one
2019 auto replacementProof = buildProofWithSequence(
2020 key, {{conflictingOutpoints[i + 1]}}, sequence + 1);
2021 BOOST_CHECK(pm.registerProof(replacementProof));
2022 BOOST_CHECK_EQUAL(expectedProofs.erase(conflictingProofs[i + 1]), 1);
2023 BOOST_CHECK(expectedProofs.insert(replacementProof).second);
2024 }
2025
2026 tree = pm.getShareableProofsSnapshot();
2027 BOOST_CHECK(matchExpectedContent(tree));
2028
2029 // Check for consistency
2030 pm.verify();
2031}
2032
2033BOOST_AUTO_TEST_CASE(received_avaproofs) {
2034 ChainstateManager &chainman = *Assert(m_node.chainman);
2036
2037 auto addNode = [&](NodeId nodeid) {
2038 auto proof = buildRandomProof(chainman.ActiveChainstate(),
2040 BOOST_CHECK(pm.registerProof(proof));
2041 BOOST_CHECK(pm.addNode(nodeid, proof->getId(),
2043 };
2044
2045 for (NodeId nodeid = 0; nodeid < 10; nodeid++) {
2046 // Node doesn't exist
2047 BOOST_CHECK(!pm.latchAvaproofsSent(nodeid));
2048
2049 addNode(nodeid);
2050 BOOST_CHECK(pm.latchAvaproofsSent(nodeid));
2051
2052 // The flag is already set
2053 BOOST_CHECK(!pm.latchAvaproofsSent(nodeid));
2054 }
2055}
2056
2057BOOST_FIXTURE_TEST_CASE(cleanup_dangling_proof, NoCoolDownFixture) {
2058 ChainstateManager &chainman = *Assert(m_node.chainman);
2059
2061
2062 const auto now = GetTime<std::chrono::seconds>();
2063 auto mocktime = now;
2064
2065 auto elapseTime = [&](std::chrono::seconds seconds) {
2066 mocktime += seconds;
2067 SetMockTime(mocktime.count());
2068 };
2069 elapseTime(0s);
2070
2071 const CKey key = CKey::MakeCompressedKey();
2072
2073 const size_t numProofs = 10;
2074
2075 std::vector<COutPoint> outpoints(numProofs);
2076 std::vector<ProofRef> proofs(numProofs);
2077 std::vector<ProofRef> conflictingProofs(numProofs);
2078 for (size_t i = 0; i < numProofs; i++) {
2079 outpoints[i] = createUtxo(chainman.ActiveChainstate(), key);
2080 proofs[i] = buildProofWithSequence(key, {outpoints[i]}, 2);
2081 conflictingProofs[i] = buildProofWithSequence(key, {outpoints[i]}, 1);
2082
2083 BOOST_CHECK(pm.registerProof(proofs[i]));
2084 BOOST_CHECK(pm.isBoundToPeer(proofs[i]->getId()));
2085
2086 BOOST_CHECK(!pm.registerProof(conflictingProofs[i]));
2087 BOOST_CHECK(pm.isInConflictingPool(conflictingProofs[i]->getId()));
2088
2089 if (i % 2) {
2090 // Odd indexes get a node attached to them
2091 BOOST_CHECK(pm.addNode(i, proofs[i]->getId(),
2093 }
2094 BOOST_CHECK_EQUAL(pm.forPeer(proofs[i]->getId(),
2095 [&](const avalanche::Peer &peer) {
2096 return peer.node_count;
2097 }),
2098 i % 2);
2099
2100 elapseTime(1s);
2101 }
2102
2103 // No proof expired yet
2104 TestPeerManager::cleanupDanglingProofs(pm);
2105 for (size_t i = 0; i < numProofs; i++) {
2106 BOOST_CHECK(pm.isBoundToPeer(proofs[i]->getId()));
2107 BOOST_CHECK(pm.isInConflictingPool(conflictingProofs[i]->getId()));
2108 }
2109
2110 // Elapse the dangling timeout
2112 TestPeerManager::cleanupDanglingProofs(pm);
2113 for (size_t i = 0; i < numProofs; i++) {
2114 const bool hasNodeAttached = i % 2;
2115
2116 // Only the peers with no nodes attached are getting discarded
2117 BOOST_CHECK_EQUAL(pm.isBoundToPeer(proofs[i]->getId()),
2118 hasNodeAttached);
2119 BOOST_CHECK_EQUAL(!pm.exists(proofs[i]->getId()), !hasNodeAttached);
2120
2121 // The proofs conflicting with the discarded ones are pulled back
2122 BOOST_CHECK_EQUAL(pm.isInConflictingPool(conflictingProofs[i]->getId()),
2123 hasNodeAttached);
2124 BOOST_CHECK_EQUAL(pm.isBoundToPeer(conflictingProofs[i]->getId()),
2125 !hasNodeAttached);
2126 }
2127
2128 // Attach a node to the first conflicting proof, which has been promoted
2129 BOOST_CHECK(pm.addNode(42, conflictingProofs[0]->getId(),
2132 conflictingProofs[0]->getId(),
2133 [&](const avalanche::Peer &peer) { return peer.node_count == 1; }));
2134
2135 // Elapse the dangling timeout again
2137 TestPeerManager::cleanupDanglingProofs(pm);
2138 for (size_t i = 0; i < numProofs; i++) {
2139 const bool hasNodeAttached = i % 2;
2140
2141 // The initial peers with a node attached are still there
2142 BOOST_CHECK_EQUAL(pm.isBoundToPeer(proofs[i]->getId()),
2143 hasNodeAttached);
2144 BOOST_CHECK_EQUAL(!pm.exists(proofs[i]->getId()), !hasNodeAttached);
2145
2146 // This time the previouly promoted conflicting proofs are evicted
2147 // because they have no node attached, except the index 0.
2148 BOOST_CHECK_EQUAL(pm.exists(conflictingProofs[i]->getId()),
2149 hasNodeAttached || i == 0);
2150 BOOST_CHECK_EQUAL(pm.isInConflictingPool(conflictingProofs[i]->getId()),
2151 hasNodeAttached);
2152 BOOST_CHECK_EQUAL(pm.isBoundToPeer(conflictingProofs[i]->getId()),
2153 i == 0);
2154 }
2155
2156 // Disconnect all the nodes
2157 for (size_t i = 1; i < numProofs; i += 2) {
2158 BOOST_CHECK(pm.removeNode(i));
2160 pm.forPeer(proofs[i]->getId(), [&](const avalanche::Peer &peer) {
2161 return peer.node_count == 0;
2162 }));
2163 }
2164 BOOST_CHECK(pm.removeNode(42));
2166 conflictingProofs[0]->getId(),
2167 [&](const avalanche::Peer &peer) { return peer.node_count == 0; }));
2168
2169 TestPeerManager::cleanupDanglingProofs(pm);
2170 for (size_t i = 0; i < numProofs; i++) {
2171 const bool hadNodeAttached = i % 2;
2172
2173 // All initially valid proofs have now been discarded
2174 BOOST_CHECK(!pm.exists(proofs[i]->getId()));
2175
2176 // The remaining conflicting proofs are promoted
2177 BOOST_CHECK_EQUAL(!pm.exists(conflictingProofs[i]->getId()),
2178 !hadNodeAttached);
2179 BOOST_CHECK(!pm.isInConflictingPool(conflictingProofs[i]->getId()));
2180 BOOST_CHECK_EQUAL(pm.isBoundToPeer(conflictingProofs[i]->getId()),
2181 hadNodeAttached);
2182 }
2183
2184 // Elapse the timeout for the newly promoted conflicting proofs
2186
2187 // All other proofs have now been discarded
2188 TestPeerManager::cleanupDanglingProofs(pm);
2189
2190 for (size_t i = 0; i < numProofs; i++) {
2191 // All proofs have finally been discarded
2192 BOOST_CHECK(!pm.exists(proofs[i]->getId()));
2193 BOOST_CHECK(!pm.exists(conflictingProofs[i]->getId()));
2194 }
2195}
2196
2197BOOST_AUTO_TEST_CASE(register_proof_missing_utxo) {
2198 ChainstateManager &chainman = *Assert(m_node.chainman);
2200
2202 auto proof = buildProofWithOutpoints(key, {{TxId(GetRandHash()), 0}},
2204
2206 BOOST_CHECK(!pm.registerProof(proof, state));
2207 BOOST_CHECK(state.GetResult() == ProofRegistrationResult::MISSING_UTXO);
2208}
2209
2210BOOST_FIXTURE_TEST_CASE(proof_expiry, NoCoolDownFixture) {
2211 ChainstateManager &chainman = *Assert(m_node.chainman);
2213
2214 const int64_t tipTime =
2215 WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip())
2216 ->GetBlockTime();
2217
2219
2220 auto utxo = createUtxo(chainman.ActiveChainstate(), key);
2221 auto proofToExpire = buildProof(key, {{utxo, PROOF_DUST_THRESHOLD}}, key, 2,
2222 100, false, tipTime + 1);
2223 auto conflictingProof = buildProof(key, {{utxo, PROOF_DUST_THRESHOLD}}, key,
2224 1, 100, false, tipTime + 2);
2225
2226 // Our proofToExpire is not expired yet, so it registers fine
2227 BOOST_CHECK(pm.registerProof(proofToExpire));
2228 BOOST_CHECK(pm.isBoundToPeer(proofToExpire->getId()));
2229
2230 // The conflicting proof has a longer expiration time but a lower sequence
2231 // number, so it is moved to the conflicting pool.
2232 BOOST_CHECK(!pm.registerProof(conflictingProof));
2233 BOOST_CHECK(pm.isInConflictingPool(conflictingProof->getId()));
2234
2235 // Mine blocks until the MTP of the tip moves to the proof expiration
2236 for (int64_t i = 0; i < 6; i++) {
2237 SetMockTime(proofToExpire->getExpirationTime() + i);
2238 CreateAndProcessBlock({}, CScript());
2239 }
2241 WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip())
2242 ->GetMedianTimePast(),
2243 proofToExpire->getExpirationTime());
2244
2245 pm.updatedBlockTip();
2246
2247 // The now expired proof is removed
2248 BOOST_CHECK(!pm.exists(proofToExpire->getId()));
2249
2250 // The conflicting proof has been pulled back to the valid pool
2251 BOOST_CHECK(pm.isBoundToPeer(conflictingProof->getId()));
2252}
2253
2254BOOST_AUTO_TEST_CASE(select_staking_reward_winner) {
2255 ChainstateManager &chainman = *Assert(m_node.chainman);
2257 Chainstate &active_chainstate = chainman.ActiveChainstate();
2258
2259 auto buildProofWithAmountAndPayout = [&](Amount amount,
2260 const CScript &payoutScript) {
2261 const CKey key = CKey::MakeCompressedKey();
2262 COutPoint utxo = createUtxo(active_chainstate, key, amount);
2263 return buildProof(key, {{std::move(utxo), amount}},
2264 /*master=*/CKey::MakeCompressedKey(), /*sequence=*/1,
2265 /*height=*/100, /*is_coinbase=*/false,
2266 /*expirationTime=*/0, payoutScript);
2267 };
2268
2269 std::vector<std::pair<ProofId, CScript>> winners;
2270 // Null pprev
2271 BOOST_CHECK(!pm.selectStakingRewardWinner(nullptr, winners));
2272
2273 CBlockIndex prevBlock;
2274
2275 auto now = GetTime<std::chrono::seconds>();
2276 SetMockTime(now);
2277 prevBlock.nTime = now.count();
2278
2279 BlockHash prevHash{uint256::ONE};
2280 prevBlock.phashBlock = &prevHash;
2281 // No peer
2282 BOOST_CHECK(!pm.selectStakingRewardWinner(&prevBlock, winners));
2283
2284 // Let's build a list of payout addresses, and register a proofs for each
2285 // address
2286 size_t numProofs = 8;
2287 std::vector<ProofRef> proofs;
2288 proofs.reserve(numProofs);
2289 for (size_t i = 0; i < numProofs; i++) {
2290 const CKey key = CKey::MakeCompressedKey();
2291 CScript payoutScript = GetScriptForRawPubKey(key.GetPubKey());
2292
2293 auto proof =
2294 buildProofWithAmountAndPayout(PROOF_DUST_THRESHOLD, payoutScript);
2295 PeerId peerid = TestPeerManager::registerAndGetPeerId(pm, proof);
2296 BOOST_CHECK_NE(peerid, NO_PEER);
2297
2298 // Finalize the proof
2299 BOOST_CHECK(pm.setFinalized(peerid));
2300
2301 proofs.emplace_back(std::move(proof));
2302 }
2303
2304 // Make sure the proofs have been registered before the prev block was found
2305 // and before 6x the peer replacement cooldown.
2306 now += 6 * avalanche::Peer::DANGLING_TIMEOUT + 1s;
2307 SetMockTime(now);
2308 prevBlock.nTime = now.count();
2309
2310 // At this stage we have a set of peers out of which none has any node
2311 // attached, so they're all considered flaky. Note that we have no remote
2312 // proofs status yet.
2313 BOOST_CHECK(pm.selectStakingRewardWinner(&prevBlock, winners));
2314 BOOST_CHECK_LE(winners.size(), numProofs);
2315
2316 // Let's add a node for each peer
2317 for (size_t i = 0; i < numProofs; i++) {
2318 BOOST_CHECK(TestPeerManager::isFlaky(pm, proofs[i]->getId()));
2319 BOOST_CHECK(pm.selectStakingRewardWinner(&prevBlock, winners));
2320 BOOST_CHECK_LE(winners.size(), numProofs);
2321
2322 BOOST_CHECK(pm.addNode(NodeId(i), proofs[i]->getId(),
2324
2325 BOOST_CHECK(!TestPeerManager::isFlaky(pm, proofs[i]->getId()));
2326 BOOST_CHECK(pm.selectStakingRewardWinner(&prevBlock, winners));
2327 BOOST_CHECK_LE(winners.size(), numProofs - i);
2328 }
2329
2330 // Now we have a single winner
2331 BOOST_CHECK(pm.selectStakingRewardWinner(&prevBlock, winners));
2332 BOOST_CHECK_LE(winners.size(), 1);
2333
2334 // All proofs have the same amount, so the same probability to get picked.
2335 // Let's compute how many loop iterations we need to have a low false
2336 // negative rate when checking for this. Target false positive rate is
2337 // 10ppm (aka 1/100000).
2338 const size_t loop_iters =
2339 size_t(-1.0 * std::log(100000.0) /
2340 std::log((double(numProofs) - 1) / numProofs)) +
2341 1;
2342 BOOST_CHECK_GT(loop_iters, numProofs);
2343 std::unordered_map<std::string, size_t> winningCounts;
2344 for (size_t i = 0; i < loop_iters; i++) {
2345 BlockHash randomHash = BlockHash(GetRandHash());
2346 prevBlock.phashBlock = &randomHash;
2347 BOOST_CHECK(pm.selectStakingRewardWinner(&prevBlock, winners));
2348 winningCounts[FormatScript(winners[0].second)]++;
2349 }
2350 BOOST_CHECK_EQUAL(winningCounts.size(), numProofs);
2351
2352 prevBlock.phashBlock = &prevHash;
2353
2354 // Ensure all nodes have all the proofs
2355 for (size_t i = 0; i < numProofs; i++) {
2356 for (size_t j = 0; j < numProofs; j++) {
2358 pm.saveRemoteProof(proofs[j]->getId(), NodeId(i), true));
2359 }
2360 }
2361
2362 // Make all the proofs flaky. This loop needs to be updated if the threshold
2363 // or the number of proofs change, so assert the test precondition.
2364 BOOST_CHECK_GT(3. / numProofs, 0.3);
2365 for (size_t i = 0; i < numProofs; i++) {
2366 const NodeId nodeid = NodeId(i);
2367
2369 proofs[(i - 1 + numProofs) % numProofs]->getId(), nodeid, false));
2371 proofs[(i + numProofs) % numProofs]->getId(), nodeid, false));
2373 proofs[(i + 1 + numProofs) % numProofs]->getId(), nodeid, false));
2374 }
2375
2376 // Now all the proofs are flaky
2377 BOOST_CHECK(pm.selectStakingRewardWinner(&prevBlock, winners));
2378 for (const auto &proof : proofs) {
2379 BOOST_CHECK(TestPeerManager::isFlaky(pm, proof->getId()));
2380 }
2381 BOOST_CHECK_EQUAL(winners.size(), numProofs);
2382
2383 // Revert flakyness for all proofs
2384 for (const auto &proof : proofs) {
2385 for (NodeId nodeid = 0; nodeid < NodeId(numProofs); nodeid++) {
2386 BOOST_CHECK(pm.saveRemoteProof(proof->getId(), nodeid, true));
2387 }
2388 }
2389
2390 BOOST_CHECK(pm.selectStakingRewardWinner(&prevBlock, winners));
2391 BOOST_CHECK_EQUAL(winners.size(), 1);
2392
2393 // Increase the list from 1 to 4 winners by making them flaky
2394 for (size_t numWinner = 1; numWinner < 4; numWinner++) {
2395 // Who is the last possible winner ?
2396 CScript lastWinner = winners[numWinner - 1].second;
2397
2398 // Make the last winner flaky, the other proofs untouched
2399 ProofId winnerProofId = ProofId(uint256::ZERO);
2400 for (const auto &proof : proofs) {
2401 if (proof->getPayoutScript() == lastWinner) {
2402 winnerProofId = proof->getId();
2403 break;
2404 }
2405 }
2406 BOOST_CHECK_NE(winnerProofId, ProofId(uint256::ZERO));
2407
2408 for (NodeId nodeid = 0; nodeid < NodeId(numProofs); nodeid++) {
2409 BOOST_CHECK(pm.saveRemoteProof(winnerProofId, nodeid, false));
2410 }
2411 BOOST_CHECK(TestPeerManager::isFlaky(pm, winnerProofId));
2412
2413 // There should be now exactly numWinner + 1 winners
2414 BOOST_CHECK(pm.selectStakingRewardWinner(&prevBlock, winners));
2415 BOOST_CHECK_EQUAL(winners.size(), numWinner + 1);
2416 }
2417
2418 // One more time and the nodes will be missing too many proofs, so they are
2419 // no longer considered for flakyness evaluation and we're back to a single
2420 // winner.
2421 CScript lastWinner = winners[3].second;
2422
2423 ProofId winnerProofId = ProofId(uint256::ZERO);
2424 for (const auto &proof : proofs) {
2425 if (proof->getPayoutScript() == lastWinner) {
2426 winnerProofId = proof->getId();
2427 break;
2428 }
2429 }
2430 BOOST_CHECK_NE(winnerProofId, ProofId(uint256::ZERO));
2431
2432 for (NodeId nodeid = 0; nodeid < NodeId(numProofs); nodeid++) {
2433 BOOST_CHECK(pm.saveRemoteProof(winnerProofId, nodeid, false));
2434 }
2435
2436 // We're back to exactly 1 winner
2437 BOOST_CHECK(pm.selectStakingRewardWinner(&prevBlock, winners));
2438 BOOST_CHECK_EQUAL(winners.size(), 1);
2439
2440 // Remove all proofs
2441 for (auto &proof : proofs) {
2444 }
2445 // No more winner
2446 prevBlock.phashBlock = &prevHash;
2447 BOOST_CHECK(!pm.selectStakingRewardWinner(&prevBlock, winners));
2448
2449 {
2450 // Add back a single proof
2451 const CKey key = CKey::MakeCompressedKey();
2452 CScript payoutScript = GetScriptForRawPubKey(key.GetPubKey());
2453
2454 auto proof =
2455 buildProofWithAmountAndPayout(PROOF_DUST_THRESHOLD, payoutScript);
2456 PeerId peerid = TestPeerManager::registerAndGetPeerId(pm, proof);
2457 BOOST_CHECK_NE(peerid, NO_PEER);
2458
2459 // The single proof should always be selected, but:
2460 // 1. The proof is not finalized, and has been registered after the last
2461 // block was mined.
2462 BOOST_CHECK(!pm.selectStakingRewardWinner(&prevBlock, winners));
2463
2464 // 2. The proof has has been registered after the last block was mined.
2465 BOOST_CHECK(pm.setFinalized(peerid));
2466 BOOST_CHECK(!pm.selectStakingRewardWinner(&prevBlock, winners));
2467
2468 // 3. The proof has been registered 60min from the previous block time,
2469 // but the previous block time is in the future.
2470 now += 50min + 1s;
2471 SetMockTime(now);
2472 prevBlock.nTime = (now + 10min).count();
2473 BOOST_CHECK(!pm.selectStakingRewardWinner(&prevBlock, winners));
2474
2475 // 4. The proof has been registered 60min from now, but only 50min from
2476 // the previous block time.
2477 now += 10min;
2478 SetMockTime(now);
2479 prevBlock.nTime = (now - 10min).count();
2480 BOOST_CHECK(!pm.selectStakingRewardWinner(&prevBlock, winners));
2481
2482 // 5. Now the proof has it all
2483 prevBlock.nTime = now.count();
2484 BOOST_CHECK(pm.selectStakingRewardWinner(&prevBlock, winners));
2485 // With a single proof, it's easy to determine the winner
2486 BOOST_CHECK_EQUAL(FormatScript(winners[0].second),
2487 FormatScript(payoutScript));
2488
2489 // Remove the proof
2492 }
2493
2494 {
2495 BOOST_CHECK_EQUAL(TestPeerManager::getPeerCount(pm), 0);
2496
2497 proofs.clear();
2498 for (size_t i = 0; i < 4; i++) {
2499 // Add 4 proofs, registered at a 30 minutes interval
2500 SetMockTime(now + i * 30min);
2501
2502 const CKey key = CKey::MakeCompressedKey();
2503 CScript payoutScript = GetScriptForRawPubKey(key.GetPubKey());
2504
2505 auto proof = buildProofWithAmountAndPayout(PROOF_DUST_THRESHOLD,
2506 payoutScript);
2507 PeerId peerid = TestPeerManager::registerAndGetPeerId(pm, proof);
2508 BOOST_CHECK_NE(peerid, NO_PEER);
2509 BOOST_CHECK(pm.forPeer(proof->getId(), [&](const Peer &peer) {
2510 return peer.registration_time == now + i * 30min;
2511 }));
2512
2513 BOOST_CHECK(pm.addNode(NodeId(i), proof->getId(),
2515
2516 BOOST_CHECK(pm.setFinalized(peerid));
2517
2518 proofs.push_back(proof);
2519 }
2520
2521 // No proof has been registered before the previous block time
2522 SetMockTime(now);
2523 prevBlock.nTime = now.count();
2524 BOOST_CHECK(!pm.selectStakingRewardWinner(&prevBlock, winners));
2525
2526 // 1 proof has been registered > 30min from the previous block time, but
2527 // none > 60 minutes from the previous block time
2528 // => we have no winner.
2529 now += 30min + 1s;
2530 SetMockTime(now);
2531 prevBlock.nTime = now.count();
2532 BOOST_CHECK(!pm.selectStakingRewardWinner(&prevBlock, winners));
2533
2534 auto checkRegistrationTime =
2535 [&](const std::pair<ProofId, CScript> &winner) {
2536 pm.forEachPeer([&](const Peer &peer) {
2537 if (peer.proof->getPayoutScript() == winner.second) {
2538 BOOST_CHECK_LT(peer.registration_time.count(),
2539 (now - 60min).count());
2540 }
2541 return true;
2542 });
2543 };
2544
2545 // 1 proof has been registered > 60min but < 90min from the previous
2546 // block time and 1 more has been registered > 30 minutes
2547 // => we have a winner and one acceptable substitute.
2548 now += 30min;
2549 SetMockTime(now);
2550 prevBlock.nTime = now.count();
2551 BOOST_CHECK(pm.selectStakingRewardWinner(&prevBlock, winners));
2552 BOOST_CHECK_EQUAL(winners.size(), 2);
2553 checkRegistrationTime(winners[0]);
2554
2555 // 1 proof has been registered > 60min but < 90min from the
2556 // previous block time, 1 has been registered > 90 minutes and 1 more
2557 // has been registered > 30 minutes
2558 // => we have 1 winner and up to 2 acceptable substitutes.
2559 now += 30min;
2560 SetMockTime(now);
2561 prevBlock.nTime = now.count();
2562 BOOST_CHECK(pm.selectStakingRewardWinner(&prevBlock, winners));
2563 BOOST_CHECK_LE(winners.size(), 3);
2564 checkRegistrationTime(winners[0]);
2565
2566 // 1 proofs has been registered > 60min but < 90min from the
2567 // previous block time, 2 has been registered > 90 minutes and 1 more
2568 // has been registered > 30 minutes
2569 // => we have 1 winner, and up to 2 substitutes.
2570 now += 30min;
2571 SetMockTime(now);
2572 prevBlock.nTime = now.count();
2573 BOOST_CHECK(pm.selectStakingRewardWinner(&prevBlock, winners));
2574 BOOST_CHECK_LE(winners.size(), 3);
2575 checkRegistrationTime(winners[0]);
2576
2577 // 1 proof has been registered > 60min but < 90min from the
2578 // previous block time and 3 more has been registered > 90 minutes
2579 // => we have 1 winner, and up to 1 substitute.
2580 now += 30min;
2581 SetMockTime(now);
2582 prevBlock.nTime = now.count();
2583 BOOST_CHECK(pm.selectStakingRewardWinner(&prevBlock, winners));
2584 BOOST_CHECK_LE(winners.size(), 2);
2585 checkRegistrationTime(winners[0]);
2586
2587 // All proofs has been registered > 90min from the previous block time
2588 // => we have 1 winner, and no substitute.
2589 now += 30min;
2590 SetMockTime(now);
2591 prevBlock.nTime = now.count();
2592 BOOST_CHECK(pm.selectStakingRewardWinner(&prevBlock, winners));
2593 BOOST_CHECK_EQUAL(winners.size(), 1);
2594 checkRegistrationTime(winners[0]);
2595 }
2596}
2597
2598BOOST_FIXTURE_TEST_CASE(stake_contender_vote, NoCoolDownFixture) {
2599 ChainstateManager &chainman = *Assert(m_node.chainman);
2600 Chainstate &active_chainstate = chainman.ActiveChainstate();
2602 /*stakingPreConsensus=*/true);
2603
2604 auto now = GetTime<std::chrono::seconds>();
2605 SetMockTime(now);
2606
2607 CBlockIndex *tip = WITH_LOCK(cs_main, return chainman.ActiveTip());
2608 BOOST_CHECK(tip != nullptr);
2609 tip->nTime = now.count();
2610 const BlockHash tipHash = tip->GetBlockHash();
2611 BlockHash outHash;
2612 NodeId nextNodeId = 0;
2613
2614 struct ContenderOption {
2615 bool accept{true};
2616 bool invalid{false};
2617 bool attachNode{true};
2618 bool finalize{true};
2619 bool knownForLongEnough{true};
2620 };
2621
2622 // Build a bound peer contender that would be voted yes (0) by default
2623 auto makeEligibleContender = [&](const ContenderOption &opt = {}) {
2624 const ProofRef proof =
2625 buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
2626
2627 // Contender cache stores tip time at first insert (registerProof with
2628 // staking preconsensus). Set the tip time ahead before registering so
2629 // registration_time is early and the proof is old enough to be
2630 // eligible.
2631 tip->nTime =
2632 opt.knownForLongEnough
2633 ? (now + 4 * avalanche::Peer::DANGLING_TIMEOUT + 1s).count()
2634 : now.count();
2635
2636 const PeerId peerid = TestPeerManager::registerAndGetPeerId(pm, proof);
2637 if (opt.finalize) {
2638 BOOST_CHECK(pm.setFinalized(peerid));
2639 }
2640 if (opt.attachNode) {
2641 BOOST_CHECK(pm.addNode(nextNodeId++, proof->getId(),
2643 }
2644 if (opt.knownForLongEnough) {
2645 now += 4 * avalanche::Peer::DANGLING_TIMEOUT + 1s;
2646 SetMockTime(now);
2647 }
2648
2649 const StakeContenderId contenderId(tipHash, proof->getId());
2650 if (opt.accept) {
2651 pm.acceptStakeContender(contenderId);
2652 }
2653 if (opt.invalid) {
2654 pm.setInvalid(proof->getId());
2655 }
2656 return std::make_tuple(proof, contenderId, peerid);
2657 };
2658
2659 // Contender not found -> -1
2660 {
2661 const StakeContenderId contenderId(tipHash, ProofId(uint256::ZERO));
2662 BOOST_CHECK_EQUAL(pm.getStakeContenderStatus(contenderId, outHash), -1);
2663 }
2664
2665 // Baseline: eligible finalized peer -> yes (0)
2666 {
2667 auto [proof, contenderId, peerid] = makeEligibleContender();
2668 BOOST_CHECK_EQUAL(pm.getStakeContenderStatus(contenderId, outHash), 0);
2669 }
2670
2671 // Cache rejection -> 1
2672 {
2673 auto [proof, contenderId, peerid] =
2674 makeEligibleContender({.accept = false});
2675 BOOST_CHECK_EQUAL(pm.getStakeContenderStatus(contenderId, outHash), 1);
2676 }
2677
2678 // Cache rejection takes precedence over any other rejection
2679 {
2680 auto [proof, contenderId, peerid] =
2681 makeEligibleContender({.accept = false,
2682 .invalid = true,
2683 .attachNode = false,
2684 .finalize = false,
2685 .knownForLongEnough = false});
2686 BOOST_CHECK_EQUAL(pm.getStakeContenderStatus(contenderId, outHash), 1);
2687 }
2688
2689 // Invalid -> 2
2690 {
2691 auto [proof, contenderId, peerid] =
2692 makeEligibleContender({.invalid = true});
2693 BOOST_CHECK_EQUAL(pm.getStakeContenderStatus(contenderId, outHash), 2);
2694 }
2695
2696 // Conflicting (needs a preferred replacement on the same UTXO) -> 3
2697 {
2698 const CKey key = CKey::MakeCompressedKey();
2699 const COutPoint conflictingOutpoint =
2700 createUtxo(active_chainstate, key);
2701 auto preferredProof =
2702 buildProofWithSequence(key, {conflictingOutpoint}, 20);
2703 auto conflictingProof =
2704 buildProofWithSequence(key, {conflictingOutpoint}, 10);
2705 BOOST_CHECK(pm.registerProof(preferredProof));
2706 BOOST_CHECK(!pm.registerProof(conflictingProof));
2707 BOOST_CHECK(pm.isInConflictingPool(conflictingProof->getId()));
2708
2709 pm.addStakeContender(conflictingProof);
2710 const StakeContenderId contenderId(tipHash, conflictingProof->getId());
2711 pm.acceptStakeContender(contenderId);
2712 BOOST_CHECK_EQUAL(pm.getStakeContenderStatus(contenderId, outHash), 3);
2713 }
2714
2715 // Dangling -> 4 (even if remotely present; quorum peers with the proof
2716 // bound can still vote yes and finalize)
2717 {
2718 auto [proof, contenderId, peerid] =
2719 makeEligibleContender({.attachNode = false});
2721 SetMockTime(now);
2722 tip->nTime = now.count();
2723 TestPeerManager::cleanupDanglingProofs(pm);
2724 BOOST_CHECK(pm.isDangling(proof->getId()));
2725 BOOST_CHECK_EQUAL(pm.getStakeContenderStatus(contenderId, outHash), 4);
2726 BOOST_CHECK(pm.saveRemoteProof(proof->getId(), 0, true));
2727 BOOST_CHECK_EQUAL(pm.getStakeContenderStatus(contenderId, outHash), 4);
2728 }
2729
2730 // Not finalized -> 5
2731 {
2732 auto [proof, contenderId, peerid] =
2733 makeEligibleContender({.finalize = false});
2734 BOOST_CHECK_EQUAL(pm.getStakeContenderStatus(contenderId, outHash), 5);
2735 }
2736
2737 // Too young for paid slot -> 6
2738 {
2739 auto [proof, contenderId, peerid] =
2740 makeEligibleContender({.knownForLongEnough = false});
2741 BOOST_CHECK_EQUAL(pm.getStakeContenderStatus(contenderId, outHash), 6);
2742 }
2743}
2744
2746 ChainstateManager &chainman = *Assert(m_node.chainman);
2748
2749 auto mockTime = GetTime<std::chrono::seconds>();
2750 SetMockTime(mockTime);
2751
2756
2757 auto checkRemoteProof =
2758 [&](const ProofId &proofid, const NodeId nodeid,
2759 const bool expectedPresent,
2760 const std::chrono::seconds &expectedlastUpdate) {
2761 BOOST_CHECK(pm.hasRemoteProofStatus(proofid));
2762 BOOST_CHECK(pm.isRemotelyPresentProof(proofid) == expectedPresent);
2763 auto remoteProof =
2764 TestPeerManager::getRemoteProof(pm, proofid, nodeid);
2765 BOOST_CHECK(remoteProof.has_value());
2766 BOOST_CHECK_EQUAL(remoteProof->proofid, proofid);
2767 BOOST_CHECK_EQUAL(remoteProof->nodeid, nodeid);
2768 BOOST_CHECK_EQUAL(remoteProof->present, expectedPresent);
2769 BOOST_CHECK_EQUAL(remoteProof->lastUpdate.count(),
2770 expectedlastUpdate.count());
2771 };
2772
2773 checkRemoteProof(ProofId(uint256::ZERO), 0, true, mockTime);
2774 checkRemoteProof(ProofId(uint256::ONE), 0, false, mockTime);
2775 checkRemoteProof(ProofId(uint256::ZERO), 1, true, mockTime);
2776 checkRemoteProof(ProofId(uint256::ONE), 1, false, mockTime);
2777
2778 mockTime += 1s;
2779 SetMockTime(mockTime);
2780
2781 // Reverse the state
2786
2787 checkRemoteProof(ProofId(uint256::ZERO), 0, false, mockTime);
2788 checkRemoteProof(ProofId(uint256::ONE), 0, true, mockTime);
2789 checkRemoteProof(ProofId(uint256::ZERO), 1, false, mockTime);
2790 checkRemoteProof(ProofId(uint256::ONE), 1, true, mockTime);
2791
2792 Chainstate &active_chainstate = chainman.ActiveChainstate();
2793
2794 // Actually register the nodes
2795 auto proof0 = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
2796 BOOST_CHECK(pm.registerProof(proof0));
2798 pm.addNode(0, proof0->getId(), DEFAULT_AVALANCHE_MAX_ELEMENT_POLL));
2799 auto proof1 = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
2800 BOOST_CHECK(pm.registerProof(proof1));
2802 pm.addNode(1, proof1->getId(), DEFAULT_AVALANCHE_MAX_ELEMENT_POLL));
2803
2804 // Removing the node removes all the associated remote proofs
2805 BOOST_CHECK(pm.removeNode(0));
2807 !TestPeerManager::getRemoteProof(pm, ProofId(uint256::ZERO), 0));
2808 BOOST_CHECK(!TestPeerManager::getRemoteProof(pm, ProofId(uint256::ONE), 0));
2809 // Other nodes are left untouched
2810 checkRemoteProof(ProofId(uint256::ZERO), 1, false, mockTime);
2811 checkRemoteProof(ProofId(uint256::ONE), 1, true, mockTime);
2812
2813 BOOST_CHECK(pm.removeNode(1));
2815 !TestPeerManager::getRemoteProof(pm, ProofId(uint256::ZERO), 0));
2816 BOOST_CHECK(!TestPeerManager::getRemoteProof(pm, ProofId(uint256::ONE), 0));
2818 !TestPeerManager::getRemoteProof(pm, ProofId(uint256::ZERO), 1));
2819 BOOST_CHECK(!TestPeerManager::getRemoteProof(pm, ProofId(uint256::ONE), 1));
2820
2824 pm.clearRemoteProofs(0);
2826 !TestPeerManager::getRemoteProof(pm, ProofId(uint256::ZERO), 0));
2827 BOOST_CHECK(!TestPeerManager::getRemoteProof(pm, ProofId(uint256::ONE), 0));
2828 checkRemoteProof(ProofId(uint256::ZERO), 1, true, mockTime);
2829
2830 for (size_t i = 0; i < avalanche::PeerManager::MAX_REMOTE_PROOFS; i++) {
2831 mockTime += 1s;
2832 SetMockTime(mockTime);
2833
2834 const ProofId proofid{uint256(i)};
2835
2836 BOOST_CHECK(pm.saveRemoteProof(proofid, 0, true));
2837 checkRemoteProof(proofid, 0, true, mockTime);
2838 }
2839
2840 // The last updated proof is still there
2841 checkRemoteProof(ProofId(uint256::ZERO), 0, true,
2842 mockTime -
2844
2845 // If we add one more it gets evicted
2846 mockTime += 1s;
2847 SetMockTime(mockTime);
2848
2849 ProofId proofid{
2851
2852 BOOST_CHECK(pm.saveRemoteProof(proofid, 0, true));
2853 checkRemoteProof(proofid, 0, true, mockTime);
2854 // Proof id 0 has been evicted
2856 !TestPeerManager::getRemoteProof(pm, ProofId(uint256::ZERO), 0));
2857
2858 // Proof id 1 is still there
2859 BOOST_CHECK(TestPeerManager::getRemoteProof(pm, ProofId(uint256::ONE), 0));
2860
2861 // Add MAX_REMOTE_PROOFS / 2 + 1 proofs to our node to bump the limit
2862 // Note that we already have proofs from the beginning of the test.
2863 std::vector<ProofRef> proofs;
2864 for (size_t i = 0; i < avalanche::PeerManager::MAX_REMOTE_PROOFS / 2 - 1;
2865 i++) {
2866 auto proof = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
2867 BOOST_CHECK(pm.registerProof(proof));
2868 proofs.push_back(proof);
2869 }
2870 BOOST_CHECK_EQUAL(TestPeerManager::getPeerCount(pm),
2872
2873 // We can now add one more without eviction
2874 mockTime += 1s;
2875 SetMockTime(mockTime);
2876
2877 proofid = ProofId{
2879
2880 BOOST_CHECK(pm.saveRemoteProof(proofid, 0, true));
2881 checkRemoteProof(proofid, 0, true, mockTime);
2882 // Proof id 1 is still there
2883 BOOST_CHECK(TestPeerManager::getRemoteProof(pm, ProofId(uint256::ONE), 0));
2884
2885 // Shrink our proofs to MAX_REMOTE_PROOFS / 2 - 1
2890
2891 BOOST_CHECK_EQUAL(TestPeerManager::getPeerCount(pm),
2893
2894 // Upon update the first proof got evicted
2895 proofid = ProofId{
2897 BOOST_CHECK(pm.saveRemoteProof(proofid, 0, true));
2898 // Proof id 1 is evicted
2899 BOOST_CHECK(!TestPeerManager::getRemoteProof(pm, ProofId(uint256::ONE), 0));
2900 // So is proof id 2
2901 BOOST_CHECK(!TestPeerManager::getRemoteProof(pm, ProofId(uint256(2)), 0));
2902 // But proof id 3 is still here
2903 BOOST_CHECK(TestPeerManager::getRemoteProof(pm, ProofId(uint256(3)), 0));
2904}
2905
2906BOOST_AUTO_TEST_CASE(get_remote_status) {
2907 ChainstateManager &chainman = *Assert(m_node.chainman);
2909 Chainstate &active_chainstate = chainman.ActiveChainstate();
2910
2911 auto mockTime = GetTime<std::chrono::seconds>();
2912 SetMockTime(mockTime);
2913
2914 // No remote proof yet
2916 !TestPeerManager::getRemotePresenceStatus(pm, ProofId(uint256::ZERO))
2917 .has_value());
2918
2919 // 6/12 (50%) of the stakes
2920 for (NodeId nodeid = 0; nodeid < 12; nodeid++) {
2921 auto proof = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
2922 BOOST_CHECK(pm.registerProof(proof));
2923 BOOST_CHECK(pm.addNode(nodeid, proof->getId(),
2926 nodeid % 2 == 0));
2927 }
2928
2930 !TestPeerManager::getRemotePresenceStatus(pm, ProofId(uint256::ZERO))
2931 .has_value());
2932
2933 // 7/12 (~58%) of the stakes
2934 for (NodeId nodeid = 0; nodeid < 5; nodeid++) {
2935 BOOST_CHECK(pm.saveRemoteProof(ProofId(uint256::ZERO), nodeid, false));
2936 }
2937 for (NodeId nodeid = 5; nodeid < 12; nodeid++) {
2939 }
2941 TestPeerManager::getRemotePresenceStatus(pm, ProofId(uint256::ZERO))
2942 .value());
2943
2944 // Add our local proof so we have 7/13 (~54% < 55%)
2945 auto localProof =
2946 buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
2947 TestPeerManager::setLocalProof(pm, localProof);
2948 BOOST_CHECK(pm.registerProof(localProof));
2950 !TestPeerManager::getRemotePresenceStatus(pm, ProofId(uint256::ZERO))
2951 .has_value());
2952
2953 // Remove the local proof to revert back to 7/12 (~58%)
2954 pm.rejectProof(localProof->getId());
2955 TestPeerManager::setLocalProof(pm, ProofRef());
2957 TestPeerManager::getRemotePresenceStatus(pm, ProofId(uint256::ZERO))
2958 .value());
2959
2960 // 5/12 (~42%) of the stakes
2961 for (NodeId nodeid = 0; nodeid < 5; nodeid++) {
2963 }
2964 for (NodeId nodeid = 5; nodeid < 12; nodeid++) {
2965 BOOST_CHECK(pm.saveRemoteProof(ProofId(uint256::ZERO), nodeid, false));
2966 }
2968 !TestPeerManager::getRemotePresenceStatus(pm, ProofId(uint256::ZERO))
2969 .value());
2970
2971 // Most nodes agree but not enough of the stakes
2972 auto bigProof =
2973 buildRandomProof(active_chainstate, 100 * MIN_VALID_PROOF_SCORE);
2974 BOOST_CHECK(pm.registerProof(bigProof));
2975 // Update the node's proof
2977 pm.addNode(0, bigProof->getId(), DEFAULT_AVALANCHE_MAX_ELEMENT_POLL));
2978
2979 // 7/12 (~58%) of the remotes, but < 10% of the stakes => absent
2980 for (NodeId nodeid = 0; nodeid < 5; nodeid++) {
2981 BOOST_CHECK(pm.saveRemoteProof(ProofId(uint256::ZERO), nodeid, false));
2982 }
2983 for (NodeId nodeid = 5; nodeid < 12; nodeid++) {
2985 }
2987 !TestPeerManager::getRemotePresenceStatus(pm, ProofId(uint256::ZERO))
2988 .value());
2989
2990 // 5/12 (42%) of the remotes, but > 90% of the stakes => present
2991 for (NodeId nodeid = 0; nodeid < 5; nodeid++) {
2993 }
2994 for (NodeId nodeid = 5; nodeid < 12; nodeid++) {
2995 BOOST_CHECK(pm.saveRemoteProof(ProofId(uint256::ZERO), nodeid, false));
2996 }
2998 TestPeerManager::getRemotePresenceStatus(pm, ProofId(uint256::ZERO))
2999 .value());
3000
3001 TestPeerManager::clearPeers(pm);
3002
3003 // Peer 1 has 1 node (id 0)
3004 auto proof1 = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
3005 BOOST_CHECK(pm.registerProof(proof1));
3007 pm.addNode(0, proof1->getId(), DEFAULT_AVALANCHE_MAX_ELEMENT_POLL));
3008
3009 // Peer 2 has 5 nodes (ids 1 to 5)
3010 auto proof2 = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
3011 BOOST_CHECK(pm.registerProof(proof2));
3012 for (NodeId nodeid = 1; nodeid < 6; nodeid++) {
3013 BOOST_CHECK(pm.addNode(nodeid, proof2->getId(),
3015 }
3016
3017 // Node 0 is missing proofid 0, nodes 1 to 5 have it
3019 for (NodeId nodeid = 1; nodeid < 6; nodeid++) {
3021 }
3022
3023 // At this stage we have 5/6 nodes with the proof, but since all the nodes
3024 // advertising the proof are from the same peer, we only 1/2 peers, i.e. 50%
3025 // of the stakes.
3027 !TestPeerManager::getRemotePresenceStatus(pm, ProofId(uint256::ZERO))
3028 .has_value());
3029}
3030
3031BOOST_AUTO_TEST_CASE(dangling_with_remotes) {
3032 ChainstateManager &chainman = *Assert(m_node.chainman);
3034 Chainstate &active_chainstate = chainman.ActiveChainstate();
3035
3036 auto mockTime = GetTime<std::chrono::seconds>();
3037 SetMockTime(mockTime);
3038
3039 // Add a few proofs with no node attached
3040 std::vector<ProofRef> proofs;
3041 for (size_t i = 0; i < 10; i++) {
3042 auto proof = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
3043 BOOST_CHECK(pm.registerProof(proof));
3044 proofs.push_back(proof);
3045 }
3046
3047 // The proofs are recent enough, the cleanup won't make them dangling
3048 TestPeerManager::cleanupDanglingProofs(pm);
3049 for (const auto &proof : proofs) {
3050 BOOST_CHECK(pm.isBoundToPeer(proof->getId()));
3051 BOOST_CHECK(!pm.isDangling(proof->getId()));
3052 }
3053
3054 // Elapse enough time so we get the proofs dangling
3055 mockTime += avalanche::Peer::DANGLING_TIMEOUT + 1s;
3056 SetMockTime(mockTime);
3057
3058 // The proofs are now dangling
3059 TestPeerManager::cleanupDanglingProofs(pm);
3060 for (const auto &proof : proofs) {
3061 BOOST_CHECK(!pm.isBoundToPeer(proof->getId()));
3062 BOOST_CHECK(pm.isDangling(proof->getId()));
3063 }
3064
3065 // Add some remotes having this proof
3066 for (NodeId nodeid = 0; nodeid < 10; nodeid++) {
3067 auto localProof =
3068 buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
3069 BOOST_CHECK(pm.registerProof(localProof));
3070 BOOST_CHECK(pm.addNode(nodeid, localProof->getId(),
3072
3073 for (const auto &proof : proofs) {
3074 BOOST_CHECK(pm.saveRemoteProof(proof->getId(), nodeid, true));
3075 }
3076 }
3077
3078 // The proofs are all present according to the remote status
3079 for (const auto &proof : proofs) {
3080 BOOST_CHECK(TestPeerManager::getRemotePresenceStatus(pm, proof->getId())
3081 .value());
3082 }
3083
3084 // The proofs should be added back as a peer
3085 std::unordered_set<ProofRef, SaltedProofHasher> registeredProofs;
3086 TestPeerManager::cleanupDanglingProofs(pm, registeredProofs);
3087 for (const auto &proof : proofs) {
3088 BOOST_CHECK(pm.isBoundToPeer(proof->getId()));
3089 BOOST_CHECK(!pm.isDangling(proof->getId()));
3090 BOOST_CHECK_EQUAL(registeredProofs.count(proof), 1);
3091 }
3092 BOOST_CHECK_EQUAL(proofs.size(), registeredProofs.size());
3093
3094 // Remove the proofs from the remotes
3095 for (NodeId nodeid = 0; nodeid < 10; nodeid++) {
3096 for (const auto &proof : proofs) {
3097 BOOST_CHECK(pm.saveRemoteProof(proof->getId(), nodeid, false));
3098 }
3099 }
3100
3101 // The proofs are now all absent according to the remotes
3102 for (const auto &proof : proofs) {
3104 !TestPeerManager::getRemotePresenceStatus(pm, proof->getId())
3105 .value());
3106 }
3107
3108 // The proofs are not dangling yet as they have been registered recently
3109 TestPeerManager::cleanupDanglingProofs(pm, registeredProofs);
3110 BOOST_CHECK(registeredProofs.empty());
3111 for (const auto &proof : proofs) {
3112 BOOST_CHECK(pm.isBoundToPeer(proof->getId()));
3113 BOOST_CHECK(!pm.isDangling(proof->getId()));
3114 }
3115
3116 // Wait some time then run the cleanup again, the proofs will be dangling
3117 mockTime += avalanche::Peer::DANGLING_TIMEOUT + 1s;
3118 SetMockTime(mockTime);
3119
3120 TestPeerManager::cleanupDanglingProofs(pm, registeredProofs);
3121 BOOST_CHECK(registeredProofs.empty());
3122 for (const auto &proof : proofs) {
3123 BOOST_CHECK(!pm.isBoundToPeer(proof->getId()));
3124 BOOST_CHECK(pm.isDangling(proof->getId()));
3125 }
3126
3127 // Pull them back one more time
3128 for (NodeId nodeid = 0; nodeid < 10; nodeid++) {
3129 for (const auto &proof : proofs) {
3130 BOOST_CHECK(pm.saveRemoteProof(proof->getId(), nodeid, true));
3131 }
3132 }
3133
3134 TestPeerManager::cleanupDanglingProofs(pm, registeredProofs);
3135 for (const auto &proof : proofs) {
3136 BOOST_CHECK(pm.isBoundToPeer(proof->getId()));
3137 BOOST_CHECK(!pm.isDangling(proof->getId()));
3138 BOOST_CHECK_EQUAL(registeredProofs.count(proof), 1);
3139 }
3140 BOOST_CHECK_EQUAL(proofs.size(), registeredProofs.size());
3141}
3142
3143BOOST_AUTO_TEST_CASE(avapeers_dump) {
3144 ChainstateManager &chainman = *Assert(m_node.chainman);
3146 Chainstate &active_chainstate = chainman.ActiveChainstate();
3147
3148 auto mockTime = GetTime<std::chrono::seconds>();
3149 SetMockTime(mockTime);
3150
3151 std::vector<ProofRef> proofs;
3152 for (size_t i = 0; i < 10; i++) {
3153 SetMockTime(mockTime + std::chrono::seconds{i});
3154
3155 auto proof = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
3156 // Registration time is mockTime + i
3157 BOOST_CHECK(pm.registerProof(proof));
3158
3159 auto peerid = TestPeerManager::getPeerIdForProofId(pm, proof->getId());
3160
3161 // Next conflict time is mockTime + 100 + i
3163 peerid, mockTime + std::chrono::seconds{100 + i}));
3164
3165 // The 5 first proofs are finalized
3166 if (i < 5) {
3167 BOOST_CHECK(pm.setFinalized(peerid));
3168 }
3169
3170 proofs.push_back(proof);
3171 }
3172
3173 BOOST_CHECK_EQUAL(TestPeerManager::getPeerCount(pm), 10);
3174
3175 const fs::path testDumpPath = "test_avapeers_dump.dat";
3176 BOOST_CHECK(pm.dumpPeersToFile(testDumpPath));
3177
3178 TestPeerManager::clearPeers(pm);
3179
3180 std::unordered_set<ProofRef, SaltedProofHasher> registeredProofs;
3181 BOOST_CHECK(pm.loadPeersFromFile(testDumpPath, registeredProofs));
3182 BOOST_CHECK_EQUAL(registeredProofs.size(), 10);
3183
3184 auto findProofIndex = [&proofs](const ProofId &proofid) {
3185 for (size_t i = 0; i < proofs.size(); i++) {
3186 if (proofs[i]->getId() == proofid) {
3187 return i;
3188 }
3189 }
3190
3191 // ProofId not found
3192 BOOST_CHECK(false);
3193 return size_t{0};
3194 };
3195
3196 for (const auto &proof : registeredProofs) {
3197 const ProofId &proofid = proof->getId();
3198 size_t i = findProofIndex(proofid);
3199 BOOST_CHECK(pm.forPeer(proofid, [&](auto &peer) {
3200 BOOST_CHECK_EQUAL(peer.hasFinalized, i < 5);
3201 BOOST_CHECK_EQUAL(peer.registration_time.count(),
3202 (mockTime + std::chrono::seconds{i}).count());
3204 peer.nextPossibleConflictTime.count(),
3205 (mockTime + std::chrono::seconds{100 + i}).count());
3206 return true;
3207 }));
3208 }
3209
3210 // No peer: create an empty file but generate no error
3211 TestPeerManager::clearPeers(pm);
3212 BOOST_CHECK(pm.dumpPeersToFile("test_empty_avapeers.dat"));
3213 // We can also load an empty file
3215 pm.loadPeersFromFile("test_empty_avapeers.dat", registeredProofs));
3216 BOOST_CHECK(registeredProofs.empty());
3217 BOOST_CHECK_EQUAL(TestPeerManager::getPeerCount(pm), 0);
3218
3219 // If the file exists, it is overrwritten
3220 BOOST_CHECK(pm.dumpPeersToFile("test_empty_avapeers.dat"));
3221
3222 // It fails to load if the file does not exist and the registeredProofs is
3223 // cleared
3224 registeredProofs.insert(proofs[0]);
3225 BOOST_CHECK(!registeredProofs.empty());
3226 BOOST_CHECK(!pm.loadPeersFromFile("I_dont_exist.dat", registeredProofs));
3227 BOOST_CHECK(registeredProofs.empty());
3228
3229 {
3230 // Change the version
3231 FILE *f = fsbridge::fopen("test_bad_version_avapeers.dat", "wb");
3232 BOOST_CHECK(f);
3233 AutoFile file{f};
3234 file << static_cast<uint64_t>(-1); // Version
3235 file << uint64_t{0}; // Number of peers
3236 BOOST_CHECK(FileCommit(file.Get()));
3237 file.fclose();
3238
3239 // Check loading fails and the registeredProofs is cleared
3240 registeredProofs.insert(proofs[0]);
3241 BOOST_CHECK(!registeredProofs.empty());
3242 BOOST_CHECK(!pm.loadPeersFromFile("test_bad_version_avapeers.dat",
3243 registeredProofs));
3244 BOOST_CHECK(registeredProofs.empty());
3245 }
3246
3247 {
3248 // Wrong format, will cause a deserialization error
3249 FILE *f = fsbridge::fopen("test_ill_formed_avapeers.dat", "wb");
3250 BOOST_CHECK(f);
3251 const uint64_t now = GetTime();
3252 AutoFile file{f};
3253 file << static_cast<uint64_t>(1); // Version
3254 file << uint64_t{2}; // Number of peers
3255 // Single peer content!
3256 file << proofs[0];
3257 file << true;
3258 file << now;
3259 file << now + 100;
3260
3261 BOOST_CHECK(FileCommit(file.Get()));
3262 file.fclose();
3263
3264 // Check loading fails and the registeredProofs is fed with our single
3265 // peer
3266 BOOST_CHECK(registeredProofs.empty());
3267 BOOST_CHECK(!pm.loadPeersFromFile("test_ill_formed_avapeers.dat",
3268 registeredProofs));
3269 BOOST_CHECK_EQUAL(registeredProofs.size(), 1);
3270 BOOST_CHECK_EQUAL((*registeredProofs.begin())->getId(),
3271 proofs[0]->getId());
3272 }
3273}
3274
3275BOOST_AUTO_TEST_CASE(dangling_proof_invalidation) {
3276 ChainstateManager &chainman = *Assert(m_node.chainman);
3278 Chainstate &active_chainstate = chainman.ActiveChainstate();
3279
3280 SetMockTime(GetTime<std::chrono::seconds>());
3281
3283 auto utxo = createUtxo(active_chainstate, key);
3284 auto proof =
3285 buildProof(key, {{utxo, PROOF_DUST_THRESHOLD}}, key, 2, 100, false,
3286 GetTime<std::chrono::seconds>().count() + 1000000);
3287
3288 // Register the proof
3289 BOOST_CHECK(pm.registerProof(proof));
3290 BOOST_CHECK(pm.isBoundToPeer(proof->getId()));
3291 BOOST_CHECK(!pm.isDangling(proof->getId()));
3292
3293 // Elapse the dangling timeout. No nodes are bound, so the proof is now
3294 // dangling.
3295 SetMockTime(GetTime<std::chrono::seconds>() +
3297 TestPeerManager::cleanupDanglingProofs(pm);
3298 BOOST_CHECK(!pm.isBoundToPeer(proof->getId()));
3299 BOOST_CHECK(!pm.exists(proof->getId()));
3300 BOOST_CHECK(pm.isDangling(proof->getId()));
3301
3302 {
3303 LOCK(cs_main);
3304 CCoinsViewCache &coins = active_chainstate.CoinsTip();
3305 // Make proof invalid
3306 coins.SpendCoin(utxo);
3307 }
3308
3309 // Trigger proof validity checks
3310 pm.updatedBlockTip();
3311
3312 // The now invalid proof is removed
3313 BOOST_CHECK(!pm.exists(proof->getId()));
3314 BOOST_CHECK(!pm.isDangling(proof->getId()));
3315
3316 {
3317 LOCK(cs_main);
3318 CCoinsViewCache &coins = active_chainstate.CoinsTip();
3319 // Add the utxo back so we can make the proof valid again
3321 coins.AddCoin(utxo,
3322 Coin(CTxOut(PROOF_DUST_THRESHOLD, script), 100, false),
3323 false);
3324 }
3325
3326 // Our proof is not expired yet, so it registers fine
3327 BOOST_CHECK(pm.registerProof(proof));
3328 BOOST_CHECK(pm.isBoundToPeer(proof->getId()));
3329 BOOST_CHECK(!pm.isDangling(proof->getId()));
3330
3331 // Elapse the dangling timeout. No nodes are bound, so the proof is now
3332 // dangling.
3333 SetMockTime(GetTime<std::chrono::seconds>() +
3335 TestPeerManager::cleanupDanglingProofs(pm);
3336 BOOST_CHECK(!pm.isBoundToPeer(proof->getId()));
3337 BOOST_CHECK(!pm.exists(proof->getId()));
3338 BOOST_CHECK(pm.isDangling(proof->getId()));
3339
3340 // Mine blocks until the MTP of the tip moves to the proof expiration
3341 for (int64_t i = 0; i < 6; i++) {
3342 SetMockTime(proof->getExpirationTime() + i);
3343 CreateAndProcessBlock({}, CScript());
3344 }
3346 WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip())
3347 ->GetMedianTimePast(),
3348 proof->getExpirationTime());
3349
3350 pm.updatedBlockTip();
3351
3352 // The now expired proof is removed
3353 BOOST_CHECK(!pm.exists(proof->getId()));
3354 BOOST_CHECK(!pm.isDangling(proof->getId()));
3355}
3356
3357BOOST_FIXTURE_TEST_CASE(dangling_evicted_on_preferred_peer, NoCoolDownFixture) {
3358 ChainstateManager &chainman = *Assert(m_node.chainman);
3360
3361 SetMockTime(GetTime<std::chrono::seconds>());
3362
3364 auto utxo = createUtxo(chainman.ActiveChainstate(), key);
3365 auto danglingProof = buildProofWithSequence(key, {utxo}, 1);
3366 auto preferredProof = buildProofWithSequence(key, {utxo}, 2);
3367
3368 BOOST_CHECK(pm.registerProof(danglingProof));
3369 BOOST_CHECK(pm.isBoundToPeer(danglingProof->getId()));
3370
3371 // Park the lower-sequence proof in the dangling pool
3372 SetMockTime(GetTime<std::chrono::seconds>() +
3374 TestPeerManager::cleanupDanglingProofs(pm);
3375 BOOST_CHECK(!pm.isBoundToPeer(danglingProof->getId()));
3376 BOOST_CHECK(pm.isDangling(danglingProof->getId()));
3377
3378 // Preferred proof can join the peer set because dangling UTXOs are not in
3379 // the valid pool. Registering it must evict the conflicting dangling proof.
3380 BOOST_CHECK(pm.registerProof(preferredProof));
3381 BOOST_CHECK(pm.isBoundToPeer(preferredProof->getId()));
3382 BOOST_CHECK(!pm.isDangling(danglingProof->getId()));
3383 BOOST_CHECK(!pm.isDangling(preferredProof->getId()));
3384
3385 // The superseded proof can be accepted again later (e.g. after the
3386 // preferred peer leaves), and is not stuck behind a dangling tombstone.
3388 pm.rejectProof(preferredProof->getId(),
3391 BOOST_CHECK(pm.registerProof(danglingProof, state));
3393 BOOST_CHECK(pm.isBoundToPeer(danglingProof->getId()));
3394}
3395
3396BOOST_FIXTURE_TEST_CASE(cleanup_dangling_conflict, NoCoolDownFixture) {
3397 ChainstateManager &chainman = *Assert(m_node.chainman);
3399 Chainstate &active_chainstate = chainman.ActiveChainstate();
3400
3401 SetMockTime(GetTime<std::chrono::seconds>());
3402
3404 auto utxo = createUtxo(active_chainstate, key);
3405 auto lowSeqProof = buildProofWithSequence(key, {utxo}, 1);
3406 auto highSeqProof = buildProofWithSequence(key, {utxo}, 2);
3407
3408 BOOST_CHECK(pm.registerProof(lowSeqProof));
3409 BOOST_CHECK(pm.isBoundToPeer(lowSeqProof->getId()));
3410
3411 // Park the high-sequence proof as dangling while the low-sequence proof is
3412 // still a peer. Registering the high-sequence proof would normally evict it
3413 // from the dangling pool; keep this state via the test helper so cleanup
3414 // can pull it back in the same pass that ages out the low-sequence peer.
3415 BOOST_CHECK(TestPeerManager::addDanglingProof(pm, highSeqProof));
3416 BOOST_CHECK(pm.isDangling(highSeqProof->getId()));
3417
3418 // Remotes: high-sequence is present (pullback), low-sequence is absent
3419 // (newly dangling)
3420 for (NodeId nodeid = 0; nodeid < 10; nodeid++) {
3421 auto localProof =
3422 buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
3423 BOOST_CHECK(pm.registerProof(localProof));
3424 BOOST_CHECK(pm.addNode(nodeid, localProof->getId(),
3426 BOOST_CHECK(pm.saveRemoteProof(highSeqProof->getId(), nodeid, true));
3427 BOOST_CHECK(pm.saveRemoteProof(lowSeqProof->getId(), nodeid, false));
3428 }
3429
3431 TestPeerManager::getRemotePresenceStatus(pm, highSeqProof->getId())
3432 .value());
3434 !TestPeerManager::getRemotePresenceStatus(pm, lowSeqProof->getId())
3435 .value());
3436
3437 SetMockTime(GetTime<std::chrono::seconds>() +
3439
3440 std::unordered_set<ProofRef, SaltedProofHasher> registeredProofs;
3441 TestPeerManager::cleanupDanglingProofs(pm, registeredProofs);
3442
3443 // High-sequence proof is pulled back as a peer and the low-sequence proof
3444 // is rejected without being re-parked as dangling (valid pool already owns
3445 // the UTXOs).
3446 BOOST_CHECK(pm.isBoundToPeer(highSeqProof->getId()));
3447 BOOST_CHECK_EQUAL(registeredProofs.count(highSeqProof), 1);
3448 BOOST_CHECK(!pm.isBoundToPeer(lowSeqProof->getId()));
3449 BOOST_CHECK(!pm.isDangling(lowSeqProof->getId()));
3450 BOOST_CHECK(!pm.exists(lowSeqProof->getId()));
3451}
3452
3453BOOST_AUTO_TEST_SUITE_END()
ArgsManager gArgs
Definition: args.cpp:39
static constexpr PeerId NO_PEER
Definition: node.h:15
uint32_t PeerId
Definition: node.h:14
#define Assert(val)
Identity function.
Definition: check.h:87
void ForceSetArg(const std::string &strArg, const std::string &strValue)
Definition: args.cpp:565
void ClearForcedArg(const std::string &strArg)
Remove a forced arg setting, used only in testing.
Definition: args.cpp:616
Non-refcounted RAII wrapper for FILE*.
Definition: streams.h:430
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:76
BlockHash GetBlockHash() const
Definition: blockindex.h:130
CCoinsView that adds a memory cache for transactions to another CCoinsView.
Definition: coins.h:358
void AddCoin(const COutPoint &outpoint, Coin coin, bool possible_overwrite)
Add a coin.
Definition: coins.cpp:98
bool SpendCoin(const COutPoint &outpoint, Coin *moveto=nullptr)
Spend a coin.
Definition: coins.cpp:174
An encapsulated secp256k1 private key.
Definition: key.h:28
static CKey MakeCompressedKey()
Produce a valid compressed key.
Definition: key.cpp:465
CPubKey GetPubKey() const
Compute the public key from a private key.
Definition: key.cpp:209
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:725
CCoinsViewCache & CoinsTip() EXCLUSIVE_LOCKS_REQUIRED(
Definition: validation.h:851
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:1174
SnapshotCompletionResult MaybeCompleteSnapshotValidation() EXCLUSIVE_LOCKS_REQUIRED(const CBlockIndex *GetSnapshotBaseBlock() const EXCLUSIVE_LOCKS_REQUIRED(Chainstate ActiveChainstate)() const
Once the background validation chainstate has reached the height which is the base of the UTXO snapsh...
Definition: validation.h:1428
RecursiveMutex & GetMutex() const LOCK_RETURNED(
Alias for cs_main.
Definition: validation.h:1309
CBlockIndex * ActiveTip() const EXCLUSIVE_LOCKS_REQUIRED(GetMutex())
Definition: validation.h:1435
int ActiveHeight() const EXCLUSIVE_LOCKS_REQUIRED(GetMutex())
Definition: validation.h:1432
A UTXO entry.
Definition: coins.h:31
Fast randomness source.
Definition: random.h:411
static RCUPtr make(Args &&...args)
Construct a new object that is owned by the pointer.
Definition: rcu.h:112
I randrange(I range) noexcept
Generate a random integer in the range [0..range), with range > 0.
Definition: random.h:266
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:282
uint64_t getFragmentation() const
Definition: peermanager.h:511
uint32_t getConnectedPeersScore() const
Definition: peermanager.h:449
bool updateNextRequestTimeForResponse(NodeId nodeid, const Response &response)
bool isDangling(const ProofId &proofid) const
bool addNode(NodeId nodeid, const ProofId &proofid, size_t max_elements)
Node API.
Definition: peermanager.cpp:33
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:338
bool exists(const ProofId &proofid) const
Return true if the (valid) proof exists, but only for non-dangling proofs.
Definition: peermanager.h:413
size_t getNodeCount() const
Definition: peermanager.h:317
PendingNodeSet pendingNodes
Definition: peermanager.h:224
bool verify() const
Perform consistency check on internal data structures.
bool forNode(NodeId nodeid, Callable &&func) const
Definition: peermanager.h:341
bool hasRemoteProofStatus(const ProofId &proofid) const
bool forPeer(const ProofId &proofid, Callable &&func) const
Definition: peermanager.h:421
void clearRemoteProofs(NodeId nodeid)
uint32_t getTotalPeersScore() const
Definition: peermanager.h:448
bool latchAvaproofsSent(NodeId nodeid)
Flag that a node did send its compact proofs.
bool updateNextRequestTimeForPoll(NodeId nodeid, SteadyMilliseconds timeout, uint64_t round)
uint64_t getSlotCount() const
Definition: peermanager.h:510
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:530
bool isBoundToPeer(const ProofId &proofid) const
size_t getPendingNodeCount() const
Definition: peermanager.h:318
bool saveRemoteProof(const ProofId &proofid, const NodeId nodeid, const bool present)
uint64_t compact()
Trigger maintenance of internal data structures.
std::vector< Slot > slots
Definition: peermanager.h:162
ProofPool danglingProofPool
Definition: peermanager.h:187
void forEachPeer(Callable &&func) const
Definition: peermanager.h:427
void setInvalid(const ProofId &proofid)
int getStakeContenderStatus(const StakeContenderId &contenderId, BlockHash &prevblockhashout) const
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:378
static constexpr size_t MAX_REMOTE_PROOFS
Definition: peermanager.h:303
void addStakeContender(const ProofRef &proof)
PeerId selectPeer() const
Randomly select a peer to poll.
bool isInConflictingPool(const ProofId &proofid) const
bool isRemotelyPresentProof(const ProofId &proofid) const
void cleanupDanglingProofs(std::unordered_set< ProofRef, SaltedProofHasher > &registeredProofs)
void acceptStakeContender(const StakeContenderId &contenderId)
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:161
const CScript & getPayoutScript() const
Definition: proof.h:164
const ProofId & getId() const
Definition: proof.h:167
AddProofStatus addProofIfPreferred(const ProofRef &proof, ConflictingProofSet &conflictingProofs)
Attempt to add a proof to the pool.
Definition: proofpool.cpp:58
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 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
RecursiveMutex cs_main
Mutex to guard access to validation specific variables, such as reading or changing the chainstate.
Definition: cs_main.cpp:7
int64_t NodeId
Definition: eviction.h:16
bool FileCommit(FILE *file)
Ensure file contents are fully committed to disk, using a platform-specific feature analogous to fsyn...
Definition: fs_helpers.cpp:111
static RPCHelpMan generate()
Definition: mining.cpp:291
@ NONE
Definition: logging.h:68
static constexpr Amount PROOF_DUST_THRESHOLD
Minimum amount per utxo.
Definition: proof.h:38
ProofRegistrationResult
Definition: peermanager.h:144
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:22
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:20
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:183
FILE * fopen(const fs::path &p, const char *mode)
Definition: fs.cpp:30
Definition: messages.h:12
NodeContext & m_node
Definition: interfaces.cpp:825
static constexpr NodeId NO_NODE
Special NodeId that represent no node.
Definition: nodeid.h:15
#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)
static constexpr size_t DEFAULT_AVALANCHE_MAX_ELEMENT_POLL
Maximum item that can be polled at once.
Definition: processor.h:55
void Shuffle(I first, I last, R &&rng)
More efficient than using std::shuffle on a FastRandomContext.
Definition: random.h:512
uint256 GetRandHash() noexcept
========== CONVENIENCE FUNCTIONS FOR COMMONLY USED RANDOMNESS ==========
Definition: random.h:494
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
Definition: amount.h:23
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:111
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:100
ProofRef proof
Definition: peermanager.h:89
StakeContenderIds are unique for each block to ensure that the peer polling for their acceptance has ...
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
int64_t GetTime()
DEPRECATED Use either ClockType::now() or Now<TimePointType>() if a cast is needed.
Definition: time.cpp:80
void SetMockTime(int64_t nMockTimeIn)
DEPRECATED Use SetMockTime with chrono type.
Definition: time.cpp:64
std::chrono::time_point< std::chrono::steady_clock, std::chrono::milliseconds > SteadyMilliseconds
Definition: time.h:33
#define strprintf
Format arguments and return the string or write to given std::ostream (see tinyformat::format doc for...
Definition: tinyformat.h:1202