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