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