Bitcoin ABC 0.34.2
P2P Digital Currency
peermanager_tests.cpp
Go to the documentation of this file.
1// Copyright (c) 2020 The Bitcoin developers
2// Distributed under the MIT software license, see the accompanying
3// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
10#include <avalanche/test/util.h>
11#include <cashaddrenc.h>
12#include <config.h>
14#include <core_io.h>
15#include <key_io.h>
16#include <script/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) ==
87 ProofPool::AddProofStatus::SUCCEED;
88 }
89
90 static std::optional<RemoteProof> getRemoteProof(const PeerManager &pm,
91 const ProofId &proofid,
92 NodeId nodeid) {
93 auto it = pm.remoteProofs.find(boost::make_tuple(proofid, nodeid));
94 if (it == pm.remoteProofs.end()) {
95 return std::nullopt;
96 }
97 return std::make_optional(*it);
98 }
99
100 static size_t getPeerCount(const PeerManager &pm) {
101 return pm.peers.size();
102 }
103
104 static std::optional<bool>
105 getRemotePresenceStatus(const PeerManager &pm, const ProofId &proofid) {
106 return pm.getRemotePresenceStatus(proofid);
107 }
108
109 static void clearPeers(PeerManager &pm) {
110 std::vector<PeerId> peerIds;
111 for (auto &peer : pm.peers) {
112 peerIds.push_back(peer.peerid);
113 }
114 for (const PeerId &peerid : peerIds) {
115 pm.removePeer(peerid);
116 }
117 BOOST_CHECK_EQUAL(pm.peers.size(), 0);
118 }
119
120 static void setLocalProof(PeerManager &pm, const ProofRef &proof) {
121 pm.localProof = proof;
122 }
123
124 static bool isFlaky(const PeerManager &pm, const ProofId &proofid) {
125 return pm.isFlaky(proofid);
126 }
127
128 static PeerId selectPeerFromSlot(const PeerManager &pm, uint64_t slot) {
129 return selectPeerImpl(pm.slots, slot, pm.slotCount);
130 }
131 };
132
133 static void addCoin(Chainstate &chainstate, const COutPoint &outpoint,
134 const CKey &key,
135 const Amount amount = PROOF_DUST_THRESHOLD,
136 uint32_t height = 100, bool is_coinbase = false) {
138
139 LOCK(cs_main);
140 CCoinsViewCache &coins = chainstate.CoinsTip();
141 coins.AddCoin(outpoint,
142 Coin(CTxOut(amount, script), height, is_coinbase), false);
143 }
144
145 static COutPoint createUtxo(Chainstate &chainstate, const CKey &key,
146 const Amount amount = PROOF_DUST_THRESHOLD,
147 uint32_t height = 100,
148 bool is_coinbase = false) {
149 COutPoint outpoint(TxId(GetRandHash()), 0);
150 addCoin(chainstate, outpoint, key, amount, height, is_coinbase);
151 return outpoint;
152 }
153
154 static ProofRef
155 buildProof(const CKey &key,
156 const std::vector<std::tuple<COutPoint, Amount>> &outpoints,
157 const CKey &master = CKey::MakeCompressedKey(),
158 int64_t sequence = 1, uint32_t height = 100,
159 bool is_coinbase = false, int64_t expirationTime = 0,
160 const CScript &payoutScript = UNSPENDABLE_ECREG_PAYOUT_SCRIPT) {
161 ProofBuilder pb(sequence, expirationTime, master, payoutScript);
162 for (const auto &[outpoint, amount] : outpoints) {
163 BOOST_CHECK(pb.addUTXO(outpoint, amount, height, is_coinbase, key));
164 }
165 return pb.build();
166 }
167
168 /*
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));
877 !pm.registerProof(TestProofBuilder::buildDuplicatedStakes(pb)));
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));
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 output of a transaction.
Definition: transaction.h:128
Chainstate stores and provides an API to update our local knowledge of the current best chain.
Definition: validation.h: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...
Definition: validation.h:1176
SnapshotCompletionResult MaybeCompleteSnapshotValidation() EXCLUSIVE_LOCKS_REQUIRED(const CBlockIndex *GetSnapshotBaseBlock() const EXCLUSIVE_LOCKS_REQUIRED(Chainstate ActiveChainstate)() const
Once the background validation chainstate has reached the height which is the base of the UTXO snapsh...
Definition: validation.h:1446
RecursiveMutex & GetMutex() const LOCK_RETURNED(
Alias for cs_main.
Definition: validation.h:1328
CBlockIndex * ActiveTip() const EXCLUSIVE_LOCKS_REQUIRED(GetMutex())
Definition: validation.h:1453
int ActiveHeight() const EXCLUSIVE_LOCKS_REQUIRED(GetMutex())
Definition: validation.h:1450
A UTXO entry.
Definition: coins.h:31
Fast randomness source.
Definition: random.h:411
static RCUPtr make(Args &&...args)
Construct a new object that is owned by the pointer.
Definition: rcu.h:112
I randrange(I range) noexcept
Generate a random integer in the range [0..range), with range > 0.
Definition: random.h:266
bool IsValid() const
Definition: validation.h:119
Result GetResult() const
Definition: validation.h:122
bool selectStakingRewardWinner(const CBlockIndex *pprev, std::vector< std::pair< ProofId, CScript > > &winners)
Deterministically select a list of payout scripts based on the proof set and the previous block hash.
bool removeNode(NodeId nodeid)
bool setFinalized(PeerId peerid)
Latch on that this peer has a finalized proof.
bool dumpPeersToFile(const fs::path &dumpPath) const
RemoteProofSet remoteProofs
Remember which node sent which proof so we have an image of the proof set of our peers.
Definition: peermanager.h:282
uint64_t getFragmentation() const
Definition: peermanager.h:529
uint32_t getConnectedPeersScore() const
Definition: peermanager.h:454
bool updateNextRequestTimeForResponse(NodeId nodeid, const Response &response)
bool isDangling(const ProofId &proofid) const
bool addNode(NodeId nodeid, const ProofId &proofid, size_t max_elements)
Node API.
Definition: peermanager.cpp:33
std::optional< bool > getRemotePresenceStatus(const ProofId &proofid) const
Get the presence remote status of a proof.
bool shouldRequestMoreNodes()
Returns true if we encountered a lack of node since the last call.
Definition: peermanager.h:343
bool exists(const ProofId &proofid) const
Return true if the (valid) proof exists, but only for non-dangling proofs.
Definition: peermanager.h:418
size_t getNodeCount() const
Definition: peermanager.h:322
PendingNodeSet pendingNodes
Definition: peermanager.h:224
bool verify() const
Perform consistency check on internal data structures.
bool forNode(NodeId nodeid, Callable &&func) const
Definition: peermanager.h:346
bool hasRemoteProofStatus(const ProofId &proofid) const
bool forPeer(const ProofId &proofid, Callable &&func) const
Definition: peermanager.h:426
void clearRemoteProofs(NodeId nodeid)
uint32_t getTotalPeersScore() const
Definition: peermanager.h:453
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
Definition: peermanager.h:528
bool loadPeersFromFile(const fs::path &dumpPath, std::unordered_set< ProofRef, SaltedProofHasher > &registeredProofs)
std::unordered_set< ProofRef, SaltedProofHasher > updatedBlockTip()
Update the peer set when a new block is connected.
const ProofRadixTree & getShareableProofsSnapshot() const
Definition: peermanager.h:548
bool isBoundToPeer(const ProofId &proofid) const
size_t getPendingNodeCount() const
Definition: peermanager.h:323
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
Definition: peermanager.h:162
ProofPool danglingProofPool
Definition: peermanager.h:187
void forEachPeer(Callable &&func) const
Definition: peermanager.h:432
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.
Definition: peermanager.h:383
static constexpr size_t MAX_REMOTE_PROOFS
Definition: peermanager.h:308
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
const CScript & getPayoutScript() const
Definition: proof.h:164
const ProofId & getId() const
Definition: proof.h:167
AddProofStatus addProofIfPreferred(const ProofRef &proof, ConflictingProofSet &conflictingProofs)
Attempt to add a proof to the pool.
Definition: proofpool.cpp:58
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)
Definition: core_write.cpp:25
RecursiveMutex cs_main
Mutex to guard access to validation specific variables, such as reading or changing the chainstate.
Definition: cs_main.cpp:7
int64_t NodeId
Definition: eviction.h:16
bool FileCommit(FILE *file)
Ensure file contents are fully committed to disk, using a platform-specific feature analogous to fsyn...
Definition: fs_helpers.cpp:111
static RPCHelpMan generate()
Definition: mining.cpp:291
@ NONE
Definition: logging.h:68
static constexpr Amount PROOF_DUST_THRESHOLD
Minimum amount per utxo.
Definition: proof.h:38
ProofRegistrationResult
Definition: peermanager.h:144
static constexpr uint32_t AVALANCHE_MAX_IMMATURE_PROOFS
Maximum number of immature proofs the peer manager will accept from the network.
Definition: peermanager.h:44
const CScript UNSPENDABLE_ECREG_PAYOUT_SCRIPT
Definition: util.h:22
ProofRef buildRandomProof(Chainstate &active_chainstate, uint32_t score, int height, const CKey &masterKey)
Definition: util.cpp:20
constexpr uint32_t MIN_VALID_PROOF_SCORE
Definition: util.h:20
PeerId selectPeerImpl(const std::vector< Slot > &slots, const uint64_t slot, const uint64_t max)
Internal methods that are exposed for testing purposes.
RCUPtr< const Proof > ProofRef
Definition: proof.h:183
std::optional< uintN_t > FromHex(std::string_view str)
Writes the hex string (treated as little-endian) into a new uintN_t object and only returns a value i...
Definition: uint256.h:150
FILE * fopen(const fs::path &p, const char *mode)
Definition: fs.cpp:30
Definition: messages.h:12
NodeContext & m_node
Definition: interfaces.cpp:827
static constexpr NodeId NO_NODE
Special NodeId that represent no node.
Definition: nodeid.h:15
#define BOOST_CHECK_EQUAL(v1, v2)
Definition: object.cpp:18
#define BOOST_CHECK(expr)
Definition: object.cpp:17
static void addNodeWithScore(Chainstate &active_chainstate, avalanche::PeerManager &pm, NodeId node, uint32_t score)
BOOST_AUTO_TEST_CASE(select_peer_linear)
BOOST_FIXTURE_TEST_CASE(conflicting_proof_rescan, NoCoolDownFixture)
static constexpr size_t DEFAULT_AVALANCHE_MAX_ELEMENT_POLL
Maximum item that can be polled at once.
Definition: processor.h:55
void Shuffle(I first, I last, R &&rng)
More efficient than using std::shuffle on a FastRandomContext.
Definition: random.h:512
uint256 GetRandHash() noexcept
========== CONVENIENCE FUNCTIONS FOR COMMONLY USED RANDOMNESS ==========
Definition: random.h:494
@ OP_TRUE
Definition: script.h:63
CScript GetScriptForRawPubKey(const CPubKey &pubKey)
Generate a P2PK script for the given pubkey.
Definition: solver.cpp:143
Definition: amount.h:23
A BlockHash is a unqiue identifier for a block.
Definition: blockhash.h:13
bool insert(const RCUPtr< T > &value)
Insert a value into the tree.
Definition: radix.h:111
A TxId is the identifier of a transaction.
Definition: txid.h:14
Compare conflicting proofs.
std::chrono::seconds registration_time
Definition: peermanager.h:93
static constexpr auto DANGLING_TIMEOUT
Consider dropping the peer if no node is attached after this timeout expired.
Definition: peermanager.h:100
ProofRef proof
Definition: peermanager.h:89
StakeContenderIds are unique for each block to ensure that the peer polling for their acceptance has ...
Bilingual messages:
Definition: translation.h:17
#define LOCK(cs)
Definition: sync.h: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...
Definition: tinyformat.h:1203