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