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