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