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