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