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