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