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