Bitcoin ABC 0.34.2
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 // The item should remain pollable.
1018 for (int i = 0; i < AVALANCHE_MAX_INFLIGHT_POLL; i++) {
1019 resp = {getRound(), 0, {Vote(0, itemid)}};
1020 runEventLoop();
1021 BOOST_CHECK(registerVotes(avanodeid, resp, updates));
1022 BOOST_CHECK_EQUAL(getSuitableNodeToQuery(), avanodeid);
1023 }
1024
1025 invs = getInvsForNextPoll();
1026 BOOST_CHECK_EQUAL(invs.size(), 1);
1027
1028 // 4. Invalid round count. Request is not discarded.
1029 uint64_t queryRound = getRound();
1030 runEventLoop();
1031
1032 resp = {queryRound + 1, 0, {Vote()}};
1033 checkRegisterVotesError(avanodeid, resp, "unexpected-ava-response");
1034
1035 resp = {queryRound - 1, 0, {Vote()}};
1036 checkRegisterVotesError(avanodeid, resp, "unexpected-ava-response");
1037
1038 // 5. Making request for invalid nodes do not work. Request is not
1039 // discarded.
1040 resp = {queryRound, 0, {Vote(0, itemid)}};
1041 checkRegisterVotesError(avanodeid + 1234, resp, "unexpected-ava-response");
1042
1043 // Proper response gets processed and avanode is available again.
1044 resp = {queryRound, 0, {Vote(0, itemid)}};
1045 BOOST_CHECK(registerVotes(avanodeid, resp, updates));
1046 BOOST_CHECK_EQUAL(updates.size(), 0);
1047 BOOST_CHECK_EQUAL(getSuitableNodeToQuery(), avanodeid);
1048
1049 // Out of order response are rejected.
1050 const auto item2 = provider.buildVoteItem();
1051 BOOST_CHECK(addToReconcile(item2));
1052
1053 std::vector<Vote> votes = provider.buildVotesForItems(0, {item, item2});
1054 resp = {getRound(), 0, {votes[1], votes[0]}};
1055 runEventLoop();
1056 checkRegisterVotesError(avanodeid, resp, "invalid-ava-response-content");
1057 BOOST_CHECK_EQUAL(getSuitableNodeToQuery(), avanodeid);
1058
1059 // But they are accepted in order.
1060 resp = {getRound(), 0, votes};
1061 runEventLoop();
1062 BOOST_CHECK(registerVotes(avanodeid, resp, updates));
1063 BOOST_CHECK_EQUAL(updates.size(), 0);
1064 BOOST_CHECK_EQUAL(getSuitableNodeToQuery(), avanodeid);
1065}
1066
1068 P provider(this);
1069 const uint32_t invType = provider.invType;
1070
1071 auto itemA = provider.buildVoteItem();
1072 auto itemB = provider.buildVoteItem();
1073
1074 auto avanodes = ConnectNodes();
1075 int nextNodeIndex = 0;
1076
1077 // Build votes to get proper ordering
1078 std::vector<Vote> votes = provider.buildVotesForItems(0, {itemA, itemB});
1079
1080 // Register the items and check they are added to the list of elements to
1081 // poll.
1082 BOOST_CHECK(addToReconcile(itemA));
1083 BOOST_CHECK(addToReconcile(itemB));
1084 auto invs = getInvsForNextPoll();
1085 BOOST_CHECK_EQUAL(invs.size(), 2);
1086 for (size_t i = 0; i < invs.size(); i++) {
1087 BOOST_CHECK_EQUAL(invs[i].type, invType);
1088 BOOST_CHECK(invs[i].hash == votes[i].GetHash());
1089 }
1090
1091 // When an item is marked invalid, stop polling.
1092 provider.invalidateItem(itemB);
1093
1094 Response goodResp{getRound(), 0, {Vote(0, provider.getVoteItemId(itemA))}};
1095 std::vector<avalanche::VoteItemUpdate> updates;
1096 runEventLoop();
1098 registerVotes(avanodes[nextNodeIndex++ % avanodes.size()]->GetId(),
1099 goodResp, updates));
1100 BOOST_CHECK_EQUAL(updates.size(), 0);
1101
1102 // Verify itemB is no longer being polled for
1103 invs = getInvsForNextPoll();
1104 BOOST_CHECK_EQUAL(invs.size(), 1);
1105 BOOST_CHECK_EQUAL(invs[0].type, invType);
1106 BOOST_CHECK(invs[0].hash == goodResp.GetVotes()[0].GetHash());
1107
1108 // Votes including itemB are rejected
1109 Response badResp{getRound(), 0, votes};
1110 runEventLoop();
1111 std::string error;
1113 !registerVotes(avanodes[nextNodeIndex++ % avanodes.size()]->GetId(),
1114 badResp, updates, error));
1115 BOOST_CHECK_EQUAL(error, "invalid-ava-response-size");
1116
1117 // Vote until itemA is invalidated by avalanche
1118 votes = provider.buildVotesForItems(1, {itemA});
1119 auto registerNewVote = [&]() {
1120 Response resp = {getRound(), 0, votes};
1121 runEventLoop();
1122 auto nodeid = avanodes[nextNodeIndex++ % avanodes.size()]->GetId();
1123 BOOST_CHECK(registerVotes(nodeid, resp, updates));
1124 };
1125 for (size_t i = 0; i < 4000; i++) {
1126 registerNewVote();
1127 if (updates.size() > 0 &&
1128 updates[0].getStatus() == VoteStatus::Invalid) {
1129 break;
1130 }
1131 }
1132
1133 // Verify itemA is no longer being polled for
1134 invs = getInvsForNextPoll();
1135 BOOST_CHECK_EQUAL(invs.size(), 0);
1136
1137 // Votes including itemA are rejected
1138 badResp = Response(getRound(), 0, votes);
1139 runEventLoop();
1141 !registerVotes(avanodes[nextNodeIndex++ % avanodes.size()]->GetId(),
1142 badResp, updates, error));
1143 BOOST_CHECK_EQUAL(error, "unexpected-ava-response");
1144}
1145
1146BOOST_TEST_DECORATOR(*boost::unit_test::timeout(60))
1148 P provider(this);
1149 ChainstateManager &chainman = *Assert(m_node.chainman);
1150
1151 auto queryTimeDuration = std::chrono::milliseconds(10);
1152 setArg("-avatimeout", ToString(queryTimeDuration.count()));
1153 // This would fail the test for blocks
1154 setArg("-avalanchestakingpreconsensus", "0");
1155
1156 m_node.validation_signals->SyncWithValidationInterfaceQueue();
1157 bilingual_str error;
1158 m_node.avalanche = Processor::MakeProcessor(
1159 *m_node.args, *m_node.chain, m_node.connman.get(), chainman,
1160 m_node.mempool.get(), *m_node.scheduler, error);
1161
1162 const auto item = provider.buildVoteItem();
1163 const auto itemid = provider.getVoteItemId(item);
1164
1165 // Add the item
1166 BOOST_CHECK(addToReconcile(item));
1167
1168 // Create a quorum of nodes that support avalanche.
1169 ConnectNodes();
1170 NodeId avanodeid = NO_NODE;
1171
1172 // Expire requests after some time.
1173 for (int i = 0; i < 10; i++) {
1174 Response resp = {getRound(), 0, {Vote(0, itemid)}};
1175 avanodeid = getSuitableNodeToQuery();
1176
1177 auto start = Now<SteadyMilliseconds>();
1178 runEventLoop();
1179 // We cannot guarantee that we'll wait for just 1ms, so we have to bail
1180 // if we aren't within the proper time range.
1181 std::this_thread::sleep_for(std::chrono::milliseconds(1));
1182 runEventLoop();
1183
1184 std::vector<avalanche::VoteItemUpdate> updates;
1185 bool ret = registerVotes(avanodeid, next(resp), updates);
1186 if (Now<SteadyMilliseconds>() > start + queryTimeDuration) {
1187 // We waited for too long, bail. Because we can't know for sure when
1188 // previous steps ran, ret is not deterministic and we do not check
1189 // it.
1190 i--;
1191 continue;
1192 }
1193
1194 // We are within time bounds, so the vote should have worked.
1195 BOOST_CHECK(ret);
1196
1197 avanodeid = getSuitableNodeToQuery();
1198
1199 // Now try again but wait for expiration.
1200 runEventLoop();
1201 std::this_thread::sleep_for(queryTimeDuration);
1202 runEventLoop();
1203 BOOST_CHECK(!registerVotes(avanodeid, next(resp), updates));
1204 }
1205}
1206
1208 P provider(this);
1209 const uint32_t invType = provider.invType;
1210
1211 // Create enough nodes so that we run into the inflight request limit.
1212 auto proof = GetProof();
1213 BOOST_CHECK(m_node.avalanche->withPeerManager(
1214 [&](avalanche::PeerManager &pm) { return pm.registerProof(proof); }));
1215
1216 std::array<CNode *, AVALANCHE_MAX_INFLIGHT_POLL + 1> nodes;
1217 for (auto &n : nodes) {
1218 n = ConnectNode(NODE_AVALANCHE);
1219 BOOST_CHECK(addNode(n->GetId(), proof->getId()));
1220 }
1221
1222 // Add an item to poll
1223 const auto item = provider.buildVoteItem();
1224 const auto itemid = provider.getVoteItemId(item);
1225 BOOST_CHECK(addToReconcile(item));
1226
1227 // Ensure there are enough requests in flight.
1228 std::map<NodeId, uint64_t> node_round_map;
1229 for (int i = 0; i < AVALANCHE_MAX_INFLIGHT_POLL; i++) {
1230 NodeId nodeid = getSuitableNodeToQuery();
1231 BOOST_CHECK(node_round_map.find(nodeid) == node_round_map.end());
1232 node_round_map.insert(std::pair<NodeId, uint64_t>(nodeid, getRound()));
1233 auto invs = getInvsForNextPoll();
1234 BOOST_CHECK_EQUAL(invs.size(), 1);
1235 BOOST_CHECK_EQUAL(invs[0].type, invType);
1236 BOOST_CHECK(invs[0].hash == itemid);
1237 runEventLoop();
1238 }
1239
1240 // Now that we have enough in flight requests, we shouldn't poll.
1241 auto suitablenodeid = getSuitableNodeToQuery();
1242 BOOST_CHECK(suitablenodeid != NO_NODE);
1243 auto invs = getInvsForNextPoll();
1244 BOOST_CHECK_EQUAL(invs.size(), 0);
1245 runEventLoop();
1246 BOOST_CHECK_EQUAL(getSuitableNodeToQuery(), suitablenodeid);
1247
1248 // Send one response, now we can poll again.
1249 auto it = node_round_map.begin();
1250 Response resp = {it->second, 0, {Vote(0, itemid)}};
1251 std::vector<avalanche::VoteItemUpdate> updates;
1252 BOOST_CHECK(registerVotes(it->first, resp, updates));
1253 node_round_map.erase(it);
1254
1255 invs = getInvsForNextPoll();
1256 BOOST_CHECK_EQUAL(invs.size(), 1);
1257 BOOST_CHECK_EQUAL(invs[0].type, invType);
1258 BOOST_CHECK(invs[0].hash == itemid);
1259}
1260
1262 P provider(this);
1263
1264 constexpr size_t numItems = 3;
1265 std::vector<CInv> itemInvs;
1266 itemInvs.reserve(numItems);
1267 for (size_t i = 0; i < numItems; i++) {
1268 const auto item = provider.buildVoteItem();
1269 BOOST_CHECK(addToReconcile(item));
1270 itemInvs.emplace_back(provider.invType, provider.getVoteItemId(item));
1271 }
1272
1273 auto contains = [](const std::vector<CInv> &invs, const CInv &target) {
1274 return std::find_if(invs.begin(), invs.end(), [&](const CInv &inv) {
1275 return inv.type == target.type && inv.hash == target.hash;
1276 }) != invs.end();
1277 };
1278
1279 // Saturate the inflight counter for every item.
1280 for (int i = 0; i < AVALANCHE_MAX_INFLIGHT_POLL; i++) {
1281 auto invs = getInvsForNextPoll(/*forPoll=*/true);
1282 BOOST_CHECK_EQUAL(invs.size(), numItems);
1283 }
1284 BOOST_CHECK(getInvsForNextPoll().empty());
1285
1286 // Clear with different counts so the items have different remaining
1287 // headroom (1, 2 and 3 polls respectively).
1288 clearInflightRequests(
1289 {{itemInvs[0], 1}, {itemInvs[1], 2}, {itemInvs[2], 3}});
1290 BOOST_CHECK_EQUAL(getInvsForNextPoll().size(), numItems);
1291
1292 // Exhaust items one by one according to remaining headroom.
1293 BOOST_CHECK_EQUAL(getInvsForNextPoll(/*forPoll=*/true).size(), numItems);
1294 {
1295 auto remaining = getInvsForNextPoll();
1296 BOOST_CHECK_EQUAL(remaining.size(), 2);
1297 BOOST_CHECK(!contains(remaining, itemInvs[0]));
1298 BOOST_CHECK(contains(remaining, itemInvs[1]));
1299 BOOST_CHECK(contains(remaining, itemInvs[2]));
1300 }
1301
1302 BOOST_CHECK_EQUAL(getInvsForNextPoll(/*forPoll=*/true).size(), 2);
1303 {
1304 auto remaining = getInvsForNextPoll();
1305 BOOST_CHECK_EQUAL(remaining.size(), 1);
1306 BOOST_CHECK(!contains(remaining, itemInvs[1]));
1307 BOOST_CHECK(contains(remaining, itemInvs[2]));
1308 }
1309
1310 BOOST_CHECK_EQUAL(getInvsForNextPoll(/*forPoll=*/true).size(), 1);
1311 BOOST_CHECK(getInvsForNextPoll().empty());
1312
1313 // Clearing the remaining requests restores full poll headroom.
1314 clearInflightRequests({{itemInvs[0], AVALANCHE_MAX_INFLIGHT_POLL},
1315 {itemInvs[1], AVALANCHE_MAX_INFLIGHT_POLL},
1316 {itemInvs[2], AVALANCHE_MAX_INFLIGHT_POLL}});
1317 for (int i = 0; i < AVALANCHE_MAX_INFLIGHT_POLL; i++) {
1318 BOOST_CHECK_EQUAL(getInvsForNextPoll(/*forPoll=*/true).size(),
1319 numItems);
1320 }
1321 BOOST_CHECK(getInvsForNextPoll().empty());
1322
1323 // Unknown inventory and empty maps are no-ops.
1324 clearInflightRequests({});
1325 clearInflightRequests({{CInv(provider.invType, GetRandHash()), 5}});
1326 BOOST_CHECK(getInvsForNextPoll().empty());
1327}
1328
1330 P provider(this);
1331 const uint32_t invType = provider.invType;
1332
1333 auto proof = GetProof();
1334 BOOST_CHECK(m_node.avalanche->withPeerManager(
1335 [&](avalanche::PeerManager &pm) { return pm.registerProof(proof); }));
1336
1337 // More nodes than the inflight cap so a single event-loop pass can attempt
1338 // enough failed dispatches to saturate inflight if counters leak.
1339 constexpr size_t numNodes = 8 + AVALANCHE_MAX_INFLIGHT_POLL;
1340 std::vector<CNode *> nodes;
1341 nodes.reserve(numNodes);
1342 for (size_t i = 0; i < numNodes; i++) {
1343 CNode *n = ConnectNode(NODE_AVALANCHE);
1344 BOOST_CHECK(addNode(n->GetId(), proof->getId()));
1345 nodes.push_back(n);
1346 }
1347
1348 const auto item = provider.buildVoteItem();
1349 const auto itemid = provider.getVoteItemId(item);
1350 BOOST_CHECK(addToReconcile(item));
1351
1352 // Make ForNode fail for every peer while they are still selectable.
1353 for (CNode *n : nodes) {
1354 n->fDisconnect = true;
1355 }
1356
1357 runEventLoop();
1358
1359 // Inflight must not have leaked: the item remains pollable.
1360 auto invs = getInvsForNextPoll();
1361 BOOST_CHECK_EQUAL(invs.size(), 1);
1362 BOOST_CHECK_EQUAL(invs[0].type, invType);
1363 BOOST_CHECK(invs[0].hash == itemid);
1364
1365 // A fresh connected quorum can still poll the item successfully.
1366 ConnectNodes();
1367 runEventLoop();
1368 invs = getInvsForNextPoll();
1369 BOOST_CHECK_EQUAL(invs.size(), 1);
1370 BOOST_CHECK_EQUAL(invs[0].type, invType);
1371 BOOST_CHECK(invs[0].hash == itemid);
1372}
1373
1374BOOST_AUTO_TEST_CASE(quorum_diversity) {
1375 std::vector<VoteItemUpdate> updates;
1376
1377 CBlock block = CreateAndProcessBlock({}, CScript());
1378 const BlockHash blockHash = block.GetHash();
1379 const CBlockIndex *pindex;
1380 {
1381 LOCK(cs_main);
1382 pindex =
1383 Assert(m_node.chainman)->m_blockman.LookupBlockIndex(blockHash);
1384 }
1385
1386 // Create nodes that supports avalanche.
1387 auto avanodes = ConnectNodes();
1388
1389 // Querying for random block returns false.
1390 BOOST_CHECK(!m_node.avalanche->isAccepted(pindex));
1391
1392 // Add a new block. Check it is added to the polls.
1393 BOOST_CHECK(m_node.avalanche->addToReconcile(pindex));
1394
1395 // Do one valid round of voting.
1396 uint64_t round = getRound();
1397 Response resp{round, 0, {Vote(0, blockHash)}};
1398
1399 // Check that all nodes can vote.
1400 for (size_t i = 0; i < avanodes.size(); i++) {
1401 runEventLoop();
1402 BOOST_CHECK(registerVotes(avanodes[i]->GetId(), next(resp), updates));
1403 }
1404
1405 // Generate a query for every single node.
1406 const NodeId firstNodeId = getSuitableNodeToQuery();
1407 std::map<NodeId, uint64_t> node_round_map;
1408 round = getRound();
1409 for (size_t i = 0; i < avanodes.size(); i++) {
1410 NodeId nodeid = getSuitableNodeToQuery();
1411 BOOST_CHECK(node_round_map.find(nodeid) == node_round_map.end());
1412 node_round_map[nodeid] = getRound();
1413 runEventLoop();
1414 }
1415
1416 // Now only the first node can vote. All others would be duplicate in the
1417 // quorum.
1418 auto confidence = m_node.avalanche->getConfidence(pindex);
1419 BOOST_REQUIRE(confidence > 0);
1420
1421 for (auto &[nodeid, r] : node_round_map) {
1422 if (nodeid == firstNodeId) {
1423 // Node 0 is the only one which can vote at this stage.
1424 round = r;
1425 continue;
1426 }
1427
1429 registerVotes(nodeid, {r, 0, {Vote(0, blockHash)}}, updates));
1430 BOOST_CHECK_EQUAL(m_node.avalanche->getConfidence(pindex), confidence);
1431 }
1432
1434 registerVotes(firstNodeId, {round, 0, {Vote(0, blockHash)}}, updates));
1435 BOOST_CHECK_EQUAL(m_node.avalanche->getConfidence(pindex), confidence + 1);
1436}
1437
1439 CScheduler s;
1440
1441 CBlock block = CreateAndProcessBlock({}, CScript());
1442 const BlockHash blockHash = block.GetHash();
1443 const CBlockIndex *pindex;
1444 {
1445 LOCK(cs_main);
1446 pindex =
1447 Assert(m_node.chainman)->m_blockman.LookupBlockIndex(blockHash);
1448 }
1449
1450 // Starting the event loop.
1451 BOOST_CHECK(m_node.avalanche->startEventLoop(s));
1452
1453 // There is one task planned in the next hour (our event loop).
1454 std::chrono::steady_clock::time_point start, stop;
1455 BOOST_CHECK_EQUAL(s.getQueueInfo(start, stop), 1);
1456
1457 // Starting twice doesn't start it twice.
1458 BOOST_CHECK(!m_node.avalanche->startEventLoop(s));
1459
1460 // Start the scheduler thread.
1461 std::thread schedulerThread(std::bind(&CScheduler::serviceQueue, &s));
1462
1463 // Create a quorum of nodes that support avalanche.
1464 auto avanodes = ConnectNodes();
1465
1466 // There is no query in flight at the moment.
1467 NodeId nodeid = getSuitableNodeToQuery();
1468 BOOST_CHECK_NE(nodeid, NO_NODE);
1469
1470 // Add a new block. Check it is added to the polls.
1471 uint64_t queryRound = getRound();
1472 BOOST_CHECK(m_node.avalanche->addToReconcile(pindex));
1473
1474 // Wait until all nodes got a poll
1475 for (int i = 0; i < 60 * 1000; i++) {
1476 // Technically, this is a race condition, but this should do just fine
1477 // as we wait up to 1 minute for an event that should take 80ms.
1478 UninterruptibleSleep(std::chrono::milliseconds(1));
1479 if (getRound() == queryRound + avanodes.size()) {
1480 break;
1481 }
1482 }
1483
1484 // Check that we effectively got a request and not timed out.
1485 BOOST_CHECK(getRound() > queryRound);
1486
1487 // Respond and check the cooldown time is respected.
1488 uint64_t responseRound = getRound();
1489 auto queryTime = Now<SteadyMilliseconds>() + std::chrono::milliseconds(100);
1490
1491 std::vector<VoteItemUpdate> updates;
1492 // Only the first node answers, so it's the only one that gets polled again
1493 BOOST_CHECK(registerVotes(nodeid, {queryRound, 100, {Vote(0, blockHash)}},
1494 updates));
1495
1496 for (int i = 0; i < 10000; i++) {
1497 // We make sure that we do not get a request before queryTime.
1498 UninterruptibleSleep(std::chrono::milliseconds(1));
1499 if (getRound() != responseRound) {
1500 BOOST_CHECK(Now<SteadyMilliseconds>() >= queryTime);
1501 break;
1502 }
1503 }
1504
1505 // But we eventually get one.
1506 BOOST_CHECK(getRound() > responseRound);
1507
1508 // Stop event loop.
1509 BOOST_CHECK(m_node.avalanche->stopEventLoop());
1510
1511 // We don't have any task scheduled anymore.
1512 BOOST_CHECK_EQUAL(s.getQueueInfo(start, stop), 0);
1513
1514 // Can't stop the event loop twice.
1515 BOOST_CHECK(!m_node.avalanche->stopEventLoop());
1516
1517 // Wait for the scheduler to stop.
1518 s.StopWhenDrained();
1519 schedulerThread.join();
1520}
1521
1523 CScheduler s;
1524 std::chrono::steady_clock::time_point start, stop;
1525
1526 std::thread schedulerThread;
1527 BOOST_CHECK(m_node.avalanche->startEventLoop(s));
1528 BOOST_CHECK_EQUAL(s.getQueueInfo(start, stop), 1);
1529
1530 // Start the service thread after the queue size check to prevent a race
1531 // condition where the thread may be processing the event loop task during
1532 // the check.
1533 schedulerThread = std::thread(std::bind(&CScheduler::serviceQueue, &s));
1534
1535 m_node.validation_signals->SyncWithValidationInterfaceQueue();
1536 // Destroy the processor.
1537 m_node.avalanche.reset();
1538
1539 // Now that avalanche is destroyed, there is no more scheduled tasks.
1540 BOOST_CHECK_EQUAL(s.getQueueInfo(start, stop), 0);
1541
1542 // Wait for the scheduler to stop.
1543 s.StopWhenDrained();
1544 schedulerThread.join();
1545}
1546
1547BOOST_AUTO_TEST_CASE(add_proof_to_reconcile) {
1548 uint32_t score = MIN_VALID_PROOF_SCORE;
1549 Chainstate &active_chainstate = Assert(m_node.chainman)->ActiveChainstate();
1550
1551 auto addProofToReconcile = [&](uint32_t proofScore) {
1552 auto proof = buildRandomProof(active_chainstate, proofScore);
1553 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
1554 BOOST_CHECK(pm.registerProof(proof));
1555 });
1556 BOOST_CHECK(m_node.avalanche->addToReconcile(proof));
1557 return proof;
1558 };
1559
1560 for (size_t i = 0; i < DEFAULT_AVALANCHE_MAX_ELEMENT_POLL; i++) {
1561 auto proof = addProofToReconcile(++score);
1562
1563 auto invs = AvalancheTest::getInvsForNextPoll(*m_node.avalanche);
1564 BOOST_CHECK_EQUAL(invs.size(), i + 1);
1565 BOOST_CHECK(invs.front().IsMsgProof());
1566 BOOST_CHECK_EQUAL(invs.front().hash, proof->getId());
1567 }
1568
1569 // From here a new proof is only polled if its score is in the top
1570 // DEFAULT_AVALANCHE_MAX_ELEMENT_POLL
1571 ProofId lastProofId;
1572 for (size_t i = 0; i < 10; i++) {
1573 auto proof = addProofToReconcile(++score);
1574
1575 auto invs = AvalancheTest::getInvsForNextPoll(*m_node.avalanche);
1577 BOOST_CHECK(invs.front().IsMsgProof());
1578 BOOST_CHECK_EQUAL(invs.front().hash, proof->getId());
1579
1580 lastProofId = proof->getId();
1581 }
1582
1583 for (size_t i = 0; i < 10; i++) {
1584 auto proof = addProofToReconcile(--score);
1585
1586 auto invs = AvalancheTest::getInvsForNextPoll(*m_node.avalanche);
1588 BOOST_CHECK(invs.front().IsMsgProof());
1589 BOOST_CHECK_EQUAL(invs.front().hash, lastProofId);
1590 }
1591
1592 {
1593 // The score is not high enough to get polled
1594 auto proof = addProofToReconcile(MIN_VALID_PROOF_SCORE);
1595 auto invs = AvalancheTest::getInvsForNextPoll(*m_node.avalanche);
1597 for (auto &inv : invs) {
1598 BOOST_CHECK_NE(inv.hash, proof->getId());
1599 }
1600 }
1601}
1602
1604 setArg("-avaproofstakeutxoconfirmations", "2");
1605 setArg("-avalancheconflictingproofcooldown", "0");
1606
1607 BOOST_CHECK(!m_node.avalanche->isAccepted(nullptr));
1608 BOOST_CHECK_EQUAL(m_node.avalanche->getConfidence(nullptr), -1);
1609
1610 const CKey key = CKey::MakeCompressedKey();
1611
1612 const COutPoint conflictingOutpoint{TxId(GetRandHash()), 0};
1613 const COutPoint immatureOutpoint{TxId(GetRandHash()), 0};
1614 {
1616
1617 LOCK(cs_main);
1618 CCoinsViewCache &coins =
1619 Assert(m_node.chainman)->ActiveChainstate().CoinsTip();
1620 coins.AddCoin(conflictingOutpoint,
1621 Coin(CTxOut(PROOF_DUST_THRESHOLD, script), 10, false),
1622 false);
1623 coins.AddCoin(immatureOutpoint,
1624 Coin(CTxOut(PROOF_DUST_THRESHOLD, script), 100, false),
1625 false);
1626 }
1627
1628 auto buildProof = [&](const COutPoint &outpoint, uint64_t sequence,
1629 uint32_t height = 10) {
1630 ProofBuilder pb(sequence, 0, key, UNSPENDABLE_ECREG_PAYOUT_SCRIPT);
1632 pb.addUTXO(outpoint, PROOF_DUST_THRESHOLD, height, false, key));
1633 return pb.build();
1634 };
1635
1636 auto conflictingProof = buildProof(conflictingOutpoint, 1);
1637 auto validProof = buildProof(conflictingOutpoint, 2);
1638 auto immatureProof = buildProof(immatureOutpoint, 3, 100);
1639
1640 BOOST_CHECK(!m_node.avalanche->isAccepted(conflictingProof));
1641 BOOST_CHECK(!m_node.avalanche->isAccepted(validProof));
1642 BOOST_CHECK(!m_node.avalanche->isAccepted(immatureProof));
1643 BOOST_CHECK_EQUAL(m_node.avalanche->getConfidence(conflictingProof), -1);
1644 BOOST_CHECK_EQUAL(m_node.avalanche->getConfidence(validProof), -1);
1645 BOOST_CHECK_EQUAL(m_node.avalanche->getConfidence(immatureProof), -1);
1646
1647 // Reconciling proofs that don't exist will fail
1648 BOOST_CHECK(!m_node.avalanche->addToReconcile(conflictingProof));
1649 BOOST_CHECK(!m_node.avalanche->addToReconcile(validProof));
1650 BOOST_CHECK(!m_node.avalanche->addToReconcile(immatureProof));
1651
1652 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
1653 BOOST_CHECK(pm.registerProof(conflictingProof));
1654 BOOST_CHECK(pm.registerProof(validProof));
1655 BOOST_CHECK(!pm.registerProof(immatureProof));
1656
1657 BOOST_CHECK(pm.isBoundToPeer(validProof->getId()));
1658 BOOST_CHECK(pm.isInConflictingPool(conflictingProof->getId()));
1659 BOOST_CHECK(pm.isImmature(immatureProof->getId()));
1660 });
1661
1662 BOOST_CHECK(m_node.avalanche->addToReconcile(conflictingProof));
1663 BOOST_CHECK(!m_node.avalanche->isAccepted(conflictingProof));
1664 BOOST_CHECK(!m_node.avalanche->isAccepted(validProof));
1665 BOOST_CHECK(!m_node.avalanche->isAccepted(immatureProof));
1666 BOOST_CHECK_EQUAL(m_node.avalanche->getConfidence(conflictingProof), 0);
1667 BOOST_CHECK_EQUAL(m_node.avalanche->getConfidence(validProof), -1);
1668 BOOST_CHECK_EQUAL(m_node.avalanche->getConfidence(immatureProof), -1);
1669
1670 BOOST_CHECK(m_node.avalanche->addToReconcile(validProof));
1671 BOOST_CHECK(!m_node.avalanche->isAccepted(conflictingProof));
1672 BOOST_CHECK(m_node.avalanche->isAccepted(validProof));
1673 BOOST_CHECK(!m_node.avalanche->isAccepted(immatureProof));
1674 BOOST_CHECK_EQUAL(m_node.avalanche->getConfidence(conflictingProof), 0);
1675 BOOST_CHECK_EQUAL(m_node.avalanche->getConfidence(validProof), 0);
1676 BOOST_CHECK_EQUAL(m_node.avalanche->getConfidence(immatureProof), -1);
1677
1678 BOOST_CHECK(!m_node.avalanche->addToReconcile(immatureProof));
1679 BOOST_CHECK(!m_node.avalanche->isAccepted(conflictingProof));
1680 BOOST_CHECK(m_node.avalanche->isAccepted(validProof));
1681 BOOST_CHECK(!m_node.avalanche->isAccepted(immatureProof));
1682 BOOST_CHECK_EQUAL(m_node.avalanche->getConfidence(conflictingProof), 0);
1683 BOOST_CHECK_EQUAL(m_node.avalanche->getConfidence(validProof), 0);
1684 BOOST_CHECK_EQUAL(m_node.avalanche->getConfidence(immatureProof), -1);
1685}
1686
1687BOOST_AUTO_TEST_CASE(quorum_detection) {
1688 // Set min quorum parameters for our test
1689 int minStake = 400'000'000;
1690 setArg("-avaminquorumstake", ToString(minStake));
1691 setArg("-avaminquorumconnectedstakeratio", "0.5");
1692
1693 // Create a new processor with our given quorum parameters
1694 const auto &currency = Currency::get();
1695 uint32_t minScore = Proof::amountToScore(minStake * currency.baseunit);
1696
1697 Chainstate &active_chainstate = Assert(m_node.chainman)->ActiveChainstate();
1698
1699 const CKey key = CKey::MakeCompressedKey();
1700 auto localProof =
1701 buildRandomProof(active_chainstate, minScore / 4, 100, key);
1702 setArg("-avamasterkey", EncodeSecret(key));
1703 setArg("-avaproof", localProof->ToHex());
1704
1705 m_node.validation_signals->SyncWithValidationInterfaceQueue();
1706 bilingual_str error;
1707 ChainstateManager &chainman = *Assert(m_node.chainman);
1708 m_node.avalanche = Processor::MakeProcessor(
1709 *m_node.args, *m_node.chain, m_node.connman.get(), chainman,
1710 m_node.mempool.get(), *m_node.scheduler, error);
1711
1712 BOOST_CHECK(m_node.avalanche != nullptr);
1713 BOOST_CHECK(m_node.avalanche->getLocalProof() != nullptr);
1714 BOOST_CHECK_EQUAL(m_node.avalanche->getLocalProof()->getId(),
1715 localProof->getId());
1716 BOOST_CHECK_EQUAL(AvalancheTest::getMinQuorumScore(*m_node.avalanche),
1717 minScore);
1719 AvalancheTest::getMinQuorumConnectedScoreRatio(*m_node.avalanche), 0.5);
1720
1721 // The local proof has not been validated yet
1722 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
1725 });
1726 BOOST_CHECK(!m_node.avalanche->isQuorumEstablished());
1727
1728 // Register the local proof. This is normally done when the chain tip is
1729 // updated. The local proof should be accounted for in the min quorum
1730 // computation but the peer manager doesn't know about that.
1731 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
1732 BOOST_CHECK(pm.registerProof(m_node.avalanche->getLocalProof()));
1734 pm.isBoundToPeer(m_node.avalanche->getLocalProof()->getId()));
1735 BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), minScore / 4);
1737 });
1738 BOOST_CHECK(!m_node.avalanche->isQuorumEstablished());
1739
1740 // Add enough nodes to get a conclusive vote
1741 for (NodeId id = 0; id < 8; id++) {
1742 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
1743 pm.addNode(id, m_node.avalanche->getLocalProof()->getId(),
1745 BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), minScore / 4);
1746 BOOST_CHECK_EQUAL(pm.getConnectedPeersScore(), minScore / 4);
1747 });
1748 }
1749
1750 // Add part of the required stake and make sure we still report no quorum
1751 auto proof1 = buildRandomProof(active_chainstate, minScore / 2);
1752 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
1753 BOOST_CHECK(pm.registerProof(proof1));
1754 BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), 3 * minScore / 4);
1755 BOOST_CHECK_EQUAL(pm.getConnectedPeersScore(), minScore / 4);
1756 });
1757 BOOST_CHECK(!m_node.avalanche->isQuorumEstablished());
1758
1759 // Add the rest of the stake, but we are still lacking connected stake
1760 const int64_t tipTime =
1761 WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip())
1762 ->GetBlockTime();
1763 const COutPoint utxo{TxId(GetRandHash()), 0};
1764 const Amount amount = (int64_t(minScore / 4) * COIN) / 100;
1765 const int height = 100;
1766 const bool isCoinbase = false;
1767 {
1768 LOCK(cs_main);
1769 CCoinsViewCache &coins = active_chainstate.CoinsTip();
1770 coins.AddCoin(utxo,
1772 PKHash(key.GetPubKey()))),
1773 height, isCoinbase),
1774 false);
1775 }
1776 ProofBuilder pb(1, tipTime + 1, key, UNSPENDABLE_ECREG_PAYOUT_SCRIPT);
1777 BOOST_CHECK(pb.addUTXO(utxo, amount, height, isCoinbase, key));
1778 auto proof2 = pb.build();
1779
1780 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
1781 BOOST_CHECK(pm.registerProof(proof2));
1782 BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), minScore);
1783 BOOST_CHECK_EQUAL(pm.getConnectedPeersScore(), minScore / 4);
1784 });
1785 BOOST_CHECK(!m_node.avalanche->isQuorumEstablished());
1786
1787 // Adding a node should cause the quorum to be detected and locked-in
1788 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
1789 pm.addNode(8, proof2->getId(), DEFAULT_AVALANCHE_MAX_ELEMENT_POLL);
1790 BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), minScore);
1791 // The peer manager knows that proof2 has a node attached ...
1792 BOOST_CHECK_EQUAL(pm.getConnectedPeersScore(), minScore / 2);
1793 });
1794 // ... but the processor also account for the local proof, so we reached 50%
1795 BOOST_CHECK(m_node.avalanche->isQuorumEstablished());
1796
1797 // Go back to not having enough connected score, but we've already latched
1798 // the quorum as established
1799 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
1800 pm.removeNode(8);
1801 BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), minScore);
1802 BOOST_CHECK_EQUAL(pm.getConnectedPeersScore(), minScore / 4);
1803 });
1804 BOOST_CHECK(m_node.avalanche->isQuorumEstablished());
1805
1806 // Removing one more node drops our count below the minimum and the quorum
1807 // is no longer ready
1808 m_node.avalanche->withPeerManager(
1809 [&](avalanche::PeerManager &pm) { pm.removeNode(7); });
1810 BOOST_CHECK(!m_node.avalanche->isQuorumEstablished());
1811
1812 // It resumes when we have enough nodes again
1813 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
1814 pm.addNode(7, m_node.avalanche->getLocalProof()->getId(),
1816 });
1817 BOOST_CHECK(m_node.avalanche->isQuorumEstablished());
1818
1819 // Remove peers one at a time until the quorum is no longer established
1820 auto spendProofUtxo = [&](ProofRef proof) {
1821 {
1822 LOCK(cs_main);
1823 CCoinsViewCache &coins = chainman.ActiveChainstate().CoinsTip();
1824 coins.SpendCoin(proof->getStakes()[0].getStake().getUTXO());
1825 }
1826 m_node.avalanche->withPeerManager([&proof](avalanche::PeerManager &pm) {
1827 pm.updatedBlockTip();
1828 BOOST_CHECK(!pm.isBoundToPeer(proof->getId()));
1829 });
1830 };
1831
1832 // Expire proof2, the quorum is still latched
1833 for (int64_t i = 0; i < 6; i++) {
1834 SetMockTime(proof2->getExpirationTime() + i);
1835 CreateAndProcessBlock({}, CScript());
1836 }
1838 WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip())
1839 ->GetMedianTimePast(),
1840 proof2->getExpirationTime());
1841 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
1842 pm.updatedBlockTip();
1843 BOOST_CHECK(!pm.exists(proof2->getId()));
1844 });
1845 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
1846 BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), 3 * minScore / 4);
1847 BOOST_CHECK_EQUAL(pm.getConnectedPeersScore(), minScore / 4);
1848 });
1849 BOOST_CHECK(m_node.avalanche->isQuorumEstablished());
1850
1851 spendProofUtxo(proof1);
1852 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
1853 BOOST_CHECK_EQUAL(pm.getTotalPeersScore(), minScore / 4);
1854 BOOST_CHECK_EQUAL(pm.getConnectedPeersScore(), minScore / 4);
1855 });
1856 BOOST_CHECK(m_node.avalanche->isQuorumEstablished());
1857
1858 spendProofUtxo(m_node.avalanche->getLocalProof());
1859 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
1862 });
1863 // There is no node left
1864 BOOST_CHECK(!m_node.avalanche->isQuorumEstablished());
1865}
1866
1867BOOST_AUTO_TEST_CASE(quorum_detection_parameter_validation) {
1868 // Create vector of tuples of:
1869 // <min stake, min ratio, min avaproofs messages, success bool>
1870 const std::vector<std::tuple<std::string, std::string, std::string, bool>>
1871 testCases = {
1872 // All parameters are invalid
1873 {"", "", "", false},
1874 {"-1", "-1", "-1", false},
1875
1876 // Min stake is out of range
1877 {"-1", "0", "0", false},
1878 {"-0.01", "0", "0", false},
1879 {"21000000000000.01", "0", "0", false},
1880
1881 // Min connected ratio is out of range
1882 {"0", "-1", "0", false},
1883 {"0", "1.1", "0", false},
1884
1885 // Min avaproofs messages ratio is out of range
1886 {"0", "0", "-1", false},
1887
1888 // All parameters are valid
1889 {"0", "0", "0", true},
1890 {"0.00", "0", "0", true},
1891 {"0.01", "0", "0", true},
1892 {"1", "0.1", "0", true},
1893 {"10", "0.5", "0", true},
1894 {"10", "1", "0", true},
1895 {"21000000000000.00", "0", "0", true},
1896 {"0", "0", "1", true},
1897 {"0", "0", "100", true},
1898 };
1899
1900 // For each case set the parameters and check that making the processor
1901 // succeeds or fails as expected
1902 for (const auto &[stake, stakeRatio, numProofsMessages, success] :
1903 testCases) {
1904 setArg("-avaminquorumstake", stake);
1905 setArg("-avaminquorumconnectedstakeratio", stakeRatio);
1906 setArg("-avaminavaproofsnodecount", numProofsMessages);
1907
1908 bilingual_str error;
1909 std::unique_ptr<Processor> processor = Processor::MakeProcessor(
1910 *m_node.args, *m_node.chain, m_node.connman.get(),
1911 *Assert(m_node.chainman), m_node.mempool.get(), *m_node.scheduler,
1912 error);
1913
1914 if (success) {
1915 BOOST_CHECK(processor != nullptr);
1916 BOOST_CHECK(error.empty());
1917 BOOST_CHECK_EQUAL(error.original, "");
1918 } else {
1919 BOOST_CHECK(processor == nullptr);
1920 BOOST_CHECK(!error.empty());
1921 BOOST_CHECK(error.original != "");
1922 }
1923 }
1924}
1925
1926BOOST_AUTO_TEST_CASE(min_avaproofs_messages) {
1927 ChainstateManager &chainman = *Assert(m_node.chainman);
1928
1929 auto checkMinAvaproofsMessages = [&](int64_t minAvaproofsMessages) {
1930 setArg("-avaminavaproofsnodecount", ToString(minAvaproofsMessages));
1931
1932 bilingual_str error;
1933 auto processor = Processor::MakeProcessor(
1934 *m_node.args, *m_node.chain, m_node.connman.get(), chainman,
1935 m_node.mempool.get(), *m_node.scheduler, error);
1936
1937 auto addNode = [&](NodeId nodeid) {
1938 auto proof = buildRandomProof(chainman.ActiveChainstate(),
1940 processor->withPeerManager([&](avalanche::PeerManager &pm) {
1941 BOOST_CHECK(pm.registerProof(proof));
1942 BOOST_CHECK(pm.addNode(nodeid, proof->getId(),
1944 });
1945 };
1946
1947 // Add enough node to have a conclusive vote, but don't account any
1948 // avaproofs.
1949 // NOTE: we can't use the test facilites like ConnectNodes() because we
1950 // are not testing on m_node.avalanche.
1951 for (NodeId id = 100; id < 108; id++) {
1952 addNode(id);
1953 }
1954
1955 BOOST_CHECK_EQUAL(processor->isQuorumEstablished(),
1956 minAvaproofsMessages <= 0);
1957
1958 for (int64_t i = 0; i < minAvaproofsMessages - 1; i++) {
1959 addNode(i);
1960
1961 processor->avaproofsSent(i);
1962 BOOST_CHECK_EQUAL(processor->getAvaproofsNodeCounter(), i + 1);
1963
1964 // Receiving again on the same node does not increase the counter
1965 processor->avaproofsSent(i);
1966 BOOST_CHECK_EQUAL(processor->getAvaproofsNodeCounter(), i + 1);
1967
1968 BOOST_CHECK(!processor->isQuorumEstablished());
1969 }
1970
1971 addNode(minAvaproofsMessages);
1972 processor->avaproofsSent(minAvaproofsMessages);
1973 BOOST_CHECK(processor->isQuorumEstablished());
1974
1975 // Check the latch
1976 AvalancheTest::clearavaproofsNodeCounter(*processor);
1977 BOOST_CHECK(processor->isQuorumEstablished());
1978 };
1979
1980 checkMinAvaproofsMessages(0);
1981 checkMinAvaproofsMessages(1);
1982 checkMinAvaproofsMessages(10);
1983 checkMinAvaproofsMessages(100);
1984}
1985
1987 // Check that setting voting parameters has the expected effect
1988 setArg("-avastalevotethreshold",
1990 setArg("-avastalevotefactor", "2");
1991 // This would fail the test for blocks
1992 setArg("-avalanchestakingpreconsensus", "0");
1993
1994 const std::vector<std::tuple<int, int>> testCases = {
1995 // {number of yes votes, number of neutral votes}
1998 };
1999
2000 m_node.validation_signals->SyncWithValidationInterfaceQueue();
2001 bilingual_str error;
2002 m_node.avalanche = Processor::MakeProcessor(
2003 *m_node.args, *m_node.chain, m_node.connman.get(),
2004 *Assert(m_node.chainman), m_node.mempool.get(), *m_node.scheduler,
2005 error);
2006
2007 BOOST_CHECK(m_node.avalanche != nullptr);
2008 BOOST_CHECK(error.empty());
2009
2010 P provider(this);
2011 const uint32_t invType = provider.invType;
2012
2013 const auto item = provider.buildVoteItem();
2014 const auto itemid = provider.getVoteItemId(item);
2015
2016 // Create nodes that supports avalanche.
2017 auto avanodes = ConnectNodes();
2018 int nextNodeIndex = 0;
2019
2020 std::vector<avalanche::VoteItemUpdate> updates;
2021 for (const auto &[numYesVotes, numNeutralVotes] : testCases) {
2022 // Add a new item. Check it is added to the polls.
2023 BOOST_CHECK(addToReconcile(item));
2024 auto invs = getInvsForNextPoll();
2025 BOOST_CHECK_EQUAL(invs.size(), 1);
2026 BOOST_CHECK_EQUAL(invs[0].type, invType);
2027 BOOST_CHECK(invs[0].hash == itemid);
2028
2029 BOOST_CHECK(m_node.avalanche->isAccepted(item));
2030
2031 auto registerNewVote = [&](const Response &resp) {
2032 runEventLoop();
2033 auto nodeid = avanodes[nextNodeIndex++ % avanodes.size()]->GetId();
2034 BOOST_CHECK(registerVotes(nodeid, resp, updates));
2035 };
2036
2037 // Add some confidence
2038 for (int i = 0; i < numYesVotes; i++) {
2039 Response resp = {getRound(), 0, {Vote(0, itemid)}};
2040 registerNewVote(next(resp));
2041 BOOST_CHECK(m_node.avalanche->isAccepted(item));
2042 BOOST_CHECK_EQUAL(m_node.avalanche->getConfidence(item),
2043 i >= 6 ? i - 5 : 0);
2044 BOOST_CHECK_EQUAL(updates.size(), 0);
2045 }
2046
2047 // Vote until just before item goes stale
2048 for (int i = 0; i < numNeutralVotes; i++) {
2049 Response resp = {getRound(), 0, {Vote(-1, itemid)}};
2050 registerNewVote(next(resp));
2051 BOOST_CHECK_EQUAL(updates.size(), 0);
2052 }
2053
2054 // As long as it is not stale, we poll.
2055 invs = getInvsForNextPoll();
2056 BOOST_CHECK_EQUAL(invs.size(), 1);
2057 BOOST_CHECK_EQUAL(invs[0].type, invType);
2058 BOOST_CHECK(invs[0].hash == itemid);
2059
2060 // Now stale
2061 Response resp = {getRound(), 0, {Vote(-1, itemid)}};
2062 registerNewVote(next(resp));
2063 BOOST_CHECK_EQUAL(updates.size(), 1);
2064 BOOST_CHECK(provider.fromAnyVoteItem(updates[0].getVoteItem()) == item);
2065 BOOST_CHECK(updates[0].getStatus() == VoteStatus::Stale);
2066
2067 // Once stale, there is no poll for it.
2068 invs = getInvsForNextPoll();
2069 BOOST_CHECK_EQUAL(invs.size(), 0);
2070 }
2071}
2072
2073BOOST_AUTO_TEST_CASE(block_vote_finalization_tip) {
2074 BlockProvider provider(this);
2075
2076 BOOST_CHECK(!m_node.avalanche->hasFinalizedTip());
2077
2078 std::vector<CBlockIndex *> blockIndexes;
2079 for (size_t i = 0; i < DEFAULT_AVALANCHE_MAX_ELEMENT_POLL; i++) {
2080 CBlockIndex *pindex = provider.buildVoteItem();
2081 BOOST_CHECK(addToReconcile(pindex));
2082 blockIndexes.push_back(pindex);
2083 }
2084
2085 auto invs = getInvsForNextPoll();
2087 for (size_t i = 0; i < DEFAULT_AVALANCHE_MAX_ELEMENT_POLL; i++) {
2089 invs[i].hash,
2090 blockIndexes[DEFAULT_AVALANCHE_MAX_ELEMENT_POLL - i - 1]
2091 ->GetBlockHash());
2092 }
2093
2094 // Build a vote vector with the 11th block only being accepted and others
2095 // unknown.
2096 const BlockHash eleventhBlockHash =
2097 blockIndexes[DEFAULT_AVALANCHE_MAX_ELEMENT_POLL - 10 - 1]
2098 ->GetBlockHash();
2099 std::vector<Vote> votes;
2101 for (size_t i = DEFAULT_AVALANCHE_MAX_ELEMENT_POLL; i > 0; i--) {
2102 BlockHash blockhash = blockIndexes[i - 1]->GetBlockHash();
2103 votes.emplace_back(blockhash == eleventhBlockHash ? 0 : -1, blockhash);
2104 }
2105
2106 auto avanodes = ConnectNodes();
2107 int nextNodeIndex = 0;
2108
2109 std::vector<avalanche::VoteItemUpdate> updates;
2110 auto registerNewVote = [&]() {
2111 Response resp = {getRound(), 0, votes};
2112 runEventLoop();
2113 auto nodeid = avanodes[nextNodeIndex++ % avanodes.size()]->GetId();
2114 BOOST_CHECK(registerVotes(nodeid, resp, updates));
2115 };
2116
2117 BOOST_CHECK(!m_node.avalanche->hasFinalizedTip());
2118
2119 // Vote for the blocks until the one being accepted finalizes
2120 bool eleventhBlockFinalized = false;
2121 for (size_t i = 0; i < 10000 && !eleventhBlockFinalized; i++) {
2122 registerNewVote();
2123
2124 for (auto &update : updates) {
2125 if (update.getStatus() == VoteStatus::Finalized &&
2126 provider.fromAnyVoteItem(update.getVoteItem())
2127 ->GetBlockHash() == eleventhBlockHash) {
2128 eleventhBlockFinalized = true;
2129 BOOST_CHECK(m_node.avalanche->hasFinalizedTip());
2130 } else {
2131 BOOST_CHECK(!m_node.avalanche->hasFinalizedTip());
2132 }
2133 }
2134 }
2135 BOOST_CHECK(eleventhBlockFinalized);
2136 BOOST_CHECK(m_node.avalanche->hasFinalizedTip());
2137
2138 // From now only the 10 blocks with more work are polled for
2139 clearInvsNotWorthPolling();
2140 invs = getInvsForNextPoll();
2141 BOOST_CHECK_EQUAL(invs.size(), 10);
2142 for (size_t i = 0; i < 10; i++) {
2144 invs[i].hash,
2145 blockIndexes[DEFAULT_AVALANCHE_MAX_ELEMENT_POLL - i - 1]
2146 ->GetBlockHash());
2147 }
2148
2149 // Adding ancestor blocks to reconcile will fail
2150 for (size_t i = 0; i < DEFAULT_AVALANCHE_MAX_ELEMENT_POLL - 10 - 1; i++) {
2151 BOOST_CHECK(!addToReconcile(blockIndexes[i]));
2152 }
2153
2154 // Create a couple concurrent chain tips
2155 CBlockIndex *tip = provider.buildVoteItem();
2156
2157 auto &activeChainstate = m_node.chainman->ActiveChainstate();
2159 activeChainstate.InvalidateBlock(state, tip);
2160
2161 // Use another script to make sure we don't generate the same block again
2162 CBlock altblock = CreateAndProcessBlock({}, CScript() << OP_TRUE);
2163 auto alttip = WITH_LOCK(
2164 cs_main, return Assert(m_node.chainman)
2165 ->m_blockman.LookupBlockIndex(altblock.GetHash()));
2166 BOOST_CHECK(alttip);
2167 BOOST_CHECK(alttip->pprev == tip->pprev);
2168 BOOST_CHECK(alttip->GetBlockHash() != tip->GetBlockHash());
2169
2170 // Reconsider the previous tip valid, so we have concurrent tip candidates
2171 {
2172 LOCK(cs_main);
2173 activeChainstate.ResetBlockFailureFlags(tip);
2174 }
2175 activeChainstate.ActivateBestChain(state);
2176
2177 BOOST_CHECK(addToReconcile(tip));
2178 BOOST_CHECK(addToReconcile(alttip));
2179 clearInvsNotWorthPolling();
2180 invs = getInvsForNextPoll();
2181 BOOST_CHECK_EQUAL(invs.size(), 12);
2182
2183 // Vote for the tip until it finalizes
2184 BlockHash tiphash = tip->GetBlockHash();
2185 votes.clear();
2186 votes.reserve(12);
2187 for (auto &inv : invs) {
2188 votes.emplace_back(inv.hash == tiphash ? 0 : -1, inv.hash);
2189 }
2190
2191 bool tipFinalized = false;
2192 for (size_t i = 0; i < 10000 && !tipFinalized; i++) {
2193 registerNewVote();
2194
2195 for (auto &update : updates) {
2196 if (update.getStatus() == VoteStatus::Finalized &&
2197 provider.fromAnyVoteItem(update.getVoteItem())
2198 ->GetBlockHash() == tiphash) {
2199 tipFinalized = true;
2200 }
2201 }
2202 }
2203 BOOST_CHECK(tipFinalized);
2204
2205 // Now the tip and all its ancestors will be removed from polls. Only the
2206 // alttip remains because it is on a forked chain so we want to keep polling
2207 // for that one until it's invalidated or stalled.
2208 clearInvsNotWorthPolling();
2209 invs = getInvsForNextPoll();
2210 BOOST_CHECK_EQUAL(invs.size(), 1);
2211 BOOST_CHECK_EQUAL(invs[0].hash, alttip->GetBlockHash());
2212
2213 // Cannot reconcile a finalized block
2214 BOOST_CHECK(!addToReconcile(tip));
2215
2216 // Vote for alttip until it invalidates
2217 BlockHash alttiphash = alttip->GetBlockHash();
2218 votes = {{1, alttiphash}};
2219
2220 bool alttipInvalidated = false;
2221 for (size_t i = 0; i < 10000 && !alttipInvalidated; i++) {
2222 registerNewVote();
2223
2224 for (auto &update : updates) {
2225 if (update.getStatus() == VoteStatus::Invalid &&
2226 provider.fromAnyVoteItem(update.getVoteItem())
2227 ->GetBlockHash() == alttiphash) {
2228 alttipInvalidated = true;
2229 }
2230 }
2231 }
2232 BOOST_CHECK(alttipInvalidated);
2233 invs = getInvsForNextPoll();
2234 BOOST_CHECK_EQUAL(invs.size(), 0);
2235
2236 // Cannot reconcile an invalidated block
2237 BOOST_CHECK(!addToReconcile(alttip));
2238}
2239
2240BOOST_AUTO_TEST_CASE(vote_map_comparator) {
2241 ChainstateManager &chainman = *Assert(m_node.chainman);
2242 Chainstate &activeChainState = chainman.ActiveChainstate();
2243
2244 const int numberElementsEachType = 100;
2246
2247 std::vector<ProofRef> proofs;
2248 for (size_t i = 1; i <= numberElementsEachType; i++) {
2249 auto proof =
2250 buildRandomProof(activeChainState, i * MIN_VALID_PROOF_SCORE);
2251 BOOST_CHECK(proof != nullptr);
2252 proofs.emplace_back(std::move(proof));
2253 }
2254 Shuffle(proofs.begin(), proofs.end(), rng);
2255
2256 std::vector<CBlockIndex> indexes;
2257 for (size_t i = 1; i <= numberElementsEachType; i++) {
2258 CBlockIndex index;
2259 index.nChainWork = i;
2260 indexes.emplace_back(std::move(index));
2261 }
2262 Shuffle(indexes.begin(), indexes.end(), rng);
2263
2264 auto allItems = std::make_tuple(std::move(proofs), std::move(indexes));
2265 static const size_t numTypes = std::tuple_size<decltype(allItems)>::value;
2266
2267 RWCollection<VoteMap> voteMap;
2268
2269 {
2270 auto writeView = voteMap.getWriteView();
2271 for (size_t i = 0; i < numberElementsEachType; i++) {
2272 // Randomize the insert order at each loop increment
2273 const size_t firstType = rng.randrange(numTypes);
2274
2275 for (size_t j = 0; j < numTypes; j++) {
2276 switch ((firstType + j) % numTypes) {
2277 // ProofRef
2278 case 0:
2279 writeView->insert(std::make_pair(
2280 std::get<0>(allItems)[i], VoteRecord(true)));
2281 break;
2282 // CBlockIndex *
2283 case 1:
2284 writeView->insert(std::make_pair(
2285 &std::get<1>(allItems)[i], VoteRecord(true)));
2286 break;
2287 default:
2288 break;
2289 }
2290 }
2291 }
2292 }
2293
2294 {
2295 // Check ordering
2296 auto readView = voteMap.getReadView();
2297 auto it = readView.begin();
2298
2299 // The first batch of items is the proofs ordered by score (descending)
2300 uint32_t lastScore = std::numeric_limits<uint32_t>::max();
2301 for (size_t i = 0; i < numberElementsEachType; i++) {
2302 BOOST_CHECK(std::holds_alternative<const ProofRef>(it->first));
2303
2304 uint32_t currentScore =
2305 std::get<const ProofRef>(it->first)->getScore();
2306 BOOST_CHECK_LT(currentScore, lastScore);
2307 lastScore = currentScore;
2308
2309 it++;
2310 }
2311
2312 // The next batch of items is the block indexes ordered by work
2313 // (descending)
2314 arith_uint256 lastWork = ~arith_uint256(0);
2315 for (size_t i = 0; i < numberElementsEachType; i++) {
2316 BOOST_CHECK(std::holds_alternative<const CBlockIndex *>(it->first));
2317
2318 arith_uint256 currentWork =
2319 std::get<const CBlockIndex *>(it->first)->nChainWork;
2320 BOOST_CHECK(currentWork < lastWork);
2321 lastWork = currentWork;
2322
2323 it++;
2324 }
2325
2326 BOOST_CHECK(it == readView.end());
2327 }
2328}
2329
2330BOOST_AUTO_TEST_CASE(block_reconcile_initial_vote) {
2331 auto &chainman = Assert(m_node.chainman);
2332 Chainstate &chainstate = chainman->ActiveChainstate();
2333
2334 const auto block = std::make_shared<const CBlock>(
2335 this->CreateBlock({}, CScript(), chainstate));
2336 const BlockHash blockhash = block->GetHash();
2337
2339 CBlockIndex *blockindex;
2340 {
2341 LOCK(cs_main);
2342 BOOST_CHECK(chainman->AcceptBlock(block, state,
2343 /*fRequested=*/true, /*dbp=*/nullptr,
2344 /*fNewBlock=*/nullptr,
2345 /*min_pow_checked=*/true));
2346
2347 blockindex = chainman->m_blockman.LookupBlockIndex(blockhash);
2348 BOOST_CHECK(blockindex);
2349 }
2350
2351 // The block is not connected yet, and not added to the poll list yet
2352 BOOST_CHECK(AvalancheTest::getInvsForNextPoll(*m_node.avalanche).empty());
2353 BOOST_CHECK(!m_node.avalanche->isAccepted(blockindex));
2354
2355 // Call ActivateBestChain to connect the new block
2357 chainstate.ActivateBestChain(state, block, m_node.avalanche.get()));
2358 // It is a valid block so the tip is updated
2359 BOOST_CHECK_EQUAL(chainstate.m_chain.Tip(), blockindex);
2360
2361 // Check the block is added to the poll
2362 auto invs = AvalancheTest::getInvsForNextPoll(*m_node.avalanche);
2363 BOOST_CHECK_EQUAL(invs.size(), 1);
2364 BOOST_CHECK_EQUAL(invs[0].type, MSG_BLOCK);
2365 BOOST_CHECK_EQUAL(invs[0].hash, blockhash);
2366
2367 // This block is our new tip so we should vote "yes"
2368 BOOST_CHECK(m_node.avalanche->isAccepted(blockindex));
2369}
2370
2371BOOST_AUTO_TEST_CASE(compute_staking_rewards) {
2372 auto now = GetTime<std::chrono::seconds>();
2373 SetMockTime(now);
2374
2375 // Pick in the middle
2376 BlockHash prevBlockHash{uint256::ZERO};
2377
2378 std::vector<CScript> winners;
2379
2381 !m_node.avalanche->getStakingRewardWinners(prevBlockHash, winners));
2382
2383 // Null index
2384 BOOST_CHECK(!m_node.avalanche->computeStakingReward(nullptr));
2386 !m_node.avalanche->getStakingRewardWinners(prevBlockHash, winners));
2387
2388 CBlockIndex prevBlock;
2389 prevBlock.phashBlock = &prevBlockHash;
2390 prevBlock.nHeight = 100;
2391 prevBlock.nTime = now.count();
2392
2393 // No quorum
2394 BOOST_CHECK(!m_node.avalanche->computeStakingReward(&prevBlock));
2396 !m_node.avalanche->getStakingRewardWinners(prevBlockHash, winners));
2397
2398 // Setup a bunch of proofs
2399 size_t numProofs = 10;
2400 std::vector<ProofRef> proofs;
2401 proofs.reserve(numProofs);
2402 for (size_t i = 0; i < numProofs; i++) {
2403 const CKey key = CKey::MakeCompressedKey();
2404 CScript payoutScript = GetScriptForRawPubKey(key.GetPubKey());
2405
2406 auto proof = GetProof(payoutScript);
2407 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
2408 BOOST_CHECK(pm.registerProof(proof));
2409 BOOST_CHECK(pm.addNode(i, proof->getId(),
2411 // Finalize the proof
2412 BOOST_CHECK(pm.forPeer(proof->getId(), [&](const Peer peer) {
2413 return pm.setFinalized(peer.peerid);
2414 }));
2415 });
2416
2417 proofs.emplace_back(std::move(proof));
2418 }
2419
2420 BOOST_CHECK(m_node.avalanche->isQuorumEstablished());
2421
2422 // Proofs are too recent so we still have no winner
2423 BOOST_CHECK(!m_node.avalanche->computeStakingReward(&prevBlock));
2425 !m_node.avalanche->getStakingRewardWinners(prevBlockHash, winners));
2426
2427 // Make sure we picked a payout script from one of our proofs
2428 auto winnerExists = [&](const CScript &expectedWinner) {
2429 const std::string winnerString = FormatScript(expectedWinner);
2430
2431 for (const ProofRef &proof : proofs) {
2432 if (winnerString == FormatScript(proof->getPayoutScript())) {
2433 return true;
2434 }
2435 }
2436 return false;
2437 };
2438
2439 // Elapse some time
2440 now += 1h + 1s;
2441 SetMockTime(now);
2442 prevBlock.nTime = now.count();
2443
2444 // Now we successfully inserted a winner in our map
2445 BOOST_CHECK(m_node.avalanche->computeStakingReward(&prevBlock));
2447 m_node.avalanche->getStakingRewardWinners(prevBlockHash, winners));
2448 BOOST_CHECK(winnerExists(winners[0]));
2449
2450 // Subsequent calls are a no-op
2451 BOOST_CHECK(m_node.avalanche->computeStakingReward(&prevBlock));
2453 m_node.avalanche->getStakingRewardWinners(prevBlockHash, winners));
2454 BOOST_CHECK(winnerExists(winners[0]));
2455
2456 CBlockIndex prevBlockHigh = prevBlock;
2457 BlockHash prevBlockHashHigh =
2458 BlockHash(ArithToUint256({std::numeric_limits<uint64_t>::max()}));
2459 prevBlockHigh.phashBlock = &prevBlockHashHigh;
2460 prevBlockHigh.nHeight = 101;
2461 BOOST_CHECK(m_node.avalanche->computeStakingReward(&prevBlockHigh));
2463 m_node.avalanche->getStakingRewardWinners(prevBlockHashHigh, winners));
2464 BOOST_CHECK(winnerExists(winners[0]));
2465
2466 // No impact on previous winner so far
2468 m_node.avalanche->getStakingRewardWinners(prevBlockHash, winners));
2469 BOOST_CHECK(winnerExists(winners[0]));
2470
2471 // Cleanup to height 101
2472 m_node.avalanche->cleanupStakingRewards(101);
2473
2474 // Now the previous winner has been cleared
2476 !m_node.avalanche->getStakingRewardWinners(prevBlockHash, winners));
2477
2478 // But the last one remain
2480 m_node.avalanche->getStakingRewardWinners(prevBlockHashHigh, winners));
2481 BOOST_CHECK(winnerExists(winners[0]));
2482
2483 // We can add it again
2484 BOOST_CHECK(m_node.avalanche->computeStakingReward(&prevBlock));
2486 m_node.avalanche->getStakingRewardWinners(prevBlockHash, winners));
2487 BOOST_CHECK(winnerExists(winners[0]));
2488
2489 // Cleanup to higher height
2490 m_node.avalanche->cleanupStakingRewards(200);
2491
2492 // No winner anymore
2494 !m_node.avalanche->getStakingRewardWinners(prevBlockHash, winners));
2496 !m_node.avalanche->getStakingRewardWinners(prevBlockHashHigh, winners));
2497}
2498
2499BOOST_AUTO_TEST_CASE(local_proof_status) {
2500 const CKey key = CKey::MakeCompressedKey();
2501
2502 const COutPoint outpoint{TxId(GetRandHash()), 0};
2503 {
2505
2506 LOCK(cs_main);
2507 CCoinsViewCache &coins =
2508 Assert(m_node.chainman)->ActiveChainstate().CoinsTip();
2509 coins.AddCoin(outpoint,
2510 Coin(CTxOut(PROOF_DUST_THRESHOLD, script), 100, false),
2511 false);
2512 }
2513
2514 auto buildProof = [&](const COutPoint &outpoint, uint64_t sequence,
2515 uint32_t height) {
2516 ProofBuilder pb(sequence, 0, key, UNSPENDABLE_ECREG_PAYOUT_SCRIPT);
2518 pb.addUTXO(outpoint, PROOF_DUST_THRESHOLD, height, false, key));
2519 return pb.build();
2520 };
2521
2522 auto localProof = buildProof(outpoint, 1, 100);
2523
2524 setArg("-avamasterkey", EncodeSecret(key));
2525 setArg("-avaproof", localProof->ToHex());
2526 setArg("-avalancheconflictingproofcooldown", "0");
2527 setArg("-avalanchepeerreplacementcooldown", "0");
2528 setArg("-avaproofstakeutxoconfirmations", "3");
2529
2530 m_node.validation_signals->SyncWithValidationInterfaceQueue();
2531 bilingual_str error;
2532 ChainstateManager &chainman = *Assert(m_node.chainman);
2533 m_node.avalanche = Processor::MakeProcessor(
2534 *m_node.args, *m_node.chain, m_node.connman.get(), chainman,
2535 m_node.mempool.get(), *m_node.scheduler, error);
2536
2537 BOOST_CHECK_EQUAL(m_node.avalanche->getLocalProof()->getId(),
2538 localProof->getId());
2539
2540 auto checkLocalProofState = [&](const bool boundToPeer,
2542 expectedResult) {
2544 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
2545 return pm.isBoundToPeer(localProof->getId());
2546 }),
2547 boundToPeer);
2548 BOOST_CHECK_MESSAGE(
2549 m_node.avalanche->getLocalProofRegistrationState().GetResult() ==
2550 expectedResult,
2551 m_node.avalanche->getLocalProofRegistrationState().ToString());
2552 };
2553
2554 checkLocalProofState(false, ProofRegistrationResult::NONE);
2555
2556 // Not ready to share, the local proof isn't registered
2557 BOOST_CHECK(!m_node.avalanche->canShareLocalProof());
2558 AvalancheTest::updatedBlockTip(*m_node.avalanche);
2559 checkLocalProofState(false, ProofRegistrationResult::NONE);
2560
2561 // Ready to share, but the proof is immature
2562 AvalancheTest::setLocalProofShareable(*m_node.avalanche, true);
2563 BOOST_CHECK(m_node.avalanche->canShareLocalProof());
2564 AvalancheTest::updatedBlockTip(*m_node.avalanche);
2565 checkLocalProofState(false, ProofRegistrationResult::IMMATURE);
2566
2567 // Mine a block to re-evaluate the proof, it remains immature
2568 mineBlocks(1);
2569 AvalancheTest::updatedBlockTip(*m_node.avalanche);
2570 checkLocalProofState(false, ProofRegistrationResult::IMMATURE);
2571
2572 // One more block and the proof turns mature
2573 mineBlocks(1);
2574 AvalancheTest::updatedBlockTip(*m_node.avalanche);
2575 checkLocalProofState(true, ProofRegistrationResult::NONE);
2576
2577 // Build a conflicting proof and check the status is updated accordingly
2578 auto conflictingProof = buildProof(outpoint, 2, 100);
2579 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
2580 BOOST_CHECK(pm.registerProof(conflictingProof));
2581 BOOST_CHECK(pm.isBoundToPeer(conflictingProof->getId()));
2582 BOOST_CHECK(pm.isInConflictingPool(localProof->getId()));
2583 });
2584 AvalancheTest::updatedBlockTip(*m_node.avalanche);
2585 checkLocalProofState(false, ProofRegistrationResult::CONFLICTING);
2586}
2587
2588BOOST_AUTO_TEST_CASE(reconcileOrFinalize) {
2589 setArg("-avalancheconflictingproofcooldown", "0");
2590 setArg("-avalanchepeerreplacementcooldown", "0");
2591
2592 // Proof is null
2593 BOOST_CHECK(!m_node.avalanche->reconcileOrFinalize(ProofRef()));
2594
2595 ChainstateManager &chainman = *Assert(m_node.chainman);
2596 Chainstate &activeChainState = chainman.ActiveChainstate();
2597
2598 const CKey key = CKey::MakeCompressedKey();
2599 const COutPoint outpoint{TxId(GetRandHash()), 0};
2600 {
2602
2603 LOCK(cs_main);
2604 CCoinsViewCache &coins = activeChainState.CoinsTip();
2605 coins.AddCoin(outpoint,
2606 Coin(CTxOut(PROOF_DUST_THRESHOLD, script), 100, false),
2607 false);
2608 }
2609
2610 auto buildProof = [&](const COutPoint &outpoint, uint64_t sequence) {
2611 ProofBuilder pb(sequence, 0, key, UNSPENDABLE_ECREG_PAYOUT_SCRIPT);
2613 pb.addUTXO(outpoint, PROOF_DUST_THRESHOLD, 100, false, key));
2614 return pb.build();
2615 };
2616
2617 auto proof = buildProof(outpoint, 1);
2618 BOOST_CHECK(proof);
2619
2620 // Not a peer nor conflicting
2621 BOOST_CHECK(!m_node.avalanche->reconcileOrFinalize(proof));
2622
2623 // Register the proof
2624 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
2625 BOOST_CHECK(pm.registerProof(proof));
2626 BOOST_CHECK(pm.isBoundToPeer(proof->getId()));
2627 BOOST_CHECK(!pm.isInConflictingPool(proof->getId()));
2628 });
2629
2630 // Reconcile works
2631 BOOST_CHECK(m_node.avalanche->reconcileOrFinalize(proof));
2632 // Repeated calls fail and do nothing
2633 BOOST_CHECK(!m_node.avalanche->reconcileOrFinalize(proof));
2634
2635 // Finalize
2636 AvalancheTest::addProofToRecentfinalized(*m_node.avalanche, proof->getId());
2637 BOOST_CHECK(m_node.avalanche->isRecentlyFinalized(proof->getId()));
2638 BOOST_CHECK(m_node.avalanche->reconcileOrFinalize(proof));
2639
2640 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
2641 // The peer is marked as final
2642 BOOST_CHECK(pm.forPeer(proof->getId(), [&](const Peer &peer) {
2643 return peer.hasFinalized;
2644 }));
2645 BOOST_CHECK(pm.isBoundToPeer(proof->getId()));
2646 BOOST_CHECK(!pm.isInConflictingPool(proof->getId()));
2647 });
2648
2649 // Same proof with a higher sequence number
2650 auto betterProof = buildProof(outpoint, 2);
2651 BOOST_CHECK(betterProof);
2652
2653 // Not registered nor conflicting yet
2654 BOOST_CHECK(!m_node.avalanche->reconcileOrFinalize(betterProof));
2655
2656 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
2657 BOOST_CHECK(pm.registerProof(betterProof));
2658 BOOST_CHECK(pm.isBoundToPeer(betterProof->getId()));
2659 BOOST_CHECK(!pm.isInConflictingPool(betterProof->getId()));
2660
2661 BOOST_CHECK(!pm.isBoundToPeer(proof->getId()));
2663 });
2664
2665 // Recently finalized, not worth polling
2666 BOOST_CHECK(!m_node.avalanche->reconcileOrFinalize(proof));
2667 // But the better proof can be polled
2668 BOOST_CHECK(m_node.avalanche->reconcileOrFinalize(betterProof));
2669}
2670
2671BOOST_AUTO_TEST_CASE(stake_contenders) {
2672 m_node.validation_signals->SyncWithValidationInterfaceQueue();
2673 bilingual_str error;
2674 m_node.avalanche = Processor::MakeProcessor(
2675 *m_node.args, *m_node.chain, m_node.connman.get(),
2676 *Assert(m_node.chainman), m_node.mempool.get(), *m_node.scheduler,
2677 error);
2678 BOOST_CHECK(m_node.avalanche);
2679
2680 auto now = GetTime<std::chrono::seconds>();
2681 SetMockTime(now);
2682
2683 AvalancheTest::setStakingPreconsensus(*m_node.avalanche, true);
2684
2685 ChainstateManager &chainman = *Assert(m_node.chainman);
2686 Chainstate &active_chainstate = chainman.ActiveChainstate();
2687 CBlockIndex *chaintip =
2688 WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip());
2689
2690 auto proof1 = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
2691 const ProofId proofid1 = proof1->getId();
2692 const StakeContenderId contender1_block1(chaintip->GetBlockHash(),
2693 proofid1);
2694
2695 auto proof2 = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
2696 const ProofId proofid2 = proof2->getId();
2697 const StakeContenderId contender2_block1(chaintip->GetBlockHash(),
2698 proofid2);
2699
2700 // Add stake contenders. Without computing staking rewards, the status is
2701 // pending.
2702 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
2703 pm.addStakeContender(proof1);
2704 pm.addStakeContender(proof2);
2705 });
2707 m_node.avalanche->getStakeContenderStatus(contender1_block1), -2);
2709 m_node.avalanche->getStakeContenderStatus(contender2_block1), -2);
2710
2711 // Sanity check unknown contender
2712 const StakeContenderId unknownContender(chaintip->GetBlockHash(),
2713 ProofId(GetRandHash()));
2715 m_node.avalanche->getStakeContenderStatus(unknownContender), -1);
2716
2717 // Register proof2 and save it as a remote proof so that it will be promoted
2718 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
2719 pm.registerProof(proof2);
2720 for (NodeId n = 0; n < 8; n++) {
2722 }
2723 pm.saveRemoteProof(proofid2, 0, true);
2724 BOOST_CHECK(pm.forPeer(proofid2, [&](const Peer peer) {
2725 return pm.setFinalized(peer.peerid);
2726 }));
2727 });
2728
2729 // Make proofs old enough to be considered for staking rewards
2730 now += 1h + 1s;
2731 SetMockTime(now);
2732
2733 // Advance chaintip
2734 CBlock block = CreateAndProcessBlock({}, CScript());
2735 chaintip =
2736 WITH_LOCK(cs_main, return Assert(m_node.chainman)
2737 ->m_blockman.LookupBlockIndex(block.GetHash()));
2738 AvalancheTest::updatedBlockTip(*m_node.avalanche);
2739
2740 // Compute local stake winner
2741 BOOST_CHECK(m_node.avalanche->isQuorumEstablished());
2742 BOOST_CHECK(m_node.avalanche->computeStakingReward(chaintip));
2743 {
2744 std::vector<CScript> winners;
2745 BOOST_CHECK(m_node.avalanche->getStakingRewardWinners(
2746 chaintip->GetBlockHash(), winners));
2747 BOOST_CHECK_EQUAL(winners.size(), 1);
2748 BOOST_CHECK(winners[0] == proof2->getPayoutScript());
2749 }
2750
2751 // Sanity check unknown contender
2753 m_node.avalanche->getStakeContenderStatus(unknownContender), -1);
2754
2755 // Old contender cache entries unaffected
2757 m_node.avalanche->getStakeContenderStatus(contender1_block1), -2);
2759 m_node.avalanche->getStakeContenderStatus(contender2_block1), -2);
2760
2761 // contender1 was not promoted
2762 const StakeContenderId contender1_block2 =
2763 StakeContenderId(chaintip->GetBlockHash(), proofid1);
2765 m_node.avalanche->getStakeContenderStatus(contender1_block2), -1);
2766
2767 // contender2 was promoted
2768 const StakeContenderId contender2_block2 =
2769 StakeContenderId(chaintip->GetBlockHash(), proofid2);
2771 m_node.avalanche->getStakeContenderStatus(contender2_block2), 0);
2772
2773 // Now that the finalization point has passed the block where contender1 was
2774 // added, cleaning up the cache will remove its entry. contender2 will have
2775 // its old entry cleaned up, but the promoted one remains.
2776 m_node.avalanche->cleanupStakingRewards(chaintip->nHeight);
2777
2779 m_node.avalanche->getStakeContenderStatus(unknownContender), -1);
2780
2782 m_node.avalanche->getStakeContenderStatus(contender1_block1), -1);
2784 m_node.avalanche->getStakeContenderStatus(contender1_block2), -1);
2785
2787 m_node.avalanche->getStakeContenderStatus(contender2_block1), -1);
2789 m_node.avalanche->getStakeContenderStatus(contender2_block2), 0);
2790
2791 // Manually set contenders as winners
2792 m_node.avalanche->setStakingRewardWinners(
2793 chaintip, {proof1->getPayoutScript(), proof2->getPayoutScript()});
2794 // contender1 has been forgotten, which is expected. When a proof becomes
2795 // invalid and is cleaned up from the cache, we do not expect peers to poll
2796 // for it any more.
2798 m_node.avalanche->getStakeContenderStatus(contender1_block2), -1);
2799 // contender2 is a winner despite avalanche not finalizing it
2801 m_node.avalanche->getStakeContenderStatus(contender2_block2), 0);
2802
2803 // Reject proof2, mine a new chain tip, finalize it, and cleanup the cache
2804 m_node.avalanche->withPeerManager(
2805 [&](avalanche::PeerManager &pm) { pm.rejectProof(proofid2); });
2806
2807 // Reestablish quorum with a new proof
2808 BOOST_CHECK(!m_node.avalanche->isQuorumEstablished());
2809 auto proof3 = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
2810 const ProofId proofid3 = proof3->getId();
2811 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
2812 pm.registerProof(proof3);
2813 for (NodeId n = 0; n < 8; n++) {
2815 }
2816 });
2817 BOOST_CHECK(m_node.avalanche->isQuorumEstablished());
2818
2819 block = CreateAndProcessBlock({}, CScript());
2820 chaintip =
2821 WITH_LOCK(cs_main, return Assert(m_node.chainman)
2822 ->m_blockman.LookupBlockIndex(block.GetHash()));
2823 AvalancheTest::updatedBlockTip(*m_node.avalanche);
2824 m_node.avalanche->cleanupStakingRewards(chaintip->nHeight);
2825
2827 m_node.avalanche->getStakeContenderStatus(unknownContender), -1);
2828
2829 // Old entries were cleaned up
2831 m_node.avalanche->getStakeContenderStatus(contender1_block2), -1);
2833 m_node.avalanche->getStakeContenderStatus(contender2_block2), -1);
2834
2835 // Neither contender was promoted and contender2 was cleaned up even though
2836 // it was once a manual winner.
2837 const StakeContenderId contender1_block3 =
2838 StakeContenderId(chaintip->GetBlockHash(), proofid1);
2840 m_node.avalanche->getStakeContenderStatus(contender1_block3), -1);
2841 const StakeContenderId contender2_block3 =
2842 StakeContenderId(chaintip->GetBlockHash(), proofid2);
2844 m_node.avalanche->getStakeContenderStatus(contender2_block3), -1);
2845
2846 // Reject proof3 so it does not conflict with the rest of the test
2847 m_node.avalanche->withPeerManager(
2848 [&](avalanche::PeerManager &pm) { pm.rejectProof(proofid3); });
2849
2850 // Generate a bunch of flaky proofs
2851 size_t numProofs = 8;
2852 std::vector<ProofRef> proofs;
2853 proofs.reserve(numProofs);
2854 for (size_t i = 0; i < numProofs; i++) {
2855 auto proof = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
2856 const ProofId proofid = proof->getId();
2857 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
2858 // Registering the proof adds it as a contender
2859 pm.registerProof(proof);
2860 // Make it a remote proof so that it will be promoted
2861 pm.saveRemoteProof(proofid, i, true);
2862 BOOST_CHECK(pm.forPeer(proofid, [&](const Peer peer) {
2863 return pm.setFinalized(peer.peerid);
2864 }));
2865 });
2866 proofs.emplace_back(std::move(proof));
2867 }
2868
2869 // Add nodes only for the first proof so we have a quorum
2870 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
2871 const ProofId proofid = proofs[0]->getId();
2872 for (NodeId n = 0; n < 8; n++) {
2874 }
2875 });
2876
2877 // Make proofs old enough to be considered for staking rewards
2878 now += 1h + 1s;
2879 SetMockTime(now);
2880
2881 // Try a few times in case the non-flaky proof get selected as winner
2882 std::vector<CScript> winners;
2883 for (int attempt = 0; attempt < 10; attempt++) {
2884 // Advance chaintip so the proofs are older than the last block time
2885 block = CreateAndProcessBlock({}, CScript());
2886 chaintip = WITH_LOCK(
2887 cs_main, return Assert(m_node.chainman)
2888 ->m_blockman.LookupBlockIndex(block.GetHash()));
2889 AvalancheTest::updatedBlockTip(*m_node.avalanche);
2890
2891 // Compute local stake winner
2892 BOOST_CHECK(m_node.avalanche->isQuorumEstablished());
2893 BOOST_CHECK(m_node.avalanche->computeStakingReward(chaintip));
2894 BOOST_CHECK(m_node.avalanche->getStakingRewardWinners(
2895 chaintip->GetBlockHash(), winners));
2896 if (winners.size() == 8) {
2897 break;
2898 }
2899 }
2900
2901 BOOST_CHECK(winners.size() == 8);
2902
2903 // Verify that all winners were accepted
2904 size_t numAccepted = 0;
2905 for (const auto &proof : proofs) {
2906 const ProofId proofid = proof->getId();
2907 const StakeContenderId contender =
2908 StakeContenderId(chaintip->GetBlockHash(), proofid);
2909 if (m_node.avalanche->getStakeContenderStatus(contender) == 0) {
2910 numAccepted++;
2911 BOOST_CHECK(std::find(winners.begin(), winners.end(),
2912 proof->getPayoutScript()) != winners.end());
2913 }
2914 }
2915 BOOST_CHECK_EQUAL(winners.size(), numAccepted);
2916
2917 // Check that a highest ranking contender that was not selected as local
2918 // winner is still accepted.
2919 block = CreateAndProcessBlock({}, CScript());
2920 chaintip =
2921 WITH_LOCK(cs_main, return Assert(m_node.chainman)
2922 ->m_blockman.LookupBlockIndex(block.GetHash()));
2923 auto bestproof = buildRandomProof(
2924 active_chainstate,
2925 // Subtract some score so totalPeersScore doesn't overflow
2926 std::numeric_limits<uint32_t>::max() - MIN_VALID_PROOF_SCORE * 8);
2927 m_node.avalanche->withPeerManager(
2928 [&](avalanche::PeerManager &pm) { pm.addStakeContender(bestproof); });
2929 AvalancheTest::updatedBlockTip(*m_node.avalanche);
2930
2931 // Compute local stake winners
2932 BOOST_CHECK(m_node.avalanche->isQuorumEstablished());
2933 BOOST_CHECK(m_node.avalanche->computeStakingReward(chaintip));
2934 BOOST_CHECK(m_node.avalanche->getStakingRewardWinners(
2935 chaintip->GetBlockHash(), winners));
2936
2937 // Sanity check bestproof was not selected as a winner
2938 BOOST_CHECK(std::find(winners.begin(), winners.end(),
2939 bestproof->getPayoutScript()) == winners.end());
2940
2941 // Best contender is accepted
2942 {
2943 const StakeContenderId bestcontender =
2944 StakeContenderId(chaintip->GetBlockHash(), bestproof->getId());
2946 m_node.avalanche->getStakeContenderStatus(bestcontender), 0);
2947 }
2948
2949 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
2950 // Register bestproof so it will become dangling later
2951 pm.registerProof(bestproof);
2952 // Make it a remote proof so that it will be promoted
2953 pm.saveRemoteProof(bestproof->getId(), 0, true);
2954 pm.saveRemoteProof(bestproof->getId(), 1, false);
2955 });
2956
2957 block = CreateAndProcessBlock({}, CScript());
2958 chaintip =
2959 WITH_LOCK(cs_main, return Assert(m_node.chainman)
2960 ->m_blockman.LookupBlockIndex(block.GetHash()));
2961 AvalancheTest::updatedBlockTip(*m_node.avalanche);
2962 AvalancheTest::setFinalizationTip(*m_node.avalanche, chaintip);
2963 m_node.avalanche->cleanupStakingRewards(chaintip->nHeight);
2964
2965 // Make bestproof dangling since it has no nodes attached
2966 now += 15min + 1s;
2967 SetMockTime(now);
2968 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
2969 std::unordered_set<ProofRef, SaltedProofHasher> dummy;
2970 pm.cleanupDanglingProofs(dummy);
2971 });
2972 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
2973 BOOST_CHECK(pm.isDangling(bestproof->getId()));
2974 });
2975
2976 // Compute local stake winners
2977 BOOST_CHECK(m_node.avalanche->isQuorumEstablished());
2978 BOOST_CHECK(m_node.avalanche->computeStakingReward(chaintip));
2979 BOOST_CHECK(m_node.avalanche->getStakingRewardWinners(
2980 chaintip->GetBlockHash(), winners));
2981
2982 // Sanity check bestproof was not selected as a winner
2983 BOOST_CHECK(std::find(winners.begin(), winners.end(),
2984 bestproof->getPayoutScript()) == winners.end());
2985
2986 // Best contender is still polled but we vote no while it is dangling
2987 {
2988 const StakeContenderId bestcontender =
2989 StakeContenderId(chaintip->GetBlockHash(), bestproof->getId());
2991 m_node.avalanche->getStakeContenderStatus(bestcontender), 4);
2992 }
2993}
2994
2995BOOST_AUTO_TEST_CASE(stake_contender_local_winners) {
2996 ChainstateManager &chainman = *Assert(m_node.chainman);
2997 Chainstate &active_chainstate = chainman.ActiveChainstate();
2998 CBlockIndex *chaintip =
2999 WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip());
3000 const BlockHash chaintipHash = chaintip->GetBlockHash();
3001
3002 auto now = GetTime<std::chrono::seconds>();
3003 SetMockTime(now);
3004
3005 // Create a proof that will be the local stake winner
3006 auto localWinnerProof =
3007 buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
3008 ProofId localWinnerProofId = localWinnerProof->getId();
3009 const StakeContenderId localWinnerContenderId(chaintipHash,
3010 localWinnerProof->getId());
3011
3012 // Contender cache stores tip time at first insert (registerProof with
3013 // staking preconsensus). Set the tip time ahead before registering so
3014 // registration_time is early and the proof is old enough to be eligible.
3015 auto registration_delay = 4 * avalanche::Peer::DANGLING_TIMEOUT + 1s;
3016 chaintip->nTime = (now + registration_delay).count();
3017
3018 // Prepare the proof so that it becomes the local stake winner
3019 m_node.avalanche->withPeerManager([&](avalanche::PeerManager &pm) {
3020 ConnectNode(NODE_AVALANCHE);
3021 pm.registerProof(localWinnerProof);
3022 for (NodeId n = 0; n < 8; n++) {
3023 pm.addNode(n, localWinnerProofId,
3025 }
3026 BOOST_CHECK(pm.forPeer(localWinnerProofId, [&](const Peer peer) {
3027 return pm.setFinalized(peer.peerid);
3028 }));
3029 });
3030
3031 // Make proof old enough to be considered for staking rewards
3032 now += registration_delay;
3033 SetMockTime(now);
3034
3035 // Compute local stake winner
3036 BOOST_CHECK(m_node.avalanche->isQuorumEstablished());
3037 BOOST_CHECK(m_node.avalanche->computeStakingReward(chaintip));
3038
3039 std::vector<ProofRef> acceptedContenderProofs;
3040 acceptedContenderProofs.push_back(localWinnerProof);
3041 double bestRank =
3042 localWinnerContenderId.ComputeProofRewardRank(MIN_VALID_PROOF_SCORE);
3043
3044 // Test well past the max since we need to test the max number of accepted
3045 // contenders as well. Starts at 2 because the local winner is already
3046 // added.
3047 for (size_t numContenders = 2;
3048 numContenders < AVALANCHE_CONTENDER_MAX_POLLABLE * 10;
3049 numContenders++) {
3050 auto proof = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
3051 m_node.avalanche->withPeerManager(
3052 [&](avalanche::PeerManager &pm) { pm.addStakeContender(proof); });
3053
3054 const StakeContenderId contenderId(chaintipHash, proof->getId());
3055 double rank = contenderId.ComputeProofRewardRank(MIN_VALID_PROOF_SCORE);
3056
3057 if (rank <= bestRank) {
3058 bestRank = rank;
3059 acceptedContenderProofs.push_back(proof);
3060 const size_t numAccepted =
3062 acceptedContenderProofs.size());
3063 std::sort(acceptedContenderProofs.begin(),
3064 acceptedContenderProofs.begin() + numAccepted,
3065 [&](const ProofRef &left, const ProofRef &right) {
3066 const ProofId leftProofId = left->getId();
3067 const ProofId rightProofId = right->getId();
3068 const StakeContenderId leftContenderId(chaintipHash,
3069 leftProofId);
3070 const StakeContenderId rightContenderId(chaintipHash,
3071 rightProofId);
3072 return RewardRankComparator()(
3073 leftContenderId,
3074 leftContenderId.ComputeProofRewardRank(
3075 MIN_VALID_PROOF_SCORE),
3076 leftProofId, rightContenderId,
3077 rightContenderId.ComputeProofRewardRank(
3078 MIN_VALID_PROOF_SCORE),
3079 rightProofId);
3080 });
3081 }
3082
3083 std::vector<StakeContenderId> pollableContenders;
3084 BOOST_CHECK(AvalancheTest::setContenderStatusForLocalWinners(
3085 *m_node.avalanche, chaintip, pollableContenders));
3087 pollableContenders.size(),
3088 std::min(numContenders, AVALANCHE_CONTENDER_MAX_POLLABLE));
3089
3090 // Accepted contenders (up to the max, best first) are always included
3091 // in pollableContenders
3092 for (size_t i = 0; i < std::min(acceptedContenderProofs.size(),
3094 i++) {
3095 StakeContenderId acceptedContenderId = StakeContenderId(
3096 chaintipHash, acceptedContenderProofs[i]->getId());
3098 std::find(pollableContenders.begin(), pollableContenders.end(),
3099 acceptedContenderId) != pollableContenders.end());
3101 m_node.avalanche->getStakeContenderStatus(acceptedContenderId),
3102 0);
3103 }
3104
3105 // Check unaccepted contenders are still as we expect
3106 std::set<StakeContenderId> unacceptedContenderIds(
3107 pollableContenders.begin(), pollableContenders.end());
3108 for (auto &acceptedContenderProof : acceptedContenderProofs) {
3109 const StakeContenderId acceptedContenderId(
3110 chaintipHash, acceptedContenderProof->getId());
3111 unacceptedContenderIds.erase(acceptedContenderId);
3112 }
3113
3114 for (auto cid : unacceptedContenderIds) {
3115 BOOST_CHECK_EQUAL(m_node.avalanche->getStakeContenderStatus(cid),
3116 1);
3117 }
3118
3119 // Sanity check the local winner stays accepted
3121 m_node.avalanche->getStakeContenderStatus(localWinnerContenderId),
3122 0);
3123 }
3124}
3125
3126BOOST_AUTO_TEST_SUITE_END()
CScript GetScriptForDestination(const CTxDestination &dest)
Generate a Bitcoin scriptPubKey for the given CTxDestination.
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:75
BlockHash GetBlockHash() const
Definition: blockindex.h:129
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:357
void AddCoin(const COutPoint &outpoint, Coin coin, bool possible_overwrite)
Add a coin.
Definition: coins.cpp:97
bool SpendCoin(const COutPoint &outpoint, Coin *moveto=nullptr)
Spend a coin.
Definition: coins.cpp:173
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:687
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:250
bool exists(const TxId &txid) const
Definition: txmempool.h:525
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:727
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:826
CCoinsViewCache & CoinsTip() EXCLUSIVE_LOCKS_REQUIRED(
Definition: validation.h:853
Provides an interface for creating and interacting with one or two chainstates: an IBD chainstate gen...
Definition: validation.h:1176
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:1446
RecursiveMutex & GetMutex() const LOCK_RETURNED(
Alias for cs_main.
Definition: validation.h:1328
CBlockIndex * ActiveTip() const EXCLUSIVE_LOCKS_REQUIRED(GetMutex())
Definition: validation.h:1453
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:1336
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:454
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:418
bool forPeer(const ProofId &proofid, Callable &&func) const
Definition: peermanager.h:426
uint32_t getTotalPeersScore() const
Definition: peermanager.h:453
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:485
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:1228
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:1301
void updatedBlockTip() EXCLUSIVE_LOCKS_REQUIRED(!cs_peerManager
Definition: processor.cpp:1240
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:1460
void clearInflightRequests(const std::vector< CInv > &invs) EXCLUSIVE_LOCKS_REQUIRED(!cs_peerManager)
Decrement VoteRecord::inflight for each inventory.
Definition: processor.cpp:1429
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:1398
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:128
CBlockIndex * LookupBlockIndex(const BlockHash &hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
256-bit opaque blob.
Definition: uint256.h:199
static const uint256 ZERO
Definition: uint256.h:210
@ 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:25
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:156
NodeContext & m_node
Definition: interfaces.cpp:827
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:537
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:63
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: solver.cpp:143
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:50
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:298
#define LOCK(cs)
Definition: sync.h:295
#define WITH_LOCK(cs, code)
Run code while locking a mutex.
Definition: sync.h:343
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:41
static constexpr uint32_t AVALANCHE_VOTE_STALE_MIN_THRESHOLD
Lowest configurable staleness threshold (finalization score + necessary votes to increase confidence ...
Definition: voterecord.h:29
static constexpr int AVALANCHE_FINALIZATION_SCORE
Finalization score.
Definition: voterecord.h:18