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