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