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