Bitcoin ABC 0.32.12
P2P Digital Currency
processor_tests.cpp
Go to the documentation of this file.
1// Copyright (c) 2018-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
6
7#include <arith_uint256.h>
14#include <chain.h>
15#include <config.h>
16#include <core_io.h>
17#include <key_io.h>
18#include <net_processing.h> // For ::PeerManager
19#include <reverse_iterator.h>
20#include <scheduler.h>
21#include <util/time.h>
22#include <util/translation.h> // For bilingual_str
23#include <validation.h>
24
25#include <avalanche/test/util.h>
26#include <test/util/net.h>
27#include <test/util/setup_common.h>
28
29#include <boost/mpl/list.hpp>
30#include <boost/test/unit_test.hpp>
31
32#include <functional>
33#include <limits>
34#include <type_traits>
35#include <vector>
36
37using namespace avalanche;
38using util::ToString;
39
40namespace avalanche {
41namespace {
42 struct AvalancheTest {
43 static void runEventLoop(avalanche::Processor &p) { p.runEventLoop(); }
44
45 static std::vector<CInv> getInvsForNextPoll(Processor &p) {
46 auto r = p.voteRecords.getReadView();
47 return p.getInvsForNextPoll(r, false);
48 }
49
50 static NodeId getSuitableNodeToQuery(Processor &p) {
52 return p.peerManager->selectNode());
53 }
54
55 static uint64_t getRound(const Processor &p) { return p.round; }
56
57 static uint32_t getMinQuorumScore(const Processor &p) {
58 return p.minQuorumScore;
59 }
60
61 static double getMinQuorumConnectedScoreRatio(const Processor &p) {
63 }
64
65 static void clearavaproofsNodeCounter(Processor &p) {
67 }
68
69 static void addVoteRecord(Processor &p, AnyVoteItem &item,
70 VoteRecord &voteRecord) {
71 p.voteRecords.getWriteView()->insert(
72 std::make_pair(item, voteRecord));
73 }
74
75 static void removeVoteRecord(Processor &p, AnyVoteItem &item) {
76 p.voteRecords.getWriteView()->erase(item);
77 }
78
79 static void setFinalizationTip(Processor &p,
80 const CBlockIndex *pindex) {
82 p.finalizationTip = pindex;
83 }
84
85 static void setLocalProofShareable(Processor &p, bool shareable) {
86 p.m_canShareLocalProof = shareable;
87 }
88
89 static void updatedBlockTip(Processor &p) { p.updatedBlockTip(); }
90
91 static void addProofToRecentfinalized(Processor &p,
92 const ProofId &proofid) {
94 return p.finalizedItems.insert(proofid));
95 }
96
97 static bool setContenderStatusForLocalWinners(
98 Processor &p, const CBlockIndex *pindex,
99 std::vector<StakeContenderId> &pollableContenders) {
100 return p.setContenderStatusForLocalWinners(pindex,
101 pollableContenders);
102 }
103
104 static void setStakingPreconsensus(Processor &p, bool enabled) {
105 p.m_stakingPreConsensus = enabled;
106 }
107
108 static void clearInvsNotWorthPolling(Processor &p) {
110 }
111 };
112} // namespace
113
114struct TestVoteRecord : public VoteRecord {
115 explicit TestVoteRecord(uint16_t conf) : VoteRecord(true) {
116 confidence |= conf << 1;
117 }
118};
119} // namespace avalanche
120
121namespace {
122CService ip(uint32_t i) {
123 struct in_addr s;
124 s.s_addr = i;
125 return CService(CNetAddr(s), Params().GetDefaultPort());
126}
127
128struct AvalancheProcessorTestingSetup : public AvalancheTestChain100Setup {
129 AvalancheProcessorTestingSetup() : AvalancheTestChain100Setup() {
130 AvalancheTest::setStakingPreconsensus(*m_node.avalanche, false);
131 }
132
133 CNode *ConnectNode(ServiceFlags nServices) {
134 static NodeId id = 0;
135
136 CAddress addr(ip(FastRandomContext().rand<uint32_t>()), NODE_NONE);
137 auto node =
138 new CNode(id++, /*sock=*/nullptr, addr,
139 /* nKeyedNetGroupIn */ 0,
140 /* nLocalHostNonceIn */ 0,
141 /* nLocalExtraEntropyIn */ 0, CAddress(),
142 /* pszDest */ "", ConnectionType::OUTBOUND_FULL_RELAY,
143 /* inbound_onion */ false);
144 node->SetCommonVersion(PROTOCOL_VERSION);
145 node->m_has_all_wanted_services =
147 m_node.peerman->InitializeNode(config, *node, NODE_NETWORK);
148 node->nVersion = 1;
149 node->fSuccessfullyConnected = true;
150
151 m_connman->AddTestNode(*node);
152 return node;
153 }
154
155 ProofRef GetProof(CScript payoutScript = UNSPENDABLE_ECREG_PAYOUT_SCRIPT) {
156 const CKey key = CKey::MakeCompressedKey();
157 const COutPoint outpoint{TxId(GetRandHash()), 0};
159 const Amount amount = PROOF_DUST_THRESHOLD;
160 const uint32_t height = 100;
161
162 LOCK(cs_main);
163 CCoinsViewCache &coins =
164 Assert(m_node.chainman)->ActiveChainstate().CoinsTip();
165 coins.AddCoin(outpoint, Coin(CTxOut(amount, script), height, false),
166 false);
167
168 ProofBuilder pb(0, 0, masterpriv, payoutScript);
169 BOOST_CHECK(pb.addUTXO(outpoint, amount, height, false, key));
170 return pb.build();
171 }
172
173 bool addNode(NodeId nodeid, const ProofId &proofid) {
174 return m_node.avalanche->withPeerManager(
175 [&](avalanche::PeerManager &pm) {
176 return pm.addNode(nodeid, proofid);
177 });
178 }
179
180 bool addNode(NodeId nodeid) {
181 auto proof = GetProof();
182 return m_node.avalanche->withPeerManager(
183 [&](avalanche::PeerManager &pm) {
184 return pm.registerProof(proof) &&
185 pm.addNode(nodeid, proof->getId());
186 });
187 }
188
189 std::array<CNode *, 8> ConnectNodes() {
190 auto proof = GetProof();
192 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
193 return pm.registerProof(proof);
194 }));
195 const ProofId &proofid = proof->getId();
196
197 std::array<CNode *, 8> nodes;
198 for (CNode *&n : nodes) {
199 n = ConnectNode(NODE_AVALANCHE);
200 BOOST_CHECK(addNode(n->GetId(), proofid));
201 }
202
203 return nodes;
204 }
205
206 void runEventLoop() { AvalancheTest::runEventLoop(*m_node.avalanche); }
207
208 NodeId getSuitableNodeToQuery() {
209 return AvalancheTest::getSuitableNodeToQuery(*m_node.avalanche);
210 }
211
212 std::vector<CInv> getInvsForNextPoll() {
213 return AvalancheTest::getInvsForNextPoll(*m_node.avalanche);
214 }
215
216 uint64_t getRound() const {
217 return AvalancheTest::getRound(*m_node.avalanche);
218 }
219
220 bool registerVotes(NodeId nodeid, const avalanche::Response &response,
221 std::vector<avalanche::VoteItemUpdate> &updates,
222 std::string &error) {
223 bool disconnect;
224 return m_node.avalanche->registerVotes(nodeid, response, updates,
225 disconnect, error);
226 }
227
228 bool registerVotes(NodeId nodeid, const avalanche::Response &response,
229 std::vector<avalanche::VoteItemUpdate> &updates) {
230 bool disconnect;
231 std::string error;
232 return m_node.avalanche->registerVotes(nodeid, response, updates,
233 disconnect, error);
234 }
235
236 bool addToReconcile(const AnyVoteItem &item) {
237 return m_node.avalanche->addToReconcile(item);
238 }
239
240 void clearInvsNotWorthPolling() {
241 AvalancheTest::clearInvsNotWorthPolling(*m_node.avalanche);
242 }
243};
244
245struct BlockProvider {
246 AvalancheProcessorTestingSetup *fixture;
247 uint32_t invType{MSG_BLOCK};
248
249 BlockProvider(AvalancheProcessorTestingSetup *_fixture)
250 : fixture(_fixture) {}
251
252 CBlockIndex *buildVoteItem() const {
253 CBlock block = fixture->CreateAndProcessBlock({}, CScript());
254 const BlockHash blockHash = block.GetHash();
255
256 LOCK(cs_main);
257 return Assert(fixture->m_node.chainman)
258 ->m_blockman.LookupBlockIndex(blockHash);
259 }
260
261 uint256 getVoteItemId(const CBlockIndex *pindex) const {
262 return pindex->GetBlockHash();
263 }
264
265 std::vector<Vote> buildVotesForItems(uint32_t error,
266 std::vector<CBlockIndex *> &&items) {
267 size_t numItems = items.size();
268
269 std::vector<Vote> votes;
270 votes.reserve(numItems);
271
272 // Votes are sorted by most work first
273 std::sort(items.begin(), items.end(), CBlockIndexWorkComparator());
274 for (auto &item : reverse_iterate(items)) {
275 votes.emplace_back(error, item->GetBlockHash());
276 }
277
278 return votes;
279 }
280
281 void invalidateItem(CBlockIndex *pindex) {
283 pindex->nStatus = pindex->nStatus.withFailed();
284 }
285
286 const CBlockIndex *fromAnyVoteItem(const AnyVoteItem &item) {
287 return std::get<const CBlockIndex *>(item);
288 }
289};
290
291struct ProofProvider {
292 AvalancheProcessorTestingSetup *fixture;
293 uint32_t invType{MSG_AVA_PROOF};
294
295 ProofProvider(AvalancheProcessorTestingSetup *_fixture)
296 : fixture(_fixture) {}
297
298 ProofRef buildVoteItem() const {
299 ProofRef proof = fixture->GetProof();
300 fixture->m_node.avalanche->withPeerManager(
301 [&](avalanche::PeerManager &pm) {
302 BOOST_CHECK(pm.registerProof(proof));
303 });
304 return proof;
305 }
306
307 uint256 getVoteItemId(const ProofRef &proof) const {
308 return proof->getId();
309 }
310
311 std::vector<Vote> buildVotesForItems(uint32_t error,
312 std::vector<ProofRef> &&items) {
313 size_t numItems = items.size();
314
315 std::vector<Vote> votes;
316 votes.reserve(numItems);
317
318 // Votes are sorted by high score first
319 std::sort(items.begin(), items.end(), ProofComparatorByScore());
320 for (auto &item : items) {
321 votes.emplace_back(error, item->getId());
322 }
323
324 return votes;
325 }
326
327 void invalidateItem(const ProofRef &proof) {
328 fixture->m_node.avalanche->withPeerManager(
329 [&](avalanche::PeerManager &pm) {
330 pm.rejectProof(
331 proof->getId(),
333 });
334 }
335
336 ProofRef fromAnyVoteItem(const AnyVoteItem &item) {
337 return std::get<const ProofRef>(item);
338 }
339};
340
341struct StakeContenderProvider {
342 AvalancheProcessorTestingSetup *fixture;
343
344 std::vector<avalanche::VoteItemUpdate> updates;
345 uint32_t invType{MSG_AVA_STAKE_CONTENDER};
346
347 StakeContenderProvider(AvalancheProcessorTestingSetup *_fixture)
348 : fixture(_fixture) {}
349
350 StakeContenderId buildVoteItem() const {
351 ChainstateManager &chainman = *Assert(fixture->m_node.chainman);
352 const CBlockIndex *chaintip =
353 WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip());
354
355 std::vector<CScript> winners;
356 if (!fixture->m_node.avalanche->getStakingRewardWinners(
357 chaintip->GetBlockHash(), winners)) {
358 // If staking rewards are not ready, just set it to some winner.
359 // This ensures getStakeContenderStatus will not return pending.
360 const ProofRef proofWinner = fixture->GetProof();
361 std::vector<CScript> payouts{proofWinner->getPayoutScript()};
362 fixture->m_node.avalanche->setStakingRewardWinners(chaintip,
363 payouts);
364 }
365
366 // Create a new contender
367 const ProofRef proof = fixture->GetProof();
368 const StakeContenderId contenderId(chaintip->GetBlockHash(),
369 proof->getId());
370
371 fixture->m_node.avalanche->withPeerManager(
372 [&](avalanche::PeerManager &pm) { pm.addStakeContender(proof); });
373
374 // Many of these tests assume that building a new item means it is
375 // accepted by default. Contenders are different in that they are
376 // only accepted if they are a stake winner. We stick the the
377 // convention for these tests and accept the contender.
378 fixture->m_node.avalanche->acceptStakeContender(contenderId);
379
380 BOOST_CHECK(fixture->m_node.avalanche->getStakeContenderStatus(
381 contenderId) == 0);
382 return contenderId;
383 }
384
385 uint256 getVoteItemId(const StakeContenderId &contenderId) const {
386 return contenderId;
387 }
388
389 std::vector<Vote>
390 buildVotesForItems(uint32_t error, std::vector<StakeContenderId> &&items) {
391 size_t numItems = items.size();
392
393 std::vector<Vote> votes;
394 votes.reserve(numItems);
395
396 // Contenders are sorted by id
397 std::sort(items.begin(), items.end(),
398 [](const StakeContenderId &lhs, const StakeContenderId &rhs) {
399 return lhs < rhs;
400 });
401 for (auto &item : items) {
402 votes.emplace_back(error, item);
403 }
404
405 return votes;
406 }
407
408 void invalidateItem(const StakeContenderId &contenderId) {
409 fixture->m_node.avalanche->rejectStakeContender(contenderId);
410
411 // Warning: This is a special case for stake contenders because
412 // invalidation does not cause isWorthPolling to return false. This is
413 // because invalidation of contenders is only intended to halt polling.
414 // They will continue to be tracked in the cache, being promoted and
415 // polled again (respective to the proof) for each block.
416 AnyVoteItem contenderVoteItem(contenderId);
417 AvalancheTest::removeVoteRecord(*(fixture->m_node.avalanche),
418 contenderVoteItem);
419 }
420
421 StakeContenderId fromAnyVoteItem(const AnyVoteItem &item) {
422 return std::get<const StakeContenderId>(item);
423 }
424};
425
426struct TxProvider {
427 AvalancheProcessorTestingSetup *fixture;
428
429 std::vector<avalanche::VoteItemUpdate> updates;
430 uint32_t invType{MSG_TX};
431
432 TxProvider(AvalancheProcessorTestingSetup *_fixture) : fixture(_fixture) {}
433
434 CTransactionRef buildVoteItem() const {
436 mtx.nVersion = 2;
437 mtx.vin.emplace_back(COutPoint{TxId(FastRandomContext().rand256()), 0});
438 mtx.vout.emplace_back(1 * COIN, CScript() << OP_TRUE);
439
440 CTransactionRef tx = MakeTransactionRef(std::move(mtx));
441
442 TestMemPoolEntryHelper mempoolEntryHelper;
443 auto entry = mempoolEntryHelper.Fee(1000 * SATOSHI).FromTx(tx);
444
445 CTxMemPool *mempool = Assert(fixture->m_node.mempool.get());
446 {
447 LOCK2(cs_main, mempool->cs);
448 mempool->addUnchecked(entry);
449 BOOST_CHECK(mempool->exists(tx->GetId()));
450 }
451
452 return tx;
453 }
454
455 uint256 getVoteItemId(const CTransactionRef &tx) const {
456 return tx->GetId();
457 }
458
459 std::vector<Vote> buildVotesForItems(uint32_t error,
460 std::vector<CTransactionRef> &&items) {
461 size_t numItems = items.size();
462
463 std::vector<Vote> votes;
464 votes.reserve(numItems);
465
466 // Transactions are sorted by TxId
467 std::sort(items.begin(), items.end(),
468 [](const CTransactionRef &lhs, const CTransactionRef &rhs) {
469 return lhs->GetId() < rhs->GetId();
470 });
471 for (auto &item : items) {
472 votes.emplace_back(error, item->GetId());
473 }
474
475 return votes;
476 }
477
478 void invalidateItem(const CTransactionRef &tx) {
479 BOOST_CHECK(tx != nullptr);
480 CTxMemPool *mempool = Assert(fixture->m_node.mempool.get());
481
482 LOCK(mempool->cs);
484 BOOST_CHECK(!mempool->exists(tx->GetId()));
485 }
486
487 CTransactionRef fromAnyVoteItem(const AnyVoteItem &item) {
488 return std::get<const CTransactionRef>(item);
489 }
490};
491
492} // namespace
493
494BOOST_FIXTURE_TEST_SUITE(processor_tests, AvalancheProcessorTestingSetup)
495
496// FIXME A std::tuple can be used instead of boost::mpl::list after boost 1.67
497using VoteItemProviders = boost::mpl::list<BlockProvider, ProofProvider,
498 StakeContenderProvider, TxProvider>;
500 boost::mpl::list<BlockProvider, ProofProvider, TxProvider>;
501using Uint256VoteItemProviders = boost::mpl::list<StakeContenderProvider>;
502static_assert(boost::mpl::size<VoteItemProviders>::value ==
503 boost::mpl::size<NullableVoteItemProviders>::value +
504 boost::mpl::size<Uint256VoteItemProviders>::value);
505
507 P provider(this);
508
509 std::set<VoteStatus> status{
510 VoteStatus::Invalid, VoteStatus::Rejected, VoteStatus::Accepted,
511 VoteStatus::Finalized, VoteStatus::Stale,
512 };
513
514 auto item = provider.buildVoteItem();
515
516 for (auto s : status) {
517 VoteItemUpdate itemUpdate(item, s);
518 // The use of BOOST_CHECK instead of BOOST_CHECK_EQUAL prevents from
519 // having to define operator<<() for each argument type.
520 BOOST_CHECK(provider.fromAnyVoteItem(itemUpdate.getVoteItem()) == item);
521 BOOST_CHECK(itemUpdate.getStatus() == s);
522 }
523}
524
525namespace {
526Response next(Response &r) {
527 auto copy = r;
528 r = {r.getRound() + 1, r.getCooldown(), r.GetVotes()};
529 return copy;
530}
531} // namespace
532
534 P provider(this);
535 ChainstateManager &chainman = *Assert(m_node.chainman);
536 const CBlockIndex *chaintip =
537 WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip());
538
539 auto item = provider.buildVoteItem();
540 auto itemid = provider.getVoteItemId(item);
541
542 // Adding the item twice does nothing.
543 BOOST_CHECK(addToReconcile(item));
544 BOOST_CHECK(!addToReconcile(item));
545 BOOST_CHECK(m_node.avalanche->isPolled(item));
546 BOOST_CHECK(m_node.avalanche->isAccepted(item));
547
548 // Create nodes that supports avalanche so we can finalize the item.
549 auto avanodes = ConnectNodes();
550
551 int nextNodeIndex = 0;
552 std::vector<avalanche::VoteItemUpdate> updates;
553 auto registerNewVote = [&](const Response &resp) {
554 runEventLoop();
555 auto nodeid = avanodes[nextNodeIndex++ % avanodes.size()]->GetId();
556 BOOST_CHECK(registerVotes(nodeid, resp, updates));
557 };
558
559 // Finalize the item.
560 auto finalize = [&](const auto finalizeItemId) {
561 Response resp = {getRound(), 0, {Vote(0, finalizeItemId)}};
562 for (int i = 0; i < AVALANCHE_FINALIZATION_SCORE + 6; i++) {
563 registerNewVote(next(resp));
564 if (updates.size() > 0) {
565 break;
566 }
567 }
568 BOOST_CHECK_EQUAL(updates.size(), 1);
569 BOOST_CHECK(updates[0].getStatus() == VoteStatus::Finalized);
570 m_node.avalanche->setRecentlyFinalized(finalizeItemId);
571 };
572 finalize(itemid);
573
574 // The finalized item cannot be reconciled for a while.
575 BOOST_CHECK(!addToReconcile(item));
576
577 auto finalizeNewItem = [&]() {
578 auto anotherItem = provider.buildVoteItem();
579 AnyVoteItem anotherVoteItem = AnyVoteItem(anotherItem);
580 auto anotherItemId = provider.getVoteItemId(anotherItem);
581
583 AvalancheTest::addVoteRecord(*m_node.avalanche, anotherVoteItem,
584 voteRecord);
585 finalize(anotherItemId);
586 };
587
588 // The filter can have new items added up to its size and the item will
589 // still not reconcile.
590 for (uint32_t i = 0; i < AVALANCHE_FINALIZED_ITEMS_FILTER_NUM_ELEMENTS;
591 i++) {
592 finalizeNewItem();
593 BOOST_CHECK(!addToReconcile(item));
594 }
595
596 // But if we keep going it will eventually roll out of the filter and can
597 // be reconciled again.
598 for (uint32_t i = 0; i < AVALANCHE_FINALIZED_ITEMS_FILTER_NUM_ELEMENTS;
599 i++) {
600 finalizeNewItem();
601 }
602
603 // Roll back the finalization point so that reconciling the old block does
604 // not fail the finalization check. This is a no-op for other types.
605 AvalancheTest::setFinalizationTip(*m_node.avalanche, chaintip);
606
607 BOOST_CHECK(addToReconcile(item));
608}
609
611 P provider(this);
612
613 // Check that null case is handled on the public interface
614 BOOST_CHECK(!m_node.avalanche->isPolled(nullptr));
615 BOOST_CHECK(!m_node.avalanche->isAccepted(nullptr));
616 BOOST_CHECK_EQUAL(m_node.avalanche->getConfidence(nullptr), -1);
617
618 auto item = decltype(provider.buildVoteItem())();
619 BOOST_CHECK(item == nullptr);
620 BOOST_CHECK(!addToReconcile(item));
621
622 // Check that adding item to vote on doesn't change the outcome. A
623 // comparator is used under the hood, and this is skipped if there are no
624 // vote records.
625 item = provider.buildVoteItem();
626 BOOST_CHECK(addToReconcile(item));
627
628 BOOST_CHECK(!m_node.avalanche->isPolled(nullptr));
629 BOOST_CHECK(!m_node.avalanche->isAccepted(nullptr));
630 BOOST_CHECK_EQUAL(m_node.avalanche->getConfidence(nullptr), -1);
631}
632
634 P provider(this);
635
636 auto itemZero = decltype(provider.buildVoteItem())();
637
638 // Check that zero case is handled on the public interface
639 BOOST_CHECK(!m_node.avalanche->isPolled(itemZero));
640 BOOST_CHECK(!m_node.avalanche->isAccepted(itemZero));
641 BOOST_CHECK_EQUAL(m_node.avalanche->getConfidence(itemZero), -1);
642
643 BOOST_CHECK(itemZero == uint256::ZERO);
644 BOOST_CHECK(!addToReconcile(itemZero));
645
646 // Check that adding item to vote on doesn't change the outcome. A
647 // comparator is used under the hood, and this is skipped if there are no
648 // vote records.
649 auto item = provider.buildVoteItem();
650 BOOST_CHECK(addToReconcile(item));
651
652 BOOST_CHECK(!m_node.avalanche->isPolled(itemZero));
653 BOOST_CHECK(!m_node.avalanche->isAccepted(itemZero));
654 BOOST_CHECK_EQUAL(m_node.avalanche->getConfidence(itemZero), -1);
655}
656
658 P provider(this);
659 const uint32_t invType = provider.invType;
660
661 auto item = provider.buildVoteItem();
662 auto itemid = provider.getVoteItemId(item);
663
664 // Create nodes that supports avalanche.
665 auto avanodes = ConnectNodes();
666
667 // Querying for random item returns false.
668 BOOST_CHECK(!m_node.avalanche->isPolled(item));
669 BOOST_CHECK(!m_node.avalanche->isAccepted(item));
670
671 // Add a new item. Check it is added to the polls.
672 BOOST_CHECK(addToReconcile(item));
673 auto invs = getInvsForNextPoll();
674 BOOST_CHECK_EQUAL(invs.size(), 1);
675 BOOST_CHECK_EQUAL(invs[0].type, invType);
676 BOOST_CHECK(invs[0].hash == itemid);
677
678 BOOST_CHECK(m_node.avalanche->isPolled(item));
679 BOOST_CHECK(m_node.avalanche->isAccepted(item));
680
681 int nextNodeIndex = 0;
682 std::vector<avalanche::VoteItemUpdate> updates;
683 auto registerNewVote = [&](const Response &resp) {
684 runEventLoop();
685 auto nodeid = avanodes[nextNodeIndex++ % avanodes.size()]->GetId();
686 BOOST_CHECK(registerVotes(nodeid, resp, updates));
687 };
688
689 // Let's vote for this item a few times.
690 Response resp{0, 0, {Vote(0, itemid)}};
691 for (int i = 0; i < 6; i++) {
692 registerNewVote(next(resp));
693 BOOST_CHECK(m_node.avalanche->isAccepted(item));
694 BOOST_CHECK_EQUAL(m_node.avalanche->getConfidence(item), 0);
695 BOOST_CHECK_EQUAL(updates.size(), 0);
696 }
697
698 // A single neutral vote do not change anything.
699 resp = {getRound(), 0, {Vote(-1, itemid)}};
700 registerNewVote(next(resp));
701 BOOST_CHECK(m_node.avalanche->isAccepted(item));
702 BOOST_CHECK_EQUAL(m_node.avalanche->getConfidence(item), 0);
703 BOOST_CHECK_EQUAL(updates.size(), 0);
704
705 resp = {getRound(), 0, {Vote(0, itemid)}};
706 for (int i = 1; i < 7; i++) {
707 registerNewVote(next(resp));
708 BOOST_CHECK(m_node.avalanche->isAccepted(item));
709 BOOST_CHECK_EQUAL(m_node.avalanche->getConfidence(item), i);
710 BOOST_CHECK_EQUAL(updates.size(), 0);
711 }
712
713 // Two neutral votes will stall progress.
714 resp = {getRound(), 0, {Vote(-1, itemid)}};
715 registerNewVote(next(resp));
716 BOOST_CHECK(m_node.avalanche->isAccepted(item));
717 BOOST_CHECK_EQUAL(m_node.avalanche->getConfidence(item), 6);
718 BOOST_CHECK_EQUAL(updates.size(), 0);
719 registerNewVote(next(resp));
720 BOOST_CHECK(m_node.avalanche->isAccepted(item));
721 BOOST_CHECK_EQUAL(m_node.avalanche->getConfidence(item), 6);
722 BOOST_CHECK_EQUAL(updates.size(), 0);
723
724 resp = {getRound(), 0, {Vote(0, itemid)}};
725 for (int i = 2; i < 8; i++) {
726 registerNewVote(next(resp));
727 BOOST_CHECK(m_node.avalanche->isAccepted(item));
728 BOOST_CHECK_EQUAL(m_node.avalanche->getConfidence(item), 6);
729 BOOST_CHECK_EQUAL(updates.size(), 0);
730 }
731
732 // We vote for it numerous times to finalize it.
733 for (int i = 7; i < AVALANCHE_FINALIZATION_SCORE; i++) {
734 registerNewVote(next(resp));
735 BOOST_CHECK(m_node.avalanche->isAccepted(item));
736 BOOST_CHECK_EQUAL(m_node.avalanche->getConfidence(item), i);
737 BOOST_CHECK_EQUAL(updates.size(), 0);
738 }
739
740 // As long as it is not finalized, we poll.
741 invs = getInvsForNextPoll();
742 BOOST_CHECK_EQUAL(invs.size(), 1);
743 BOOST_CHECK_EQUAL(invs[0].type, invType);
744 BOOST_CHECK(invs[0].hash == itemid);
745
746 // Now finalize the decision.
747 registerNewVote(next(resp));
748 BOOST_CHECK_EQUAL(updates.size(), 1);
749 BOOST_CHECK(provider.fromAnyVoteItem(updates[0].getVoteItem()) == item);
750 BOOST_CHECK(updates[0].getStatus() == VoteStatus::Finalized);
751
752 // Once the decision is finalized, there is no poll for it.
753 invs = getInvsForNextPoll();
754 BOOST_CHECK_EQUAL(invs.size(), 0);
755
756 // Get a new item to vote on
757 item = provider.buildVoteItem();
758 itemid = provider.getVoteItemId(item);
759 BOOST_CHECK(addToReconcile(item));
760
761 // Now let's finalize rejection.
762 invs = getInvsForNextPoll();
763 BOOST_CHECK_EQUAL(invs.size(), 1);
764 BOOST_CHECK_EQUAL(invs[0].type, invType);
765 BOOST_CHECK(invs[0].hash == itemid);
766
767 resp = {getRound(), 0, {Vote(1, itemid)}};
768 for (int i = 0; i < 6; i++) {
769 registerNewVote(next(resp));
770 BOOST_CHECK(m_node.avalanche->isAccepted(item));
771 BOOST_CHECK_EQUAL(updates.size(), 0);
772 }
773
774 // Now the state will flip.
775 registerNewVote(next(resp));
776 BOOST_CHECK(!m_node.avalanche->isAccepted(item));
777 BOOST_CHECK_EQUAL(updates.size(), 1);
778 BOOST_CHECK(provider.fromAnyVoteItem(updates[0].getVoteItem()) == item);
779 BOOST_CHECK(updates[0].getStatus() == VoteStatus::Rejected);
780
781 // Now it is rejected, but we can vote for it numerous times.
782 for (int i = 1; i < AVALANCHE_FINALIZATION_SCORE; i++) {
783 registerNewVote(next(resp));
784 BOOST_CHECK(!m_node.avalanche->isAccepted(item));
785 BOOST_CHECK_EQUAL(updates.size(), 0);
786 }
787
788 // As long as it is not finalized, we poll.
789 invs = getInvsForNextPoll();
790 BOOST_CHECK_EQUAL(invs.size(), 1);
791 BOOST_CHECK_EQUAL(invs[0].type, invType);
792 BOOST_CHECK(invs[0].hash == itemid);
793
794 // Now finalize the decision.
795 registerNewVote(next(resp));
796 BOOST_CHECK(!m_node.avalanche->isAccepted(item));
797 BOOST_CHECK_EQUAL(updates.size(), 1);
798 BOOST_CHECK(provider.fromAnyVoteItem(updates[0].getVoteItem()) == item);
799 BOOST_CHECK(updates[0].getStatus() == VoteStatus::Invalid);
800
801 // Once the decision is finalized, there is no poll for it.
802 invs = getInvsForNextPoll();
803 BOOST_CHECK_EQUAL(invs.size(), 0);
804}
805
807 P provider(this);
808 const uint32_t invType = provider.invType;
809
810 auto itemA = provider.buildVoteItem();
811 auto itemidA = provider.getVoteItemId(itemA);
812
813 auto itemB = provider.buildVoteItem();
814 auto itemidB = provider.getVoteItemId(itemB);
815
816 // Create several nodes that support avalanche.
817 auto avanodes = ConnectNodes();
818
819 // Querying for random item returns false.
820 BOOST_CHECK(!m_node.avalanche->isAccepted(itemA));
821 BOOST_CHECK(!m_node.avalanche->isAccepted(itemB));
822
823 // Start voting on item A.
824 BOOST_CHECK(addToReconcile(itemA));
825 auto invs = getInvsForNextPoll();
826 BOOST_CHECK_EQUAL(invs.size(), 1);
827 BOOST_CHECK_EQUAL(invs[0].type, invType);
828 BOOST_CHECK(invs[0].hash == itemidA);
829
830 uint64_t round = getRound();
831 runEventLoop();
832 std::vector<avalanche::VoteItemUpdate> updates;
833 BOOST_CHECK(registerVotes(avanodes[0]->GetId(),
834 {round, 0, {Vote(0, itemidA)}}, updates));
835 BOOST_CHECK_EQUAL(updates.size(), 0);
836
837 // Start voting on item B after one vote.
838 std::vector<Vote> votes = provider.buildVotesForItems(0, {itemA, itemB});
839 Response resp{round + 1, 0, votes};
840 BOOST_CHECK(addToReconcile(itemB));
841 invs = getInvsForNextPoll();
842 BOOST_CHECK_EQUAL(invs.size(), 2);
843
844 // Ensure the inv ordering is as expected
845 for (size_t i = 0; i < invs.size(); i++) {
846 BOOST_CHECK_EQUAL(invs[i].type, invType);
847 BOOST_CHECK(invs[i].hash == votes[i].GetHash());
848 }
849
850 // Let's vote for these items a few times.
851 for (int i = 0; i < 4; i++) {
852 NodeId nodeid = getSuitableNodeToQuery();
853 runEventLoop();
854 BOOST_CHECK(registerVotes(nodeid, next(resp), updates));
855 BOOST_CHECK_EQUAL(updates.size(), 0);
856 }
857
858 // Now it is accepted, but we can vote for it numerous times.
859 for (int i = 0; i < AVALANCHE_FINALIZATION_SCORE; i++) {
860 NodeId nodeid = getSuitableNodeToQuery();
861 runEventLoop();
862 BOOST_CHECK(registerVotes(nodeid, next(resp), updates));
863 BOOST_CHECK_EQUAL(updates.size(), 0);
864 }
865
866 // Running two iterration of the event loop so that vote gets triggered on A
867 // and B.
868 NodeId firstNodeid = getSuitableNodeToQuery();
869 runEventLoop();
870 NodeId secondNodeid = getSuitableNodeToQuery();
871 runEventLoop();
872
873 BOOST_CHECK(firstNodeid != secondNodeid);
874
875 // Next vote will finalize item A.
876 BOOST_CHECK(registerVotes(firstNodeid, next(resp), updates));
877 BOOST_CHECK_EQUAL(updates.size(), 1);
878 BOOST_CHECK(provider.fromAnyVoteItem(updates[0].getVoteItem()) == itemA);
879 BOOST_CHECK(updates[0].getStatus() == VoteStatus::Finalized);
880
881 // We do not vote on A anymore.
882 invs = getInvsForNextPoll();
883 BOOST_CHECK_EQUAL(invs.size(), 1);
884 BOOST_CHECK_EQUAL(invs[0].type, invType);
885 BOOST_CHECK(invs[0].hash == itemidB);
886
887 // Next vote will finalize item B.
888 BOOST_CHECK(registerVotes(secondNodeid, resp, updates));
889 BOOST_CHECK_EQUAL(updates.size(), 1);
890 BOOST_CHECK(provider.fromAnyVoteItem(updates[0].getVoteItem()) == itemB);
891 BOOST_CHECK(updates[0].getStatus() == VoteStatus::Finalized);
892
893 // There is nothing left to vote on.
894 invs = getInvsForNextPoll();
895 BOOST_CHECK_EQUAL(invs.size(), 0);
896}
897
899 P provider(this);
900 const uint32_t invType = provider.invType;
901
902 auto item = provider.buildVoteItem();
903 auto itemid = provider.getVoteItemId(item);
904
905 // There is no node to query.
906 BOOST_CHECK_EQUAL(getSuitableNodeToQuery(), NO_NODE);
907
908 // Add enough nodes to have a valid quorum, and the same amount with no
909 // avalanche support
910 std::set<NodeId> avanodeIds;
911 auto avanodes = ConnectNodes();
912 for (auto avanode : avanodes) {
913 ConnectNode(NODE_NONE);
914 avanodeIds.insert(avanode->GetId());
915 }
916
917 auto getSelectedAvanodeId = [&]() {
918 NodeId avanodeid = getSuitableNodeToQuery();
919 BOOST_CHECK(avanodeIds.find(avanodeid) != avanodeIds.end());
920 return avanodeid;
921 };
922
923 // It returns one of the avalanche peer.
924 NodeId avanodeid = getSelectedAvanodeId();
925
926 // Register an item and check it is added to the list of elements to poll.
927 BOOST_CHECK(addToReconcile(item));
928 auto invs = getInvsForNextPoll();
929 BOOST_CHECK_EQUAL(invs.size(), 1);
930 BOOST_CHECK_EQUAL(invs[0].type, invType);
931 BOOST_CHECK(invs[0].hash == itemid);
932
933 std::set<NodeId> unselectedNodeids = avanodeIds;
934 unselectedNodeids.erase(avanodeid);
935 const size_t remainingNodeIds = unselectedNodeids.size();
936
937 uint64_t round = getRound();
938 for (size_t i = 0; i < remainingNodeIds; i++) {
939 // Trigger a poll on avanode.
940 runEventLoop();
941
942 // Another node is selected
943 NodeId nodeid = getSuitableNodeToQuery();
944 BOOST_CHECK(unselectedNodeids.find(nodeid) != avanodeIds.end());
945 unselectedNodeids.erase(nodeid);
946 }
947
948 // There is no more suitable peer available, so return nothing.
949 BOOST_CHECK(unselectedNodeids.empty());
950 runEventLoop();
951 BOOST_CHECK_EQUAL(getSuitableNodeToQuery(), NO_NODE);
952
953 // Respond to the request.
954 Response resp = {round, 0, {Vote(0, itemid)}};
955 std::vector<avalanche::VoteItemUpdate> updates;
956 BOOST_CHECK(registerVotes(avanodeid, resp, updates));
957 BOOST_CHECK_EQUAL(updates.size(), 0);
958
959 // Now that avanode fullfilled his request, it is added back to the list of
960 // queriable nodes.
961 BOOST_CHECK_EQUAL(getSuitableNodeToQuery(), avanodeid);
962
963 auto checkRegisterVotesError = [&](NodeId nodeid,
965 const std::string &expectedError) {
966 std::string error;
967 BOOST_CHECK(!registerVotes(nodeid, response, updates, error));
968 BOOST_CHECK_EQUAL(error, expectedError);
969 BOOST_CHECK_EQUAL(updates.size(), 0);
970 };
971
972 // Sending a response when not polled fails.
973 checkRegisterVotesError(avanodeid, next(resp), "unexpected-ava-response");
974
975 // Trigger a poll on avanode.
976 round = getRound();
977 runEventLoop();
978 BOOST_CHECK_EQUAL(getSuitableNodeToQuery(), NO_NODE);
979
980 // Sending responses that do not match the request also fails.
981 // 1. Too many results.
982 resp = {round, 0, {Vote(0, itemid), Vote(0, itemid)}};
983 runEventLoop();
984 checkRegisterVotesError(avanodeid, resp, "invalid-ava-response-size");
985 BOOST_CHECK_EQUAL(getSuitableNodeToQuery(), avanodeid);
986
987 // 2. Not enough results.
988 resp = {getRound(), 0, {}};
989 runEventLoop();
990 checkRegisterVotesError(avanodeid, resp, "invalid-ava-response-size");
991 BOOST_CHECK_EQUAL(getSuitableNodeToQuery(), avanodeid);
992
993 // 3. Do not match the poll.
994 resp = {getRound(), 0, {Vote()}};
995 runEventLoop();
996 checkRegisterVotesError(avanodeid, resp, "invalid-ava-response-content");
997 BOOST_CHECK_EQUAL(getSuitableNodeToQuery(), avanodeid);
998
999 // At this stage we have reached the max inflight requests for our inv, so
1000 // it won't be requested anymore until the requests are fullfilled. Let's
1001 // vote on another item with no inflight request so the remaining tests
1002 // makes sense.
1003 invs = getInvsForNextPoll();
1004 BOOST_CHECK(invs.empty());
1005
1006 item = provider.buildVoteItem();
1007 itemid = provider.getVoteItemId(item);
1008 BOOST_CHECK(addToReconcile(item));
1009
1010 invs = getInvsForNextPoll();
1011 BOOST_CHECK_EQUAL(invs.size(), 1);
1012
1013 // 4. Invalid round count. Request is not discarded.
1014 uint64_t queryRound = getRound();
1015 runEventLoop();
1016
1017 resp = {queryRound + 1, 0, {Vote()}};
1018 checkRegisterVotesError(avanodeid, resp, "unexpected-ava-response");
1019
1020 resp = {queryRound - 1, 0, {Vote()}};
1021 checkRegisterVotesError(avanodeid, resp, "unexpected-ava-response");
1022
1023 // 5. Making request for invalid nodes do not work. Request is not
1024 // discarded.
1025 resp = {queryRound, 0, {Vote(0, itemid)}};
1026 checkRegisterVotesError(avanodeid + 1234, resp, "unexpected-ava-response");
1027
1028 // Proper response gets processed and avanode is available again.
1029 resp = {queryRound, 0, {Vote(0, itemid)}};
1030 BOOST_CHECK(registerVotes(avanodeid, resp, updates));
1031 BOOST_CHECK_EQUAL(updates.size(), 0);
1032 BOOST_CHECK_EQUAL(getSuitableNodeToQuery(), avanodeid);
1033
1034 // Out of order response are rejected.
1035 const auto item2 = provider.buildVoteItem();
1036 BOOST_CHECK(addToReconcile(item2));
1037
1038 std::vector<Vote> votes = provider.buildVotesForItems(0, {item, item2});
1039 resp = {getRound(), 0, {votes[1], votes[0]}};
1040 runEventLoop();
1041 checkRegisterVotesError(avanodeid, resp, "invalid-ava-response-content");
1042 BOOST_CHECK_EQUAL(getSuitableNodeToQuery(), avanodeid);
1043
1044 // But they are accepted in order.
1045 resp = {getRound(), 0, votes};
1046 runEventLoop();
1047 BOOST_CHECK(registerVotes(avanodeid, resp, updates));
1048 BOOST_CHECK_EQUAL(updates.size(), 0);
1049 BOOST_CHECK_EQUAL(getSuitableNodeToQuery(), avanodeid);
1050}
1051
1053 P provider(this);
1054 const uint32_t invType = provider.invType;
1055
1056 auto itemA = provider.buildVoteItem();
1057 auto itemB = provider.buildVoteItem();
1058
1059 auto avanodes = ConnectNodes();
1060 int nextNodeIndex = 0;
1061
1062 // Build votes to get proper ordering
1063 std::vector<Vote> votes = provider.buildVotesForItems(0, {itemA, itemB});
1064
1065 // Register the items and check they are added to the list of elements to
1066 // poll.
1067 BOOST_CHECK(addToReconcile(itemA));
1068 BOOST_CHECK(addToReconcile(itemB));
1069 auto invs = getInvsForNextPoll();
1070 BOOST_CHECK_EQUAL(invs.size(), 2);
1071 for (size_t i = 0; i < invs.size(); i++) {
1072 BOOST_CHECK_EQUAL(invs[i].type, invType);
1073 BOOST_CHECK(invs[i].hash == votes[i].GetHash());
1074 }
1075
1076 // When an item is marked invalid, stop polling.
1077 provider.invalidateItem(itemB);
1078
1079 Response goodResp{getRound(), 0, {Vote(0, provider.getVoteItemId(itemA))}};
1080 std::vector<avalanche::VoteItemUpdate> updates;
1081 runEventLoop();
1083 registerVotes(avanodes[nextNodeIndex++ % avanodes.size()]->GetId(),
1084 goodResp, updates));
1085 BOOST_CHECK_EQUAL(updates.size(), 0);
1086
1087 // Verify itemB is no longer being polled for
1088 invs = getInvsForNextPoll();
1089 BOOST_CHECK_EQUAL(invs.size(), 1);
1090 BOOST_CHECK_EQUAL(invs[0].type, invType);
1091 BOOST_CHECK(invs[0].hash == goodResp.GetVotes()[0].GetHash());
1092
1093 // Votes including itemB are rejected
1094 Response badResp{getRound(), 0, votes};
1095 runEventLoop();
1096 std::string error;
1098 !registerVotes(avanodes[nextNodeIndex++ % avanodes.size()]->GetId(),
1099 badResp, updates, error));
1100 BOOST_CHECK_EQUAL(error, "invalid-ava-response-size");
1101
1102 // Vote until itemA is invalidated by avalanche
1103 votes = provider.buildVotesForItems(1, {itemA});
1104 auto registerNewVote = [&]() {
1105 Response resp = {getRound(), 0, votes};
1106 runEventLoop();
1107 auto nodeid = avanodes[nextNodeIndex++ % avanodes.size()]->GetId();
1108 BOOST_CHECK(registerVotes(nodeid, resp, updates));
1109 };
1110 for (size_t i = 0; i < 4000; i++) {
1111 registerNewVote();
1112 if (updates.size() > 0 &&
1113 updates[0].getStatus() == VoteStatus::Invalid) {
1114 break;
1115 }
1116 }
1117
1118 // Verify itemA is no longer being polled for
1119 invs = getInvsForNextPoll();
1120 BOOST_CHECK_EQUAL(invs.size(), 0);
1121
1122 // Votes including itemA are rejected
1123 badResp = Response(getRound(), 0, votes);
1124 runEventLoop();
1126 !registerVotes(avanodes[nextNodeIndex++ % avanodes.size()]->GetId(),
1127 badResp, updates, error));
1128 BOOST_CHECK_EQUAL(error, "unexpected-ava-response");
1129}
1130
1131BOOST_TEST_DECORATOR(*boost::unit_test::timeout(60))
1133 P provider(this);
1134 ChainstateManager &chainman = *Assert(m_node.chainman);
1135
1136 auto queryTimeDuration = std::chrono::milliseconds(10);
1137 setArg("-avatimeout", ToString(queryTimeDuration.count()));
1138 // This would fail the test for blocks
1139 setArg("-avalanchestakingpreconsensus", "0");
1140
1141 bilingual_str error;
1142 m_node.avalanche = Processor::MakeProcessor(
1143 *m_node.args, *m_node.chain, m_node.connman.get(), chainman,
1144 m_node.mempool.get(), *m_node.scheduler, error);
1145
1146 const auto item = provider.buildVoteItem();
1147 const auto itemid = provider.getVoteItemId(item);
1148
1149 // Add the item
1150 BOOST_CHECK(addToReconcile(item));
1151
1152 // Create a quorum of nodes that support avalanche.
1153 ConnectNodes();
1154 NodeId avanodeid = NO_NODE;
1155
1156 // Expire requests after some time.
1157 for (int i = 0; i < 10; i++) {
1158 Response resp = {getRound(), 0, {Vote(0, itemid)}};
1159 avanodeid = getSuitableNodeToQuery();
1160
1161 auto start = Now<SteadyMilliseconds>();
1162 runEventLoop();
1163 // We cannot guarantee that we'll wait for just 1ms, so we have to bail
1164 // if we aren't within the proper time range.
1165 std::this_thread::sleep_for(std::chrono::milliseconds(1));
1166 runEventLoop();
1167
1168 std::vector<avalanche::VoteItemUpdate> updates;
1169 bool ret = registerVotes(avanodeid, next(resp), updates);
1170 if (Now<SteadyMilliseconds>() > start + queryTimeDuration) {
1171 // We waited for too long, bail. Because we can't know for sure when
1172 // previous steps ran, ret is not deterministic and we do not check
1173 // it.
1174 i--;
1175 continue;
1176 }
1177
1178 // We are within time bounds, so the vote should have worked.
1179 BOOST_CHECK(ret);
1180
1181 avanodeid = getSuitableNodeToQuery();
1182
1183 // Now try again but wait for expiration.
1184 runEventLoop();
1185 std::this_thread::sleep_for(queryTimeDuration);
1186 runEventLoop();
1187 BOOST_CHECK(!registerVotes(avanodeid, next(resp), updates));
1188 }
1189}
1190
1192 P provider(this);
1193 const uint32_t invType = provider.invType;
1194
1195 // Create enough nodes so that we run into the inflight request limit.
1196 auto proof = GetProof();
1197 BOOST_CHECK(m_node.avalanche->withPeerManager(
1198 [&](avalanche::PeerManager &pm) { return pm.registerProof(proof); }));
1199
1200 std::array<CNode *, AVALANCHE_MAX_INFLIGHT_POLL + 1> nodes;
1201 for (auto &n : nodes) {
1202 n = ConnectNode(NODE_AVALANCHE);
1203 BOOST_CHECK(addNode(n->GetId(), proof->getId()));
1204 }
1205
1206 // Add an item to poll
1207 const auto item = provider.buildVoteItem();
1208 const auto itemid = provider.getVoteItemId(item);
1209 BOOST_CHECK(addToReconcile(item));
1210
1211 // Ensure there are enough requests in flight.
1212 std::map<NodeId, uint64_t> node_round_map;
1213 for (int i = 0; i < AVALANCHE_MAX_INFLIGHT_POLL; i++) {
1214 NodeId nodeid = getSuitableNodeToQuery();
1215 BOOST_CHECK(node_round_map.find(nodeid) == node_round_map.end());
1216 node_round_map.insert(std::pair<NodeId, uint64_t>(nodeid, getRound()));
1217 auto invs = getInvsForNextPoll();
1218 BOOST_CHECK_EQUAL(invs.size(), 1);
1219 BOOST_CHECK_EQUAL(invs[0].type, invType);
1220 BOOST_CHECK(invs[0].hash == itemid);
1221 runEventLoop();
1222 }
1223
1224 // Now that we have enough in flight requests, we shouldn't poll.
1225 auto suitablenodeid = getSuitableNodeToQuery();
1226 BOOST_CHECK(suitablenodeid != NO_NODE);
1227 auto invs = getInvsForNextPoll();
1228 BOOST_CHECK_EQUAL(invs.size(), 0);
1229 runEventLoop();
1230 BOOST_CHECK_EQUAL(getSuitableNodeToQuery(), suitablenodeid);
1231
1232 // Send one response, now we can poll again.
1233 auto it = node_round_map.begin();
1234 Response resp = {it->second, 0, {Vote(0, itemid)}};
1235 std::vector<avalanche::VoteItemUpdate> updates;
1236 BOOST_CHECK(registerVotes(it->first, resp, updates));
1237 node_round_map.erase(it);
1238
1239 invs = getInvsForNextPoll();
1240 BOOST_CHECK_EQUAL(invs.size(), 1);
1241 BOOST_CHECK_EQUAL(invs[0].type, invType);
1242 BOOST_CHECK(invs[0].hash == itemid);
1243}
1244
1245BOOST_AUTO_TEST_CASE(quorum_diversity) {
1246 std::vector<VoteItemUpdate> updates;
1247
1248 CBlock block = CreateAndProcessBlock({}, CScript());
1249 const BlockHash blockHash = block.GetHash();
1250 const CBlockIndex *pindex;
1251 {
1252 LOCK(cs_main);
1253 pindex =
1254 Assert(m_node.chainman)->m_blockman.LookupBlockIndex(blockHash);
1255 }
1256
1257 // Create nodes that supports avalanche.
1258 auto avanodes = ConnectNodes();
1259
1260 // Querying for random block returns false.
1261 BOOST_CHECK(!m_node.avalanche->isAccepted(pindex));
1262
1263 // Add a new block. Check it is added to the polls.
1264 BOOST_CHECK(m_node.avalanche->addToReconcile(pindex));
1265
1266 // Do one valid round of voting.
1267 uint64_t round = getRound();
1268 Response resp{round, 0, {Vote(0, blockHash)}};
1269
1270 // Check that all nodes can vote.
1271 for (size_t i = 0; i < avanodes.size(); i++) {
1272 runEventLoop();
1273 BOOST_CHECK(registerVotes(avanodes[i]->GetId(), next(resp), updates));
1274 }
1275
1276 // Generate a query for every single node.
1277 const NodeId firstNodeId = getSuitableNodeToQuery();
1278 std::map<NodeId, uint64_t> node_round_map;
1279 round = getRound();
1280 for (size_t i = 0; i < avanodes.size(); i++) {
1281 NodeId nodeid = getSuitableNodeToQuery();
1282 BOOST_CHECK(node_round_map.find(nodeid) == node_round_map.end());
1283 node_round_map[nodeid] = getRound();
1284 runEventLoop();
1285 }
1286
1287 // Now only the first node can vote. All others would be duplicate in the
1288 // quorum.
1289 auto confidence = m_node.avalanche->getConfidence(pindex);
1290 BOOST_REQUIRE(confidence > 0);
1291
1292 for (auto &[nodeid, r] : node_round_map) {
1293 if (nodeid == firstNodeId) {
1294 // Node 0 is the only one which can vote at this stage.
1295 round = r;
1296 continue;
1297 }
1298
1300 registerVotes(nodeid, {r, 0, {Vote(0, blockHash)}}, updates));
1301 BOOST_CHECK_EQUAL(m_node.avalanche->getConfidence(pindex), confidence);
1302 }
1303
1305 registerVotes(firstNodeId, {round, 0, {Vote(0, blockHash)}}, updates));
1306 BOOST_CHECK_EQUAL(m_node.avalanche->getConfidence(pindex), confidence + 1);
1307}
1308
1310 CScheduler s;
1311
1312 CBlock block = CreateAndProcessBlock({}, CScript());
1313 const BlockHash blockHash = block.GetHash();
1314 const CBlockIndex *pindex;
1315 {
1316 LOCK(cs_main);
1317 pindex =
1318 Assert(m_node.chainman)->m_blockman.LookupBlockIndex(blockHash);
1319 }
1320
1321 // Starting the event loop.
1322 BOOST_CHECK(m_node.avalanche->startEventLoop(s));
1323
1324 // There is one task planned in the next hour (our event loop).
1325 std::chrono::steady_clock::time_point start, stop;
1326 BOOST_CHECK_EQUAL(s.getQueueInfo(start, stop), 1);
1327
1328 // Starting twice doesn't start it twice.
1329 BOOST_CHECK(!m_node.avalanche->startEventLoop(s));
1330
1331 // Start the scheduler thread.
1332 std::thread schedulerThread(std::bind(&CScheduler::serviceQueue, &s));
1333
1334 // Create a quorum of nodes that support avalanche.
1335 auto avanodes = ConnectNodes();
1336
1337 // There is no query in flight at the moment.
1338 NodeId nodeid = getSuitableNodeToQuery();
1339 BOOST_CHECK_NE(nodeid, NO_NODE);
1340
1341 // Add a new block. Check it is added to the polls.
1342 uint64_t queryRound = getRound();
1343 BOOST_CHECK(m_node.avalanche->addToReconcile(pindex));
1344
1345 // Wait until all nodes got a poll
1346 for (int i = 0; i < 60 * 1000; i++) {
1347 // Technically, this is a race condition, but this should do just fine
1348 // as we wait up to 1 minute for an event that should take 80ms.
1349 UninterruptibleSleep(std::chrono::milliseconds(1));
1350 if (getRound() == queryRound + avanodes.size()) {
1351 break;
1352 }
1353 }
1354
1355 // Check that we effectively got a request and not timed out.
1356 BOOST_CHECK(getRound() > queryRound);
1357
1358 // Respond and check the cooldown time is respected.
1359 uint64_t responseRound = getRound();
1360 auto queryTime = Now<SteadyMilliseconds>() + std::chrono::milliseconds(100);
1361
1362 std::vector<VoteItemUpdate> updates;
1363 // Only the first node answers, so it's the only one that gets polled again
1364 BOOST_CHECK(registerVotes(nodeid, {queryRound, 100, {Vote(0, blockHash)}},
1365 updates));
1366
1367 for (int i = 0; i < 10000; i++) {
1368 // We make sure that we do not get a request before queryTime.
1369 UninterruptibleSleep(std::chrono::milliseconds(1));
1370 if (getRound() != responseRound) {
1371 BOOST_CHECK(Now<SteadyMilliseconds>() >= queryTime);
1372 break;
1373 }
1374 }
1375
1376 // But we eventually get one.
1377 BOOST_CHECK(getRound() > responseRound);
1378
1379 // Stop event loop.
1380 BOOST_CHECK(m_node.avalanche->stopEventLoop());
1381
1382 // We don't have any task scheduled anymore.
1383 BOOST_CHECK_EQUAL(s.getQueueInfo(start, stop), 0);
1384
1385 // Can't stop the event loop twice.
1386 BOOST_CHECK(!m_node.avalanche->stopEventLoop());
1387
1388 // Wait for the scheduler to stop.
1389 s.StopWhenDrained();
1390 schedulerThread.join();
1391}
1392
1394 CScheduler s;
1395 std::chrono::steady_clock::time_point start, stop;
1396
1397 std::thread schedulerThread;
1398 BOOST_CHECK(m_node.avalanche->startEventLoop(s));
1399 BOOST_CHECK_EQUAL(s.getQueueInfo(start, stop), 1);
1400
1401 // Start the service thread after the queue size check to prevent a race
1402 // condition where the thread may be processing the event loop task during
1403 // the check.
1404 schedulerThread = std::thread(std::bind(&CScheduler::serviceQueue, &s));
1405
1406 // Destroy the processor.
1407 m_node.avalanche.reset();
1408
1409 // Now that avalanche is destroyed, there is no more scheduled tasks.
1410 BOOST_CHECK_EQUAL(s.getQueueInfo(start, stop), 0);
1411
1412 // Wait for the scheduler to stop.
1413 s.StopWhenDrained();
1414 schedulerThread.join();
1415}
1416
1417BOOST_AUTO_TEST_CASE(add_proof_to_reconcile) {
1418 uint32_t score = MIN_VALID_PROOF_SCORE;
1419 Chainstate &active_chainstate = Assert(m_node.chainman)->ActiveChainstate();
1420
1421 auto addProofToReconcile = [&](uint32_t proofScore) {
1422 auto proof = buildRandomProof(active_chainstate, proofScore);
1423 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
1424 BOOST_CHECK(pm.registerProof(proof));
1425 });
1426 BOOST_CHECK(m_node.avalanche->addToReconcile(proof));
1427 return proof;
1428 };
1429
1430 for (size_t i = 0; i < AVALANCHE_MAX_ELEMENT_POLL; i++) {
1431 auto proof = addProofToReconcile(++score);
1432
1433 auto invs = AvalancheTest::getInvsForNextPoll(*m_node.avalanche);
1434 BOOST_CHECK_EQUAL(invs.size(), i + 1);
1435 BOOST_CHECK(invs.front().IsMsgProof());
1436 BOOST_CHECK_EQUAL(invs.front().hash, proof->getId());
1437 }
1438
1439 // From here a new proof is only polled if its score is in the top
1440 // AVALANCHE_MAX_ELEMENT_POLL
1441 ProofId lastProofId;
1442 for (size_t i = 0; i < 10; i++) {
1443 auto proof = addProofToReconcile(++score);
1444
1445 auto invs = AvalancheTest::getInvsForNextPoll(*m_node.avalanche);
1447 BOOST_CHECK(invs.front().IsMsgProof());
1448 BOOST_CHECK_EQUAL(invs.front().hash, proof->getId());
1449
1450 lastProofId = proof->getId();
1451 }
1452
1453 for (size_t i = 0; i < 10; i++) {
1454 auto proof = addProofToReconcile(--score);
1455
1456 auto invs = AvalancheTest::getInvsForNextPoll(*m_node.avalanche);
1458 BOOST_CHECK(invs.front().IsMsgProof());
1459 BOOST_CHECK_EQUAL(invs.front().hash, lastProofId);
1460 }
1461
1462 {
1463 // The score is not high enough to get polled
1464 auto proof = addProofToReconcile(--score);
1465 auto invs = AvalancheTest::getInvsForNextPoll(*m_node.avalanche);
1466 for (auto &inv : invs) {
1467 BOOST_CHECK_NE(inv.hash, proof->getId());
1468 }
1469 }
1470}
1471
1473 setArg("-avaproofstakeutxoconfirmations", "2");
1474 setArg("-avalancheconflictingproofcooldown", "0");
1475
1476 BOOST_CHECK(!m_node.avalanche->isAccepted(nullptr));
1477 BOOST_CHECK_EQUAL(m_node.avalanche->getConfidence(nullptr), -1);
1478
1479 const CKey key = CKey::MakeCompressedKey();
1480
1481 const COutPoint conflictingOutpoint{TxId(GetRandHash()), 0};
1482 const COutPoint immatureOutpoint{TxId(GetRandHash()), 0};
1483 {
1485
1486 LOCK(cs_main);
1487 CCoinsViewCache &coins =
1488 Assert(m_node.chainman)->ActiveChainstate().CoinsTip();
1489 coins.AddCoin(conflictingOutpoint,
1490 Coin(CTxOut(PROOF_DUST_THRESHOLD, script), 10, false),
1491 false);
1492 coins.AddCoin(immatureOutpoint,
1493 Coin(CTxOut(PROOF_DUST_THRESHOLD, script), 100, false),
1494 false);
1495 }
1496
1497 auto buildProof = [&](const COutPoint &outpoint, uint64_t sequence,
1498 uint32_t height = 10) {
1499 ProofBuilder pb(sequence, 0, key, UNSPENDABLE_ECREG_PAYOUT_SCRIPT);
1501 pb.addUTXO(outpoint, PROOF_DUST_THRESHOLD, height, false, key));
1502 return pb.build();
1503 };
1504
1505 auto conflictingProof = buildProof(conflictingOutpoint, 1);
1506 auto validProof = buildProof(conflictingOutpoint, 2);
1507 auto immatureProof = buildProof(immatureOutpoint, 3, 100);
1508
1509 BOOST_CHECK(!m_node.avalanche->isAccepted(conflictingProof));
1510 BOOST_CHECK(!m_node.avalanche->isAccepted(validProof));
1511 BOOST_CHECK(!m_node.avalanche->isAccepted(immatureProof));
1512 BOOST_CHECK_EQUAL(m_node.avalanche->getConfidence(conflictingProof), -1);
1513 BOOST_CHECK_EQUAL(m_node.avalanche->getConfidence(validProof), -1);
1514 BOOST_CHECK_EQUAL(m_node.avalanche->getConfidence(immatureProof), -1);
1515
1516 // Reconciling proofs that don't exist will fail
1517 BOOST_CHECK(!m_node.avalanche->addToReconcile(conflictingProof));
1518 BOOST_CHECK(!m_node.avalanche->addToReconcile(validProof));
1519 BOOST_CHECK(!m_node.avalanche->addToReconcile(immatureProof));
1520
1521 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
1522 BOOST_CHECK(pm.registerProof(conflictingProof));
1523 BOOST_CHECK(pm.registerProof(validProof));
1524 BOOST_CHECK(!pm.registerProof(immatureProof));
1525
1526 BOOST_CHECK(pm.isBoundToPeer(validProof->getId()));
1527 BOOST_CHECK(pm.isInConflictingPool(conflictingProof->getId()));
1528 BOOST_CHECK(pm.isImmature(immatureProof->getId()));
1529 });
1530
1531 BOOST_CHECK(m_node.avalanche->addToReconcile(conflictingProof));
1532 BOOST_CHECK(!m_node.avalanche->isAccepted(conflictingProof));
1533 BOOST_CHECK(!m_node.avalanche->isAccepted(validProof));
1534 BOOST_CHECK(!m_node.avalanche->isAccepted(immatureProof));
1535 BOOST_CHECK_EQUAL(m_node.avalanche->getConfidence(conflictingProof), 0);
1536 BOOST_CHECK_EQUAL(m_node.avalanche->getConfidence(validProof), -1);
1537 BOOST_CHECK_EQUAL(m_node.avalanche->getConfidence(immatureProof), -1);
1538
1539 BOOST_CHECK(m_node.avalanche->addToReconcile(validProof));
1540 BOOST_CHECK(!m_node.avalanche->isAccepted(conflictingProof));
1541 BOOST_CHECK(m_node.avalanche->isAccepted(validProof));
1542 BOOST_CHECK(!m_node.avalanche->isAccepted(immatureProof));
1543 BOOST_CHECK_EQUAL(m_node.avalanche->getConfidence(conflictingProof), 0);
1544 BOOST_CHECK_EQUAL(m_node.avalanche->getConfidence(validProof), 0);
1545 BOOST_CHECK_EQUAL(m_node.avalanche->getConfidence(immatureProof), -1);
1546
1547 BOOST_CHECK(!m_node.avalanche->addToReconcile(immatureProof));
1548 BOOST_CHECK(!m_node.avalanche->isAccepted(conflictingProof));
1549 BOOST_CHECK(m_node.avalanche->isAccepted(validProof));
1550 BOOST_CHECK(!m_node.avalanche->isAccepted(immatureProof));
1551 BOOST_CHECK_EQUAL(m_node.avalanche->getConfidence(conflictingProof), 0);
1552 BOOST_CHECK_EQUAL(m_node.avalanche->getConfidence(validProof), 0);
1553 BOOST_CHECK_EQUAL(m_node.avalanche->getConfidence(immatureProof), -1);
1554}
1555
1556BOOST_AUTO_TEST_CASE(quorum_detection) {
1557 // Set min quorum parameters for our test
1558 int minStake = 400'000'000;
1559 setArg("-avaminquorumstake", ToString(minStake));
1560 setArg("-avaminquorumconnectedstakeratio", "0.5");
1561
1562 // Create a new processor with our given quorum parameters
1563 const auto &currency = Currency::get();
1564 uint32_t minScore = Proof::amountToScore(minStake * currency.baseunit);
1565
1566 Chainstate &active_chainstate = Assert(m_node.chainman)->ActiveChainstate();
1567
1568 const CKey key = CKey::MakeCompressedKey();
1569 auto localProof =
1570 buildRandomProof(active_chainstate, minScore / 4, 100, key);
1571 setArg("-avamasterkey", EncodeSecret(key));
1572 setArg("-avaproof", localProof->ToHex());
1573
1574 bilingual_str error;
1575 ChainstateManager &chainman = *Assert(m_node.chainman);
1576 m_node.avalanche = Processor::MakeProcessor(
1577 *m_node.args, *m_node.chain, m_node.connman.get(), chainman,
1578 m_node.mempool.get(), *m_node.scheduler, error);
1579
1580 BOOST_CHECK(m_node.avalanche != nullptr);
1581 BOOST_CHECK(m_node.avalanche->getLocalProof() != nullptr);
1582 BOOST_CHECK_EQUAL(m_node.avalanche->getLocalProof()->getId(),
1583 localProof->getId());
1584 BOOST_CHECK_EQUAL(AvalancheTest::getMinQuorumScore(*m_node.avalanche),
1585 minScore);
1587 AvalancheTest::getMinQuorumConnectedScoreRatio(*m_node.avalanche), 0.5);
1588
1589 // The local proof has not been validated yet
1590 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
1593 });
1594 BOOST_CHECK(!m_node.avalanche->isQuorumEstablished());
1595
1596 // Register the local proof. This is normally done when the chain tip is
1597 // updated. The local proof should be accounted for in the min quorum
1598 // computation but the peer manager doesn't know about that.
1599 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
1600 BOOST_CHECK(pm.registerProof(m_node.avalanche->getLocalProof()));
1602 pm.isBoundToPeer(m_node.avalanche->getLocalProof()->getId()));
1603 BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), minScore / 4);
1605 });
1606 BOOST_CHECK(!m_node.avalanche->isQuorumEstablished());
1607
1608 // Add enough nodes to get a conclusive vote
1609 for (NodeId id = 0; id < 8; id++) {
1610 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
1611 pm.addNode(id, m_node.avalanche->getLocalProof()->getId());
1612 BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), minScore / 4);
1613 BOOST_CHECK_EQUAL(pm.getConnectedPeersScore(), minScore / 4);
1614 });
1615 }
1616
1617 // Add part of the required stake and make sure we still report no quorum
1618 auto proof1 = buildRandomProof(active_chainstate, minScore / 2);
1619 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
1620 BOOST_CHECK(pm.registerProof(proof1));
1621 BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), 3 * minScore / 4);
1622 BOOST_CHECK_EQUAL(pm.getConnectedPeersScore(), minScore / 4);
1623 });
1624 BOOST_CHECK(!m_node.avalanche->isQuorumEstablished());
1625
1626 // Add the rest of the stake, but we are still lacking connected stake
1627 const int64_t tipTime =
1628 WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip())
1629 ->GetBlockTime();
1630 const COutPoint utxo{TxId(GetRandHash()), 0};
1631 const Amount amount = (int64_t(minScore / 4) * COIN) / 100;
1632 const int height = 100;
1633 const bool isCoinbase = false;
1634 {
1635 LOCK(cs_main);
1636 CCoinsViewCache &coins = active_chainstate.CoinsTip();
1637 coins.AddCoin(utxo,
1639 PKHash(key.GetPubKey()))),
1640 height, isCoinbase),
1641 false);
1642 }
1643 ProofBuilder pb(1, tipTime + 1, key, UNSPENDABLE_ECREG_PAYOUT_SCRIPT);
1644 BOOST_CHECK(pb.addUTXO(utxo, amount, height, isCoinbase, key));
1645 auto proof2 = pb.build();
1646
1647 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
1648 BOOST_CHECK(pm.registerProof(proof2));
1649 BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), minScore);
1650 BOOST_CHECK_EQUAL(pm.getConnectedPeersScore(), minScore / 4);
1651 });
1652 BOOST_CHECK(!m_node.avalanche->isQuorumEstablished());
1653
1654 // Adding a node should cause the quorum to be detected and locked-in
1655 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
1656 pm.addNode(8, proof2->getId());
1657 BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), minScore);
1658 // The peer manager knows that proof2 has a node attached ...
1659 BOOST_CHECK_EQUAL(pm.getConnectedPeersScore(), minScore / 2);
1660 });
1661 // ... but the processor also account for the local proof, so we reached 50%
1662 BOOST_CHECK(m_node.avalanche->isQuorumEstablished());
1663
1664 // Go back to not having enough connected score, but we've already latched
1665 // the quorum as established
1666 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
1667 pm.removeNode(8);
1668 BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), minScore);
1669 BOOST_CHECK_EQUAL(pm.getConnectedPeersScore(), minScore / 4);
1670 });
1671 BOOST_CHECK(m_node.avalanche->isQuorumEstablished());
1672
1673 // Removing one more node drops our count below the minimum and the quorum
1674 // is no longer ready
1675 m_node.avalanche->withPeerManager(
1676 [&](avalanche::PeerManager &pm) { pm.removeNode(7); });
1677 BOOST_CHECK(!m_node.avalanche->isQuorumEstablished());
1678
1679 // It resumes when we have enough nodes again
1680 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
1681 pm.addNode(7, m_node.avalanche->getLocalProof()->getId());
1682 });
1683 BOOST_CHECK(m_node.avalanche->isQuorumEstablished());
1684
1685 // Remove peers one at a time until the quorum is no longer established
1686 auto spendProofUtxo = [&](ProofRef proof) {
1687 {
1688 LOCK(cs_main);
1689 CCoinsViewCache &coins = chainman.ActiveChainstate().CoinsTip();
1690 coins.SpendCoin(proof->getStakes()[0].getStake().getUTXO());
1691 }
1692 m_node.avalanche->withPeerManager([&proof](avalanche::PeerManager &pm) {
1693 pm.updatedBlockTip();
1694 BOOST_CHECK(!pm.isBoundToPeer(proof->getId()));
1695 });
1696 };
1697
1698 // Expire proof2, the quorum is still latched
1699 for (int64_t i = 0; i < 6; i++) {
1700 SetMockTime(proof2->getExpirationTime() + i);
1701 CreateAndProcessBlock({}, CScript());
1702 }
1704 WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip())
1705 ->GetMedianTimePast(),
1706 proof2->getExpirationTime());
1707 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
1708 pm.updatedBlockTip();
1709 BOOST_CHECK(!pm.exists(proof2->getId()));
1710 });
1711 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
1712 BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), 3 * minScore / 4);
1713 BOOST_CHECK_EQUAL(pm.getConnectedPeersScore(), minScore / 4);
1714 });
1715 BOOST_CHECK(m_node.avalanche->isQuorumEstablished());
1716
1717 spendProofUtxo(proof1);
1718 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
1719 BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), minScore / 4);
1720 BOOST_CHECK_EQUAL(pm.getConnectedPeersScore(), minScore / 4);
1721 });
1722 BOOST_CHECK(m_node.avalanche->isQuorumEstablished());
1723
1724 spendProofUtxo(m_node.avalanche->getLocalProof());
1725 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
1728 });
1729 // There is no node left
1730 BOOST_CHECK(!m_node.avalanche->isQuorumEstablished());
1731}
1732
1733BOOST_AUTO_TEST_CASE(quorum_detection_parameter_validation) {
1734 // Create vector of tuples of:
1735 // <min stake, min ratio, min avaproofs messages, success bool>
1736 const std::vector<std::tuple<std::string, std::string, std::string, bool>>
1737 testCases = {
1738 // All parameters are invalid
1739 {"", "", "", false},
1740 {"-1", "-1", "-1", false},
1741
1742 // Min stake is out of range
1743 {"-1", "0", "0", false},
1744 {"-0.01", "0", "0", false},
1745 {"21000000000000.01", "0", "0", false},
1746
1747 // Min connected ratio is out of range
1748 {"0", "-1", "0", false},
1749 {"0", "1.1", "0", false},
1750
1751 // Min avaproofs messages ratio is out of range
1752 {"0", "0", "-1", false},
1753
1754 // All parameters are valid
1755 {"0", "0", "0", true},
1756 {"0.00", "0", "0", true},
1757 {"0.01", "0", "0", true},
1758 {"1", "0.1", "0", true},
1759 {"10", "0.5", "0", true},
1760 {"10", "1", "0", true},
1761 {"21000000000000.00", "0", "0", true},
1762 {"0", "0", "1", true},
1763 {"0", "0", "100", true},
1764 };
1765
1766 // For each case set the parameters and check that making the processor
1767 // succeeds or fails as expected
1768 for (const auto &[stake, stakeRatio, numProofsMessages, success] :
1769 testCases) {
1770 setArg("-avaminquorumstake", stake);
1771 setArg("-avaminquorumconnectedstakeratio", stakeRatio);
1772 setArg("-avaminavaproofsnodecount", numProofsMessages);
1773
1774 bilingual_str error;
1775 std::unique_ptr<Processor> processor = Processor::MakeProcessor(
1776 *m_node.args, *m_node.chain, m_node.connman.get(),
1777 *Assert(m_node.chainman), m_node.mempool.get(), *m_node.scheduler,
1778 error);
1779
1780 if (success) {
1781 BOOST_CHECK(processor != nullptr);
1782 BOOST_CHECK(error.empty());
1783 BOOST_CHECK_EQUAL(error.original, "");
1784 } else {
1785 BOOST_CHECK(processor == nullptr);
1786 BOOST_CHECK(!error.empty());
1787 BOOST_CHECK(error.original != "");
1788 }
1789 }
1790}
1791
1792BOOST_AUTO_TEST_CASE(min_avaproofs_messages) {
1793 ChainstateManager &chainman = *Assert(m_node.chainman);
1794
1795 auto checkMinAvaproofsMessages = [&](int64_t minAvaproofsMessages) {
1796 setArg("-avaminavaproofsnodecount", ToString(minAvaproofsMessages));
1797
1798 bilingual_str error;
1799 auto processor = Processor::MakeProcessor(
1800 *m_node.args, *m_node.chain, m_node.connman.get(), chainman,
1801 m_node.mempool.get(), *m_node.scheduler, error);
1802
1803 auto addNode = [&](NodeId nodeid) {
1804 auto proof = buildRandomProof(chainman.ActiveChainstate(),
1806 processor->withPeerManager([&](avalanche::PeerManager &pm) {
1807 BOOST_CHECK(pm.registerProof(proof));
1808 BOOST_CHECK(pm.addNode(nodeid, proof->getId()));
1809 });
1810 };
1811
1812 // Add enough node to have a conclusive vote, but don't account any
1813 // avaproofs.
1814 // NOTE: we can't use the test facilites like ConnectNodes() because we
1815 // are not testing on m_node.avalanche.
1816 for (NodeId id = 100; id < 108; id++) {
1817 addNode(id);
1818 }
1819
1820 BOOST_CHECK_EQUAL(processor->isQuorumEstablished(),
1821 minAvaproofsMessages <= 0);
1822
1823 for (int64_t i = 0; i < minAvaproofsMessages - 1; i++) {
1824 addNode(i);
1825
1826 processor->avaproofsSent(i);
1827 BOOST_CHECK_EQUAL(processor->getAvaproofsNodeCounter(), i + 1);
1828
1829 // Receiving again on the same node does not increase the counter
1830 processor->avaproofsSent(i);
1831 BOOST_CHECK_EQUAL(processor->getAvaproofsNodeCounter(), i + 1);
1832
1833 BOOST_CHECK(!processor->isQuorumEstablished());
1834 }
1835
1836 addNode(minAvaproofsMessages);
1837 processor->avaproofsSent(minAvaproofsMessages);
1838 BOOST_CHECK(processor->isQuorumEstablished());
1839
1840 // Check the latch
1841 AvalancheTest::clearavaproofsNodeCounter(*processor);
1842 BOOST_CHECK(processor->isQuorumEstablished());
1843 };
1844
1845 checkMinAvaproofsMessages(0);
1846 checkMinAvaproofsMessages(1);
1847 checkMinAvaproofsMessages(10);
1848 checkMinAvaproofsMessages(100);
1849}
1850
1852 // Check that setting voting parameters has the expected effect
1853 setArg("-avastalevotethreshold",
1855 setArg("-avastalevotefactor", "2");
1856 // This would fail the test for blocks
1857 setArg("-avalanchestakingpreconsensus", "0");
1858
1859 const std::vector<std::tuple<int, int>> testCases = {
1860 // {number of yes votes, number of neutral votes}
1863 };
1864
1865 bilingual_str error;
1866 m_node.avalanche = Processor::MakeProcessor(
1867 *m_node.args, *m_node.chain, m_node.connman.get(),
1868 *Assert(m_node.chainman), m_node.mempool.get(), *m_node.scheduler,
1869 error);
1870
1871 BOOST_CHECK(m_node.avalanche != nullptr);
1872 BOOST_CHECK(error.empty());
1873
1874 P provider(this);
1875 const uint32_t invType = provider.invType;
1876
1877 const auto item = provider.buildVoteItem();
1878 const auto itemid = provider.getVoteItemId(item);
1879
1880 // Create nodes that supports avalanche.
1881 auto avanodes = ConnectNodes();
1882 int nextNodeIndex = 0;
1883
1884 std::vector<avalanche::VoteItemUpdate> updates;
1885 for (const auto &[numYesVotes, numNeutralVotes] : testCases) {
1886 // Add a new item. Check it is added to the polls.
1887 BOOST_CHECK(addToReconcile(item));
1888 auto invs = getInvsForNextPoll();
1889 BOOST_CHECK_EQUAL(invs.size(), 1);
1890 BOOST_CHECK_EQUAL(invs[0].type, invType);
1891 BOOST_CHECK(invs[0].hash == itemid);
1892
1893 BOOST_CHECK(m_node.avalanche->isAccepted(item));
1894
1895 auto registerNewVote = [&](const Response &resp) {
1896 runEventLoop();
1897 auto nodeid = avanodes[nextNodeIndex++ % avanodes.size()]->GetId();
1898 BOOST_CHECK(registerVotes(nodeid, resp, updates));
1899 };
1900
1901 // Add some confidence
1902 for (int i = 0; i < numYesVotes; i++) {
1903 Response resp = {getRound(), 0, {Vote(0, itemid)}};
1904 registerNewVote(next(resp));
1905 BOOST_CHECK(m_node.avalanche->isAccepted(item));
1906 BOOST_CHECK_EQUAL(m_node.avalanche->getConfidence(item),
1907 i >= 6 ? i - 5 : 0);
1908 BOOST_CHECK_EQUAL(updates.size(), 0);
1909 }
1910
1911 // Vote until just before item goes stale
1912 for (int i = 0; i < numNeutralVotes; i++) {
1913 Response resp = {getRound(), 0, {Vote(-1, itemid)}};
1914 registerNewVote(next(resp));
1915 BOOST_CHECK_EQUAL(updates.size(), 0);
1916 }
1917
1918 // As long as it is not stale, we poll.
1919 invs = getInvsForNextPoll();
1920 BOOST_CHECK_EQUAL(invs.size(), 1);
1921 BOOST_CHECK_EQUAL(invs[0].type, invType);
1922 BOOST_CHECK(invs[0].hash == itemid);
1923
1924 // Now stale
1925 Response resp = {getRound(), 0, {Vote(-1, itemid)}};
1926 registerNewVote(next(resp));
1927 BOOST_CHECK_EQUAL(updates.size(), 1);
1928 BOOST_CHECK(provider.fromAnyVoteItem(updates[0].getVoteItem()) == item);
1929 BOOST_CHECK(updates[0].getStatus() == VoteStatus::Stale);
1930
1931 // Once stale, there is no poll for it.
1932 invs = getInvsForNextPoll();
1933 BOOST_CHECK_EQUAL(invs.size(), 0);
1934 }
1935}
1936
1937BOOST_AUTO_TEST_CASE(block_vote_finalization_tip) {
1938 BlockProvider provider(this);
1939
1940 BOOST_CHECK(!m_node.avalanche->hasFinalizedTip());
1941
1942 std::vector<CBlockIndex *> blockIndexes;
1943 for (size_t i = 0; i < AVALANCHE_MAX_ELEMENT_POLL; i++) {
1944 CBlockIndex *pindex = provider.buildVoteItem();
1945 BOOST_CHECK(addToReconcile(pindex));
1946 blockIndexes.push_back(pindex);
1947 }
1948
1949 auto invs = getInvsForNextPoll();
1951 for (size_t i = 0; i < AVALANCHE_MAX_ELEMENT_POLL; i++) {
1953 invs[i].hash,
1954 blockIndexes[AVALANCHE_MAX_ELEMENT_POLL - i - 1]->GetBlockHash());
1955 }
1956
1957 // Build a vote vector with the 11th block only being accepted and others
1958 // unknown.
1959 const BlockHash eleventhBlockHash =
1960 blockIndexes[AVALANCHE_MAX_ELEMENT_POLL - 10 - 1]->GetBlockHash();
1961 std::vector<Vote> votes;
1962 votes.reserve(AVALANCHE_MAX_ELEMENT_POLL);
1963 for (size_t i = AVALANCHE_MAX_ELEMENT_POLL; i > 0; i--) {
1964 BlockHash blockhash = blockIndexes[i - 1]->GetBlockHash();
1965 votes.emplace_back(blockhash == eleventhBlockHash ? 0 : -1, blockhash);
1966 }
1967
1968 auto avanodes = ConnectNodes();
1969 int nextNodeIndex = 0;
1970
1971 std::vector<avalanche::VoteItemUpdate> updates;
1972 auto registerNewVote = [&]() {
1973 Response resp = {getRound(), 0, votes};
1974 runEventLoop();
1975 auto nodeid = avanodes[nextNodeIndex++ % avanodes.size()]->GetId();
1976 BOOST_CHECK(registerVotes(nodeid, resp, updates));
1977 };
1978
1979 BOOST_CHECK(!m_node.avalanche->hasFinalizedTip());
1980
1981 // Vote for the blocks until the one being accepted finalizes
1982 bool eleventhBlockFinalized = false;
1983 for (size_t i = 0; i < 10000 && !eleventhBlockFinalized; i++) {
1984 registerNewVote();
1985
1986 for (auto &update : updates) {
1987 if (update.getStatus() == VoteStatus::Finalized &&
1988 provider.fromAnyVoteItem(update.getVoteItem())
1989 ->GetBlockHash() == eleventhBlockHash) {
1990 eleventhBlockFinalized = true;
1991 BOOST_CHECK(m_node.avalanche->hasFinalizedTip());
1992 } else {
1993 BOOST_CHECK(!m_node.avalanche->hasFinalizedTip());
1994 }
1995 }
1996 }
1997 BOOST_CHECK(eleventhBlockFinalized);
1998 BOOST_CHECK(m_node.avalanche->hasFinalizedTip());
1999
2000 // From now only the 10 blocks with more work are polled for
2001 clearInvsNotWorthPolling();
2002 invs = getInvsForNextPoll();
2003 BOOST_CHECK_EQUAL(invs.size(), 10);
2004 for (size_t i = 0; i < 10; i++) {
2006 invs[i].hash,
2007 blockIndexes[AVALANCHE_MAX_ELEMENT_POLL - i - 1]->GetBlockHash());
2008 }
2009
2010 // Adding ancestor blocks to reconcile will fail
2011 for (size_t i = 0; i < AVALANCHE_MAX_ELEMENT_POLL - 10 - 1; i++) {
2012 BOOST_CHECK(!addToReconcile(blockIndexes[i]));
2013 }
2014
2015 // Create a couple concurrent chain tips
2016 CBlockIndex *tip = provider.buildVoteItem();
2017
2018 auto &activeChainstate = m_node.chainman->ActiveChainstate();
2020 activeChainstate.InvalidateBlock(state, tip);
2021
2022 // Use another script to make sure we don't generate the same block again
2023 CBlock altblock = CreateAndProcessBlock({}, CScript() << OP_TRUE);
2024 auto alttip = WITH_LOCK(
2025 cs_main, return Assert(m_node.chainman)
2026 ->m_blockman.LookupBlockIndex(altblock.GetHash()));
2027 BOOST_CHECK(alttip);
2028 BOOST_CHECK(alttip->pprev == tip->pprev);
2029 BOOST_CHECK(alttip->GetBlockHash() != tip->GetBlockHash());
2030
2031 // Reconsider the previous tip valid, so we have concurrent tip candidates
2032 {
2033 LOCK(cs_main);
2034 activeChainstate.ResetBlockFailureFlags(tip);
2035 }
2036 activeChainstate.ActivateBestChain(state);
2037
2038 BOOST_CHECK(addToReconcile(tip));
2039 BOOST_CHECK(addToReconcile(alttip));
2040 clearInvsNotWorthPolling();
2041 invs = getInvsForNextPoll();
2042 BOOST_CHECK_EQUAL(invs.size(), 12);
2043
2044 // Vote for the tip until it finalizes
2045 BlockHash tiphash = tip->GetBlockHash();
2046 votes.clear();
2047 votes.reserve(12);
2048 for (auto &inv : invs) {
2049 votes.emplace_back(inv.hash == tiphash ? 0 : -1, inv.hash);
2050 }
2051
2052 bool tipFinalized = false;
2053 for (size_t i = 0; i < 10000 && !tipFinalized; i++) {
2054 registerNewVote();
2055
2056 for (auto &update : updates) {
2057 if (update.getStatus() == VoteStatus::Finalized &&
2058 provider.fromAnyVoteItem(update.getVoteItem())
2059 ->GetBlockHash() == tiphash) {
2060 tipFinalized = true;
2061 }
2062 }
2063 }
2064 BOOST_CHECK(tipFinalized);
2065
2066 // Now the tip and all its ancestors will be removed from polls. Only the
2067 // alttip remains because it is on a forked chain so we want to keep polling
2068 // for that one until it's invalidated or stalled.
2069 clearInvsNotWorthPolling();
2070 invs = getInvsForNextPoll();
2071 BOOST_CHECK_EQUAL(invs.size(), 1);
2072 BOOST_CHECK_EQUAL(invs[0].hash, alttip->GetBlockHash());
2073
2074 // Cannot reconcile a finalized block
2075 BOOST_CHECK(!addToReconcile(tip));
2076
2077 // Vote for alttip until it invalidates
2078 BlockHash alttiphash = alttip->GetBlockHash();
2079 votes = {{1, alttiphash}};
2080
2081 bool alttipInvalidated = false;
2082 for (size_t i = 0; i < 10000 && !alttipInvalidated; i++) {
2083 registerNewVote();
2084
2085 for (auto &update : updates) {
2086 if (update.getStatus() == VoteStatus::Invalid &&
2087 provider.fromAnyVoteItem(update.getVoteItem())
2088 ->GetBlockHash() == alttiphash) {
2089 alttipInvalidated = true;
2090 }
2091 }
2092 }
2093 BOOST_CHECK(alttipInvalidated);
2094 invs = getInvsForNextPoll();
2095 BOOST_CHECK_EQUAL(invs.size(), 0);
2096
2097 // Cannot reconcile an invalidated block
2098 BOOST_CHECK(!addToReconcile(alttip));
2099}
2100
2101BOOST_AUTO_TEST_CASE(vote_map_comparator) {
2102 ChainstateManager &chainman = *Assert(m_node.chainman);
2103 Chainstate &activeChainState = chainman.ActiveChainstate();
2104
2105 const int numberElementsEachType = 100;
2107
2108 std::vector<ProofRef> proofs;
2109 for (size_t i = 1; i <= numberElementsEachType; i++) {
2110 auto proof =
2111 buildRandomProof(activeChainState, i * MIN_VALID_PROOF_SCORE);
2112 BOOST_CHECK(proof != nullptr);
2113 proofs.emplace_back(std::move(proof));
2114 }
2115 Shuffle(proofs.begin(), proofs.end(), rng);
2116
2117 std::vector<CBlockIndex> indexes;
2118 for (size_t i = 1; i <= numberElementsEachType; i++) {
2119 CBlockIndex index;
2120 index.nChainWork = i;
2121 indexes.emplace_back(std::move(index));
2122 }
2123 Shuffle(indexes.begin(), indexes.end(), rng);
2124
2125 auto allItems = std::make_tuple(std::move(proofs), std::move(indexes));
2126 static const size_t numTypes = std::tuple_size<decltype(allItems)>::value;
2127
2128 RWCollection<VoteMap> voteMap;
2129
2130 {
2131 auto writeView = voteMap.getWriteView();
2132 for (size_t i = 0; i < numberElementsEachType; i++) {
2133 // Randomize the insert order at each loop increment
2134 const size_t firstType = rng.randrange(numTypes);
2135
2136 for (size_t j = 0; j < numTypes; j++) {
2137 switch ((firstType + j) % numTypes) {
2138 // ProofRef
2139 case 0:
2140 writeView->insert(std::make_pair(
2141 std::get<0>(allItems)[i], VoteRecord(true)));
2142 break;
2143 // CBlockIndex *
2144 case 1:
2145 writeView->insert(std::make_pair(
2146 &std::get<1>(allItems)[i], VoteRecord(true)));
2147 break;
2148 default:
2149 break;
2150 }
2151 }
2152 }
2153 }
2154
2155 {
2156 // Check ordering
2157 auto readView = voteMap.getReadView();
2158 auto it = readView.begin();
2159
2160 // The first batch of items is the proofs ordered by score (descending)
2161 uint32_t lastScore = std::numeric_limits<uint32_t>::max();
2162 for (size_t i = 0; i < numberElementsEachType; i++) {
2163 BOOST_CHECK(std::holds_alternative<const ProofRef>(it->first));
2164
2165 uint32_t currentScore =
2166 std::get<const ProofRef>(it->first)->getScore();
2167 BOOST_CHECK_LT(currentScore, lastScore);
2168 lastScore = currentScore;
2169
2170 it++;
2171 }
2172
2173 // The next batch of items is the block indexes ordered by work
2174 // (descending)
2175 arith_uint256 lastWork = ~arith_uint256(0);
2176 for (size_t i = 0; i < numberElementsEachType; i++) {
2177 BOOST_CHECK(std::holds_alternative<const CBlockIndex *>(it->first));
2178
2179 arith_uint256 currentWork =
2180 std::get<const CBlockIndex *>(it->first)->nChainWork;
2181 BOOST_CHECK(currentWork < lastWork);
2182 lastWork = currentWork;
2183
2184 it++;
2185 }
2186
2187 BOOST_CHECK(it == readView.end());
2188 }
2189}
2190
2191BOOST_AUTO_TEST_CASE(block_reconcile_initial_vote) {
2192 auto &chainman = Assert(m_node.chainman);
2193 Chainstate &chainstate = chainman->ActiveChainstate();
2194
2195 const auto block = std::make_shared<const CBlock>(
2196 this->CreateBlock({}, CScript(), chainstate));
2197 const BlockHash blockhash = block->GetHash();
2198
2200 CBlockIndex *blockindex;
2201 {
2202 LOCK(cs_main);
2203 BOOST_CHECK(chainman->AcceptBlock(block, state,
2204 /*fRequested=*/true, /*dbp=*/nullptr,
2205 /*fNewBlock=*/nullptr,
2206 /*min_pow_checked=*/true));
2207
2208 blockindex = chainman->m_blockman.LookupBlockIndex(blockhash);
2209 BOOST_CHECK(blockindex);
2210 }
2211
2212 // The block is not connected yet, and not added to the poll list yet
2213 BOOST_CHECK(AvalancheTest::getInvsForNextPoll(*m_node.avalanche).empty());
2214 BOOST_CHECK(!m_node.avalanche->isAccepted(blockindex));
2215
2216 // Call ActivateBestChain to connect the new block
2218 chainstate.ActivateBestChain(state, block, m_node.avalanche.get()));
2219 // It is a valid block so the tip is updated
2220 BOOST_CHECK_EQUAL(chainstate.m_chain.Tip(), blockindex);
2221
2222 // Check the block is added to the poll
2223 auto invs = AvalancheTest::getInvsForNextPoll(*m_node.avalanche);
2224 BOOST_CHECK_EQUAL(invs.size(), 1);
2225 BOOST_CHECK_EQUAL(invs[0].type, MSG_BLOCK);
2226 BOOST_CHECK_EQUAL(invs[0].hash, blockhash);
2227
2228 // This block is our new tip so we should vote "yes"
2229 BOOST_CHECK(m_node.avalanche->isAccepted(blockindex));
2230
2231 // Prevent a data race between UpdatedBlockTip and the Processor destructor
2233}
2234
2235BOOST_AUTO_TEST_CASE(compute_staking_rewards) {
2236 auto now = GetTime<std::chrono::seconds>();
2237 SetMockTime(now);
2238
2239 // Pick in the middle
2240 BlockHash prevBlockHash{uint256::ZERO};
2241
2242 std::vector<CScript> winners;
2243
2245 !m_node.avalanche->getStakingRewardWinners(prevBlockHash, winners));
2246
2247 // Null index
2248 BOOST_CHECK(!m_node.avalanche->computeStakingReward(nullptr));
2250 !m_node.avalanche->getStakingRewardWinners(prevBlockHash, winners));
2251
2252 CBlockIndex prevBlock;
2253 prevBlock.phashBlock = &prevBlockHash;
2254 prevBlock.nHeight = 100;
2255 prevBlock.nTime = now.count();
2256
2257 // No quorum
2258 BOOST_CHECK(!m_node.avalanche->computeStakingReward(&prevBlock));
2260 !m_node.avalanche->getStakingRewardWinners(prevBlockHash, winners));
2261
2262 // Setup a bunch of proofs
2263 size_t numProofs = 10;
2264 std::vector<ProofRef> proofs;
2265 proofs.reserve(numProofs);
2266 for (size_t i = 0; i < numProofs; i++) {
2267 const CKey key = CKey::MakeCompressedKey();
2268 CScript payoutScript = GetScriptForRawPubKey(key.GetPubKey());
2269
2270 auto proof = GetProof(payoutScript);
2271 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
2272 BOOST_CHECK(pm.registerProof(proof));
2273 BOOST_CHECK(pm.addNode(i, proof->getId()));
2274 // Finalize the proof
2275 BOOST_CHECK(pm.forPeer(proof->getId(), [&](const Peer peer) {
2276 return pm.setFinalized(peer.peerid);
2277 }));
2278 });
2279
2280 proofs.emplace_back(std::move(proof));
2281 }
2282
2283 BOOST_CHECK(m_node.avalanche->isQuorumEstablished());
2284
2285 // Proofs are too recent so we still have no winner
2286 BOOST_CHECK(!m_node.avalanche->computeStakingReward(&prevBlock));
2288 !m_node.avalanche->getStakingRewardWinners(prevBlockHash, winners));
2289
2290 // Make sure we picked a payout script from one of our proofs
2291 auto winnerExists = [&](const CScript &expectedWinner) {
2292 const std::string winnerString = FormatScript(expectedWinner);
2293
2294 for (const ProofRef &proof : proofs) {
2295 if (winnerString == FormatScript(proof->getPayoutScript())) {
2296 return true;
2297 }
2298 }
2299 return false;
2300 };
2301
2302 // Elapse some time
2303 now += 1h + 1s;
2304 SetMockTime(now);
2305 prevBlock.nTime = now.count();
2306
2307 // Now we successfully inserted a winner in our map
2308 BOOST_CHECK(m_node.avalanche->computeStakingReward(&prevBlock));
2310 m_node.avalanche->getStakingRewardWinners(prevBlockHash, winners));
2311 BOOST_CHECK(winnerExists(winners[0]));
2312
2313 // Subsequent calls are a no-op
2314 BOOST_CHECK(m_node.avalanche->computeStakingReward(&prevBlock));
2316 m_node.avalanche->getStakingRewardWinners(prevBlockHash, winners));
2317 BOOST_CHECK(winnerExists(winners[0]));
2318
2319 CBlockIndex prevBlockHigh = prevBlock;
2320 BlockHash prevBlockHashHigh =
2321 BlockHash(ArithToUint256({std::numeric_limits<uint64_t>::max()}));
2322 prevBlockHigh.phashBlock = &prevBlockHashHigh;
2323 prevBlockHigh.nHeight = 101;
2324 BOOST_CHECK(m_node.avalanche->computeStakingReward(&prevBlockHigh));
2326 m_node.avalanche->getStakingRewardWinners(prevBlockHashHigh, winners));
2327 BOOST_CHECK(winnerExists(winners[0]));
2328
2329 // No impact on previous winner so far
2331 m_node.avalanche->getStakingRewardWinners(prevBlockHash, winners));
2332 BOOST_CHECK(winnerExists(winners[0]));
2333
2334 // Cleanup to height 101
2335 m_node.avalanche->cleanupStakingRewards(101);
2336
2337 // Now the previous winner has been cleared
2339 !m_node.avalanche->getStakingRewardWinners(prevBlockHash, winners));
2340
2341 // But the last one remain
2343 m_node.avalanche->getStakingRewardWinners(prevBlockHashHigh, winners));
2344 BOOST_CHECK(winnerExists(winners[0]));
2345
2346 // We can add it again
2347 BOOST_CHECK(m_node.avalanche->computeStakingReward(&prevBlock));
2349 m_node.avalanche->getStakingRewardWinners(prevBlockHash, winners));
2350 BOOST_CHECK(winnerExists(winners[0]));
2351
2352 // Cleanup to higher height
2353 m_node.avalanche->cleanupStakingRewards(200);
2354
2355 // No winner anymore
2357 !m_node.avalanche->getStakingRewardWinners(prevBlockHash, winners));
2359 !m_node.avalanche->getStakingRewardWinners(prevBlockHashHigh, winners));
2360}
2361
2362BOOST_AUTO_TEST_CASE(local_proof_status) {
2363 const CKey key = CKey::MakeCompressedKey();
2364
2365 const COutPoint outpoint{TxId(GetRandHash()), 0};
2366 {
2368
2369 LOCK(cs_main);
2370 CCoinsViewCache &coins =
2371 Assert(m_node.chainman)->ActiveChainstate().CoinsTip();
2372 coins.AddCoin(outpoint,
2373 Coin(CTxOut(PROOF_DUST_THRESHOLD, script), 100, false),
2374 false);
2375 }
2376
2377 auto buildProof = [&](const COutPoint &outpoint, uint64_t sequence,
2378 uint32_t height) {
2379 ProofBuilder pb(sequence, 0, key, UNSPENDABLE_ECREG_PAYOUT_SCRIPT);
2381 pb.addUTXO(outpoint, PROOF_DUST_THRESHOLD, height, false, key));
2382 return pb.build();
2383 };
2384
2385 auto localProof = buildProof(outpoint, 1, 100);
2386
2387 setArg("-avamasterkey", EncodeSecret(key));
2388 setArg("-avaproof", localProof->ToHex());
2389 setArg("-avalancheconflictingproofcooldown", "0");
2390 setArg("-avalanchepeerreplacementcooldown", "0");
2391 setArg("-avaproofstakeutxoconfirmations", "3");
2392
2393 bilingual_str error;
2394 ChainstateManager &chainman = *Assert(m_node.chainman);
2395 m_node.avalanche = Processor::MakeProcessor(
2396 *m_node.args, *m_node.chain, m_node.connman.get(), chainman,
2397 m_node.mempool.get(), *m_node.scheduler, error);
2398
2399 BOOST_CHECK_EQUAL(m_node.avalanche->getLocalProof()->getId(),
2400 localProof->getId());
2401
2402 auto checkLocalProofState = [&](const bool boundToPeer,
2404 expectedResult) {
2406 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
2407 return pm.isBoundToPeer(localProof->getId());
2408 }),
2409 boundToPeer);
2410 BOOST_CHECK_MESSAGE(
2411 m_node.avalanche->getLocalProofRegistrationState().GetResult() ==
2412 expectedResult,
2413 m_node.avalanche->getLocalProofRegistrationState().ToString());
2414 };
2415
2416 checkLocalProofState(false, ProofRegistrationResult::NONE);
2417
2418 // Not ready to share, the local proof isn't registered
2419 BOOST_CHECK(!m_node.avalanche->canShareLocalProof());
2420 AvalancheTest::updatedBlockTip(*m_node.avalanche);
2421 checkLocalProofState(false, ProofRegistrationResult::NONE);
2422
2423 // Ready to share, but the proof is immature
2424 AvalancheTest::setLocalProofShareable(*m_node.avalanche, true);
2425 BOOST_CHECK(m_node.avalanche->canShareLocalProof());
2426 AvalancheTest::updatedBlockTip(*m_node.avalanche);
2427 checkLocalProofState(false, ProofRegistrationResult::IMMATURE);
2428
2429 // Mine a block to re-evaluate the proof, it remains immature
2430 mineBlocks(1);
2431 AvalancheTest::updatedBlockTip(*m_node.avalanche);
2432 checkLocalProofState(false, ProofRegistrationResult::IMMATURE);
2433
2434 // One more block and the proof turns mature
2435 mineBlocks(1);
2436 AvalancheTest::updatedBlockTip(*m_node.avalanche);
2437 checkLocalProofState(true, ProofRegistrationResult::NONE);
2438
2439 // Build a conflicting proof and check the status is updated accordingly
2440 auto conflictingProof = buildProof(outpoint, 2, 100);
2441 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
2442 BOOST_CHECK(pm.registerProof(conflictingProof));
2443 BOOST_CHECK(pm.isBoundToPeer(conflictingProof->getId()));
2444 BOOST_CHECK(pm.isInConflictingPool(localProof->getId()));
2445 });
2446 AvalancheTest::updatedBlockTip(*m_node.avalanche);
2447 checkLocalProofState(false, ProofRegistrationResult::CONFLICTING);
2448}
2449
2450BOOST_AUTO_TEST_CASE(reconcileOrFinalize) {
2451 setArg("-avalancheconflictingproofcooldown", "0");
2452 setArg("-avalanchepeerreplacementcooldown", "0");
2453
2454 // Proof is null
2455 BOOST_CHECK(!m_node.avalanche->reconcileOrFinalize(ProofRef()));
2456
2457 ChainstateManager &chainman = *Assert(m_node.chainman);
2458 Chainstate &activeChainState = chainman.ActiveChainstate();
2459
2460 const CKey key = CKey::MakeCompressedKey();
2461 const COutPoint outpoint{TxId(GetRandHash()), 0};
2462 {
2464
2465 LOCK(cs_main);
2466 CCoinsViewCache &coins = activeChainState.CoinsTip();
2467 coins.AddCoin(outpoint,
2468 Coin(CTxOut(PROOF_DUST_THRESHOLD, script), 100, false),
2469 false);
2470 }
2471
2472 auto buildProof = [&](const COutPoint &outpoint, uint64_t sequence) {
2473 ProofBuilder pb(sequence, 0, key, UNSPENDABLE_ECREG_PAYOUT_SCRIPT);
2475 pb.addUTXO(outpoint, PROOF_DUST_THRESHOLD, 100, false, key));
2476 return pb.build();
2477 };
2478
2479 auto proof = buildProof(outpoint, 1);
2480 BOOST_CHECK(proof);
2481
2482 // Not a peer nor conflicting
2483 BOOST_CHECK(!m_node.avalanche->reconcileOrFinalize(proof));
2484
2485 // Register the proof
2486 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
2487 BOOST_CHECK(pm.registerProof(proof));
2488 BOOST_CHECK(pm.isBoundToPeer(proof->getId()));
2489 BOOST_CHECK(!pm.isInConflictingPool(proof->getId()));
2490 });
2491
2492 // Reconcile works
2493 BOOST_CHECK(m_node.avalanche->reconcileOrFinalize(proof));
2494 // Repeated calls fail and do nothing
2495 BOOST_CHECK(!m_node.avalanche->reconcileOrFinalize(proof));
2496
2497 // Finalize
2498 AvalancheTest::addProofToRecentfinalized(*m_node.avalanche, proof->getId());
2499 BOOST_CHECK(m_node.avalanche->isRecentlyFinalized(proof->getId()));
2500 BOOST_CHECK(m_node.avalanche->reconcileOrFinalize(proof));
2501
2502 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
2503 // The peer is marked as final
2504 BOOST_CHECK(pm.forPeer(proof->getId(), [&](const Peer &peer) {
2505 return peer.hasFinalized;
2506 }));
2507 BOOST_CHECK(pm.isBoundToPeer(proof->getId()));
2508 BOOST_CHECK(!pm.isInConflictingPool(proof->getId()));
2509 });
2510
2511 // Same proof with a higher sequence number
2512 auto betterProof = buildProof(outpoint, 2);
2513 BOOST_CHECK(betterProof);
2514
2515 // Not registered nor conflicting yet
2516 BOOST_CHECK(!m_node.avalanche->reconcileOrFinalize(betterProof));
2517
2518 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
2519 BOOST_CHECK(pm.registerProof(betterProof));
2520 BOOST_CHECK(pm.isBoundToPeer(betterProof->getId()));
2521 BOOST_CHECK(!pm.isInConflictingPool(betterProof->getId()));
2522
2523 BOOST_CHECK(!pm.isBoundToPeer(proof->getId()));
2525 });
2526
2527 // Recently finalized, not worth polling
2528 BOOST_CHECK(!m_node.avalanche->reconcileOrFinalize(proof));
2529 // But the better proof can be polled
2530 BOOST_CHECK(m_node.avalanche->reconcileOrFinalize(betterProof));
2531}
2532
2533BOOST_AUTO_TEST_CASE(stake_contenders) {
2534 bilingual_str error;
2535 m_node.avalanche = Processor::MakeProcessor(
2536 *m_node.args, *m_node.chain, m_node.connman.get(),
2537 *Assert(m_node.chainman), m_node.mempool.get(), *m_node.scheduler,
2538 error);
2539 BOOST_CHECK(m_node.avalanche);
2540
2541 auto now = GetTime<std::chrono::seconds>();
2542 SetMockTime(now);
2543
2544 AvalancheTest::setStakingPreconsensus(*m_node.avalanche, true);
2545
2546 ChainstateManager &chainman = *Assert(m_node.chainman);
2547 Chainstate &active_chainstate = chainman.ActiveChainstate();
2548 CBlockIndex *chaintip =
2549 WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip());
2550
2551 auto proof1 = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
2552 const ProofId proofid1 = proof1->getId();
2553 const StakeContenderId contender1_block1(chaintip->GetBlockHash(),
2554 proofid1);
2555
2556 auto proof2 = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
2557 const ProofId proofid2 = proof2->getId();
2558 const StakeContenderId contender2_block1(chaintip->GetBlockHash(),
2559 proofid2);
2560
2561 // Add stake contenders. Without computing staking rewards, the status is
2562 // pending.
2563 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
2564 pm.addStakeContender(proof1);
2565 pm.addStakeContender(proof2);
2566 });
2568 m_node.avalanche->getStakeContenderStatus(contender1_block1), -2);
2570 m_node.avalanche->getStakeContenderStatus(contender2_block1), -2);
2571
2572 // Sanity check unknown contender
2573 const StakeContenderId unknownContender(chaintip->GetBlockHash(),
2574 ProofId(GetRandHash()));
2576 m_node.avalanche->getStakeContenderStatus(unknownContender), -1);
2577
2578 // Register proof2 and save it as a remote proof so that it will be promoted
2579 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
2580 pm.registerProof(proof2);
2581 for (NodeId n = 0; n < 8; n++) {
2582 pm.addNode(n, proofid2);
2583 }
2584 pm.saveRemoteProof(proofid2, 0, true);
2585 BOOST_CHECK(pm.forPeer(proofid2, [&](const Peer peer) {
2586 return pm.setFinalized(peer.peerid);
2587 }));
2588 });
2589
2590 // Make proofs old enough to be considered for staking rewards
2591 now += 1h + 1s;
2592 SetMockTime(now);
2593
2594 // Advance chaintip
2595 CBlock block = CreateAndProcessBlock({}, CScript());
2596 chaintip =
2597 WITH_LOCK(cs_main, return Assert(m_node.chainman)
2598 ->m_blockman.LookupBlockIndex(block.GetHash()));
2599 AvalancheTest::updatedBlockTip(*m_node.avalanche);
2600
2601 // Compute local stake winner
2602 BOOST_CHECK(m_node.avalanche->isQuorumEstablished());
2603 BOOST_CHECK(m_node.avalanche->computeStakingReward(chaintip));
2604 {
2605 std::vector<CScript> winners;
2606 BOOST_CHECK(m_node.avalanche->getStakingRewardWinners(
2607 chaintip->GetBlockHash(), winners));
2608 BOOST_CHECK_EQUAL(winners.size(), 1);
2609 BOOST_CHECK(winners[0] == proof2->getPayoutScript());
2610 }
2611
2612 // Sanity check unknown contender
2614 m_node.avalanche->getStakeContenderStatus(unknownContender), -1);
2615
2616 // Old contender cache entries unaffected
2618 m_node.avalanche->getStakeContenderStatus(contender1_block1), -2);
2620 m_node.avalanche->getStakeContenderStatus(contender2_block1), -2);
2621
2622 // contender1 was not promoted
2623 const StakeContenderId contender1_block2 =
2624 StakeContenderId(chaintip->GetBlockHash(), proofid1);
2626 m_node.avalanche->getStakeContenderStatus(contender1_block2), -1);
2627
2628 // contender2 was promoted
2629 const StakeContenderId contender2_block2 =
2630 StakeContenderId(chaintip->GetBlockHash(), proofid2);
2632 m_node.avalanche->getStakeContenderStatus(contender2_block2), 0);
2633
2634 // Now that the finalization point has passed the block where contender1 was
2635 // added, cleaning up the cache will remove its entry. contender2 will have
2636 // its old entry cleaned up, but the promoted one remains.
2637 m_node.avalanche->cleanupStakingRewards(chaintip->nHeight);
2638
2640 m_node.avalanche->getStakeContenderStatus(unknownContender), -1);
2641
2643 m_node.avalanche->getStakeContenderStatus(contender1_block1), -1);
2645 m_node.avalanche->getStakeContenderStatus(contender1_block2), -1);
2646
2648 m_node.avalanche->getStakeContenderStatus(contender2_block1), -1);
2650 m_node.avalanche->getStakeContenderStatus(contender2_block2), 0);
2651
2652 // Manually set contenders as winners
2653 m_node.avalanche->setStakingRewardWinners(
2654 chaintip, {proof1->getPayoutScript(), proof2->getPayoutScript()});
2655 // contender1 has been forgotten, which is expected. When a proof becomes
2656 // invalid and is cleaned up from the cache, we do not expect peers to poll
2657 // for it any more.
2659 m_node.avalanche->getStakeContenderStatus(contender1_block2), -1);
2660 // contender2 is a winner despite avalanche not finalizing it
2662 m_node.avalanche->getStakeContenderStatus(contender2_block2), 0);
2663
2664 // Reject proof2, mine a new chain tip, finalize it, and cleanup the cache
2665 m_node.avalanche->withPeerManager(
2666 [&](avalanche::PeerManager &pm) { pm.rejectProof(proofid2); });
2667
2668 // Reestablish quorum with a new proof
2669 BOOST_CHECK(!m_node.avalanche->isQuorumEstablished());
2670 auto proof3 = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
2671 const ProofId proofid3 = proof3->getId();
2672 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
2673 pm.registerProof(proof3);
2674 for (NodeId n = 0; n < 8; n++) {
2675 pm.addNode(n, proofid3);
2676 }
2677 });
2678 BOOST_CHECK(m_node.avalanche->isQuorumEstablished());
2679
2680 block = CreateAndProcessBlock({}, CScript());
2681 chaintip =
2682 WITH_LOCK(cs_main, return Assert(m_node.chainman)
2683 ->m_blockman.LookupBlockIndex(block.GetHash()));
2684 AvalancheTest::updatedBlockTip(*m_node.avalanche);
2685 m_node.avalanche->cleanupStakingRewards(chaintip->nHeight);
2686
2688 m_node.avalanche->getStakeContenderStatus(unknownContender), -1);
2689
2690 // Old entries were cleaned up
2692 m_node.avalanche->getStakeContenderStatus(contender1_block2), -1);
2694 m_node.avalanche->getStakeContenderStatus(contender2_block2), -1);
2695
2696 // Neither contender was promoted and contender2 was cleaned up even though
2697 // it was once a manual winner.
2698 const StakeContenderId contender1_block3 =
2699 StakeContenderId(chaintip->GetBlockHash(), proofid1);
2701 m_node.avalanche->getStakeContenderStatus(contender1_block3), -1);
2702 const StakeContenderId contender2_block3 =
2703 StakeContenderId(chaintip->GetBlockHash(), proofid2);
2705 m_node.avalanche->getStakeContenderStatus(contender2_block3), -1);
2706
2707 // Reject proof3 so it does not conflict with the rest of the test
2708 m_node.avalanche->withPeerManager(
2709 [&](avalanche::PeerManager &pm) { pm.rejectProof(proofid3); });
2710
2711 // Generate a bunch of flaky proofs
2712 size_t numProofs = 8;
2713 std::vector<ProofRef> proofs;
2714 proofs.reserve(numProofs);
2715 for (size_t i = 0; i < numProofs; i++) {
2716 auto proof = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
2717 const ProofId proofid = proof->getId();
2718 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
2719 // Registering the proof adds it as a contender
2720 pm.registerProof(proof);
2721 // Make it a remote proof so that it will be promoted
2722 pm.saveRemoteProof(proofid, i, true);
2723 BOOST_CHECK(pm.forPeer(proofid, [&](const Peer peer) {
2724 return pm.setFinalized(peer.peerid);
2725 }));
2726 });
2727 proofs.emplace_back(std::move(proof));
2728 }
2729
2730 // Add nodes only for the first proof so we have a quorum
2731 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
2732 const ProofId proofid = proofs[0]->getId();
2733 for (NodeId n = 0; n < 8; n++) {
2734 pm.addNode(n, proofid);
2735 }
2736 });
2737
2738 // Make proofs old enough to be considered for staking rewards
2739 now += 1h + 1s;
2740 SetMockTime(now);
2741
2742 // Try a few times in case the non-flaky proof get selected as winner
2743 std::vector<CScript> winners;
2744 for (int attempt = 0; attempt < 10; attempt++) {
2745 // Advance chaintip so the proofs are older than the last block time
2746 block = CreateAndProcessBlock({}, CScript());
2747 chaintip = WITH_LOCK(
2748 cs_main, return Assert(m_node.chainman)
2749 ->m_blockman.LookupBlockIndex(block.GetHash()));
2750 AvalancheTest::updatedBlockTip(*m_node.avalanche);
2751
2752 // Compute local stake winner
2753 BOOST_CHECK(m_node.avalanche->isQuorumEstablished());
2754 BOOST_CHECK(m_node.avalanche->computeStakingReward(chaintip));
2755 BOOST_CHECK(m_node.avalanche->getStakingRewardWinners(
2756 chaintip->GetBlockHash(), winners));
2757 if (winners.size() == 8) {
2758 break;
2759 }
2760 }
2761
2762 BOOST_CHECK(winners.size() == 8);
2763
2764 // Verify that all winners were accepted
2765 size_t numAccepted = 0;
2766 for (const auto &proof : proofs) {
2767 const ProofId proofid = proof->getId();
2768 const StakeContenderId contender =
2769 StakeContenderId(chaintip->GetBlockHash(), proofid);
2770 if (m_node.avalanche->getStakeContenderStatus(contender) == 0) {
2771 numAccepted++;
2772 BOOST_CHECK(std::find(winners.begin(), winners.end(),
2773 proof->getPayoutScript()) != winners.end());
2774 }
2775 }
2776 BOOST_CHECK_EQUAL(winners.size(), numAccepted);
2777
2778 // Check that a highest ranking contender that was not selected as local
2779 // winner is still accepted.
2780 block = CreateAndProcessBlock({}, CScript());
2781 chaintip =
2782 WITH_LOCK(cs_main, return Assert(m_node.chainman)
2783 ->m_blockman.LookupBlockIndex(block.GetHash()));
2784 auto bestproof = buildRandomProof(
2785 active_chainstate,
2786 // Subtract some score so totalPeersScore doesn't overflow
2787 std::numeric_limits<uint32_t>::max() - MIN_VALID_PROOF_SCORE * 8);
2788 m_node.avalanche->withPeerManager(
2789 [&](avalanche::PeerManager &pm) { pm.addStakeContender(bestproof); });
2790 AvalancheTest::updatedBlockTip(*m_node.avalanche);
2791
2792 // Compute local stake winners
2793 BOOST_CHECK(m_node.avalanche->isQuorumEstablished());
2794 BOOST_CHECK(m_node.avalanche->computeStakingReward(chaintip));
2795 BOOST_CHECK(m_node.avalanche->getStakingRewardWinners(
2796 chaintip->GetBlockHash(), winners));
2797
2798 // Sanity check bestproof was not selected as a winner
2799 BOOST_CHECK(std::find(winners.begin(), winners.end(),
2800 bestproof->getPayoutScript()) == winners.end());
2801
2802 // Best contender is accepted
2803 {
2804 const StakeContenderId bestcontender =
2805 StakeContenderId(chaintip->GetBlockHash(), bestproof->getId());
2807 m_node.avalanche->getStakeContenderStatus(bestcontender), 0);
2808 }
2809
2810 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
2811 // Register bestproof so it will become dangling later
2812 pm.registerProof(bestproof);
2813 // Make it a remote proof so that it will be promoted
2814 pm.saveRemoteProof(bestproof->getId(), 0, true);
2815 pm.saveRemoteProof(bestproof->getId(), 1, false);
2816 });
2817
2818 block = CreateAndProcessBlock({}, CScript());
2819 chaintip =
2820 WITH_LOCK(cs_main, return Assert(m_node.chainman)
2821 ->m_blockman.LookupBlockIndex(block.GetHash()));
2822 AvalancheTest::updatedBlockTip(*m_node.avalanche);
2823 AvalancheTest::setFinalizationTip(*m_node.avalanche, chaintip);
2824 m_node.avalanche->cleanupStakingRewards(chaintip->nHeight);
2825
2826 // Make bestproof dangling since it has no nodes attached
2827 now += 15min + 1s;
2828 SetMockTime(now);
2829 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
2830 std::unordered_set<ProofRef, SaltedProofHasher> dummy;
2831 pm.cleanupDanglingProofs(dummy);
2832 });
2833 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
2834 BOOST_CHECK(pm.isDangling(bestproof->getId()));
2835 });
2836
2837 // Compute local stake winners
2838 BOOST_CHECK(m_node.avalanche->isQuorumEstablished());
2839 BOOST_CHECK(m_node.avalanche->computeStakingReward(chaintip));
2840 BOOST_CHECK(m_node.avalanche->getStakingRewardWinners(
2841 chaintip->GetBlockHash(), winners));
2842
2843 // Sanity check bestproof was not selected as a winner
2844 BOOST_CHECK(std::find(winners.begin(), winners.end(),
2845 bestproof->getPayoutScript()) == winners.end());
2846
2847 // Best contender is still accepted because it is a high ranking contender
2848 // with a remote proof
2849 {
2850 const StakeContenderId bestcontender =
2851 StakeContenderId(chaintip->GetBlockHash(), bestproof->getId());
2853 m_node.avalanche->getStakeContenderStatus(bestcontender), 0);
2854 }
2855}
2856
2857BOOST_AUTO_TEST_CASE(stake_contender_local_winners) {
2858 ChainstateManager &chainman = *Assert(m_node.chainman);
2859 Chainstate &active_chainstate = chainman.ActiveChainstate();
2860 CBlockIndex *chaintip =
2861 WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip());
2862 const BlockHash chaintipHash = chaintip->GetBlockHash();
2863
2864 auto now = GetTime<std::chrono::seconds>();
2865 SetMockTime(now);
2866
2867 // Create a proof that will be the local stake winner
2868 auto localWinnerProof =
2869 buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
2870 ProofId localWinnerProofId = localWinnerProof->getId();
2871 const StakeContenderId localWinnerContenderId(chaintipHash,
2872 localWinnerProof->getId());
2873 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
2874 pm.addStakeContender(localWinnerProof);
2875 });
2876
2877 // Prepare the proof so that it becomes the local stake winner
2878 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
2879 ConnectNode(NODE_AVALANCHE);
2880 pm.registerProof(localWinnerProof);
2881 for (NodeId n = 0; n < 8; n++) {
2882 pm.addNode(n, localWinnerProofId);
2883 }
2884 BOOST_CHECK(pm.forPeer(localWinnerProofId, [&](const Peer peer) {
2885 return pm.setFinalized(peer.peerid);
2886 }));
2887 });
2888
2889 // Make proof old enough to be considered for staking rewards
2890 now += 1h + 1s;
2891 SetMockTime(now);
2892 chaintip->nTime = now.count();
2893
2894 // Compute local stake winner
2895 BOOST_CHECK(m_node.avalanche->isQuorumEstablished());
2896 BOOST_CHECK(m_node.avalanche->computeStakingReward(chaintip));
2897
2898 std::vector<ProofRef> acceptedContenderProofs;
2899 acceptedContenderProofs.push_back(localWinnerProof);
2900 double bestRank =
2901 localWinnerContenderId.ComputeProofRewardRank(MIN_VALID_PROOF_SCORE);
2902
2903 // Test well past the max since we need to test the max number of accepted
2904 // contenders as well. Starts at 2 because the local winner is already
2905 // added.
2906 for (size_t numContenders = 2;
2907 numContenders < AVALANCHE_CONTENDER_MAX_POLLABLE * 10;
2908 numContenders++) {
2909 auto proof = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
2910 m_node.avalanche->withPeerManager(
2911 [&](avalanche::PeerManager &pm) { pm.addStakeContender(proof); });
2912
2913 const StakeContenderId contenderId(chaintipHash, proof->getId());
2914 double rank = contenderId.ComputeProofRewardRank(MIN_VALID_PROOF_SCORE);
2915
2916 if (rank <= bestRank) {
2917 bestRank = rank;
2918 acceptedContenderProofs.push_back(proof);
2919 const size_t numAccepted =
2921 acceptedContenderProofs.size());
2922 std::sort(acceptedContenderProofs.begin(),
2923 acceptedContenderProofs.begin() + numAccepted,
2924 [&](const ProofRef &left, const ProofRef &right) {
2925 const ProofId leftProofId = left->getId();
2926 const ProofId rightProofId = right->getId();
2927 const StakeContenderId leftContenderId(chaintipHash,
2928 leftProofId);
2929 const StakeContenderId rightContenderId(chaintipHash,
2930 rightProofId);
2931 return RewardRankComparator()(
2932 leftContenderId,
2933 leftContenderId.ComputeProofRewardRank(
2934 MIN_VALID_PROOF_SCORE),
2935 leftProofId, rightContenderId,
2936 rightContenderId.ComputeProofRewardRank(
2937 MIN_VALID_PROOF_SCORE),
2938 rightProofId);
2939 });
2940 }
2941
2942 std::vector<StakeContenderId> pollableContenders;
2943 BOOST_CHECK(AvalancheTest::setContenderStatusForLocalWinners(
2944 *m_node.avalanche, chaintip, pollableContenders));
2946 pollableContenders.size(),
2947 std::min(numContenders, AVALANCHE_CONTENDER_MAX_POLLABLE));
2948
2949 // Accepted contenders (up to the max, best first) are always included
2950 // in pollableContenders
2951 for (size_t i = 0; i < std::min(acceptedContenderProofs.size(),
2953 i++) {
2954 StakeContenderId acceptedContenderId = StakeContenderId(
2955 chaintipHash, acceptedContenderProofs[i]->getId());
2957 std::find(pollableContenders.begin(), pollableContenders.end(),
2958 acceptedContenderId) != pollableContenders.end());
2960 m_node.avalanche->getStakeContenderStatus(acceptedContenderId),
2961 0);
2962 }
2963
2964 // Check unaccepted contenders are still as we expect
2965 std::set<StakeContenderId> unacceptedContenderIds(
2966 pollableContenders.begin(), pollableContenders.end());
2967 for (auto &acceptedContenderProof : acceptedContenderProofs) {
2968 const StakeContenderId acceptedContenderId(
2969 chaintipHash, acceptedContenderProof->getId());
2970 unacceptedContenderIds.erase(acceptedContenderId);
2971 }
2972
2973 for (auto cid : unacceptedContenderIds) {
2974 BOOST_CHECK_EQUAL(m_node.avalanche->getStakeContenderStatus(cid),
2975 1);
2976 }
2977
2978 // Sanity check the local winner stays accepted
2980 m_node.avalanche->getStakeContenderStatus(localWinnerContenderId),
2981 0);
2982 }
2983}
2984
2985BOOST_AUTO_TEST_SUITE_END()
static constexpr Amount SATOSHI
Definition: amount.h:148
static constexpr Amount COIN
Definition: amount.h:149
uint256 ArithToUint256(const arith_uint256 &a)
const CChainParams & Params()
Return the currently selected parameters.
Definition: chainparams.cpp:21
#define Assert(val)
Identity function.
Definition: check.h:84
A CService with information about it as peer.
Definition: protocol.h:443
BlockHash GetHash() const
Definition: block.cpp:11
Definition: block.h:60
The block chain is a tree shaped structure starting with the genesis block at the root,...
Definition: blockindex.h:25
CBlockIndex * pprev
pointer to the index of the predecessor of this block
Definition: blockindex.h:32
arith_uint256 nChainWork
(memory only) Total amount of work (expected number of hashes) in the chain up to and including this ...
Definition: blockindex.h:51
const BlockHash * phashBlock
pointer to the hash of the block, if any.
Definition: blockindex.h:29
uint32_t nTime
Definition: blockindex.h:76
BlockHash GetBlockHash() const
Definition: blockindex.h:130
int nHeight
height of the entry in the chain. The genesis block has height 0
Definition: blockindex.h:38
CBlockIndex * Tip() const
Returns the index entry for the tip of this chain, or nullptr if none.
Definition: chain.h:154
CCoinsView that adds a memory cache for transactions to another CCoinsView.
Definition: coins.h:363
void AddCoin(const COutPoint &outpoint, Coin coin, bool possible_overwrite)
Add a coin.
Definition: coins.cpp:99
bool SpendCoin(const COutPoint &outpoint, Coin *moveto=nullptr)
Spend a coin.
Definition: coins.cpp:171
An encapsulated secp256k1 private key.
Definition: key.h:28
static CKey MakeCompressedKey()
Produce a valid compressed key.
Definition: key.cpp:465
CPubKey GetPubKey() const
Compute the public key from a private key.
Definition: key.cpp:209
A mutable version of CTransaction.
Definition: transaction.h:274
std::vector< CTxOut > vout
Definition: transaction.h:277
std::vector< CTxIn > vin
Definition: transaction.h:276
Network address.
Definition: netaddress.h:114
Information about a peer.
Definition: net.h:389
Simple class for background tasks that should be run periodically or once "after a while".
Definition: scheduler.h:41
void serviceQueue() EXCLUSIVE_LOCKS_REQUIRED(!newTaskMutex)
Services the queue 'forever'.
Definition: scheduler.cpp:23
size_t getQueueInfo(std::chrono::steady_clock::time_point &first, std::chrono::steady_clock::time_point &last) const EXCLUSIVE_LOCKS_REQUIRED(!newTaskMutex)
Returns number of tasks waiting to be serviced, and first and last task times.
Definition: scheduler.cpp:120
void StopWhenDrained() EXCLUSIVE_LOCKS_REQUIRED(!newTaskMutex)
Tell any threads running serviceQueue to stop when there is no work left to be done.
Definition: scheduler.h:100
A combination of a network address (CNetAddr) and a (TCP) port.
Definition: netaddress.h:573
CTxMemPool stores valid-according-to-the-current-best-chain transactions that may be included in the ...
Definition: txmempool.h:221
RecursiveMutex cs
This mutex needs to be locked when accessing mapTx or other members that are guarded by it.
Definition: txmempool.h:317
void removeRecursive(const CTransaction &tx, MemPoolRemovalReason reason) EXCLUSIVE_LOCKS_REQUIRED(cs)
Definition: txmempool.cpp:269
bool exists(const TxId &txid) const
Definition: txmempool.h:535
void check(const CCoinsViewCache &active_coins_tip, int64_t spendheight) const EXCLUSIVE_LOCKS_REQUIRED(void addUnchecked(CTxMemPoolEntryRef entry) EXCLUSIVE_LOCKS_REQUIRED(cs
If sanity-checking is turned on, check makes sure the pool is consistent (does not contain two transa...
Definition: txmempool.h:382
An output of a transaction.
Definition: transaction.h:128
Chainstate stores and provides an API to update our local knowledge of the current best chain.
Definition: validation.h:733
bool ActivateBestChain(BlockValidationState &state, std::shared_ptr< const CBlock > pblock=nullptr, avalanche::Processor *const avalanche=nullptr) EXCLUSIVE_LOCKS_REQUIRED(!m_chainstate_mutex
Find the best known block, and make it the tip of the block chain.
CChain m_chain
The current chain of blockheaders we consult and build on.
Definition: validation.h:832
CCoinsViewCache & CoinsTip() EXCLUSIVE_LOCKS_REQUIRED(
Definition: validation.h:859
Provides an interface for creating and interacting with one or two chainstates: an IBD chainstate gen...
Definition: validation.h:1185
SnapshotCompletionResult MaybeCompleteSnapshotValidation() EXCLUSIVE_LOCKS_REQUIRED(const CBlockIndex *GetSnapshotBaseBlock() const EXCLUSIVE_LOCKS_REQUIRED(Chainstate ActiveChainstate)() const
Once the background validation chainstate has reached the height which is the base of the UTXO snapsh...
Definition: validation.h:1436
RecursiveMutex & GetMutex() const LOCK_RETURNED(
Alias for cs_main.
Definition: validation.h:1317
CBlockIndex * ActiveTip() const EXCLUSIVE_LOCKS_REQUIRED(GetMutex())
Definition: validation.h:1443
bool AcceptBlock(const std::shared_ptr< const CBlock > &pblock, BlockValidationState &state, bool fRequested, const FlatFilePos *dbp, bool *fNewBlock, bool min_pow_checked) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
Sufficiently validate a block for disk storage (and store on disk).
node::BlockManager m_blockman
A single BlockManager instance is shared across each constructed chainstate to avoid duplicating bloc...
Definition: validation.h:1326
A UTXO entry.
Definition: coins.h:29
Fast randomness source.
Definition: random.h:411
ReadView getReadView() const
Definition: rwcollection.h:76
WriteView getWriteView()
Definition: rwcollection.h:82
I randrange(I range) noexcept
Generate a random integer in the range [0..range), with range > 0.
Definition: random.h:266
256-bit unsigned big integer.
bool removeNode(NodeId nodeid)
uint32_t getConnectedPeersScore() const
Definition: peermanager.h:450
bool isDangling(const ProofId &proofid) const
bool exists(const ProofId &proofid) const
Return true if the (valid) proof exists, but only for non-dangling proofs.
Definition: peermanager.h:414
bool forPeer(const ProofId &proofid, Callable &&func) const
Definition: peermanager.h:422
uint32_t getTotalPeersScore() const
Definition: peermanager.h:449
bool addNode(NodeId nodeid, const ProofId &proofid)
Node API.
Definition: peermanager.cpp:33
std::unordered_set< ProofRef, SaltedProofHasher > updatedBlockTip()
Update the peer set when a new block is connected.
bool isBoundToPeer(const ProofId &proofid) const
bool saveRemoteProof(const ProofId &proofid, const NodeId nodeid, const bool present)
bool isImmature(const ProofId &proofid) const
bool rejectProof(const ProofId &proofid, RejectionMode mode=RejectionMode::DEFAULT)
void addStakeContender(const ProofRef &proof)
bool isInConflictingPool(const ProofId &proofid) const
void cleanupDanglingProofs(std::unordered_set< ProofRef, SaltedProofHasher > &registeredProofs)
bool registerProof(const ProofRef &proof, ProofRegistrationState &registrationState, RegistrationMode mode=RegistrationMode::DEFAULT)
Mutex cs_finalizedItems
Rolling bloom filter to track recently finalized inventory items of any type.
Definition: processor.h:459
bool setContenderStatusForLocalWinners(const CBlockIndex *pindex, std::vector< StakeContenderId > &pollableContenders) EXCLUSIVE_LOCKS_REQUIRED(!cs_peerManager
Helper to set the vote status for local winners in the contender cache.
Definition: processor.cpp:1142
std::atomic< uint64_t > round
Keep track of peers and queries sent.
Definition: processor.h:173
std::vector< CInv > getInvsForNextPoll(RWCollection< VoteMap >::ReadView &voteRecordsReadView, bool forPoll=true) const
Definition: processor.cpp:1346
void runEventLoop() EXCLUSIVE_LOCKS_REQUIRED(!cs_peerManager
Definition: processor.cpp:1215
void updatedBlockTip() EXCLUSIVE_LOCKS_REQUIRED(!cs_peerManager
Definition: processor.cpp:1154
RWCollection< VoteMap > voteRecords
Items to run avalanche on.
Definition: processor.h:168
uint32_t minQuorumScore
Quorum management.
Definition: processor.h:222
std::atomic< bool > m_canShareLocalProof
Definition: processor.h:225
std::atomic< int64_t > avaproofsNodeCounter
Definition: processor.h:227
std::atomic_bool m_stakingPreConsensus
Definition: processor.h:269
Mutex cs_peerManager
Keep track of the peers and associated infos.
Definition: processor.h:178
void clearInvsNotWorthPolling() EXCLUSIVE_LOCKS_REQUIRED(!cs_peerManager
Definition: processor.cpp:1296
double minQuorumConnectedScoreRatio
Definition: processor.h:223
bool addUTXO(COutPoint utxo, Amount amount, uint32_t height, bool is_coinbase, CKey key)
const CScript & getPayoutScript() const
Definition: proof.h:167
const ProofId & getId() const
Definition: proof.h:170
const std::vector< SignedStake > & getStakes() const
Definition: proof.h:166
uint32_t getCooldown() const
Definition: protocol.h:44
const std::vector< Vote > & GetVotes() const
Definition: protocol.h:45
uint64_t getRound() const
Definition: protocol.h:43
const AnyVoteItem & getVoteItem() const
Definition: processor.h:106
const VoteStatus & getStatus() const
Definition: processor.h:105
static constexpr unsigned int size()
Definition: uint256.h:93
CBlockIndex * LookupBlockIndex(const BlockHash &hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
256-bit opaque blob.
Definition: uint256.h:129
static const uint256 ZERO
Definition: uint256.h:134
@ OUTBOUND_FULL_RELAY
These are the default connections that we use to connect with the network.
std::string FormatScript(const CScript &script)
Definition: core_write.cpp:24
RecursiveMutex cs_main
Mutex to guard access to validation specific variables, such as reading or changing the chainstate.
Definition: cs_main.cpp:7
int64_t NodeId
Definition: eviction.h:16
std::string EncodeSecret(const CKey &key)
Definition: key_io.cpp:102
@ NONE
Definition: logging.h:68
static constexpr Amount PROOF_DUST_THRESHOLD
Minimum amount per utxo.
Definition: proof.h:41
ProofRegistrationResult
Definition: peermanager.h:145
std::variant< const ProofRef, const CBlockIndex *, const StakeContenderId, const CTransactionRef > AnyVoteItem
Definition: processor.h:95
const CScript UNSPENDABLE_ECREG_PAYOUT_SCRIPT
Definition: util.h:22
ProofRef buildRandomProof(Chainstate &active_chainstate, uint32_t score, int height, const CKey &masterKey)
Definition: util.cpp:20
constexpr uint32_t MIN_VALID_PROOF_SCORE
Definition: util.h:20
Definition: messages.h:12
std::string ToString(const T &t)
Locale-independent version of std::to_string.
Definition: string.h:150
NodeContext & m_node
Definition: interfaces.cpp:823
static constexpr NodeId NO_NODE
Special NodeId that represent no node.
Definition: nodeid.h:15
#define BOOST_CHECK_EQUAL(v1, v2)
Definition: object.cpp:18
#define BOOST_CHECK(expr)
Definition: object.cpp:17
static CTransactionRef MakeTransactionRef()
Definition: transaction.h:316
std::shared_ptr< const CTransaction > CTransactionRef
Definition: transaction.h:315
Response response
Definition: processor.cpp:522
static constexpr size_t AVALANCHE_CONTENDER_MAX_POLLABLE
Maximum number of stake contenders to poll for, leaving room for polling blocks and proofs in the sam...
Definition: processor.h:60
static constexpr size_t AVALANCHE_MAX_ELEMENT_POLL
Maximum item that can be polled at once.
Definition: processor.h:54
static constexpr uint32_t AVALANCHE_FINALIZED_ITEMS_FILTER_NUM_ELEMENTS
The size of the finalized items filter.
Definition: processor.h:76
BOOST_AUTO_TEST_CASE_TEMPLATE(voteitemupdate, P, VoteItemProviders)
BOOST_AUTO_TEST_CASE(quorum_diversity)
boost::mpl::list< BlockProvider, ProofProvider, StakeContenderProvider, TxProvider > VoteItemProviders
boost::mpl::list< StakeContenderProvider > Uint256VoteItemProviders
boost::mpl::list< BlockProvider, ProofProvider, TxProvider > NullableVoteItemProviders
static bool HasAllDesirableServiceFlags(ServiceFlags services)
A shortcut for (services & GetDesirableServiceFlags(services)) == GetDesirableServiceFlags(services),...
Definition: protocol.h:428
@ MSG_TX
Definition: protocol.h:574
@ MSG_AVA_STAKE_CONTENDER
Definition: protocol.h:582
@ MSG_AVA_PROOF
Definition: protocol.h:581
@ MSG_BLOCK
Definition: protocol.h:575
ServiceFlags
nServices flags.
Definition: protocol.h:336
@ NODE_NONE
Definition: protocol.h:339
@ NODE_NETWORK
Definition: protocol.h:343
@ NODE_AVALANCHE
Definition: protocol.h:381
static const int PROTOCOL_VERSION
network protocol versioning
void Shuffle(I first, I last, R &&rng)
More efficient than using std::shuffle on a FastRandomContext.
Definition: random.h:512
uint256 GetRandHash() noexcept
========== CONVENIENCE FUNCTIONS FOR COMMONLY USED RANDOMNESS ==========
Definition: random.h:494
reverse_range< T > reverse_iterate(T &x)
@ OP_TRUE
Definition: script.h:61
static uint16_t GetDefaultPort()
Definition: bitcoin.h:18
static std::string ToString(const CService &ip)
Definition: db.h:36
static RPCHelpMan stop()
Definition: server.cpp:214
CScript GetScriptForRawPubKey(const CPubKey &pubKey)
Generate a P2PK script for the given pubkey.
Definition: standard.cpp:244
CScript GetScriptForDestination(const CTxDestination &dest)
Generate a Bitcoin scriptPubKey for the given CTxDestination.
Definition: standard.cpp:240
Definition: amount.h:21
A BlockHash is a unqiue identifier for a block.
Definition: blockhash.h:13
static const Currency & get()
Definition: amount.cpp:18
A TxId is the identifier of a transaction.
Definition: txid.h:14
Compare proofs by score, then by id in case of equality.
StakeContenderIds are unique for each block to ensure that the peer polling for their acceptance has ...
double ComputeProofRewardRank(uint32_t proofScore) const
To make sure the selection is properly weighted according to the proof score, we normalize the conten...
Vote history.
Definition: voterecord.h:49
Bilingual messages:
Definition: translation.h:17
bool empty() const
Definition: translation.h:27
std::string original
Definition: translation.h:18
#define LOCK2(cs1, cs2)
Definition: sync.h:309
#define LOCK(cs)
Definition: sync.h:306
#define WITH_LOCK(cs, code)
Run code while locking a mutex.
Definition: sync.h:357
void UninterruptibleSleep(const std::chrono::microseconds &n)
Definition: time.cpp:21
void SetMockTime(int64_t nMockTimeIn)
DEPRECATED Use SetMockTime with chrono type.
Definition: time.cpp:46
@ CONFLICT
Removed for conflict with in-block transaction.
void SyncWithValidationInterfaceQueue()
This is a synonym for the following, which asserts certain locks are not held: std::promise<void> pro...
static constexpr int AVALANCHE_MAX_INFLIGHT_POLL
How many inflight requests can exist for one item.
Definition: voterecord.h:40
static constexpr uint32_t AVALANCHE_VOTE_STALE_MIN_THRESHOLD
Lowest configurable staleness threshold (finalization score + necessary votes to increase confidence ...
Definition: voterecord.h:28
static constexpr int AVALANCHE_FINALIZATION_SCORE
Finalization score.
Definition: voterecord.h:17