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