Bitcoin ABC 0.32.12
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));
349 BOOST_CHECK(pm.addNode(node, proof->getId()));
350};
351
352BOOST_AUTO_TEST_CASE(peer_probabilities) {
353 ChainstateManager &chainman = *Assert(m_node.chainman);
354 // No peers.
357
358 const NodeId node0 = 42, node1 = 69, node2 = 37;
359
360 Chainstate &active_chainstate = chainman.ActiveChainstate();
361 // One peer, we always return it.
362 addNodeWithScore(active_chainstate, pm, node0, MIN_VALID_PROOF_SCORE);
363 BOOST_CHECK_EQUAL(pm.selectNode(), node0);
364
365 // Two peers, verify ratio.
366 addNodeWithScore(active_chainstate, pm, node1, 2 * MIN_VALID_PROOF_SCORE);
367
368 std::unordered_map<PeerId, int> results = {};
369 for (int i = 0; i < 10000; i++) {
370 size_t n = pm.selectNode();
371 BOOST_CHECK(n == node0 || n == node1);
372 results[n]++;
373 }
374
375 BOOST_CHECK(abs(2 * results[0] - results[1]) < 500);
376
377 // Three peers, verify ratio.
378 addNodeWithScore(active_chainstate, pm, node2, MIN_VALID_PROOF_SCORE);
379
380 results.clear();
381 for (int i = 0; i < 10000; i++) {
382 size_t n = pm.selectNode();
383 BOOST_CHECK(n == node0 || n == node1 || n == node2);
384 results[n]++;
385 }
386
387 BOOST_CHECK(abs(results[0] - results[1] + results[2]) < 500);
388}
389
391 ChainstateManager &chainman = *Assert(m_node.chainman);
392 // No peers.
395
396 Chainstate &active_chainstate = chainman.ActiveChainstate();
397 // Add 4 peers.
398 std::array<PeerId, 8> peerids;
399 for (int i = 0; i < 4; i++) {
400 auto p = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
401 peerids[i] = TestPeerManager::registerAndGetPeerId(pm, p);
402 BOOST_CHECK(pm.addNode(m_rng.rand32(), p->getId()));
403 }
404
405 BOOST_CHECK_EQUAL(pm.getSlotCount(), 40000);
407
408 for (int i = 0; i < 100; i++) {
409 PeerId p = pm.selectPeer();
410 BOOST_CHECK(p == peerids[0] || p == peerids[1] || p == peerids[2] ||
411 p == peerids[3]);
412 }
413
414 // Remove one peer, it nevers show up now.
415 BOOST_CHECK(pm.removePeer(peerids[2]));
416 BOOST_CHECK_EQUAL(pm.getSlotCount(), 40000);
418
419 // Make sure we compact to never get NO_PEER.
420 BOOST_CHECK_EQUAL(pm.compact(), 10000);
421 BOOST_CHECK(pm.verify());
422 BOOST_CHECK_EQUAL(pm.getSlotCount(), 30000);
424
425 for (int i = 0; i < 100; i++) {
426 PeerId p = pm.selectPeer();
427 BOOST_CHECK(p == peerids[0] || p == peerids[1] || p == peerids[3]);
428 }
429
430 // Add 4 more peers.
431 for (int i = 0; i < 4; i++) {
432 auto p = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
433 peerids[i + 4] = TestPeerManager::registerAndGetPeerId(pm, p);
434 BOOST_CHECK(pm.addNode(m_rng.rand32(), p->getId()));
435 }
436
437 BOOST_CHECK_EQUAL(pm.getSlotCount(), 70000);
439
440 BOOST_CHECK(pm.removePeer(peerids[0]));
441 BOOST_CHECK_EQUAL(pm.getSlotCount(), 70000);
443
444 // Removing the last entry do not increase fragmentation.
445 BOOST_CHECK(pm.removePeer(peerids[7]));
446 BOOST_CHECK_EQUAL(pm.getSlotCount(), 60000);
448
449 // Make sure we compact to never get NO_PEER.
450 BOOST_CHECK_EQUAL(pm.compact(), 10000);
451 BOOST_CHECK(pm.verify());
452 BOOST_CHECK_EQUAL(pm.getSlotCount(), 50000);
454
455 for (int i = 0; i < 100; i++) {
456 PeerId p = pm.selectPeer();
457 BOOST_CHECK(p == peerids[1] || p == peerids[3] || p == peerids[4] ||
458 p == peerids[5] || p == peerids[6]);
459 }
460
461 // Removing non existent peers fails.
462 BOOST_CHECK(!pm.removePeer(peerids[0]));
463 BOOST_CHECK(!pm.removePeer(peerids[2]));
464 BOOST_CHECK(!pm.removePeer(peerids[7]));
466}
467
468BOOST_AUTO_TEST_CASE(compact_slots) {
469 ChainstateManager &chainman = *Assert(m_node.chainman);
471
472 // Add 4 peers.
473 std::array<PeerId, 4> peerids;
474 for (int i = 0; i < 4; i++) {
475 auto p = buildRandomProof(chainman.ActiveChainstate(),
477 peerids[i] = TestPeerManager::registerAndGetPeerId(pm, p);
478 BOOST_CHECK(pm.addNode(m_rng.rand32(), p->getId()));
479 }
480
481 // Remove all peers.
482 for (auto p : peerids) {
483 pm.removePeer(p);
484 }
485
486 BOOST_CHECK_EQUAL(pm.getSlotCount(), 30000);
488
489 for (int i = 0; i < 100; i++) {
491 }
492
493 BOOST_CHECK_EQUAL(pm.compact(), 30000);
494 BOOST_CHECK(pm.verify());
497}
498
500 ChainstateManager &chainman = *Assert(m_node.chainman);
502
503 Chainstate &active_chainstate = chainman.ActiveChainstate();
504
505 // Create one peer.
506 auto proof =
507 buildRandomProof(active_chainstate, 10000000 * MIN_VALID_PROOF_SCORE);
508 BOOST_CHECK(pm.registerProof(proof));
510
511 // Add 4 nodes.
512 const ProofId &proofid = proof->getId();
513 for (int i = 0; i < 4; i++) {
514 BOOST_CHECK(pm.addNode(i, proofid));
515 }
516
517 uint64_t round{0};
518 for (int i = 0; i < 100; i++) {
519 NodeId n = pm.selectNode();
520 BOOST_CHECK(n >= 0 && n < 4);
522 n, Now<SteadyMilliseconds>(), round++));
523 }
524
525 // Remove a node, check that it doesn't show up.
526 BOOST_CHECK(pm.removeNode(2));
527
528 for (int i = 0; i < 100; i++) {
529 NodeId n = pm.selectNode();
530 BOOST_CHECK(n == 0 || n == 1 || n == 3);
532 n, Now<SteadyMilliseconds>(), round++));
533 }
534
535 // Push a node's timeout in the future, so that it doesn't show up.
537 1, Now<SteadyMilliseconds>() + std::chrono::hours(24), round++));
538
539 for (int i = 0; i < 100; i++) {
540 NodeId n = pm.selectNode();
541 BOOST_CHECK(n == 0 || n == 3);
543 n, Now<SteadyMilliseconds>(), round++));
544 }
545
546 // Move a node from a peer to another. This peer has a very low score such
547 // as chances of being picked are 1 in 10 million.
548 addNodeWithScore(active_chainstate, pm, 3, MIN_VALID_PROOF_SCORE);
549
550 int node3selected = 0;
551 for (int i = 0; i < 100; i++) {
552 NodeId n = pm.selectNode();
553 if (n == 3) {
554 // Selecting this node should be exceedingly unlikely.
555 BOOST_CHECK(node3selected++ < 1);
556 } else {
557 BOOST_CHECK_EQUAL(n, 0);
558 }
560 n, Now<SteadyMilliseconds>(), round++));
561 }
562
564 for (int i = 0; i < 100; i++) {
565 NodeId n = pm.selectNode();
566
567 round =
568 pm.forNode(n, [&](const Node &node) { return node.last_round; });
569 // [0..range] (upper bound is inclusive)
570 round = rng.randrange(round + 1);
571
572 // Response to old rounds don't update the next request time.
574 !pm.updateNextRequestTimeForResponse(n, Response{round--, 0, {}}));
575 }
576}
577
578BOOST_AUTO_TEST_CASE(node_binding) {
579 ChainstateManager &chainman = *Assert(m_node.chainman);
581
582 Chainstate &active_chainstate = chainman.ActiveChainstate();
583
584 auto proof = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
585 const ProofId &proofid = proof->getId();
586
589
590 // Add a bunch of nodes with no associated peer
591 for (int i = 0; i < 10; i++) {
592 BOOST_CHECK(!pm.addNode(i, proofid));
593 BOOST_CHECK(TestPeerManager::isNodePending(pm, i));
596 }
597
598 // Now create the peer and check all the nodes are bound
599 const PeerId peerid = TestPeerManager::registerAndGetPeerId(pm, proof);
600 BOOST_CHECK_NE(peerid, NO_PEER);
601 for (int i = 0; i < 10; i++) {
602 BOOST_CHECK(!TestPeerManager::isNodePending(pm, i));
603 BOOST_CHECK(TestPeerManager::nodeBelongToPeer(pm, i, peerid));
606 }
607 BOOST_CHECK(pm.verify());
608
609 // Disconnect some nodes
610 for (int i = 0; i < 5; i++) {
611 BOOST_CHECK(pm.removeNode(i));
612 BOOST_CHECK(!TestPeerManager::isNodePending(pm, i));
613 BOOST_CHECK(!TestPeerManager::nodeBelongToPeer(pm, i, peerid));
614 BOOST_CHECK_EQUAL(pm.getNodeCount(), 10 - i - 1);
616 }
617
618 // Add nodes when the peer already exists
619 for (int i = 0; i < 5; i++) {
620 BOOST_CHECK(pm.addNode(i, proofid));
621 BOOST_CHECK(!TestPeerManager::isNodePending(pm, i));
622 BOOST_CHECK(TestPeerManager::nodeBelongToPeer(pm, i, peerid));
623 BOOST_CHECK_EQUAL(pm.getNodeCount(), 5 + i + 1);
625 }
626
627 auto alt_proof = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
628 const ProofId &alt_proofid = alt_proof->getId();
629
630 // Update some nodes from a known proof to an unknown proof
631 for (int i = 0; i < 5; i++) {
632 BOOST_CHECK(!pm.addNode(i, alt_proofid));
633 BOOST_CHECK(TestPeerManager::isNodePending(pm, i));
634 BOOST_CHECK(!TestPeerManager::nodeBelongToPeer(pm, i, peerid));
635 BOOST_CHECK_EQUAL(pm.getNodeCount(), 10 - i - 1);
637 }
638
639 auto alt2_proof =
640 buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
641 const ProofId &alt2_proofid = alt2_proof->getId();
642
643 // Update some nodes from an unknown proof to another unknown proof
644 for (int i = 0; i < 5; i++) {
645 BOOST_CHECK(!pm.addNode(i, alt2_proofid));
646 BOOST_CHECK(TestPeerManager::isNodePending(pm, i));
649 }
650
651 // Update some nodes from an unknown proof to a known proof
652 for (int i = 0; i < 5; i++) {
653 BOOST_CHECK(pm.addNode(i, proofid));
654 BOOST_CHECK(!TestPeerManager::isNodePending(pm, i));
655 BOOST_CHECK(TestPeerManager::nodeBelongToPeer(pm, i, peerid));
656 BOOST_CHECK_EQUAL(pm.getNodeCount(), 5 + i + 1);
657 BOOST_CHECK_EQUAL(pm.getPendingNodeCount(), 5 - i - 1);
658 }
659
660 // Remove the peer, the nodes should be pending again
661 BOOST_CHECK(pm.removePeer(peerid));
662 BOOST_CHECK(!pm.exists(proof->getId()));
663 for (int i = 0; i < 10; i++) {
664 BOOST_CHECK(TestPeerManager::isNodePending(pm, i));
665 BOOST_CHECK(!TestPeerManager::nodeBelongToPeer(pm, i, peerid));
668 }
669 BOOST_CHECK(pm.verify());
670
671 // Remove the remaining pending nodes, check the count drops accordingly
672 for (int i = 0; i < 10; i++) {
673 BOOST_CHECK(pm.removeNode(i));
674 BOOST_CHECK(!TestPeerManager::isNodePending(pm, i));
675 BOOST_CHECK(!TestPeerManager::nodeBelongToPeer(pm, i, peerid));
677 BOOST_CHECK_EQUAL(pm.getPendingNodeCount(), 10 - i - 1);
678 }
679}
680
681BOOST_AUTO_TEST_CASE(node_binding_reorg) {
682 gArgs.ForceSetArg("-avaproofstakeutxoconfirmations", "2");
683 ChainstateManager &chainman = *Assert(m_node.chainman);
684
686
687 auto proof = buildRandomProof(chainman.ActiveChainstate(),
689 const ProofId &proofid = proof->getId();
690
691 PeerId peerid = TestPeerManager::registerAndGetPeerId(pm, proof);
692 BOOST_CHECK_NE(peerid, NO_PEER);
693 BOOST_CHECK(pm.verify());
694
695 // Add nodes to our peer
696 for (int i = 0; i < 10; i++) {
697 BOOST_CHECK(pm.addNode(i, proofid));
698 BOOST_CHECK(!TestPeerManager::isNodePending(pm, i));
699 BOOST_CHECK(TestPeerManager::nodeBelongToPeer(pm, i, peerid));
700 }
701
702 // Make the proof immature by reorging to a shorter chain
703 {
705 chainman.ActiveChainstate().InvalidateBlock(
706 state, WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip()));
708 WITH_LOCK(chainman.GetMutex(), return chainman.ActiveHeight()), 99);
709 }
710
711 pm.updatedBlockTip();
712 BOOST_CHECK(pm.isImmature(proofid));
713 BOOST_CHECK(!pm.isBoundToPeer(proofid));
714 for (int i = 0; i < 10; i++) {
715 BOOST_CHECK(TestPeerManager::isNodePending(pm, i));
716 BOOST_CHECK(!TestPeerManager::nodeBelongToPeer(pm, i, peerid));
717 }
718 BOOST_CHECK(pm.verify());
719
720 // Make the proof great again
721 {
722 // Advance the clock so the newly mined block won't collide with the
723 // other deterministically-generated blocks
724 SetMockTime(GetTime() + 20);
725 mineBlocks(1);
727 BOOST_CHECK(chainman.ActiveChainstate().ActivateBestChain(state));
728 LOCK(chainman.GetMutex());
729 BOOST_CHECK_EQUAL(chainman.ActiveHeight(), 100);
730 }
731
732 pm.updatedBlockTip();
733 BOOST_CHECK(!pm.isImmature(proofid));
734 BOOST_CHECK(pm.isBoundToPeer(proofid));
735 // The peerid has certainly been updated
736 peerid = TestPeerManager::registerAndGetPeerId(pm, proof);
737 BOOST_CHECK_NE(peerid, NO_PEER);
738 for (int i = 0; i < 10; i++) {
739 BOOST_CHECK(!TestPeerManager::isNodePending(pm, i));
740 BOOST_CHECK(TestPeerManager::nodeBelongToPeer(pm, i, peerid));
741 }
742 BOOST_CHECK(pm.verify());
743}
744
745BOOST_AUTO_TEST_CASE(proof_conflict) {
746 auto key = CKey::MakeCompressedKey();
747
748 TxId txid1(GetRandHash());
749 TxId txid2(GetRandHash());
750 BOOST_CHECK(txid1 != txid2);
751
753 const int height = 100;
754
755 ChainstateManager &chainman = *Assert(m_node.chainman);
756 for (uint32_t i = 0; i < 10; i++) {
757 addCoin(chainman.ActiveChainstate(), {txid1, i}, key);
758 addCoin(chainman.ActiveChainstate(), {txid2, i}, key);
759 }
760
762 CKey masterKey = CKey::MakeCompressedKey();
763 const auto getPeerId = [&](const std::vector<COutPoint> &outpoints) {
764 return TestPeerManager::registerAndGetPeerId(
765 pm, buildProofWithOutpoints(key, outpoints, v, masterKey, 0, height,
766 false, 0));
767 };
768
769 // Add one peer.
770 const PeerId peer1 = getPeerId({COutPoint(txid1, 0)});
771 BOOST_CHECK(peer1 != NO_PEER);
772
773 // Same proof, same peer.
774 BOOST_CHECK_EQUAL(getPeerId({COutPoint(txid1, 0)}), peer1);
775
776 // Different txid, different proof.
777 const PeerId peer2 = getPeerId({COutPoint(txid2, 0)});
778 BOOST_CHECK(peer2 != NO_PEER && peer2 != peer1);
779
780 // Different index, different proof.
781 const PeerId peer3 = getPeerId({COutPoint(txid1, 1)});
782 BOOST_CHECK(peer3 != NO_PEER && peer3 != peer1);
783
784 // Empty proof, no peer.
785 BOOST_CHECK_EQUAL(getPeerId({}), NO_PEER);
786
787 // Multiple inputs.
788 const PeerId peer4 = getPeerId({COutPoint(txid1, 2), COutPoint(txid2, 2)});
789 BOOST_CHECK(peer4 != NO_PEER && peer4 != peer1);
790
791 // Duplicated input.
792 {
795 COutPoint o(txid1, 3);
796 BOOST_CHECK(pb.addUTXO(o, v, height, false, key));
798 !pm.registerProof(TestProofBuilder::buildDuplicatedStakes(pb)));
799 }
800
801 // Multiple inputs, collision on first input.
802 BOOST_CHECK_EQUAL(getPeerId({COutPoint(txid1, 0), COutPoint(txid2, 4)}),
803 NO_PEER);
804
805 // Mutliple inputs, collision on second input.
806 BOOST_CHECK_EQUAL(getPeerId({COutPoint(txid1, 4), COutPoint(txid2, 0)}),
807 NO_PEER);
808
809 // Mutliple inputs, collision on both inputs.
810 BOOST_CHECK_EQUAL(getPeerId({COutPoint(txid1, 0), COutPoint(txid2, 2)}),
811 NO_PEER);
812}
813
814BOOST_AUTO_TEST_CASE(immature_proofs) {
815 ChainstateManager &chainman = *Assert(m_node.chainman);
816 gArgs.ForceSetArg("-avaproofstakeutxoconfirmations", "2");
818
819 auto key = CKey::MakeCompressedKey();
820 int immatureHeight = 100;
821
822 auto registerImmature = [&](const ProofRef &proof) {
824 BOOST_CHECK(!pm.registerProof(proof, state));
825 BOOST_CHECK(state.GetResult() == ProofRegistrationResult::IMMATURE);
826 };
827
828 auto checkImmature = [&](const ProofRef &proof, bool expectedImmature) {
829 const ProofId &proofid = proof->getId();
830 BOOST_CHECK(pm.exists(proofid));
831
832 BOOST_CHECK_EQUAL(pm.isImmature(proofid), expectedImmature);
833 BOOST_CHECK_EQUAL(pm.isBoundToPeer(proofid), !expectedImmature);
834
835 bool ret = false;
836 pm.forEachPeer([&](const Peer &peer) {
837 if (proof->getId() == peer.proof->getId()) {
838 ret = true;
839 }
840 });
841 BOOST_CHECK_EQUAL(ret, !expectedImmature);
842 };
843
844 // Track immature proofs so we can test them later
845 std::vector<ProofRef> immatureProofs;
846
847 // Fill up the immature pool to test the size limit
848 for (int64_t i = 1; i <= AVALANCHE_MAX_IMMATURE_PROOFS; i++) {
849 COutPoint outpoint = COutPoint(TxId(GetRandHash()), 0);
850 auto proof = buildProofWithOutpoints(
851 key, {outpoint}, i * PROOF_DUST_THRESHOLD, key, 0, immatureHeight);
852 addCoin(chainman.ActiveChainstate(), outpoint, key,
853 i * PROOF_DUST_THRESHOLD, immatureHeight);
854 registerImmature(proof);
855 checkImmature(proof, true);
856 immatureProofs.push_back(proof);
857 }
858
859 // More immature proofs evict lower scoring proofs
860 for (auto i = 0; i < 100; i++) {
861 COutPoint outpoint = COutPoint(TxId(GetRandHash()), 0);
862 auto proof =
863 buildProofWithOutpoints(key, {outpoint}, 200 * PROOF_DUST_THRESHOLD,
864 key, 0, immatureHeight);
865 addCoin(chainman.ActiveChainstate(), outpoint, key,
866 200 * PROOF_DUST_THRESHOLD, immatureHeight);
867 registerImmature(proof);
868 checkImmature(proof, true);
869 immatureProofs.push_back(proof);
870 BOOST_CHECK(!pm.exists(immatureProofs.front()->getId()));
871 immatureProofs.erase(immatureProofs.begin());
872 }
873
874 // Replacement when the pool is full still works
875 {
876 const COutPoint &outpoint =
877 immatureProofs.front()->getStakes()[0].getStake().getUTXO();
878 auto proof =
879 buildProofWithOutpoints(key, {outpoint}, 101 * PROOF_DUST_THRESHOLD,
880 key, 1, immatureHeight);
881 registerImmature(proof);
882 checkImmature(proof, true);
883 immatureProofs.push_back(proof);
884 BOOST_CHECK(!pm.exists(immatureProofs.front()->getId()));
885 immatureProofs.erase(immatureProofs.begin());
886 }
887
888 // Mine a block to increase the chain height, turning all immature proofs to
889 // mature
890 mineBlocks(1);
891 pm.updatedBlockTip();
892 for (const auto &proof : immatureProofs) {
893 checkImmature(proof, false);
894 }
895}
896
897BOOST_AUTO_TEST_CASE(dangling_node) {
898 ChainstateManager &chainman = *Assert(m_node.chainman);
900
901 Chainstate &active_chainstate = chainman.ActiveChainstate();
902
903 auto proof = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
904 PeerId peerid = TestPeerManager::registerAndGetPeerId(pm, proof);
905 BOOST_CHECK_NE(peerid, NO_PEER);
906
907 const SteadyMilliseconds theFuture(Now<SteadyMilliseconds>() +
908 std::chrono::hours(24));
909
910 // Add nodes to this peer and update their request time far in the future
911 for (int i = 0; i < 10; i++) {
912 BOOST_CHECK(pm.addNode(i, proof->getId()));
913 BOOST_CHECK(pm.updateNextRequestTimeForPoll(i, theFuture, i));
914 }
915
916 // Remove the peer
917 BOOST_CHECK(pm.removePeer(peerid));
918
919 // Check the nodes are still there
920 for (int i = 0; i < 10; i++) {
921 BOOST_CHECK(pm.forNode(i, [](const Node &n) { return true; }));
922 }
923
924 // Build a new one
925 proof = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
926 peerid = TestPeerManager::registerAndGetPeerId(pm, proof);
927 BOOST_CHECK_NE(peerid, NO_PEER);
928
929 // Update the nodes with the new proof
930 for (int i = 0; i < 10; i++) {
931 BOOST_CHECK(pm.addNode(i, proof->getId()));
933 i, [&](const Node &n) { return n.nextRequestTime == theFuture; }));
934 }
935
936 // Remove the peer
937 BOOST_CHECK(pm.removePeer(peerid));
938
939 // Disconnect the nodes
940 for (int i = 0; i < 10; i++) {
941 BOOST_CHECK(pm.removeNode(i));
942 }
943}
944
945BOOST_AUTO_TEST_CASE(proof_accessors) {
946 ChainstateManager &chainman = *Assert(m_node.chainman);
948
949 constexpr int numProofs = 10;
950
951 std::vector<ProofRef> proofs;
952 proofs.reserve(numProofs);
953 for (int i = 0; i < numProofs; i++) {
954 proofs.push_back(buildRandomProof(chainman.ActiveChainstate(),
956 }
957
958 for (int i = 0; i < numProofs; i++) {
959 BOOST_CHECK(pm.registerProof(proofs[i]));
960
961 {
963 // Fail to add an existing proof
964 BOOST_CHECK(!pm.registerProof(proofs[i], state));
965 BOOST_CHECK(state.GetResult() ==
966 ProofRegistrationResult::ALREADY_REGISTERED);
967 }
968
969 for (int added = 0; added <= i; added++) {
970 auto proof = pm.getProof(proofs[added]->getId());
971 BOOST_CHECK(proof != nullptr);
972
973 const ProofId &proofid = proof->getId();
974 BOOST_CHECK_EQUAL(proofid, proofs[added]->getId());
975 }
976 }
977
978 // No stake, copied from proof_tests.cpp
979 const std::string badProofHex(
980 "96527eae083f1f24625f049d9e54bb9a21023beefdde700a6bc02036335b4df141c8b"
981 "c67bb05a971f5ac2745fd683797dde3002321023beefdde700a6bc02036335b4df141"
982 "c8bc67bb05a971f5ac2745fd683797dde3ac135da984db510334abe41134e3d4ef09a"
983 "d006b1152be8bc413182bf6f947eac1f8580fe265a382195aa2d73935cabf86d90a8f"
984 "666d0a62385ae24732eca51575");
985 bilingual_str error;
986 auto badProof = RCUPtr<Proof>::make();
987 BOOST_CHECK(Proof::FromHex(*badProof, badProofHex, error));
988
990 BOOST_CHECK(!pm.registerProof(badProof, state));
991 BOOST_CHECK(state.GetResult() == ProofRegistrationResult::INVALID);
992}
993
994BOOST_FIXTURE_TEST_CASE(conflicting_proof_rescan, NoCoolDownFixture) {
995 ChainstateManager &chainman = *Assert(m_node.chainman);
997
998 const CKey key = CKey::MakeCompressedKey();
999
1000 Chainstate &active_chainstate = chainman.ActiveChainstate();
1001
1002 const COutPoint conflictingOutpoint = createUtxo(active_chainstate, key);
1003 const COutPoint outpointToSend = createUtxo(active_chainstate, key);
1004
1005 ProofRef proofToInvalidate =
1006 buildProofWithSequence(key, {conflictingOutpoint, outpointToSend}, 20);
1007 BOOST_CHECK(pm.registerProof(proofToInvalidate));
1008
1009 ProofRef conflictingProof =
1010 buildProofWithSequence(key, {conflictingOutpoint}, 10);
1012 BOOST_CHECK(!pm.registerProof(conflictingProof, state));
1013 BOOST_CHECK(state.GetResult() == ProofRegistrationResult::CONFLICTING);
1014 BOOST_CHECK(pm.isInConflictingPool(conflictingProof->getId()));
1015
1016 {
1017 LOCK(cs_main);
1018 CCoinsViewCache &coins = active_chainstate.CoinsTip();
1019 // Make proofToInvalidate invalid
1020 coins.SpendCoin(outpointToSend);
1021 }
1022
1023 pm.updatedBlockTip();
1024
1025 BOOST_CHECK(!pm.exists(proofToInvalidate->getId()));
1026
1027 BOOST_CHECK(!pm.isInConflictingPool(conflictingProof->getId()));
1028 BOOST_CHECK(pm.isBoundToPeer(conflictingProof->getId()));
1029}
1030
1031BOOST_FIXTURE_TEST_CASE(conflicting_proof_selection, NoCoolDownFixture) {
1032 const CKey key = CKey::MakeCompressedKey();
1033
1034 const Amount amount(PROOF_DUST_THRESHOLD);
1035 const uint32_t height = 100;
1036 const bool is_coinbase = false;
1037
1038 ChainstateManager &chainman = *Assert(m_node.chainman);
1039 Chainstate &active_chainstate = chainman.ActiveChainstate();
1040
1041 // This will be the conflicting UTXO for all the following proofs
1042 auto conflictingOutpoint = createUtxo(active_chainstate, key, amount);
1043
1044 auto proof_base = buildProofWithSequence(key, {conflictingOutpoint}, 10);
1045
1046 ConflictingProofComparator comparator;
1047 auto checkPreferred = [&](const ProofRef &candidate,
1048 const ProofRef &reference, bool expectAccepted) {
1049 BOOST_CHECK_EQUAL(comparator(candidate, reference), expectAccepted);
1050 BOOST_CHECK_EQUAL(comparator(reference, candidate), !expectAccepted);
1051
1053 BOOST_CHECK(pm.registerProof(reference));
1054 BOOST_CHECK(pm.isBoundToPeer(reference->getId()));
1055
1057 BOOST_CHECK_EQUAL(pm.registerProof(candidate, state), expectAccepted);
1058 BOOST_CHECK_EQUAL(state.IsValid(), expectAccepted);
1059 BOOST_CHECK_EQUAL(state.GetResult() ==
1060 ProofRegistrationResult::CONFLICTING,
1061 !expectAccepted);
1062
1063 BOOST_CHECK_EQUAL(pm.isBoundToPeer(candidate->getId()), expectAccepted);
1065 !expectAccepted);
1066
1067 BOOST_CHECK_EQUAL(pm.isBoundToPeer(reference->getId()),
1068 !expectAccepted);
1069 BOOST_CHECK_EQUAL(pm.isInConflictingPool(reference->getId()),
1070 expectAccepted);
1071 };
1072
1073 // Same master key, lower sequence number
1074 checkPreferred(buildProofWithSequence(key, {conflictingOutpoint}, 9),
1075 proof_base, false);
1076 // Same master key, higher sequence number
1077 checkPreferred(buildProofWithSequence(key, {conflictingOutpoint}, 11),
1078 proof_base, true);
1079
1080 auto buildProofFromAmounts = [&](const CKey &master,
1081 std::vector<Amount> &&amounts) {
1082 std::vector<std::tuple<COutPoint, Amount>> outpointsWithAmount{
1083 {conflictingOutpoint, amount}};
1084 std::transform(amounts.begin(), amounts.end(),
1085 std::back_inserter(outpointsWithAmount),
1086 [&key, &active_chainstate](const Amount amount) {
1087 return std::make_tuple(
1088 createUtxo(active_chainstate, key, amount),
1089 amount);
1090 });
1091 return buildProof(key, outpointsWithAmount, master, 0, height,
1092 is_coinbase, 0);
1093 };
1094
1095 auto proof_multiUtxo = buildProofFromAmounts(
1097
1098 // Test for both the same master and a different one. The sequence number
1099 // is the same for all these tests.
1100 for (const CKey &k : {key, CKey::MakeCompressedKey()}) {
1101 // Low amount
1102 checkPreferred(buildProofFromAmounts(
1104 proof_multiUtxo, false);
1105 // High amount
1106 checkPreferred(buildProofFromAmounts(k, {2 * PROOF_DUST_THRESHOLD,
1108 proof_multiUtxo, true);
1109 // Same amount, low stake count
1110 checkPreferred(buildProofFromAmounts(k, {4 * PROOF_DUST_THRESHOLD}),
1111 proof_multiUtxo, true);
1112 // Same amount, high stake count
1113 checkPreferred(buildProofFromAmounts(k, {2 * PROOF_DUST_THRESHOLD,
1116 proof_multiUtxo, false);
1117 // Same amount, same stake count, selection is done on proof id
1118 auto proofSimilar = buildProofFromAmounts(
1120 checkPreferred(proofSimilar, proof_multiUtxo,
1121 proofSimilar->getId() < proof_multiUtxo->getId());
1122 }
1123}
1124
1125BOOST_AUTO_TEST_CASE(conflicting_immature_proofs) {
1126 ChainstateManager &chainman = *Assert(m_node.chainman);
1127 gArgs.ForceSetArg("-avaproofstakeutxoconfirmations", "2");
1129
1130 const CKey key = CKey::MakeCompressedKey();
1131
1132 Chainstate &active_chainstate = chainman.ActiveChainstate();
1133
1134 const COutPoint conflictingOutpoint = createUtxo(active_chainstate, key);
1135 const COutPoint matureOutpoint =
1136 createUtxo(active_chainstate, key, PROOF_DUST_THRESHOLD, 99);
1137
1138 auto immature10 = buildProofWithSequence(key, {conflictingOutpoint}, 10);
1139 auto immature20 =
1140 buildProofWithSequence(key, {conflictingOutpoint, matureOutpoint}, 20);
1141
1142 BOOST_CHECK(!pm.registerProof(immature10));
1143 BOOST_CHECK(pm.isImmature(immature10->getId()));
1144
1145 BOOST_CHECK(!pm.registerProof(immature20));
1146 BOOST_CHECK(pm.isImmature(immature20->getId()));
1147 BOOST_CHECK(!pm.exists(immature10->getId()));
1148
1149 // Build and register a valid proof that will conflict with the immature one
1150 auto proof30 = buildProofWithOutpoints(key, {matureOutpoint},
1151 PROOF_DUST_THRESHOLD, key, 30, 99);
1152 BOOST_CHECK(pm.registerProof(proof30));
1153 BOOST_CHECK(pm.isBoundToPeer(proof30->getId()));
1154
1155 // Reorg to a shorter chain to make proof30 immature
1156 {
1158 active_chainstate.InvalidateBlock(
1159 state, WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip()));
1161 WITH_LOCK(chainman.GetMutex(), return chainman.ActiveHeight()), 99);
1162 }
1163
1164 // Check that a rescan will also select the preferred immature proof, in
1165 // this case proof30 will replace immature20.
1166 pm.updatedBlockTip();
1167
1168 BOOST_CHECK(!pm.isBoundToPeer(proof30->getId()));
1169 BOOST_CHECK(pm.isImmature(proof30->getId()));
1170 BOOST_CHECK(!pm.exists(immature20->getId()));
1171}
1172
1173BOOST_FIXTURE_TEST_CASE(preferred_conflicting_proof, NoCoolDownFixture) {
1174 ChainstateManager &chainman = *Assert(m_node.chainman);
1176
1177 const CKey key = CKey::MakeCompressedKey();
1178 const COutPoint conflictingOutpoint =
1179 createUtxo(chainman.ActiveChainstate(), key);
1180
1181 auto proofSeq10 = buildProofWithSequence(key, {conflictingOutpoint}, 10);
1182 auto proofSeq20 = buildProofWithSequence(key, {conflictingOutpoint}, 20);
1183 auto proofSeq30 = buildProofWithSequence(key, {conflictingOutpoint}, 30);
1184
1185 BOOST_CHECK(pm.registerProof(proofSeq30));
1186 BOOST_CHECK(pm.isBoundToPeer(proofSeq30->getId()));
1187 BOOST_CHECK(!pm.isInConflictingPool(proofSeq30->getId()));
1188
1189 // proofSeq10 is a worst candidate than proofSeq30, so it goes to the
1190 // conflicting pool.
1191 BOOST_CHECK(!pm.registerProof(proofSeq10));
1192 BOOST_CHECK(pm.isBoundToPeer(proofSeq30->getId()));
1193 BOOST_CHECK(!pm.isBoundToPeer(proofSeq10->getId()));
1194 BOOST_CHECK(pm.isInConflictingPool(proofSeq10->getId()));
1195
1196 // proofSeq20 is a worst candidate than proofSeq30 but a better one than
1197 // proogSeq10, so it replaces it in the conflicting pool and proofSeq10 is
1198 // evicted.
1199 BOOST_CHECK(!pm.registerProof(proofSeq20));
1200 BOOST_CHECK(pm.isBoundToPeer(proofSeq30->getId()));
1201 BOOST_CHECK(!pm.isBoundToPeer(proofSeq20->getId()));
1202 BOOST_CHECK(pm.isInConflictingPool(proofSeq20->getId()));
1203 BOOST_CHECK(!pm.exists(proofSeq10->getId()));
1204}
1205
1206BOOST_FIXTURE_TEST_CASE(update_next_conflict_time, NoCoolDownFixture) {
1207 ChainstateManager &chainman = *Assert(m_node.chainman);
1209
1210 auto now = GetTime<std::chrono::seconds>();
1211 SetMockTime(now.count());
1212
1213 // Updating the time of an unknown peer should fail
1214 for (size_t i = 0; i < 10; i++) {
1216 PeerId(FastRandomContext().randrange<int>(1000)), now));
1217 }
1218
1219 auto proof =
1221 PeerId peerid = TestPeerManager::registerAndGetPeerId(pm, proof);
1222
1223 auto checkNextPossibleConflictTime = [&](std::chrono::seconds expected) {
1224 BOOST_CHECK(pm.forPeer(proof->getId(), [&](const Peer &p) {
1225 return p.nextPossibleConflictTime == expected;
1226 }));
1227 };
1228
1229 checkNextPossibleConflictTime(now);
1230
1231 // Move the time in the past is not possible
1233 peerid, now - std::chrono::seconds{1}));
1234 checkNextPossibleConflictTime(now);
1235
1237 peerid, now + std::chrono::seconds{1}));
1238 checkNextPossibleConflictTime(now + std::chrono::seconds{1});
1239}
1240
1241BOOST_FIXTURE_TEST_CASE(register_force_accept, NoCoolDownFixture) {
1242 ChainstateManager &chainman = *Assert(m_node.chainman);
1244
1245 const CKey key = CKey::MakeCompressedKey();
1246
1247 const COutPoint conflictingOutpoint =
1248 createUtxo(chainman.ActiveChainstate(), key);
1249
1250 auto proofSeq10 = buildProofWithSequence(key, {conflictingOutpoint}, 10);
1251 auto proofSeq20 = buildProofWithSequence(key, {conflictingOutpoint}, 20);
1252 auto proofSeq30 = buildProofWithSequence(key, {conflictingOutpoint}, 30);
1253
1254 BOOST_CHECK(pm.registerProof(proofSeq30));
1255 BOOST_CHECK(pm.isBoundToPeer(proofSeq30->getId()));
1256 BOOST_CHECK(!pm.isInConflictingPool(proofSeq30->getId()));
1257
1258 // proofSeq20 is a worst candidate than proofSeq30, so it goes to the
1259 // conflicting pool.
1260 BOOST_CHECK(!pm.registerProof(proofSeq20));
1261 BOOST_CHECK(pm.isBoundToPeer(proofSeq30->getId()));
1262 BOOST_CHECK(pm.isInConflictingPool(proofSeq20->getId()));
1263
1264 // We can force the acceptance of proofSeq20
1265 using RegistrationMode = avalanche::PeerManager::RegistrationMode;
1266 BOOST_CHECK(pm.registerProof(proofSeq20, RegistrationMode::FORCE_ACCEPT));
1267 BOOST_CHECK(pm.isBoundToPeer(proofSeq20->getId()));
1268 BOOST_CHECK(pm.isInConflictingPool(proofSeq30->getId()));
1269
1270 // We can also force the acceptance of a proof which is not already in the
1271 // conflicting pool.
1272 BOOST_CHECK(!pm.registerProof(proofSeq10));
1273 BOOST_CHECK(!pm.exists(proofSeq10->getId()));
1274
1275 BOOST_CHECK(pm.registerProof(proofSeq10, RegistrationMode::FORCE_ACCEPT));
1276 BOOST_CHECK(pm.isBoundToPeer(proofSeq10->getId()));
1277 BOOST_CHECK(!pm.exists(proofSeq20->getId()));
1278 BOOST_CHECK(pm.isInConflictingPool(proofSeq30->getId()));
1279
1280 // Attempting to register again fails, and has no impact on the pools
1281 for (size_t i = 0; i < 10; i++) {
1282 BOOST_CHECK(!pm.registerProof(proofSeq10));
1284 !pm.registerProof(proofSeq10, RegistrationMode::FORCE_ACCEPT));
1285
1286 BOOST_CHECK(pm.isBoundToPeer(proofSeq10->getId()));
1287 BOOST_CHECK(!pm.exists(proofSeq20->getId()));
1288 BOOST_CHECK(pm.isInConflictingPool(proofSeq30->getId()));
1289 }
1290
1291 // Revert between proofSeq10 and proofSeq30 a few times
1292 for (size_t i = 0; i < 10; i++) {
1294 pm.registerProof(proofSeq30, RegistrationMode::FORCE_ACCEPT));
1295
1296 BOOST_CHECK(pm.isBoundToPeer(proofSeq30->getId()));
1297 BOOST_CHECK(pm.isInConflictingPool(proofSeq10->getId()));
1298
1300 pm.registerProof(proofSeq10, RegistrationMode::FORCE_ACCEPT));
1301
1302 BOOST_CHECK(pm.isBoundToPeer(proofSeq10->getId()));
1303 BOOST_CHECK(pm.isInConflictingPool(proofSeq30->getId()));
1304 }
1305}
1306
1307BOOST_FIXTURE_TEST_CASE(evicted_proof, NoCoolDownFixture) {
1308 ChainstateManager &chainman = *Assert(m_node.chainman);
1310
1311 const CKey key = CKey::MakeCompressedKey();
1312
1313 const COutPoint conflictingOutpoint =
1314 createUtxo(chainman.ActiveChainstate(), key);
1315
1316 auto proofSeq10 = buildProofWithSequence(key, {conflictingOutpoint}, 10);
1317 auto proofSeq20 = buildProofWithSequence(key, {conflictingOutpoint}, 20);
1318 auto proofSeq30 = buildProofWithSequence(key, {conflictingOutpoint}, 30);
1319
1320 {
1322 BOOST_CHECK(pm.registerProof(proofSeq30, state));
1323 BOOST_CHECK(state.IsValid());
1324 }
1325
1326 {
1328 BOOST_CHECK(!pm.registerProof(proofSeq20, state));
1329 BOOST_CHECK(state.GetResult() == ProofRegistrationResult::CONFLICTING);
1330 }
1331
1332 {
1334 BOOST_CHECK(!pm.registerProof(proofSeq10, state));
1335 BOOST_CHECK(state.GetResult() == ProofRegistrationResult::REJECTED);
1336 }
1337}
1338
1339BOOST_AUTO_TEST_CASE(conflicting_proof_cooldown) {
1340 ChainstateManager &chainman = *Assert(m_node.chainman);
1342
1343 const CKey key = CKey::MakeCompressedKey();
1344
1345 const COutPoint conflictingOutpoint =
1346 createUtxo(chainman.ActiveChainstate(), key);
1347
1348 auto proofSeq20 = buildProofWithSequence(key, {conflictingOutpoint}, 20);
1349 auto proofSeq30 = buildProofWithSequence(key, {conflictingOutpoint}, 30);
1350 auto proofSeq40 = buildProofWithSequence(key, {conflictingOutpoint}, 40);
1351
1352 int64_t conflictingProofCooldown = 100;
1353 gArgs.ForceSetArg("-avalancheconflictingproofcooldown",
1354 strprintf("%d", conflictingProofCooldown));
1355
1356 int64_t now = GetTime();
1357
1358 auto increaseMockTime = [&](int64_t s) {
1359 now += s;
1360 SetMockTime(now);
1361 };
1362 increaseMockTime(0);
1363
1364 BOOST_CHECK(pm.registerProof(proofSeq30));
1365 BOOST_CHECK(pm.isBoundToPeer(proofSeq30->getId()));
1366
1367 auto checkRegistrationFailure = [&](const ProofRef &proof,
1368 ProofRegistrationResult reason) {
1370 BOOST_CHECK(!pm.registerProof(proof, state));
1371 BOOST_CHECK(state.GetResult() == reason);
1372 };
1373
1374 // Registering a conflicting proof will fail due to the conflicting proof
1375 // cooldown
1376 checkRegistrationFailure(proofSeq20,
1377 ProofRegistrationResult::COOLDOWN_NOT_ELAPSED);
1378 BOOST_CHECK(!pm.exists(proofSeq20->getId()));
1379
1380 // The cooldown applies as well if the proof is the favorite
1381 checkRegistrationFailure(proofSeq40,
1382 ProofRegistrationResult::COOLDOWN_NOT_ELAPSED);
1383 BOOST_CHECK(!pm.exists(proofSeq40->getId()));
1384
1385 // Elapse the cooldown
1386 increaseMockTime(conflictingProofCooldown);
1387
1388 // The proof will now be added to conflicting pool
1389 checkRegistrationFailure(proofSeq20, ProofRegistrationResult::CONFLICTING);
1390 BOOST_CHECK(pm.isInConflictingPool(proofSeq20->getId()));
1391
1392 // But no other
1393 checkRegistrationFailure(proofSeq40,
1394 ProofRegistrationResult::COOLDOWN_NOT_ELAPSED);
1395 BOOST_CHECK(!pm.exists(proofSeq40->getId()));
1396 BOOST_CHECK(pm.isInConflictingPool(proofSeq20->getId()));
1397
1398 // Elapse the cooldown
1399 increaseMockTime(conflictingProofCooldown);
1400
1401 // The proof will now be accepted to replace proofSeq30, proofSeq30 will
1402 // move to the conflicting pool, and proofSeq20 will be evicted.
1403 BOOST_CHECK(pm.registerProof(proofSeq40));
1404 BOOST_CHECK(pm.isBoundToPeer(proofSeq40->getId()));
1405 BOOST_CHECK(pm.isInConflictingPool(proofSeq30->getId()));
1406 BOOST_CHECK(!pm.exists(proofSeq20->getId()));
1407
1408 gArgs.ClearForcedArg("-avalancheconflictingproofcooldown");
1409}
1410
1411BOOST_FIXTURE_TEST_CASE(reject_proof, NoCoolDownFixture) {
1412 ChainstateManager &chainman = *Assert(m_node.chainman);
1413 gArgs.ForceSetArg("-avaproofstakeutxoconfirmations", "2");
1415
1416 const CKey key = CKey::MakeCompressedKey();
1417
1418 Chainstate &active_chainstate = chainman.ActiveChainstate();
1419
1420 const COutPoint conflictingOutpoint =
1421 createUtxo(active_chainstate, key, PROOF_DUST_THRESHOLD, 99);
1422 const COutPoint immatureOutpoint = createUtxo(active_chainstate, key);
1423
1424 // The good, the bad and the ugly
1425 auto proofSeq10 = buildProofWithOutpoints(
1426 key, {conflictingOutpoint}, PROOF_DUST_THRESHOLD, key, 10, 99);
1427 auto proofSeq20 = buildProofWithOutpoints(
1428 key, {conflictingOutpoint}, PROOF_DUST_THRESHOLD, key, 20, 99);
1429 auto immature30 = buildProofWithSequence(
1430 key, {conflictingOutpoint, immatureOutpoint}, 30);
1431
1432 BOOST_CHECK(pm.registerProof(proofSeq20));
1433 BOOST_CHECK(!pm.registerProof(proofSeq10));
1434 BOOST_CHECK(!pm.registerProof(immature30));
1435
1436 BOOST_CHECK(pm.isBoundToPeer(proofSeq20->getId()));
1437 BOOST_CHECK(pm.isInConflictingPool(proofSeq10->getId()));
1438 BOOST_CHECK(pm.isImmature(immature30->getId()));
1439
1440 // Rejecting a proof that doesn't exist should fail
1441 for (size_t i = 0; i < 10; i++) {
1448 }
1449
1450 auto checkRejectDefault = [&](const ProofId &proofid) {
1451 BOOST_CHECK(pm.exists(proofid));
1452 const bool isImmature = pm.isImmature(proofid);
1455 BOOST_CHECK(!pm.isBoundToPeer(proofid));
1456 BOOST_CHECK_EQUAL(pm.exists(proofid), !isImmature);
1457 };
1458
1459 auto checkRejectInvalidate = [&](const ProofId &proofid) {
1460 BOOST_CHECK(pm.exists(proofid));
1463 };
1464
1465 // Reject from the immature pool
1466 checkRejectDefault(immature30->getId());
1467 BOOST_CHECK(!pm.registerProof(immature30));
1468 BOOST_CHECK(pm.isImmature(immature30->getId()));
1469 checkRejectInvalidate(immature30->getId());
1470
1471 // Reject from the conflicting pool
1472 checkRejectDefault(proofSeq10->getId());
1473 checkRejectInvalidate(proofSeq10->getId());
1474
1475 // Add again a proof to the conflicting pool
1476 BOOST_CHECK(!pm.registerProof(proofSeq10));
1477 BOOST_CHECK(pm.isInConflictingPool(proofSeq10->getId()));
1478
1479 // Reject from the valid pool, default mode
1480 checkRejectDefault(proofSeq20->getId());
1481
1482 // The conflicting proof should be promoted to a peer
1483 BOOST_CHECK(!pm.isInConflictingPool(proofSeq10->getId()));
1484 BOOST_CHECK(pm.isBoundToPeer(proofSeq10->getId()));
1485
1486 // Reject from the valid pool, invalidate mode
1487 checkRejectInvalidate(proofSeq10->getId());
1488
1489 // The conflicting proof should also be promoted to a peer
1490 BOOST_CHECK(!pm.isInConflictingPool(proofSeq20->getId()));
1491 BOOST_CHECK(pm.isBoundToPeer(proofSeq20->getId()));
1492}
1493
1494BOOST_AUTO_TEST_CASE(should_request_more_nodes) {
1495 ChainstateManager &chainman = *Assert(m_node.chainman);
1497
1498 // Set mock time so that proof registration time is predictable and
1499 // testable.
1501
1502 auto proof =
1504 BOOST_CHECK(pm.registerProof(proof));
1505 // Not dangling yet, the proof will remain active for some time before it
1506 // turns dangling if no node is connecting in the meantime.
1507 BOOST_CHECK(!pm.isDangling(proof->getId()));
1508
1509 // We have no nodes, so select node will fail and flag that we need more
1510 // nodes
1513
1514 for (size_t i = 0; i < 10; i++) {
1515 // The flag will not trigger again until we fail to select nodes again
1517 }
1518
1519 // Add a few nodes.
1520 const ProofId &proofid = proof->getId();
1521 for (size_t i = 0; i < 10; i++) {
1522 BOOST_CHECK(pm.addNode(i, proofid));
1523 }
1524
1525 BOOST_CHECK(!pm.isDangling(proof->getId()));
1526
1527 auto cooldownTimepoint = Now<SteadyMilliseconds>() + 10s;
1528
1529 uint64_t round{0};
1530
1531 // All the nodes can be selected once
1532 for (size_t i = 0; i < 10; i++) {
1533 NodeId selectedId = pm.selectNode();
1534 BOOST_CHECK_NE(selectedId, NO_NODE);
1536 selectedId, cooldownTimepoint, round++));
1538 }
1539
1540 // All the nodes have been requested, next select will fail and the flag
1541 // should trigger
1544
1545 for (size_t i = 0; i < 10; i++) {
1546 // The flag will not trigger again until we fail to select nodes again
1548 }
1549
1550 // Make it possible to request a node again
1552 pm.updateNextRequestTimeForPoll(0, Now<SteadyMilliseconds>(), round++));
1553 BOOST_CHECK_NE(pm.selectNode(), NO_NODE);
1555
1556 // Add another proof with no node attached
1557 auto proof2 =
1559 BOOST_CHECK(pm.registerProof(proof2));
1560 BOOST_CHECK(!pm.isDangling(proof2->getId()));
1561 TestPeerManager::cleanupDanglingProofs(pm);
1562 BOOST_CHECK(!pm.isDangling(proof2->getId()));
1564
1565 // After some time the proof will be considered dangling and more nodes will
1566 // be requested.
1567 SetMockTime(GetTime() + 15 * 60);
1568 TestPeerManager::cleanupDanglingProofs(pm);
1569 BOOST_CHECK(pm.isDangling(proof2->getId()));
1571
1572 for (size_t i = 0; i < 10; i++) {
1573 BOOST_CHECK(pm.isDangling(proof2->getId()));
1574 // The flag will not trigger again until the condition is met again
1576 }
1577
1578 // Attempt to register the dangling proof again. This should fail but
1579 // trigger a request for more nodes.
1581 BOOST_CHECK(!pm.registerProof(proof2, state));
1582 BOOST_CHECK(state.GetResult() == ProofRegistrationResult::DANGLING);
1583 BOOST_CHECK(pm.isDangling(proof2->getId()));
1585
1586 for (size_t i = 0; i < 10; i++) {
1587 BOOST_CHECK(pm.isDangling(proof2->getId()));
1588 // The flag will not trigger again until the condition is met again
1590 }
1591
1592 // Attach a node to that proof
1593 BOOST_CHECK(!pm.addNode(11, proof2->getId()));
1594 BOOST_CHECK(pm.registerProof(proof2));
1595 SetMockTime(GetTime() + 15 * 60);
1596 TestPeerManager::cleanupDanglingProofs(pm);
1597 BOOST_CHECK(!pm.isDangling(proof2->getId()));
1599
1600 // Disconnect the node, the proof is dangling again
1601 BOOST_CHECK(pm.removeNode(11));
1602 TestPeerManager::cleanupDanglingProofs(pm);
1603 BOOST_CHECK(pm.isDangling(proof2->getId()));
1605
1606 // Invalidating the proof, removes the proof from the dangling pool but not
1607 // a simple rejection.
1610 BOOST_CHECK(pm.isDangling(proof2->getId()));
1613 BOOST_CHECK(!pm.isDangling(proof2->getId()));
1614}
1615
1616BOOST_AUTO_TEST_CASE(score_ordering) {
1617 ChainstateManager &chainman = *Assert(m_node.chainman);
1619
1620 std::vector<uint32_t> expectedScores(10);
1621 // Expect the peers to be ordered by descending score
1622 std::generate(expectedScores.rbegin(), expectedScores.rend(),
1623 [n = 1]() mutable { return n++ * MIN_VALID_PROOF_SCORE; });
1624
1625 std::vector<ProofRef> proofs;
1626 proofs.reserve(expectedScores.size());
1627 for (uint32_t score : expectedScores) {
1628 proofs.push_back(buildRandomProof(chainman.ActiveChainstate(), score));
1629 }
1630
1631 // Shuffle the proofs so they are registered in a random score order
1632 Shuffle(proofs.begin(), proofs.end(), FastRandomContext());
1633 for (auto &proof : proofs) {
1634 BOOST_CHECK(pm.registerProof(proof));
1635 }
1636
1637 auto peersScores = TestPeerManager::getOrderedScores(pm);
1638 BOOST_CHECK_EQUAL_COLLECTIONS(peersScores.begin(), peersScores.end(),
1639 expectedScores.begin(), expectedScores.end());
1640}
1641
1642BOOST_FIXTURE_TEST_CASE(known_score_tracking, NoCoolDownFixture) {
1643 ChainstateManager &chainman = *Assert(m_node.chainman);
1644 gArgs.ForceSetArg("-avaproofstakeutxoconfirmations", "2");
1646
1647 const CKey key = CKey::MakeCompressedKey();
1648
1649 const Amount amount1(PROOF_DUST_THRESHOLD);
1650 const Amount amount2(2 * PROOF_DUST_THRESHOLD);
1651
1652 Chainstate &active_chainstate = chainman.ActiveChainstate();
1653
1654 const COutPoint peer1ConflictingOutput =
1655 createUtxo(active_chainstate, key, amount1, 99);
1656 const COutPoint peer1SecondaryOutpoint =
1657 createUtxo(active_chainstate, key, amount2, 99);
1658
1659 auto peer1Proof1 = buildProof(
1660 key,
1661 {{peer1ConflictingOutput, amount1}, {peer1SecondaryOutpoint, amount2}},
1662 key, 10, 99);
1663 auto peer1Proof2 =
1664 buildProof(key, {{peer1ConflictingOutput, amount1}}, key, 20, 99);
1665
1666 // Create a proof with an immature UTXO, so the proof will be immature
1667 auto peer1Proof3 =
1668 buildProof(key,
1669 {{peer1ConflictingOutput, amount1},
1670 {createUtxo(active_chainstate, key, amount1), amount1}},
1671 key, 30);
1672
1673 const uint32_t peer1Score1 = Proof::amountToScore(amount1 + amount2);
1674 const uint32_t peer1Score2 = Proof::amountToScore(amount1);
1675
1676 // Add first peer and check that we have its score tracked
1678 BOOST_CHECK(pm.registerProof(peer1Proof2));
1679 BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), peer1Score2);
1680
1681 // Ensure failing to add conflicting proofs doesn't affect the score, the
1682 // first proof stays bound and counted
1683 BOOST_CHECK(!pm.registerProof(peer1Proof1));
1684 BOOST_CHECK(!pm.registerProof(peer1Proof3));
1685
1686 BOOST_CHECK(pm.isBoundToPeer(peer1Proof2->getId()));
1687 BOOST_CHECK(pm.isInConflictingPool(peer1Proof1->getId()));
1688 BOOST_CHECK(pm.isImmature(peer1Proof3->getId()));
1689
1690 BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), peer1Score2);
1691
1692 auto checkRejectDefault = [&](const ProofId &proofid) {
1693 BOOST_CHECK(pm.exists(proofid));
1694 const bool isImmature = pm.isImmature(proofid);
1697 BOOST_CHECK(!pm.isBoundToPeer(proofid));
1698 BOOST_CHECK_EQUAL(pm.exists(proofid), !isImmature);
1699 };
1700
1701 auto checkRejectInvalidate = [&](const ProofId &proofid) {
1702 BOOST_CHECK(pm.exists(proofid));
1705 };
1706
1707 // Reject from the immature pool doesn't affect tracked score
1708 checkRejectDefault(peer1Proof3->getId());
1709 BOOST_CHECK(!pm.registerProof(peer1Proof3));
1710 BOOST_CHECK(pm.isImmature(peer1Proof3->getId()));
1711 BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), peer1Score2);
1712 checkRejectInvalidate(peer1Proof3->getId());
1713 BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), peer1Score2);
1714
1715 // Reject from the conflicting pool
1716 checkRejectDefault(peer1Proof1->getId());
1717 checkRejectInvalidate(peer1Proof1->getId());
1718
1719 // Add again a proof to the conflicting pool
1720 BOOST_CHECK(!pm.registerProof(peer1Proof1));
1721 BOOST_CHECK(pm.isInConflictingPool(peer1Proof1->getId()));
1722 BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), peer1Score2);
1723
1724 // Reject from the valid pool, default mode
1725 // Now the score should change as the new peer is promoted
1726 checkRejectDefault(peer1Proof2->getId());
1727 BOOST_CHECK(!pm.isInConflictingPool(peer1Proof1->getId()));
1728 BOOST_CHECK(pm.isBoundToPeer(peer1Proof1->getId()));
1729 BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), peer1Score1);
1730
1731 // Reject from the valid pool, invalidate mode
1732 // Now the score should change as the old peer is re-promoted
1733 checkRejectInvalidate(peer1Proof1->getId());
1734
1735 // The conflicting proof should also be promoted to a peer
1736 BOOST_CHECK(!pm.isInConflictingPool(peer1Proof2->getId()));
1737 BOOST_CHECK(pm.isBoundToPeer(peer1Proof2->getId()));
1738 BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), peer1Score2);
1739
1740 // Now add another peer and check that combined scores are correct
1741 uint32_t peer2Score = 1 * MIN_VALID_PROOF_SCORE;
1742 auto peer2Proof1 = buildRandomProof(active_chainstate, peer2Score, 99);
1743 PeerId peerid2 = TestPeerManager::registerAndGetPeerId(pm, peer2Proof1);
1744 BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), peer1Score2 + peer2Score);
1745
1746 // Trying to remove non-existent peer doesn't affect score
1747 BOOST_CHECK(!pm.removePeer(1234));
1748 BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), peer1Score2 + peer2Score);
1749
1750 // Removing new peer removes its score
1751 BOOST_CHECK(pm.removePeer(peerid2));
1752 BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), peer1Score2);
1753 PeerId peerid1 =
1754 TestPeerManager::getPeerIdForProofId(pm, peer1Proof2->getId());
1755 BOOST_CHECK(pm.removePeer(peerid1));
1757}
1758
1759BOOST_AUTO_TEST_CASE(connected_score_tracking) {
1760 ChainstateManager &chainman = *Assert(m_node.chainman);
1762
1763 const auto checkScores = [&pm](uint32_t known, uint32_t connected) {
1766 };
1767
1768 // Start out with 0s
1769 checkScores(0, 0);
1770
1771 Chainstate &active_chainstate = chainman.ActiveChainstate();
1772
1773 // Create one peer without a node. Its score should be registered but not
1774 // connected
1775 uint32_t score1 = 10000000 * MIN_VALID_PROOF_SCORE;
1776 auto proof1 = buildRandomProof(active_chainstate, score1);
1777 PeerId peerid1 = TestPeerManager::registerAndGetPeerId(pm, proof1);
1778 checkScores(score1, 0);
1779
1780 // Add nodes. We now have a connected score, but it doesn't matter how many
1781 // nodes we add the score is the same
1782 const ProofId &proofid1 = proof1->getId();
1783 const uint8_t nodesToAdd = 10;
1784 for (int i = 0; i < nodesToAdd; i++) {
1785 BOOST_CHECK(pm.addNode(i, proofid1));
1786 checkScores(score1, score1);
1787 }
1788
1789 // Remove all but 1 node and ensure the score doesn't change
1790 for (int i = 0; i < nodesToAdd - 1; i++) {
1791 BOOST_CHECK(pm.removeNode(i));
1792 checkScores(score1, score1);
1793 }
1794
1795 // Removing the last node should remove the score from the connected count
1796 BOOST_CHECK(pm.removeNode(nodesToAdd - 1));
1797 checkScores(score1, 0);
1798
1799 // Add 2 nodes to peer and create peer2. Without a node peer2 has no
1800 // connected score but after adding a node it does.
1801 BOOST_CHECK(pm.addNode(0, proofid1));
1802 BOOST_CHECK(pm.addNode(1, proofid1));
1803 checkScores(score1, score1);
1804
1805 uint32_t score2 = 1 * MIN_VALID_PROOF_SCORE;
1806 auto proof2 = buildRandomProof(active_chainstate, score2);
1807 PeerId peerid2 = TestPeerManager::registerAndGetPeerId(pm, proof2);
1808 checkScores(score1 + score2, score1);
1809 BOOST_CHECK(pm.addNode(2, proof2->getId()));
1810 checkScores(score1 + score2, score1 + score2);
1811
1812 // The first peer has two nodes left. Remove one and nothing happens, remove
1813 // the other and its score is no longer in the connected counter..
1814 BOOST_CHECK(pm.removeNode(0));
1815 checkScores(score1 + score2, score1 + score2);
1816 BOOST_CHECK(pm.removeNode(1));
1817 checkScores(score1 + score2, score2);
1818
1819 // Removing a peer with no allocated score has no affect.
1820 BOOST_CHECK(pm.removePeer(peerid1));
1821 checkScores(score2, score2);
1822
1823 // Remove the second peer's node removes its allocated score.
1824 BOOST_CHECK(pm.removeNode(2));
1825 checkScores(score2, 0);
1826
1827 // Removing the second peer takes us back to 0.
1828 BOOST_CHECK(pm.removePeer(peerid2));
1829 checkScores(0, 0);
1830
1831 // Add 2 peers with nodes and remove them without removing the nodes first.
1832 // Both score counters should be reduced by each peer's score when it's
1833 // removed.
1834 peerid1 = TestPeerManager::registerAndGetPeerId(pm, proof1);
1835 checkScores(score1, 0);
1836 peerid2 = TestPeerManager::registerAndGetPeerId(pm, proof2);
1837 checkScores(score1 + score2, 0);
1838 BOOST_CHECK(pm.addNode(0, proof1->getId()));
1839 checkScores(score1 + score2, score1);
1840 BOOST_CHECK(pm.addNode(1, proof2->getId()));
1841 checkScores(score1 + score2, score1 + score2);
1842
1843 BOOST_CHECK(pm.removePeer(peerid2));
1844 checkScores(score1, score1);
1845
1846 BOOST_CHECK(pm.removePeer(peerid1));
1847 checkScores(0, 0);
1848}
1849
1850BOOST_FIXTURE_TEST_CASE(proof_radix_tree, NoCoolDownFixture) {
1851 ChainstateManager &chainman = *Assert(m_node.chainman);
1853
1854 struct ProofComparatorById {
1855 bool operator()(const ProofRef &lhs, const ProofRef &rhs) const {
1856 return lhs->getId() < rhs->getId();
1857 };
1858 };
1859 using ProofSetById = std::set<ProofRef, ProofComparatorById>;
1860 // Maintain a list of the expected proofs through this test
1861 ProofSetById expectedProofs;
1862
1863 auto matchExpectedContent = [&](const auto &tree) {
1864 auto it = expectedProofs.begin();
1865 return tree.forEachLeaf([&](auto pLeaf) {
1866 return it != expectedProofs.end() &&
1867 pLeaf->getId() == (*it++)->getId();
1868 });
1869 };
1870
1872 const int64_t sequence = 10;
1873
1874 Chainstate &active_chainstate = chainman.ActiveChainstate();
1875
1876 // Add some initial proofs
1877 for (size_t i = 0; i < 10; i++) {
1878 auto outpoint = createUtxo(active_chainstate, key);
1879 auto proof = buildProofWithSequence(key, {{outpoint}}, sequence);
1880 BOOST_CHECK(pm.registerProof(proof));
1881 expectedProofs.insert(std::move(proof));
1882 }
1883
1884 const auto &treeRef = pm.getShareableProofsSnapshot();
1885 BOOST_CHECK(matchExpectedContent(treeRef));
1886
1887 // Create a copy
1888 auto tree = pm.getShareableProofsSnapshot();
1889
1890 // Adding more proofs doesn't change the tree...
1891 ProofSetById addedProofs;
1892 std::vector<COutPoint> outpointsToSpend;
1893 for (size_t i = 0; i < 10; i++) {
1894 auto outpoint = createUtxo(active_chainstate, key);
1895 auto proof = buildProofWithSequence(key, {{outpoint}}, sequence);
1896 BOOST_CHECK(pm.registerProof(proof));
1897 addedProofs.insert(std::move(proof));
1898 outpointsToSpend.push_back(std::move(outpoint));
1899 }
1900
1901 BOOST_CHECK(matchExpectedContent(tree));
1902
1903 // ...until we get a new copy
1904 tree = pm.getShareableProofsSnapshot();
1905 expectedProofs.insert(addedProofs.begin(), addedProofs.end());
1906 BOOST_CHECK(matchExpectedContent(tree));
1907
1908 // Spend some coins to make the associated proofs invalid
1909 {
1910 LOCK(cs_main);
1911 CCoinsViewCache &coins = active_chainstate.CoinsTip();
1912 for (const auto &outpoint : outpointsToSpend) {
1913 coins.SpendCoin(outpoint);
1914 }
1915 }
1916
1917 pm.updatedBlockTip();
1918
1919 // This doesn't change the tree...
1920 BOOST_CHECK(matchExpectedContent(tree));
1921
1922 // ...until we get a new copy
1923 tree = pm.getShareableProofsSnapshot();
1924 for (const auto &proof : addedProofs) {
1925 BOOST_CHECK_EQUAL(expectedProofs.erase(proof), 1);
1926 }
1927 BOOST_CHECK(matchExpectedContent(tree));
1928
1929 // Add some more proof for which we will create conflicts
1930 std::vector<ProofRef> conflictingProofs;
1931 std::vector<COutPoint> conflictingOutpoints;
1932 for (size_t i = 0; i < 10; i++) {
1933 auto outpoint = createUtxo(active_chainstate, key);
1934 auto proof = buildProofWithSequence(key, {{outpoint}}, sequence);
1935 BOOST_CHECK(pm.registerProof(proof));
1936 conflictingProofs.push_back(std::move(proof));
1937 conflictingOutpoints.push_back(std::move(outpoint));
1938 }
1939
1940 tree = pm.getShareableProofsSnapshot();
1941 expectedProofs.insert(conflictingProofs.begin(), conflictingProofs.end());
1942 BOOST_CHECK(matchExpectedContent(tree));
1943
1944 // Build a bunch of conflicting proofs, half better, half worst
1945 for (size_t i = 0; i < 10; i += 2) {
1946 // The worst proof is not added to the expected set
1947 BOOST_CHECK(!pm.registerProof(buildProofWithSequence(
1948 key, {{conflictingOutpoints[i]}}, sequence - 1)));
1949
1950 // But the better proof should replace its conflicting one
1951 auto replacementProof = buildProofWithSequence(
1952 key, {{conflictingOutpoints[i + 1]}}, sequence + 1);
1953 BOOST_CHECK(pm.registerProof(replacementProof));
1954 BOOST_CHECK_EQUAL(expectedProofs.erase(conflictingProofs[i + 1]), 1);
1955 BOOST_CHECK(expectedProofs.insert(replacementProof).second);
1956 }
1957
1958 tree = pm.getShareableProofsSnapshot();
1959 BOOST_CHECK(matchExpectedContent(tree));
1960
1961 // Check for consistency
1962 pm.verify();
1963}
1964
1965BOOST_AUTO_TEST_CASE(received_avaproofs) {
1966 ChainstateManager &chainman = *Assert(m_node.chainman);
1968
1969 auto addNode = [&](NodeId nodeid) {
1970 auto proof = buildRandomProof(chainman.ActiveChainstate(),
1972 BOOST_CHECK(pm.registerProof(proof));
1973 BOOST_CHECK(pm.addNode(nodeid, proof->getId()));
1974 };
1975
1976 for (NodeId nodeid = 0; nodeid < 10; nodeid++) {
1977 // Node doesn't exist
1978 BOOST_CHECK(!pm.latchAvaproofsSent(nodeid));
1979
1980 addNode(nodeid);
1981 BOOST_CHECK(pm.latchAvaproofsSent(nodeid));
1982
1983 // The flag is already set
1984 BOOST_CHECK(!pm.latchAvaproofsSent(nodeid));
1985 }
1986}
1987
1988BOOST_FIXTURE_TEST_CASE(cleanup_dangling_proof, NoCoolDownFixture) {
1989 ChainstateManager &chainman = *Assert(m_node.chainman);
1990
1992
1993 const auto now = GetTime<std::chrono::seconds>();
1994 auto mocktime = now;
1995
1996 auto elapseTime = [&](std::chrono::seconds seconds) {
1997 mocktime += seconds;
1998 SetMockTime(mocktime.count());
1999 };
2000 elapseTime(0s);
2001
2002 const CKey key = CKey::MakeCompressedKey();
2003
2004 const size_t numProofs = 10;
2005
2006 std::vector<COutPoint> outpoints(numProofs);
2007 std::vector<ProofRef> proofs(numProofs);
2008 std::vector<ProofRef> conflictingProofs(numProofs);
2009 for (size_t i = 0; i < numProofs; i++) {
2010 outpoints[i] = createUtxo(chainman.ActiveChainstate(), key);
2011 proofs[i] = buildProofWithSequence(key, {outpoints[i]}, 2);
2012 conflictingProofs[i] = buildProofWithSequence(key, {outpoints[i]}, 1);
2013
2014 BOOST_CHECK(pm.registerProof(proofs[i]));
2015 BOOST_CHECK(pm.isBoundToPeer(proofs[i]->getId()));
2016
2017 BOOST_CHECK(!pm.registerProof(conflictingProofs[i]));
2018 BOOST_CHECK(pm.isInConflictingPool(conflictingProofs[i]->getId()));
2019
2020 if (i % 2) {
2021 // Odd indexes get a node attached to them
2022 BOOST_CHECK(pm.addNode(i, proofs[i]->getId()));
2023 }
2024 BOOST_CHECK_EQUAL(pm.forPeer(proofs[i]->getId(),
2025 [&](const avalanche::Peer &peer) {
2026 return peer.node_count;
2027 }),
2028 i % 2);
2029
2030 elapseTime(1s);
2031 }
2032
2033 // No proof expired yet
2034 TestPeerManager::cleanupDanglingProofs(pm);
2035 for (size_t i = 0; i < numProofs; i++) {
2036 BOOST_CHECK(pm.isBoundToPeer(proofs[i]->getId()));
2037 BOOST_CHECK(pm.isInConflictingPool(conflictingProofs[i]->getId()));
2038 }
2039
2040 // Elapse the dangling timeout
2042 TestPeerManager::cleanupDanglingProofs(pm);
2043 for (size_t i = 0; i < numProofs; i++) {
2044 const bool hasNodeAttached = i % 2;
2045
2046 // Only the peers with no nodes attached are getting discarded
2047 BOOST_CHECK_EQUAL(pm.isBoundToPeer(proofs[i]->getId()),
2048 hasNodeAttached);
2049 BOOST_CHECK_EQUAL(!pm.exists(proofs[i]->getId()), !hasNodeAttached);
2050
2051 // The proofs conflicting with the discarded ones are pulled back
2052 BOOST_CHECK_EQUAL(pm.isInConflictingPool(conflictingProofs[i]->getId()),
2053 hasNodeAttached);
2054 BOOST_CHECK_EQUAL(pm.isBoundToPeer(conflictingProofs[i]->getId()),
2055 !hasNodeAttached);
2056 }
2057
2058 // Attach a node to the first conflicting proof, which has been promoted
2059 BOOST_CHECK(pm.addNode(42, conflictingProofs[0]->getId()));
2061 conflictingProofs[0]->getId(),
2062 [&](const avalanche::Peer &peer) { return peer.node_count == 1; }));
2063
2064 // Elapse the dangling timeout again
2066 TestPeerManager::cleanupDanglingProofs(pm);
2067 for (size_t i = 0; i < numProofs; i++) {
2068 const bool hasNodeAttached = i % 2;
2069
2070 // The initial peers with a node attached are still there
2071 BOOST_CHECK_EQUAL(pm.isBoundToPeer(proofs[i]->getId()),
2072 hasNodeAttached);
2073 BOOST_CHECK_EQUAL(!pm.exists(proofs[i]->getId()), !hasNodeAttached);
2074
2075 // This time the previouly promoted conflicting proofs are evicted
2076 // because they have no node attached, except the index 0.
2077 BOOST_CHECK_EQUAL(pm.exists(conflictingProofs[i]->getId()),
2078 hasNodeAttached || i == 0);
2079 BOOST_CHECK_EQUAL(pm.isInConflictingPool(conflictingProofs[i]->getId()),
2080 hasNodeAttached);
2081 BOOST_CHECK_EQUAL(pm.isBoundToPeer(conflictingProofs[i]->getId()),
2082 i == 0);
2083 }
2084
2085 // Disconnect all the nodes
2086 for (size_t i = 1; i < numProofs; i += 2) {
2087 BOOST_CHECK(pm.removeNode(i));
2089 pm.forPeer(proofs[i]->getId(), [&](const avalanche::Peer &peer) {
2090 return peer.node_count == 0;
2091 }));
2092 }
2093 BOOST_CHECK(pm.removeNode(42));
2095 conflictingProofs[0]->getId(),
2096 [&](const avalanche::Peer &peer) { return peer.node_count == 0; }));
2097
2098 TestPeerManager::cleanupDanglingProofs(pm);
2099 for (size_t i = 0; i < numProofs; i++) {
2100 const bool hadNodeAttached = i % 2;
2101
2102 // All initially valid proofs have now been discarded
2103 BOOST_CHECK(!pm.exists(proofs[i]->getId()));
2104
2105 // The remaining conflicting proofs are promoted
2106 BOOST_CHECK_EQUAL(!pm.exists(conflictingProofs[i]->getId()),
2107 !hadNodeAttached);
2108 BOOST_CHECK(!pm.isInConflictingPool(conflictingProofs[i]->getId()));
2109 BOOST_CHECK_EQUAL(pm.isBoundToPeer(conflictingProofs[i]->getId()),
2110 hadNodeAttached);
2111 }
2112
2113 // Elapse the timeout for the newly promoted conflicting proofs
2115
2116 // All other proofs have now been discarded
2117 TestPeerManager::cleanupDanglingProofs(pm);
2118
2119 for (size_t i = 0; i < numProofs; i++) {
2120 // All proofs have finally been discarded
2121 BOOST_CHECK(!pm.exists(proofs[i]->getId()));
2122 BOOST_CHECK(!pm.exists(conflictingProofs[i]->getId()));
2123 }
2124}
2125
2126BOOST_AUTO_TEST_CASE(register_proof_missing_utxo) {
2127 ChainstateManager &chainman = *Assert(m_node.chainman);
2129
2131 auto proof = buildProofWithOutpoints(key, {{TxId(GetRandHash()), 0}},
2133
2135 BOOST_CHECK(!pm.registerProof(proof, state));
2136 BOOST_CHECK(state.GetResult() == ProofRegistrationResult::MISSING_UTXO);
2137}
2138
2139BOOST_FIXTURE_TEST_CASE(proof_expiry, NoCoolDownFixture) {
2140 ChainstateManager &chainman = *Assert(m_node.chainman);
2142
2143 const int64_t tipTime =
2144 WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip())
2145 ->GetBlockTime();
2146
2148
2149 auto utxo = createUtxo(chainman.ActiveChainstate(), key);
2150 auto proofToExpire = buildProof(key, {{utxo, PROOF_DUST_THRESHOLD}}, key, 2,
2151 100, false, tipTime + 1);
2152 auto conflictingProof = buildProof(key, {{utxo, PROOF_DUST_THRESHOLD}}, key,
2153 1, 100, false, tipTime + 2);
2154
2155 // Our proofToExpire is not expired yet, so it registers fine
2156 BOOST_CHECK(pm.registerProof(proofToExpire));
2157 BOOST_CHECK(pm.isBoundToPeer(proofToExpire->getId()));
2158
2159 // The conflicting proof has a longer expiration time but a lower sequence
2160 // number, so it is moved to the conflicting pool.
2161 BOOST_CHECK(!pm.registerProof(conflictingProof));
2162 BOOST_CHECK(pm.isInConflictingPool(conflictingProof->getId()));
2163
2164 // Mine blocks until the MTP of the tip moves to the proof expiration
2165 for (int64_t i = 0; i < 6; i++) {
2166 SetMockTime(proofToExpire->getExpirationTime() + i);
2167 CreateAndProcessBlock({}, CScript());
2168 }
2170 WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip())
2171 ->GetMedianTimePast(),
2172 proofToExpire->getExpirationTime());
2173
2174 pm.updatedBlockTip();
2175
2176 // The now expired proof is removed
2177 BOOST_CHECK(!pm.exists(proofToExpire->getId()));
2178
2179 // The conflicting proof has been pulled back to the valid pool
2180 BOOST_CHECK(pm.isBoundToPeer(conflictingProof->getId()));
2181}
2182
2183BOOST_AUTO_TEST_CASE(select_staking_reward_winner) {
2184 ChainstateManager &chainman = *Assert(m_node.chainman);
2186 Chainstate &active_chainstate = chainman.ActiveChainstate();
2187
2188 auto buildProofWithAmountAndPayout = [&](Amount amount,
2189 const CScript &payoutScript) {
2190 const CKey key = CKey::MakeCompressedKey();
2191 COutPoint utxo = createUtxo(active_chainstate, key, amount);
2192 return buildProof(key, {{std::move(utxo), amount}},
2193 /*master=*/CKey::MakeCompressedKey(), /*sequence=*/1,
2194 /*height=*/100, /*is_coinbase=*/false,
2195 /*expirationTime=*/0, payoutScript);
2196 };
2197
2198 std::vector<std::pair<ProofId, CScript>> winners;
2199 // Null pprev
2200 BOOST_CHECK(!pm.selectStakingRewardWinner(nullptr, winners));
2201
2202 CBlockIndex prevBlock;
2203
2204 auto now = GetTime<std::chrono::seconds>();
2205 SetMockTime(now);
2206 prevBlock.nTime = now.count();
2207
2208 BlockHash prevHash{uint256::ONE};
2209 prevBlock.phashBlock = &prevHash;
2210 // No peer
2211 BOOST_CHECK(!pm.selectStakingRewardWinner(&prevBlock, winners));
2212
2213 // Let's build a list of payout addresses, and register a proofs for each
2214 // address
2215 size_t numProofs = 8;
2216 std::vector<ProofRef> proofs;
2217 proofs.reserve(numProofs);
2218 for (size_t i = 0; i < numProofs; i++) {
2219 const CKey key = CKey::MakeCompressedKey();
2220 CScript payoutScript = GetScriptForRawPubKey(key.GetPubKey());
2221
2222 auto proof =
2223 buildProofWithAmountAndPayout(PROOF_DUST_THRESHOLD, payoutScript);
2224 PeerId peerid = TestPeerManager::registerAndGetPeerId(pm, proof);
2225 BOOST_CHECK_NE(peerid, NO_PEER);
2226
2227 // Finalize the proof
2228 BOOST_CHECK(pm.setFinalized(peerid));
2229
2230 proofs.emplace_back(std::move(proof));
2231 }
2232
2233 // Make sure the proofs have been registered before the prev block was found
2234 // and before 6x the peer replacement cooldown.
2235 now += 6 * avalanche::Peer::DANGLING_TIMEOUT + 1s;
2236 SetMockTime(now);
2237 prevBlock.nTime = now.count();
2238
2239 // At this stage we have a set of peers out of which none has any node
2240 // attached, so they're all considered flaky. Note that we have no remote
2241 // proofs status yet.
2242 BOOST_CHECK(pm.selectStakingRewardWinner(&prevBlock, winners));
2243 BOOST_CHECK_LE(winners.size(), numProofs);
2244
2245 // Let's add a node for each peer
2246 for (size_t i = 0; i < numProofs; i++) {
2247 BOOST_CHECK(TestPeerManager::isFlaky(pm, proofs[i]->getId()));
2248 BOOST_CHECK(pm.selectStakingRewardWinner(&prevBlock, winners));
2249 BOOST_CHECK_LE(winners.size(), numProofs);
2250
2251 BOOST_CHECK(pm.addNode(NodeId(i), proofs[i]->getId()));
2252
2253 BOOST_CHECK(!TestPeerManager::isFlaky(pm, proofs[i]->getId()));
2254 BOOST_CHECK(pm.selectStakingRewardWinner(&prevBlock, winners));
2255 BOOST_CHECK_LE(winners.size(), numProofs - i);
2256 }
2257
2258 // Now we have a single winner
2259 BOOST_CHECK(pm.selectStakingRewardWinner(&prevBlock, winners));
2260 BOOST_CHECK_LE(winners.size(), 1);
2261
2262 // All proofs have the same amount, so the same probability to get picked.
2263 // Let's compute how many loop iterations we need to have a low false
2264 // negative rate when checking for this. Target false positive rate is
2265 // 10ppm (aka 1/100000).
2266 const size_t loop_iters =
2267 size_t(-1.0 * std::log(100000.0) /
2268 std::log((double(numProofs) - 1) / numProofs)) +
2269 1;
2270 BOOST_CHECK_GT(loop_iters, numProofs);
2271 std::unordered_map<std::string, size_t> winningCounts;
2272 for (size_t i = 0; i < loop_iters; i++) {
2273 BlockHash randomHash = BlockHash(GetRandHash());
2274 prevBlock.phashBlock = &randomHash;
2275 BOOST_CHECK(pm.selectStakingRewardWinner(&prevBlock, winners));
2276 winningCounts[FormatScript(winners[0].second)]++;
2277 }
2278 BOOST_CHECK_EQUAL(winningCounts.size(), numProofs);
2279
2280 prevBlock.phashBlock = &prevHash;
2281
2282 // Ensure all nodes have all the proofs
2283 for (size_t i = 0; i < numProofs; i++) {
2284 for (size_t j = 0; j < numProofs; j++) {
2286 pm.saveRemoteProof(proofs[j]->getId(), NodeId(i), true));
2287 }
2288 }
2289
2290 // Make all the proofs flaky. This loop needs to be updated if the threshold
2291 // or the number of proofs change, so assert the test precondition.
2292 BOOST_CHECK_GT(3. / numProofs, 0.3);
2293 for (size_t i = 0; i < numProofs; i++) {
2294 const NodeId nodeid = NodeId(i);
2295
2297 proofs[(i - 1 + numProofs) % numProofs]->getId(), nodeid, false));
2299 proofs[(i + numProofs) % numProofs]->getId(), nodeid, false));
2301 proofs[(i + 1 + numProofs) % numProofs]->getId(), nodeid, false));
2302 }
2303
2304 // Now all the proofs are flaky
2305 BOOST_CHECK(pm.selectStakingRewardWinner(&prevBlock, winners));
2306 for (const auto &proof : proofs) {
2307 BOOST_CHECK(TestPeerManager::isFlaky(pm, proof->getId()));
2308 }
2309 BOOST_CHECK_EQUAL(winners.size(), numProofs);
2310
2311 // Revert flakyness for all proofs
2312 for (const auto &proof : proofs) {
2313 for (NodeId nodeid = 0; nodeid < NodeId(numProofs); nodeid++) {
2314 BOOST_CHECK(pm.saveRemoteProof(proof->getId(), nodeid, true));
2315 }
2316 }
2317
2318 BOOST_CHECK(pm.selectStakingRewardWinner(&prevBlock, winners));
2319 BOOST_CHECK_EQUAL(winners.size(), 1);
2320
2321 // Increase the list from 1 to 4 winners by making them flaky
2322 for (size_t numWinner = 1; numWinner < 4; numWinner++) {
2323 // Who is the last possible winner ?
2324 CScript lastWinner = winners[numWinner - 1].second;
2325
2326 // Make the last winner flaky, the other proofs untouched
2327 ProofId winnerProofId = ProofId(uint256::ZERO);
2328 for (const auto &proof : proofs) {
2329 if (proof->getPayoutScript() == lastWinner) {
2330 winnerProofId = proof->getId();
2331 break;
2332 }
2333 }
2334 BOOST_CHECK_NE(winnerProofId, ProofId(uint256::ZERO));
2335
2336 for (NodeId nodeid = 0; nodeid < NodeId(numProofs); nodeid++) {
2337 BOOST_CHECK(pm.saveRemoteProof(winnerProofId, nodeid, false));
2338 }
2339 BOOST_CHECK(TestPeerManager::isFlaky(pm, winnerProofId));
2340
2341 // There should be now exactly numWinner + 1 winners
2342 BOOST_CHECK(pm.selectStakingRewardWinner(&prevBlock, winners));
2343 BOOST_CHECK_EQUAL(winners.size(), numWinner + 1);
2344 }
2345
2346 // One more time and the nodes will be missing too many proofs, so they are
2347 // no longer considered for flakyness evaluation and we're back to a single
2348 // winner.
2349 CScript lastWinner = winners[3].second;
2350
2351 ProofId winnerProofId = ProofId(uint256::ZERO);
2352 for (const auto &proof : proofs) {
2353 if (proof->getPayoutScript() == lastWinner) {
2354 winnerProofId = proof->getId();
2355 break;
2356 }
2357 }
2358 BOOST_CHECK_NE(winnerProofId, ProofId(uint256::ZERO));
2359
2360 for (NodeId nodeid = 0; nodeid < NodeId(numProofs); nodeid++) {
2361 BOOST_CHECK(pm.saveRemoteProof(winnerProofId, nodeid, false));
2362 }
2363
2364 // We're back to exactly 1 winner
2365 BOOST_CHECK(pm.selectStakingRewardWinner(&prevBlock, winners));
2366 BOOST_CHECK_EQUAL(winners.size(), 1);
2367
2368 // Remove all proofs
2369 for (auto &proof : proofs) {
2372 }
2373 // No more winner
2374 prevBlock.phashBlock = &prevHash;
2375 BOOST_CHECK(!pm.selectStakingRewardWinner(&prevBlock, winners));
2376
2377 {
2378 // Add back a single proof
2379 const CKey key = CKey::MakeCompressedKey();
2380 CScript payoutScript = GetScriptForRawPubKey(key.GetPubKey());
2381
2382 auto proof =
2383 buildProofWithAmountAndPayout(PROOF_DUST_THRESHOLD, payoutScript);
2384 PeerId peerid = TestPeerManager::registerAndGetPeerId(pm, proof);
2385 BOOST_CHECK_NE(peerid, NO_PEER);
2386
2387 // The single proof should always be selected, but:
2388 // 1. The proof is not finalized, and has been registered after the last
2389 // block was mined.
2390 BOOST_CHECK(!pm.selectStakingRewardWinner(&prevBlock, winners));
2391
2392 // 2. The proof has has been registered after the last block was mined.
2393 BOOST_CHECK(pm.setFinalized(peerid));
2394 BOOST_CHECK(!pm.selectStakingRewardWinner(&prevBlock, winners));
2395
2396 // 3. The proof has been registered 60min from the previous block time,
2397 // but the previous block time is in the future.
2398 now += 50min + 1s;
2399 SetMockTime(now);
2400 prevBlock.nTime = (now + 10min).count();
2401 BOOST_CHECK(!pm.selectStakingRewardWinner(&prevBlock, winners));
2402
2403 // 4. The proof has been registered 60min from now, but only 50min from
2404 // the previous block time.
2405 now += 10min;
2406 SetMockTime(now);
2407 prevBlock.nTime = (now - 10min).count();
2408 BOOST_CHECK(!pm.selectStakingRewardWinner(&prevBlock, winners));
2409
2410 // 5. Now the proof has it all
2411 prevBlock.nTime = now.count();
2412 BOOST_CHECK(pm.selectStakingRewardWinner(&prevBlock, winners));
2413 // With a single proof, it's easy to determine the winner
2414 BOOST_CHECK_EQUAL(FormatScript(winners[0].second),
2415 FormatScript(payoutScript));
2416
2417 // Remove the proof
2420 }
2421
2422 {
2423 BOOST_CHECK_EQUAL(TestPeerManager::getPeerCount(pm), 0);
2424
2425 proofs.clear();
2426 for (size_t i = 0; i < 4; i++) {
2427 // Add 4 proofs, registered at a 30 minutes interval
2428 SetMockTime(now + i * 30min);
2429
2430 const CKey key = CKey::MakeCompressedKey();
2431 CScript payoutScript = GetScriptForRawPubKey(key.GetPubKey());
2432
2433 auto proof = buildProofWithAmountAndPayout(PROOF_DUST_THRESHOLD,
2434 payoutScript);
2435 PeerId peerid = TestPeerManager::registerAndGetPeerId(pm, proof);
2436 BOOST_CHECK_NE(peerid, NO_PEER);
2437 BOOST_CHECK(pm.forPeer(proof->getId(), [&](const Peer &peer) {
2438 return peer.registration_time == now + i * 30min;
2439 }));
2440
2441 BOOST_CHECK(pm.addNode(NodeId(i), proof->getId()));
2442
2443 BOOST_CHECK(pm.setFinalized(peerid));
2444
2445 proofs.push_back(proof);
2446 }
2447
2448 // No proof has been registered before the previous block time
2449 SetMockTime(now);
2450 prevBlock.nTime = now.count();
2451 BOOST_CHECK(!pm.selectStakingRewardWinner(&prevBlock, winners));
2452
2453 // 1 proof has been registered > 30min from the previous block time, but
2454 // none > 60 minutes from the previous block time
2455 // => we have no winner.
2456 now += 30min + 1s;
2457 SetMockTime(now);
2458 prevBlock.nTime = now.count();
2459 BOOST_CHECK(!pm.selectStakingRewardWinner(&prevBlock, winners));
2460
2461 auto checkRegistrationTime =
2462 [&](const std::pair<ProofId, CScript> &winner) {
2463 pm.forEachPeer([&](const Peer &peer) {
2464 if (peer.proof->getPayoutScript() == winner.second) {
2465 BOOST_CHECK_LT(peer.registration_time.count(),
2466 (now - 60min).count());
2467 }
2468 return true;
2469 });
2470 };
2471
2472 // 1 proof has been registered > 60min but < 90min from the previous
2473 // block time and 1 more has been registered > 30 minutes
2474 // => we have a winner and one acceptable substitute.
2475 now += 30min;
2476 SetMockTime(now);
2477 prevBlock.nTime = now.count();
2478 BOOST_CHECK(pm.selectStakingRewardWinner(&prevBlock, winners));
2479 BOOST_CHECK_EQUAL(winners.size(), 2);
2480 checkRegistrationTime(winners[0]);
2481
2482 // 1 proof has been registered > 60min but < 90min from the
2483 // previous block time, 1 has been registered > 90 minutes and 1 more
2484 // has been registered > 30 minutes
2485 // => we have 1 winner and up to 2 acceptable substitutes.
2486 now += 30min;
2487 SetMockTime(now);
2488 prevBlock.nTime = now.count();
2489 BOOST_CHECK(pm.selectStakingRewardWinner(&prevBlock, winners));
2490 BOOST_CHECK_LE(winners.size(), 3);
2491 checkRegistrationTime(winners[0]);
2492
2493 // 1 proofs has been registered > 60min but < 90min from the
2494 // previous block time, 2 has been registered > 90 minutes and 1 more
2495 // has been registered > 30 minutes
2496 // => we have 1 winner, and up to 2 substitutes.
2497 now += 30min;
2498 SetMockTime(now);
2499 prevBlock.nTime = now.count();
2500 BOOST_CHECK(pm.selectStakingRewardWinner(&prevBlock, winners));
2501 BOOST_CHECK_LE(winners.size(), 3);
2502 checkRegistrationTime(winners[0]);
2503
2504 // 1 proof has been registered > 60min but < 90min from the
2505 // previous block time and 3 more has been registered > 90 minutes
2506 // => we have 1 winner, and up to 1 substitute.
2507 now += 30min;
2508 SetMockTime(now);
2509 prevBlock.nTime = now.count();
2510 BOOST_CHECK(pm.selectStakingRewardWinner(&prevBlock, winners));
2511 BOOST_CHECK_LE(winners.size(), 2);
2512 checkRegistrationTime(winners[0]);
2513
2514 // All proofs has been registered > 90min from the previous block time
2515 // => we have 1 winner, and no substitute.
2516 now += 30min;
2517 SetMockTime(now);
2518 prevBlock.nTime = now.count();
2519 BOOST_CHECK(pm.selectStakingRewardWinner(&prevBlock, winners));
2520 BOOST_CHECK_EQUAL(winners.size(), 1);
2521 checkRegistrationTime(winners[0]);
2522 }
2523}
2524
2526 ChainstateManager &chainman = *Assert(m_node.chainman);
2528
2529 auto mockTime = GetTime<std::chrono::seconds>();
2530 SetMockTime(mockTime);
2531
2536
2537 auto checkRemoteProof =
2538 [&](const ProofId &proofid, const NodeId nodeid,
2539 const bool expectedPresent,
2540 const std::chrono::seconds &expectedlastUpdate) {
2541 BOOST_CHECK(pm.hasRemoteProofStatus(proofid));
2542 BOOST_CHECK(pm.isRemotelyPresentProof(proofid) == expectedPresent);
2543 auto remoteProof =
2544 TestPeerManager::getRemoteProof(pm, proofid, nodeid);
2545 BOOST_CHECK(remoteProof.has_value());
2546 BOOST_CHECK_EQUAL(remoteProof->proofid, proofid);
2547 BOOST_CHECK_EQUAL(remoteProof->nodeid, nodeid);
2548 BOOST_CHECK_EQUAL(remoteProof->present, expectedPresent);
2549 BOOST_CHECK_EQUAL(remoteProof->lastUpdate.count(),
2550 expectedlastUpdate.count());
2551 };
2552
2553 checkRemoteProof(ProofId(uint256::ZERO), 0, true, mockTime);
2554 checkRemoteProof(ProofId(uint256::ONE), 0, false, mockTime);
2555 checkRemoteProof(ProofId(uint256::ZERO), 1, true, mockTime);
2556 checkRemoteProof(ProofId(uint256::ONE), 1, false, mockTime);
2557
2558 mockTime += 1s;
2559 SetMockTime(mockTime);
2560
2561 // Reverse the state
2566
2567 checkRemoteProof(ProofId(uint256::ZERO), 0, false, mockTime);
2568 checkRemoteProof(ProofId(uint256::ONE), 0, true, mockTime);
2569 checkRemoteProof(ProofId(uint256::ZERO), 1, false, mockTime);
2570 checkRemoteProof(ProofId(uint256::ONE), 1, true, mockTime);
2571
2572 Chainstate &active_chainstate = chainman.ActiveChainstate();
2573
2574 // Actually register the nodes
2575 auto proof0 = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
2576 BOOST_CHECK(pm.registerProof(proof0));
2577 BOOST_CHECK(pm.addNode(0, proof0->getId()));
2578 auto proof1 = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
2579 BOOST_CHECK(pm.registerProof(proof1));
2580 BOOST_CHECK(pm.addNode(1, proof1->getId()));
2581
2582 // Removing the node removes all the associated remote proofs
2583 BOOST_CHECK(pm.removeNode(0));
2585 !TestPeerManager::getRemoteProof(pm, ProofId(uint256::ZERO), 0));
2586 BOOST_CHECK(!TestPeerManager::getRemoteProof(pm, ProofId(uint256::ONE), 0));
2587 // Other nodes are left untouched
2588 checkRemoteProof(ProofId(uint256::ZERO), 1, false, mockTime);
2589 checkRemoteProof(ProofId(uint256::ONE), 1, true, mockTime);
2590
2591 BOOST_CHECK(pm.removeNode(1));
2593 !TestPeerManager::getRemoteProof(pm, ProofId(uint256::ZERO), 0));
2594 BOOST_CHECK(!TestPeerManager::getRemoteProof(pm, ProofId(uint256::ONE), 0));
2596 !TestPeerManager::getRemoteProof(pm, ProofId(uint256::ZERO), 1));
2597 BOOST_CHECK(!TestPeerManager::getRemoteProof(pm, ProofId(uint256::ONE), 1));
2598
2599 for (size_t i = 0; i < avalanche::PeerManager::MAX_REMOTE_PROOFS; i++) {
2600 mockTime += 1s;
2601 SetMockTime(mockTime);
2602
2603 const ProofId proofid{uint256(i)};
2604
2605 BOOST_CHECK(pm.saveRemoteProof(proofid, 0, true));
2606 checkRemoteProof(proofid, 0, true, mockTime);
2607 }
2608
2609 // The last updated proof is still there
2610 checkRemoteProof(ProofId(uint256::ZERO), 0, true,
2611 mockTime -
2613
2614 // If we add one more it gets evicted
2615 mockTime += 1s;
2616 SetMockTime(mockTime);
2617
2618 ProofId proofid{
2620
2621 BOOST_CHECK(pm.saveRemoteProof(proofid, 0, true));
2622 checkRemoteProof(proofid, 0, true, mockTime);
2623 // Proof id 0 has been evicted
2625 !TestPeerManager::getRemoteProof(pm, ProofId(uint256::ZERO), 0));
2626
2627 // Proof id 1 is still there
2628 BOOST_CHECK(TestPeerManager::getRemoteProof(pm, ProofId(uint256::ONE), 0));
2629
2630 // Add MAX_REMOTE_PROOFS / 2 + 1 proofs to our node to bump the limit
2631 // Note that we already have proofs from the beginning of the test.
2632 std::vector<ProofRef> proofs;
2633 for (size_t i = 0; i < avalanche::PeerManager::MAX_REMOTE_PROOFS / 2 - 1;
2634 i++) {
2635 auto proof = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
2636 BOOST_CHECK(pm.registerProof(proof));
2637 proofs.push_back(proof);
2638 }
2639 BOOST_CHECK_EQUAL(TestPeerManager::getPeerCount(pm),
2641
2642 // We can now add one more without eviction
2643 mockTime += 1s;
2644 SetMockTime(mockTime);
2645
2646 proofid = ProofId{
2648
2649 BOOST_CHECK(pm.saveRemoteProof(proofid, 0, true));
2650 checkRemoteProof(proofid, 0, true, mockTime);
2651 // Proof id 1 is still there
2652 BOOST_CHECK(TestPeerManager::getRemoteProof(pm, ProofId(uint256::ONE), 0));
2653
2654 // Shrink our proofs to MAX_REMOTE_PROOFS / 2 - 1
2659
2660 BOOST_CHECK_EQUAL(TestPeerManager::getPeerCount(pm),
2662
2663 // Upon update the first proof got evicted
2664 proofid = ProofId{
2666 BOOST_CHECK(pm.saveRemoteProof(proofid, 0, true));
2667 // Proof id 1 is evicted
2668 BOOST_CHECK(!TestPeerManager::getRemoteProof(pm, ProofId(uint256::ONE), 0));
2669 // So is proof id 2
2670 BOOST_CHECK(!TestPeerManager::getRemoteProof(pm, ProofId(uint256(2)), 0));
2671 // But proof id 3 is still here
2672 BOOST_CHECK(TestPeerManager::getRemoteProof(pm, ProofId(uint256(3)), 0));
2673}
2674
2675BOOST_AUTO_TEST_CASE(get_remote_status) {
2676 ChainstateManager &chainman = *Assert(m_node.chainman);
2678 Chainstate &active_chainstate = chainman.ActiveChainstate();
2679
2680 auto mockTime = GetTime<std::chrono::seconds>();
2681 SetMockTime(mockTime);
2682
2683 // No remote proof yet
2685 !TestPeerManager::getRemotePresenceStatus(pm, ProofId(uint256::ZERO))
2686 .has_value());
2687
2688 // 6/12 (50%) of the stakes
2689 for (NodeId nodeid = 0; nodeid < 12; nodeid++) {
2690 auto proof = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
2691 BOOST_CHECK(pm.registerProof(proof));
2692 BOOST_CHECK(pm.addNode(nodeid, proof->getId()));
2694 nodeid % 2 == 0));
2695 }
2696
2698 !TestPeerManager::getRemotePresenceStatus(pm, ProofId(uint256::ZERO))
2699 .has_value());
2700
2701 // 7/12 (~58%) of the stakes
2702 for (NodeId nodeid = 0; nodeid < 5; nodeid++) {
2703 BOOST_CHECK(pm.saveRemoteProof(ProofId(uint256::ZERO), nodeid, false));
2704 }
2705 for (NodeId nodeid = 5; nodeid < 12; nodeid++) {
2707 }
2709 TestPeerManager::getRemotePresenceStatus(pm, ProofId(uint256::ZERO))
2710 .value());
2711
2712 // Add our local proof so we have 7/13 (~54% < 55%)
2713 auto localProof =
2714 buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
2715 TestPeerManager::setLocalProof(pm, localProof);
2716 BOOST_CHECK(pm.registerProof(localProof));
2718 !TestPeerManager::getRemotePresenceStatus(pm, ProofId(uint256::ZERO))
2719 .has_value());
2720
2721 // Remove the local proof to revert back to 7/12 (~58%)
2722 pm.rejectProof(localProof->getId());
2723 TestPeerManager::setLocalProof(pm, ProofRef());
2725 TestPeerManager::getRemotePresenceStatus(pm, ProofId(uint256::ZERO))
2726 .value());
2727
2728 // 5/12 (~42%) of the stakes
2729 for (NodeId nodeid = 0; nodeid < 5; nodeid++) {
2731 }
2732 for (NodeId nodeid = 5; nodeid < 12; nodeid++) {
2733 BOOST_CHECK(pm.saveRemoteProof(ProofId(uint256::ZERO), nodeid, false));
2734 }
2736 !TestPeerManager::getRemotePresenceStatus(pm, ProofId(uint256::ZERO))
2737 .value());
2738
2739 // Most nodes agree but not enough of the stakes
2740 auto bigProof =
2741 buildRandomProof(active_chainstate, 100 * MIN_VALID_PROOF_SCORE);
2742 BOOST_CHECK(pm.registerProof(bigProof));
2743 // Update the node's proof
2744 BOOST_CHECK(pm.addNode(0, bigProof->getId()));
2745
2746 // 7/12 (~58%) of the remotes, but < 10% of the stakes => absent
2747 for (NodeId nodeid = 0; nodeid < 5; nodeid++) {
2748 BOOST_CHECK(pm.saveRemoteProof(ProofId(uint256::ZERO), nodeid, false));
2749 }
2750 for (NodeId nodeid = 5; nodeid < 12; nodeid++) {
2752 }
2754 !TestPeerManager::getRemotePresenceStatus(pm, ProofId(uint256::ZERO))
2755 .value());
2756
2757 // 5/12 (42%) of the remotes, but > 90% of the stakes => present
2758 for (NodeId nodeid = 0; nodeid < 5; nodeid++) {
2760 }
2761 for (NodeId nodeid = 5; nodeid < 12; nodeid++) {
2762 BOOST_CHECK(pm.saveRemoteProof(ProofId(uint256::ZERO), nodeid, false));
2763 }
2765 TestPeerManager::getRemotePresenceStatus(pm, ProofId(uint256::ZERO))
2766 .value());
2767
2768 TestPeerManager::clearPeers(pm);
2769
2770 // Peer 1 has 1 node (id 0)
2771 auto proof1 = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
2772 BOOST_CHECK(pm.registerProof(proof1));
2773 BOOST_CHECK(pm.addNode(0, proof1->getId()));
2774
2775 // Peer 2 has 5 nodes (ids 1 to 5)
2776 auto proof2 = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
2777 BOOST_CHECK(pm.registerProof(proof2));
2778 for (NodeId nodeid = 1; nodeid < 6; nodeid++) {
2779 BOOST_CHECK(pm.addNode(nodeid, proof2->getId()));
2780 }
2781
2782 // Node 0 is missing proofid 0, nodes 1 to 5 have it
2784 for (NodeId nodeid = 1; nodeid < 6; nodeid++) {
2786 }
2787
2788 // At this stage we have 5/6 nodes with the proof, but since all the nodes
2789 // advertising the proof are from the same peer, we only 1/2 peers, i.e. 50%
2790 // of the stakes.
2792 !TestPeerManager::getRemotePresenceStatus(pm, ProofId(uint256::ZERO))
2793 .has_value());
2794}
2795
2796BOOST_AUTO_TEST_CASE(dangling_with_remotes) {
2797 ChainstateManager &chainman = *Assert(m_node.chainman);
2799 Chainstate &active_chainstate = chainman.ActiveChainstate();
2800
2801 auto mockTime = GetTime<std::chrono::seconds>();
2802 SetMockTime(mockTime);
2803
2804 // Add a few proofs with no node attached
2805 std::vector<ProofRef> proofs;
2806 for (size_t i = 0; i < 10; i++) {
2807 auto proof = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
2808 BOOST_CHECK(pm.registerProof(proof));
2809 proofs.push_back(proof);
2810 }
2811
2812 // The proofs are recent enough, the cleanup won't make them dangling
2813 TestPeerManager::cleanupDanglingProofs(pm);
2814 for (const auto &proof : proofs) {
2815 BOOST_CHECK(pm.isBoundToPeer(proof->getId()));
2816 BOOST_CHECK(!pm.isDangling(proof->getId()));
2817 }
2818
2819 // Elapse enough time so we get the proofs dangling
2820 mockTime += avalanche::Peer::DANGLING_TIMEOUT + 1s;
2821 SetMockTime(mockTime);
2822
2823 // The proofs are now dangling
2824 TestPeerManager::cleanupDanglingProofs(pm);
2825 for (const auto &proof : proofs) {
2826 BOOST_CHECK(!pm.isBoundToPeer(proof->getId()));
2827 BOOST_CHECK(pm.isDangling(proof->getId()));
2828 }
2829
2830 // Add some remotes having this proof
2831 for (NodeId nodeid = 0; nodeid < 10; nodeid++) {
2832 auto localProof =
2833 buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
2834 BOOST_CHECK(pm.registerProof(localProof));
2835 BOOST_CHECK(pm.addNode(nodeid, localProof->getId()));
2836
2837 for (const auto &proof : proofs) {
2838 BOOST_CHECK(pm.saveRemoteProof(proof->getId(), nodeid, true));
2839 }
2840 }
2841
2842 // The proofs are all present according to the remote status
2843 for (const auto &proof : proofs) {
2844 BOOST_CHECK(TestPeerManager::getRemotePresenceStatus(pm, proof->getId())
2845 .value());
2846 }
2847
2848 // The proofs should be added back as a peer
2849 std::unordered_set<ProofRef, SaltedProofHasher> registeredProofs;
2850 TestPeerManager::cleanupDanglingProofs(pm, registeredProofs);
2851 for (const auto &proof : proofs) {
2852 BOOST_CHECK(pm.isBoundToPeer(proof->getId()));
2853 BOOST_CHECK(!pm.isDangling(proof->getId()));
2854 BOOST_CHECK_EQUAL(registeredProofs.count(proof), 1);
2855 }
2856 BOOST_CHECK_EQUAL(proofs.size(), registeredProofs.size());
2857
2858 // Remove the proofs from the remotes
2859 for (NodeId nodeid = 0; nodeid < 10; nodeid++) {
2860 for (const auto &proof : proofs) {
2861 BOOST_CHECK(pm.saveRemoteProof(proof->getId(), nodeid, false));
2862 }
2863 }
2864
2865 // The proofs are now all absent according to the remotes
2866 for (const auto &proof : proofs) {
2868 !TestPeerManager::getRemotePresenceStatus(pm, proof->getId())
2869 .value());
2870 }
2871
2872 // The proofs are not dangling yet as they have been registered recently
2873 TestPeerManager::cleanupDanglingProofs(pm, registeredProofs);
2874 BOOST_CHECK(registeredProofs.empty());
2875 for (const auto &proof : proofs) {
2876 BOOST_CHECK(pm.isBoundToPeer(proof->getId()));
2877 BOOST_CHECK(!pm.isDangling(proof->getId()));
2878 }
2879
2880 // Wait some time then run the cleanup again, the proofs will be dangling
2881 mockTime += avalanche::Peer::DANGLING_TIMEOUT + 1s;
2882 SetMockTime(mockTime);
2883
2884 TestPeerManager::cleanupDanglingProofs(pm, registeredProofs);
2885 BOOST_CHECK(registeredProofs.empty());
2886 for (const auto &proof : proofs) {
2887 BOOST_CHECK(!pm.isBoundToPeer(proof->getId()));
2888 BOOST_CHECK(pm.isDangling(proof->getId()));
2889 }
2890
2891 // Pull them back one more time
2892 for (NodeId nodeid = 0; nodeid < 10; nodeid++) {
2893 for (const auto &proof : proofs) {
2894 BOOST_CHECK(pm.saveRemoteProof(proof->getId(), nodeid, true));
2895 }
2896 }
2897
2898 TestPeerManager::cleanupDanglingProofs(pm, registeredProofs);
2899 for (const auto &proof : proofs) {
2900 BOOST_CHECK(pm.isBoundToPeer(proof->getId()));
2901 BOOST_CHECK(!pm.isDangling(proof->getId()));
2902 BOOST_CHECK_EQUAL(registeredProofs.count(proof), 1);
2903 }
2904 BOOST_CHECK_EQUAL(proofs.size(), registeredProofs.size());
2905}
2906
2907BOOST_AUTO_TEST_CASE(avapeers_dump) {
2908 ChainstateManager &chainman = *Assert(m_node.chainman);
2910 Chainstate &active_chainstate = chainman.ActiveChainstate();
2911
2912 auto mockTime = GetTime<std::chrono::seconds>();
2913 SetMockTime(mockTime);
2914
2915 std::vector<ProofRef> proofs;
2916 for (size_t i = 0; i < 10; i++) {
2917 SetMockTime(mockTime + std::chrono::seconds{i});
2918
2919 auto proof = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
2920 // Registration time is mockTime + i
2921 BOOST_CHECK(pm.registerProof(proof));
2922
2923 auto peerid = TestPeerManager::getPeerIdForProofId(pm, proof->getId());
2924
2925 // Next conflict time is mockTime + 100 + i
2927 peerid, mockTime + std::chrono::seconds{100 + i}));
2928
2929 // The 5 first proofs are finalized
2930 if (i < 5) {
2931 BOOST_CHECK(pm.setFinalized(peerid));
2932 }
2933
2934 proofs.push_back(proof);
2935 }
2936
2937 BOOST_CHECK_EQUAL(TestPeerManager::getPeerCount(pm), 10);
2938
2939 const fs::path testDumpPath = "test_avapeers_dump.dat";
2940 BOOST_CHECK(pm.dumpPeersToFile(testDumpPath));
2941
2942 TestPeerManager::clearPeers(pm);
2943
2944 std::unordered_set<ProofRef, SaltedProofHasher> registeredProofs;
2945 BOOST_CHECK(pm.loadPeersFromFile(testDumpPath, registeredProofs));
2946 BOOST_CHECK_EQUAL(registeredProofs.size(), 10);
2947
2948 auto findProofIndex = [&proofs](const ProofId &proofid) {
2949 for (size_t i = 0; i < proofs.size(); i++) {
2950 if (proofs[i]->getId() == proofid) {
2951 return i;
2952 }
2953 }
2954
2955 // ProofId not found
2956 BOOST_CHECK(false);
2957 return size_t{0};
2958 };
2959
2960 for (const auto &proof : registeredProofs) {
2961 const ProofId &proofid = proof->getId();
2962 size_t i = findProofIndex(proofid);
2963 BOOST_CHECK(pm.forPeer(proofid, [&](auto &peer) {
2964 BOOST_CHECK_EQUAL(peer.hasFinalized, i < 5);
2965 BOOST_CHECK_EQUAL(peer.registration_time.count(),
2966 (mockTime + std::chrono::seconds{i}).count());
2968 peer.nextPossibleConflictTime.count(),
2969 (mockTime + std::chrono::seconds{100 + i}).count());
2970 return true;
2971 }));
2972 }
2973
2974 // No peer: create an empty file but generate no error
2975 TestPeerManager::clearPeers(pm);
2976 BOOST_CHECK(pm.dumpPeersToFile("test_empty_avapeers.dat"));
2977 // We can also load an empty file
2979 pm.loadPeersFromFile("test_empty_avapeers.dat", registeredProofs));
2980 BOOST_CHECK(registeredProofs.empty());
2981 BOOST_CHECK_EQUAL(TestPeerManager::getPeerCount(pm), 0);
2982
2983 // If the file exists, it is overrwritten
2984 BOOST_CHECK(pm.dumpPeersToFile("test_empty_avapeers.dat"));
2985
2986 // It fails to load if the file does not exist and the registeredProofs is
2987 // cleared
2988 registeredProofs.insert(proofs[0]);
2989 BOOST_CHECK(!registeredProofs.empty());
2990 BOOST_CHECK(!pm.loadPeersFromFile("I_dont_exist.dat", registeredProofs));
2991 BOOST_CHECK(registeredProofs.empty());
2992
2993 {
2994 // Change the version
2995 FILE *f = fsbridge::fopen("test_bad_version_avapeers.dat", "wb");
2996 BOOST_CHECK(f);
2997 AutoFile file{f};
2998 file << static_cast<uint64_t>(-1); // Version
2999 file << uint64_t{0}; // Number of peers
3000 BOOST_CHECK(FileCommit(file.Get()));
3001 file.fclose();
3002
3003 // Check loading fails and the registeredProofs is cleared
3004 registeredProofs.insert(proofs[0]);
3005 BOOST_CHECK(!registeredProofs.empty());
3006 BOOST_CHECK(!pm.loadPeersFromFile("test_bad_version_avapeers.dat",
3007 registeredProofs));
3008 BOOST_CHECK(registeredProofs.empty());
3009 }
3010
3011 {
3012 // Wrong format, will cause a deserialization error
3013 FILE *f = fsbridge::fopen("test_ill_formed_avapeers.dat", "wb");
3014 BOOST_CHECK(f);
3015 const uint64_t now = GetTime();
3016 AutoFile file{f};
3017 file << static_cast<uint64_t>(1); // Version
3018 file << uint64_t{2}; // Number of peers
3019 // Single peer content!
3020 file << proofs[0];
3021 file << true;
3022 file << now;
3023 file << now + 100;
3024
3025 BOOST_CHECK(FileCommit(file.Get()));
3026 file.fclose();
3027
3028 // Check loading fails and the registeredProofs is fed with our single
3029 // peer
3030 BOOST_CHECK(registeredProofs.empty());
3031 BOOST_CHECK(!pm.loadPeersFromFile("test_ill_formed_avapeers.dat",
3032 registeredProofs));
3033 BOOST_CHECK_EQUAL(registeredProofs.size(), 1);
3034 BOOST_CHECK_EQUAL((*registeredProofs.begin())->getId(),
3035 proofs[0]->getId());
3036 }
3037}
3038
3039BOOST_AUTO_TEST_CASE(dangling_proof_invalidation) {
3040 ChainstateManager &chainman = *Assert(m_node.chainman);
3042 Chainstate &active_chainstate = chainman.ActiveChainstate();
3043
3044 SetMockTime(GetTime<std::chrono::seconds>());
3045
3047 auto utxo = createUtxo(active_chainstate, key);
3048 auto proof =
3049 buildProof(key, {{utxo, PROOF_DUST_THRESHOLD}}, key, 2, 100, false,
3050 GetTime<std::chrono::seconds>().count() + 1000000);
3051
3052 // Register the proof
3053 BOOST_CHECK(pm.registerProof(proof));
3054 BOOST_CHECK(pm.isBoundToPeer(proof->getId()));
3055 BOOST_CHECK(!pm.isDangling(proof->getId()));
3056
3057 // Elapse the dangling timeout. No nodes are bound, so the proof is now
3058 // dangling.
3059 SetMockTime(GetTime<std::chrono::seconds>() +
3061 TestPeerManager::cleanupDanglingProofs(pm);
3062 BOOST_CHECK(!pm.isBoundToPeer(proof->getId()));
3063 BOOST_CHECK(!pm.exists(proof->getId()));
3064 BOOST_CHECK(pm.isDangling(proof->getId()));
3065
3066 {
3067 LOCK(cs_main);
3068 CCoinsViewCache &coins = active_chainstate.CoinsTip();
3069 // Make proof invalid
3070 coins.SpendCoin(utxo);
3071 }
3072
3073 // Trigger proof validity checks
3074 pm.updatedBlockTip();
3075
3076 // The now invalid proof is removed
3077 BOOST_CHECK(!pm.exists(proof->getId()));
3078 BOOST_CHECK(!pm.isDangling(proof->getId()));
3079
3080 {
3081 LOCK(cs_main);
3082 CCoinsViewCache &coins = active_chainstate.CoinsTip();
3083 // Add the utxo back so we can make the proof valid again
3085 coins.AddCoin(utxo,
3086 Coin(CTxOut(PROOF_DUST_THRESHOLD, script), 100, false),
3087 false);
3088 }
3089
3090 // Our proof is not expired yet, so it registers fine
3091 BOOST_CHECK(pm.registerProof(proof));
3092 BOOST_CHECK(pm.isBoundToPeer(proof->getId()));
3093 BOOST_CHECK(!pm.isDangling(proof->getId()));
3094
3095 // Elapse the dangling timeout. No nodes are bound, so the proof is now
3096 // dangling.
3097 SetMockTime(GetTime<std::chrono::seconds>() +
3099 TestPeerManager::cleanupDanglingProofs(pm);
3100 BOOST_CHECK(!pm.isBoundToPeer(proof->getId()));
3101 BOOST_CHECK(!pm.exists(proof->getId()));
3102 BOOST_CHECK(pm.isDangling(proof->getId()));
3103
3104 // Mine blocks until the MTP of the tip moves to the proof expiration
3105 for (int64_t i = 0; i < 6; i++) {
3106 SetMockTime(proof->getExpirationTime() + i);
3107 CreateAndProcessBlock({}, CScript());
3108 }
3110 WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip())
3111 ->GetMedianTimePast(),
3112 proof->getExpirationTime());
3113
3114 pm.updatedBlockTip();
3115
3116 // The now expired proof is removed
3117 BOOST_CHECK(!pm.exists(proof->getId()));
3118 BOOST_CHECK(!pm.isDangling(proof->getId()));
3119}
3120
3121BOOST_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:283
uint64_t getFragmentation() const
Definition: peermanager.h:511
uint32_t getConnectedPeersScore() const
Definition: peermanager.h:450
bool updateNextRequestTimeForResponse(NodeId nodeid, const Response &response)
bool isDangling(const ProofId &proofid) const
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:339
bool exists(const ProofId &proofid) const
Return true if the (valid) proof exists, but only for non-dangling proofs.
Definition: peermanager.h:414
size_t getNodeCount() const
Definition: peermanager.h:318
PendingNodeSet pendingNodes
Definition: peermanager.h:225
bool verify() const
Perform consistency check on internal data structures.
bool forNode(NodeId nodeid, Callable &&func) const
Definition: peermanager.h:342
bool hasRemoteProofStatus(const ProofId &proofid) const
bool forPeer(const ProofId &proofid, Callable &&func) const
Definition: peermanager.h:422
uint32_t getTotalPeersScore() const
Definition: peermanager.h:449
bool latchAvaproofsSent(NodeId nodeid)
Flag that a node did send its compact proofs.
bool addNode(NodeId nodeid, const ProofId &proofid)
Node API.
Definition: peermanager.cpp:33
bool updateNextRequestTimeForPoll(NodeId nodeid, SteadyMilliseconds timeout, uint64_t round)
uint64_t getSlotCount() const
Definition: peermanager.h:510
bool loadPeersFromFile(const fs::path &dumpPath, std::unordered_set< ProofRef, SaltedProofHasher > &registeredProofs)
std::unordered_set< ProofRef, SaltedProofHasher > updatedBlockTip()
Update the peer set when a new block is connected.
const ProofRadixTree & getShareableProofsSnapshot() const
Definition: peermanager.h:529
bool isBoundToPeer(const ProofId &proofid) const
size_t getPendingNodeCount() const
Definition: peermanager.h:319
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:428
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:379
static constexpr size_t MAX_REMOTE_PROOFS
Definition: peermanager.h:304
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:145
static constexpr uint32_t AVALANCHE_MAX_IMMATURE_PROOFS
Maximum number of immature proofs the peer manager will accept from the network.
Definition: peermanager.h: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:823
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)
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:62
void SetMockTime(int64_t nMockTimeIn)
DEPRECATED Use SetMockTime with chrono type.
Definition: time.cpp:46
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