Bitcoin ABC 0.34.2
P2P Digital Currency
stakecontendercache_tests.cpp
Go to the documentation of this file.
1// Copyright (c) 2024 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
8#include <script/script.h>
9
10#include <avalanche/test/util.h>
11#include <test/util/random.h>
12#include <test/util/setup_common.h>
13#include <util/time.h>
14#include <validation.h>
15
16#include <boost/test/unit_test.hpp>
17
18#include <limits>
19
20using namespace avalanche;
21
22namespace {
23struct PeerManagerFixture : public TestChain100Setup {
24 PeerManagerFixture() {
25 gArgs.ForceSetArg("-avaproofstakeutxoconfirmations", "1");
26 }
27 ~PeerManagerFixture() {
28 gArgs.ClearForcedArg("-avaproofstakeutxoconfirmations");
29 }
30};
31} // namespace
32
33BOOST_FIXTURE_TEST_SUITE(stakecontendercache_tests, TestChain100Setup)
34
36 const BlockHash &prevblockhash,
37 std::vector<CScript> manualWinners,
38 std::vector<ProofRef> acceptedWinners,
39 std::vector<ProofRef> rejectedWinners) {
40 std::vector<std::pair<ProofId, CScript>> winners;
41 size_t expectedSize =
42 manualWinners.size() + acceptedWinners.size() + rejectedWinners.size();
43
44 // Manual winners alone do not count as an avalanche-accepted stake winner.
45 BOOST_CHECK_EQUAL(cache.hasAcceptedWinner(prevblockhash),
46 !acceptedWinners.empty());
47
48 if (expectedSize == 0) {
49 BOOST_CHECK(!cache.getWinners(prevblockhash, winners));
50 return;
51 }
52
53 BOOST_CHECK(cache.getWinners(prevblockhash, winners));
54 BOOST_CHECK_EQUAL(winners.size(), expectedSize);
55
56 // Manual winners are always first and in order
57 for (size_t i = 0; i < manualWinners.size(); i++) {
58 BOOST_CHECK(winners[i].second == manualWinners[i]);
59 }
60
61 // Rest of the the winners are only those determined by avalanche.
62 for (auto &proof : acceptedWinners) {
64 std::find_if(std::next(winners.begin(), manualWinners.size()),
65 std::next(winners.begin(), manualWinners.size() +
66 acceptedWinners.size()),
67 [&](std::pair<ProofId, CScript> &p) {
68 return p.first == proof->getId();
69 }) != winners.end());
70 }
71 for (auto &proof : rejectedWinners) {
73 std::find_if(std::next(winners.begin(), manualWinners.size() +
74 acceptedWinners.size()),
75 winners.end(), [&](std::pair<ProofId, CScript> &p) {
76 return p.first == proof->getId();
77 }) != winners.end());
78 }
79
80 // Verify the winner order such that the best (lowest) reward ranked proof's
81 // payout script is always before payout scripts from proofs with worse
82 // (higher) reward ranks.
83 double previousRank = 0;
84 for (auto it = std::next(winners.begin(), manualWinners.size());
85 it != std::next(winners.begin(),
86 manualWinners.size() + acceptedWinners.size());
87 it++) {
88 double proofRank = StakeContenderId(prevblockhash, it->first)
90 BOOST_CHECK(previousRank < proofRank);
91 previousRank = proofRank;
92 }
93 previousRank = 0;
94 for (auto it = std::next(winners.begin(),
95 manualWinners.size() + acceptedWinners.size());
96 it != winners.end(); it++) {
97 double proofRank = StakeContenderId(prevblockhash, it->first)
99 BOOST_CHECK(previousRank < proofRank);
100 previousRank = proofRank;
101 }
102}
103
105 const BlockHash &prevblockhash,
106 const ProofRef &proof, int expected) {
107 BlockHash checkprevblockhash;
109 cache.getVoteStatus(StakeContenderId(prevblockhash, proof->getId()),
110 checkprevblockhash),
111 expected);
112 if (expected != -1) {
113 BOOST_CHECK_EQUAL(prevblockhash, checkprevblockhash);
114 }
115}
116
117BOOST_AUTO_TEST_CASE(vote_status_tests) {
118 Chainstate &active_chainstate = Assert(m_node.chainman)->ActiveChainstate();
120
121 CBlockIndex *pindex = active_chainstate.m_chain.Tip();
122 const BlockHash &blockhash = pindex->GetBlockHash();
123
124 std::vector<int> initialStatuses = {
128 for (uint8_t initialStatus : initialStatuses) {
129 auto proof = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
130
131 // Unknown contender
132 CheckVoteStatus(cache, blockhash, proof, -1);
133
134 // Add the contender and check its vote after avalanche updates
135 BOOST_CHECK(cache.add(pindex, proof, initialStatus));
136 CheckVoteStatus(cache, blockhash, proof,
137 !(initialStatus & StakeContenderStatus::ACCEPTED));
138
139 cache.accept(StakeContenderId(blockhash, proof->getId()));
140 CheckVoteStatus(cache, blockhash, proof, 0);
141
142 cache.reject(StakeContenderId(blockhash, proof->getId()));
143 CheckVoteStatus(cache, blockhash, proof, 1);
144
145 cache.finalize(StakeContenderId(blockhash, proof->getId()));
146 CheckVoteStatus(cache, blockhash, proof, 0);
147
148 // Add the proof as a manual winner. It should always be accepted.
149 BOOST_CHECK(cache.setWinners(pindex, {proof->getPayoutScript()}));
150 CheckVoteStatus(cache, blockhash, proof, 0);
151 }
152}
153
154BOOST_AUTO_TEST_CASE(has_accepted_winner_tests) {
155 Chainstate &active_chainstate = Assert(m_node.chainman)->ActiveChainstate();
157
158 CBlockIndex *pindex = active_chainstate.m_chain.Tip();
159 const BlockHash &blockhash = pindex->GetBlockHash();
160
161 BOOST_CHECK(!cache.hasAcceptedWinner(blockhash));
162
163 // Manual winners alone are not avalanche-accepted stake winners
164 BOOST_CHECK(cache.setWinners(pindex, {CScript() << OP_TRUE}));
165 BOOST_CHECK(!cache.hasAcceptedWinner(blockhash));
166
167 auto proofWinnerOnly =
168 buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
169 auto proofAcceptedOnly =
170 buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
171 auto proofAcceptedWinner =
172 buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
173
174 // IN_WINNER_SET without ACCEPTED
175 BOOST_CHECK(cache.add(pindex, proofWinnerOnly,
177 BOOST_CHECK(!cache.hasAcceptedWinner(blockhash));
178
179 // ACCEPTED without IN_WINNER_SET
181 cache.add(pindex, proofAcceptedOnly, StakeContenderStatus::ACCEPTED));
182 BOOST_CHECK(!cache.hasAcceptedWinner(blockhash));
183
184 // Both flags set
185 BOOST_CHECK(cache.add(pindex, proofAcceptedWinner,
188 BOOST_CHECK(cache.hasAcceptedWinner(blockhash));
189
190 // Reject clears ACCEPTED; remaining entries still lack both flags
191 cache.reject(StakeContenderId(blockhash, proofAcceptedWinner->getId()));
192 BOOST_CHECK(!cache.hasAcceptedWinner(blockhash));
193
194 // Finalize restores ACCEPTED | IN_WINNER_SET
195 cache.finalize(StakeContenderId(blockhash, proofAcceptedWinner->getId()));
196 BOOST_CHECK(cache.hasAcceptedWinner(blockhash));
197
198 // Unrelated block has no accepted winner
200}
201
202BOOST_AUTO_TEST_CASE(winners_tests) {
203 Chainstate &active_chainstate = Assert(m_node.chainman)->ActiveChainstate();
205
206 std::vector<CScript> manualWinners = {
207 CScript() << OP_TRUE,
208 CScript() << OP_FALSE,
209 };
210
211 std::vector<ProofRef> proofs;
212 proofs.reserve(4);
213 for (int i = 0; i < 4; i++) {
214 proofs.push_back(
215 buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE));
216 }
217
218 // Repeat these tests with multiple block hashes to ensure no unintended
219 // modifications are made to other entries
220 CBlockIndex *pindex = active_chainstate.m_chain.Tip();
221 for (int i = 0; i < 5; i++) {
222 const BlockHash &blockhash = pindex->GetBlockHash();
223 CheckWinners(cache, blockhash, {}, {}, {});
224
225 // Add a winner manually
226 BOOST_CHECK(cache.setWinners(pindex, {manualWinners[0]}));
227 CheckWinners(cache, blockhash, {manualWinners[0]}, {}, {});
228
229 // Before adding contenders, check that vote status is unknown
230 for (int p = 0; p < 4; p++) {
231 CheckVoteStatus(cache, blockhash, proofs[p], -1);
232 }
233
234 // Add some contenders
235 // Local winner
236 BOOST_CHECK(cache.add(pindex, proofs[0],
239 CheckVoteStatus(cache, blockhash, proofs[0], 0);
240
241 // Potential winner other than the local winner
243 cache.add(pindex, proofs[1], StakeContenderStatus::ACCEPTED));
244 CheckVoteStatus(cache, blockhash, proofs[1], 0);
245
246 // Local winner that has been rejected by avalanche so far
248 cache.add(pindex, proofs[2], StakeContenderStatus::IN_WINNER_SET));
249 CheckVoteStatus(cache, blockhash, proofs[2], 1);
250
251 // Some other contender
252 BOOST_CHECK(cache.add(pindex, proofs[3]));
253 CheckVoteStatus(cache, blockhash, proofs[3], 1);
254
255 // Attempting to add duplicates fails, even if status is different than
256 // the successfully added entries.
257 for (const auto &proof : proofs) {
258 BOOST_CHECK(!cache.add(pindex, proof));
260 !cache.add(pindex, proof, StakeContenderStatus::ACCEPTED));
261 BOOST_CHECK(!cache.add(pindex, proof,
265 !cache.add(pindex, proof, StakeContenderStatus::IN_WINNER_SET));
266 }
267
268 CheckWinners(cache, blockhash, {manualWinners[0]}, {proofs[0]},
269 {proofs[2]});
270
271 // Add another manual winner. It always comes before contenders in the
272 // winner set.
273 BOOST_CHECK(cache.setWinners(pindex, manualWinners));
274 CheckWinners(cache, blockhash, manualWinners, {proofs[0]}, {proofs[2]});
275
276 // Adding manual winners with the same payout scripts as contenders in
277 // any state never causes conflicts
278 std::vector<CScript> moreManualWinners = manualWinners;
279 for (const auto &proof : proofs) {
280 moreManualWinners.push_back(proof->getPayoutScript());
281 BOOST_CHECK(cache.setWinners(pindex, moreManualWinners));
282 CheckVoteStatus(cache, blockhash, proof, 0);
283 CheckWinners(cache, blockhash, moreManualWinners, {proofs[0]},
284 {proofs[2]});
285 }
286 CheckWinners(cache, blockhash, moreManualWinners, {proofs[0]},
287 {proofs[2]});
288
289 // Avalanche accepting all of the contenders does not change the winners
290 // yet
291 for (const auto &proof : proofs) {
292 cache.accept(StakeContenderId(blockhash, proof->getId()));
293 }
294 CheckWinners(cache, blockhash, moreManualWinners,
295 {proofs[0], proofs[2]}, {});
296
297 // Avalanche rejecting all of the contenders does not change the winners
298 // yet
299 for (const auto &proof : proofs) {
300 cache.reject(StakeContenderId(blockhash, proof->getId()));
301 }
302 CheckWinners(cache, blockhash, moreManualWinners, {},
303 {proofs[0], proofs[2]});
304
305 // Avalanche finalizing a contender already in the winner set makes no
306 // difference
307 cache.finalize(StakeContenderId(blockhash, proofs[0]->getId()));
308 CheckWinners(cache, blockhash, moreManualWinners, {proofs[0]},
309 {proofs[2]});
310
311 // Avalanche finalizing a contender that wasn't in the winner set before
312 // makes a new winner
313 cache.finalize(StakeContenderId(blockhash, proofs[1]->getId()));
314 CheckWinners(cache, blockhash, moreManualWinners,
315 {proofs[0], proofs[1]}, {proofs[2]});
316
317 // Avalanche invalidating a contender that was finalized has no effect
318 // other than ordering.
319 cache.reject(StakeContenderId(blockhash, proofs[1]->getId()));
320 CheckWinners(cache, blockhash, moreManualWinners, {proofs[0]},
321 {proofs[1], proofs[2]});
322
323 pindex = pindex->pprev;
324 }
325
326 // All contenders were added as manual winners at some point in this test,
327 // so reflect that here.
328 for (const auto &proof : proofs) {
329 manualWinners.push_back(proof->getPayoutScript());
330 }
331
332 // Sanity check that past cached state was not poisoned
333 pindex = active_chainstate.m_chain.Tip();
334 for (int i = 0; i < 5; i++) {
335 CheckWinners(cache, pindex->GetBlockHash(), manualWinners, {proofs[0]},
336 {proofs[1], proofs[2]});
337 for (int p = 0; p < 4; p++) {
338 CheckVoteStatus(cache, pindex->GetBlockHash(), proofs[p], 0);
339 }
340 pindex = pindex->pprev;
341 }
342}
343
344BOOST_AUTO_TEST_CASE(cleanup_tests) {
345 Chainstate &active_chainstate = Assert(m_node.chainman)->ActiveChainstate();
347
348 std::vector<ProofRef> proofs;
349 proofs.reserve(10);
350 for (int i = 0; i < 10; i++) {
351 proofs.push_back(
352 buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE));
353 }
354
355 CBlockIndex *pindex = active_chainstate.m_chain.Tip();
356 std::vector<BlockHash> blockhashes{pindex->GetBlockHash()};
357 pindex = pindex->pprev;
358 for (int i = 0; i < 3; i++) {
359 BlockHash blockhash = pindex->GetBlockHash();
360 blockhashes.push_back(blockhash);
361 for (const auto &proof : proofs) {
362 cache.add(pindex, proof, StakeContenderStatus::IN_WINNER_SET);
363 }
364 CheckWinners(cache, blockhash, {}, {}, proofs);
365 pindex = pindex->pprev;
366 }
367
368 // Promote up to the height that we will allow cleanup of the cache. Note
369 // that no entries are actually promoted so this test has fine tuned control
370 // over which blocks have entries.
371 pindex = active_chainstate.m_chain.Tip()->pprev->pprev->pprev;
372 BOOST_CHECK_EQUAL(pindex->nHeight, 97);
373 cache.promoteToBlock(pindex, [](const ProofId &proofid) { return false; });
374
375 // Cleaning up nonexistant entries has no impact
376 for (int height : {0, 10, 50, 90, 97}) {
377 cache.cleanup(height);
378 CheckWinners(cache, blockhashes[0], {}, {}, {});
379 CheckWinners(cache, blockhashes[1], {}, {}, proofs);
380 CheckWinners(cache, blockhashes[2], {}, {}, proofs);
381 CheckWinners(cache, blockhashes[3], {}, {}, proofs);
382 }
383
384 // Try to cleanup oldest block in the cache, except promotion at that height
385 // hasn't happened yet so cleanup has no effect.
386 cache.cleanup(98);
387 CheckWinners(cache, blockhashes[0], {}, {}, {});
388 CheckWinners(cache, blockhashes[1], {}, {}, proofs);
389 CheckWinners(cache, blockhashes[2], {}, {}, proofs);
390 CheckWinners(cache, blockhashes[3], {}, {}, proofs);
391
392 // Promote up to that height
393 cache.promoteToBlock(active_chainstate.m_chain.Tip()->pprev->pprev,
394 [](const ProofId &proofid) { return false; });
395
396 // Cleaning up the oldest block in the cache succeeds now
397 cache.cleanup(98);
398 CheckWinners(cache, blockhashes[0], {}, {}, {});
399 CheckWinners(cache, blockhashes[1], {}, {}, proofs);
400 CheckWinners(cache, blockhashes[2], {}, {}, proofs);
401 CheckWinners(cache, blockhashes[3], {}, {}, {});
402
403 // Add only a local winner to the recently cleared block
404 cache.setWinners(active_chainstate.m_chain.Tip()->pprev->pprev->pprev,
405 {CScript()});
406 CheckWinners(cache, blockhashes[0], {}, {}, {});
407 CheckWinners(cache, blockhashes[1], {}, {}, proofs);
408 CheckWinners(cache, blockhashes[2], {}, {}, proofs);
409 CheckWinners(cache, blockhashes[3], {CScript()}, {}, {});
410
411 // Clean it up again
412 cache.cleanup(98);
413 CheckWinners(cache, blockhashes[0], {}, {}, {});
414 CheckWinners(cache, blockhashes[1], {}, {}, proofs);
415 CheckWinners(cache, blockhashes[2], {}, {}, proofs);
416 CheckWinners(cache, blockhashes[3], {}, {}, {});
417
418 // Add a local winner to a block with winners already there, then clear it
419 cache.setWinners(active_chainstate.m_chain.Tip()->pprev->pprev,
420 {CScript()});
421 CheckWinners(cache, blockhashes[0], {}, {}, {});
422 CheckWinners(cache, blockhashes[1], {}, {}, proofs);
423 CheckWinners(cache, blockhashes[2], {CScript()}, {}, proofs);
424 CheckWinners(cache, blockhashes[3], {}, {}, {});
425
426 cache.promoteToBlock(active_chainstate.m_chain.Tip()->pprev,
427 [](const ProofId &proofid) { return false; });
428 cache.cleanup(99);
429 CheckWinners(cache, blockhashes[0], {}, {}, {});
430 CheckWinners(cache, blockhashes[1], {}, {}, proofs);
431 CheckWinners(cache, blockhashes[2], {}, {}, {});
432 CheckWinners(cache, blockhashes[3], {}, {}, {});
433
434 // Clean up the remaining block and the cache should be empty now
435 cache.promoteToBlock(active_chainstate.m_chain.Tip(),
436 [](const ProofId &proofid) { return false; });
437 cache.cleanup(100);
438 BOOST_CHECK(cache.isEmpty());
439 CheckWinners(cache, blockhashes[0], {}, {}, {});
440 CheckWinners(cache, blockhashes[1], {}, {}, {});
441 CheckWinners(cache, blockhashes[2], {}, {}, {});
442 CheckWinners(cache, blockhashes[3], {}, {}, {});
443
444 // Cleaning up again has no effect
445 cache.cleanup(100);
446 BOOST_CHECK(cache.isEmpty());
447 CheckWinners(cache, blockhashes[0], {}, {}, {});
448 CheckWinners(cache, blockhashes[1], {}, {}, {});
449 CheckWinners(cache, blockhashes[2], {}, {}, {});
450 CheckWinners(cache, blockhashes[3], {}, {}, {});
451
452 // Add winners back with random states and sanity check that higher heights
453 // clear the cache as we expect.
454 for (int height : {102, 200, 1000, 1000000}) {
455 pindex = active_chainstate.m_chain.Tip()->pprev;
456 for (size_t i = 1; i < 3; i++) {
457 for (const auto &proof : proofs) {
458 cache.add(pindex, proof, m_rng.randbits(2));
459 cache.setWinners(pindex, {CScript()});
460 }
461
462 // Sanity check there are some winners
463 std::vector<std::pair<ProofId, CScript>> winners;
464 BOOST_CHECK(cache.getWinners(blockhashes[i], winners));
465 BOOST_CHECK(winners.size() >= 1);
466 pindex = pindex->pprev;
467 }
468
469 // Cleaning up the cache at a height higher than any cache entry results
470 // in an empty cache and no winners.
471 cache.cleanup(height);
472 BOOST_CHECK(cache.isEmpty());
473 CheckWinners(cache, blockhashes[0], {}, {}, {});
474 CheckWinners(cache, blockhashes[1], {}, {}, {});
475 CheckWinners(cache, blockhashes[2], {}, {}, {});
476 CheckWinners(cache, blockhashes[3], {}, {}, {});
477 }
478
479 // But note that the cache will never cleanup higher than the last promoted
480 // block.
481 cache.add(active_chainstate.m_chain.Tip(), proofs[0],
483 for (int height : {102, 200, 1000, 1000000}) {
484 cache.cleanup(height);
485 CheckWinners(cache, blockhashes[0], {}, {}, {proofs[0]});
486 CheckWinners(cache, blockhashes[1], {}, {}, {});
487 CheckWinners(cache, blockhashes[2], {}, {}, {});
488 CheckWinners(cache, blockhashes[3], {}, {}, {});
489 }
490}
491
492BOOST_FIXTURE_TEST_CASE(promote_tests, PeerManagerFixture) {
493 Chainstate &active_chainstate = Assert(m_node.chainman)->ActiveChainstate();
495
496 std::vector<ProofRef> proofs;
497 proofs.reserve(3);
498 for (size_t i = 0; i < 3; i++) {
499 proofs.push_back(
500 buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE));
501 }
502
503 CBlockIndex *pindex = active_chainstate.m_chain.Tip();
504 const CBlockIndex *tip = pindex;
505 std::vector<BlockHash> blockhashes;
506 for (size_t i = 0; i < 3; i++) {
507 blockhashes.push_back(pindex->GetBlockHash());
508 pindex = pindex->pprev;
509 }
510
511 // Add one proof each to the cache for some early blocks
512 for (size_t i = 0; i < 3; i++) {
513 BlockHash blockhash = pindex->GetBlockHash();
514 blockhashes.push_back(blockhash);
515 cache.add(pindex, proofs[i], StakeContenderStatus::IN_WINNER_SET);
516 CheckWinners(cache, blockhash, {}, {}, {proofs[i]});
517 pindex = pindex->pprev;
518 }
519
520 // Attempting to cleanup the cache before promotion has occurred has no
521 // effect.
522 for (int height = 95; height <= 100; height++) {
523 cache.cleanup(height);
524 CheckWinners(cache, blockhashes[0], {}, {}, {});
525 CheckWinners(cache, blockhashes[1], {}, {}, {});
526 CheckWinners(cache, blockhashes[2], {}, {}, {});
527 CheckWinners(cache, blockhashes[3], {}, {}, {proofs[0]});
528 CheckWinners(cache, blockhashes[4], {}, {}, {proofs[1]});
529 CheckWinners(cache, blockhashes[5], {}, {}, {proofs[2]});
530 }
531
532 // Promote contenders, but they are not winners at that block yet
533 cache.promoteToBlock(tip->pprev->pprev,
534 [](const ProofId &proofid) { return true; });
535 CheckWinners(cache, blockhashes[0], {}, {}, {});
536 CheckWinners(cache, blockhashes[1], {}, {}, {});
537 CheckWinners(cache, blockhashes[2], {}, {}, {});
538 for (auto &proof : proofs) {
539 // Contenders are unknown for blocks with no cache entries
540 CheckVoteStatus(cache, blockhashes[0], proof, -1);
541 CheckVoteStatus(cache, blockhashes[1], proof, -1);
542 // Contenders are not winners yet at the promoted block
543 CheckVoteStatus(cache, blockhashes[2], proof, 1);
544 }
545
546 // The contenders are still winners for their respective blocks
547 CheckWinners(cache, blockhashes[3], {}, {}, {proofs[0]});
548 CheckWinners(cache, blockhashes[4], {}, {}, {proofs[1]});
549 CheckWinners(cache, blockhashes[5], {}, {}, {proofs[2]});
550
551 // Cleaning up the cache leaves most recent promoted entries alone
552 cache.cleanup(98);
553 CheckWinners(cache, blockhashes[0], {}, {}, {});
554 CheckWinners(cache, blockhashes[1], {}, {}, {});
555 CheckWinners(cache, blockhashes[2], {}, {}, {});
556 for (auto &proof : proofs) {
557 // Contenders are unknown for blocks with no cache entries
558 CheckVoteStatus(cache, blockhashes[0], proof, -1);
559 CheckVoteStatus(cache, blockhashes[1], proof, -1);
560 // Contenders at the promoted block are rejected
561 CheckVoteStatus(cache, blockhashes[2], proof, 1);
562 }
563 CheckWinners(cache, blockhashes[3], {}, {}, {});
564 CheckWinners(cache, blockhashes[4], {}, {}, {});
565 CheckWinners(cache, blockhashes[5], {}, {}, {});
566
567 // Finalize those proofs
568 for (auto &proof : proofs) {
569 cache.finalize(StakeContenderId(blockhashes[2], proof->getId()));
570 // Contenders are unknown for blocks with no cache entries
571 CheckVoteStatus(cache, blockhashes[0], proof, -1);
572 CheckVoteStatus(cache, blockhashes[1], proof, -1);
573 // Contenders at the promoted block are now accepted
574 CheckVoteStatus(cache, blockhashes[2], proof, 0);
575 }
576 CheckWinners(cache, blockhashes[0], {}, {}, {});
577 CheckWinners(cache, blockhashes[1], {}, {}, {});
578 CheckWinners(cache, blockhashes[2], {}, proofs, {});
579
580 // Attempting to promote to the same block again is a no-op and statuses
581 // remain unchanged.
582 cache.promoteToBlock(tip->pprev->pprev,
583 [](const ProofId &proofid) { return true; });
584 for (auto &proof : proofs) {
585 // Contenders are unknown for blocks with no cache entries
586 CheckVoteStatus(cache, blockhashes[0], proof, -1);
587 CheckVoteStatus(cache, blockhashes[1], proof, -1);
588 // Contenders at the promoted block are still accepted
589 CheckVoteStatus(cache, blockhashes[2], proof, 0);
590 }
591 CheckWinners(cache, blockhashes[0], {}, {}, {});
592 CheckWinners(cache, blockhashes[1], {}, {}, {});
593 CheckWinners(cache, blockhashes[2], {}, proofs, {});
594
595 // Now advance the tip and invalidate a proof
596 cache.promoteToBlock(tip->pprev, [&](const ProofId &proofid) {
597 return proofid != proofs[2]->getId();
598 });
599 for (auto &proof : proofs) {
600 // Contenders are unknown for blocks with no cache entries
601 CheckVoteStatus(cache, blockhashes[0], proof, -1);
602 }
603 CheckVoteStatus(cache, blockhashes[1], proofs[0], 1);
604 CheckVoteStatus(cache, blockhashes[1], proofs[1], 1);
605 CheckVoteStatus(cache, blockhashes[1], proofs[2], -1);
606}
607
608BOOST_AUTO_TEST_CASE(pollable_contenders_tests) {
609 Chainstate &active_chainstate = Assert(m_node.chainman)->ActiveChainstate();
611
612 CBlockIndex *pindex = active_chainstate.m_chain.Tip();
613 const BlockHash &blockhash = pindex->GetBlockHash();
614
615 const size_t maxPollable = 12;
616 std::vector<StakeContenderId> contenders;
618 cache.getPollableContenders(blockhash, maxPollable, contenders), 0);
619
620 size_t numAccepted = 0;
621 for (size_t c = 0; c < maxPollable * 2; c++) {
622 // Add a new contender with random initial state
623 auto proof = buildRandomProof(active_chainstate, MIN_VALID_PROOF_SCORE);
624 BOOST_CHECK(cache.add(pindex, proof, m_rng.randbits(2)));
625
626 BlockHash dummy;
627 StakeContenderId contenderId(blockhash, proof->getId());
628 numAccepted += cache.getVoteStatus(contenderId, dummy) == 0 ? 1 : 0;
629
630 // We should never get more contenders than we can poll for in a single
631 // message.
632 BOOST_CHECK(cache.getPollableContenders(blockhash, maxPollable,
633 contenders) <= maxPollable);
634 BOOST_CHECK(contenders.size() <= maxPollable);
635
636 double lastRank = 0;
637 size_t countAccepted = 0;
638 for (const auto &contender : contenders) {
639 // Check the contender rank is sorted as we expect
640 double rank =
641 contender.ComputeProofRewardRank(MIN_VALID_PROOF_SCORE);
642 BOOST_CHECK(lastRank <= rank);
643 lastRank = rank;
644
645 countAccepted += cache.getVoteStatus(contender, dummy) == 0 ? 1 : 0;
646 }
647
648 // All accepted contenders should always be returned (up to the max)
649 BOOST_CHECK_EQUAL(countAccepted, std::min(numAccepted, maxPollable));
650 }
651}
652
653BOOST_AUTO_TEST_SUITE_END()
ArgsManager gArgs
Definition: args.cpp:39
#define Assert(val)
Identity function.
Definition: check.h:87
void ForceSetArg(const std::string &strArg, const std::string &strValue)
Definition: args.cpp:565
void ClearForcedArg(const std::string &strArg)
Remove a forced arg setting, used only in testing.
Definition: args.cpp:616
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
BlockHash GetBlockHash() const
Definition: blockindex.h:129
int nHeight
height of the entry in the chain. The genesis block has height 0
Definition: blockindex.h:38
CBlockIndex * Tip() const
Returns the index entry for the tip of this chain, or nullptr if none.
Definition: chain.h:154
Chainstate stores and provides an API to update our local knowledge of the current best chain.
Definition: validation.h:727
CChain m_chain
The current chain of blockheaders we consult and build on.
Definition: validation.h:826
const ProofId & getId() const
Definition: proof.h:167
bool getWinners(const BlockHash &prevblockhash, std::vector< std::pair< ProofId, CScript > > &winners) const
bool accept(const StakeContenderId &contenderId)
Helpers to set avalanche state of a contender.
void cleanup(const int requestedMinHeight)
size_t getPollableContenders(const BlockHash &prevblockhash, size_t maxPollable, std::vector< StakeContenderId > &pollableContenders) const
Get the best ranking contenders, accepted contenders ranking first.
bool reject(const StakeContenderId &contenderId)
bool hasAcceptedWinner(const BlockHash &prevblockhash) const
True if any accepted contender is in the winner set for prevblockhash.
bool setWinners(const CBlockIndex *pindex, const std::vector< CScript > &payoutScripts)
Set proof(s) that should be treated as winners (already finalized).
bool add(const CBlockIndex *pindex, const ProofRef &proof, uint8_t status=StakeContenderStatus::UNKNOWN)
Add a proof to consider in staking rewards pre-consensus.
void promoteToBlock(const CBlockIndex *activeTip, std::function< bool(const ProofId &proofid)> const &shouldPromote)
Promote cache entries to a the active chain tip.
int getVoteStatus(const StakeContenderId &contenderId, BlockHash &prevblockhashout) const
Get contender acceptance state for avalanche voting.
bool finalize(const StakeContenderId &contenderId)
ProofRef buildRandomProof(Chainstate &active_chainstate, uint32_t score, int height, const CKey &masterKey)
Definition: util.cpp:20
constexpr uint32_t MIN_VALID_PROOF_SCORE
Definition: util.h:20
Implement std::hash so RCUPtr can be used as a key for maps or sets.
Definition: rcu.h:259
NodeContext & m_node
Definition: interfaces.cpp:827
#define BOOST_CHECK_EQUAL(v1, v2)
Definition: object.cpp:18
#define BOOST_CHECK(expr)
Definition: object.cpp:17
@ OP_FALSE
Definition: script.h:56
@ OP_TRUE
Definition: script.h:63
static void CheckVoteStatus(StakeContenderCache &cache, const BlockHash &prevblockhash, const ProofRef &proof, int expected)
BOOST_FIXTURE_TEST_CASE(promote_tests, PeerManagerFixture)
BOOST_AUTO_TEST_CASE(vote_status_tests)
static void CheckWinners(StakeContenderCache &cache, const BlockHash &prevblockhash, std::vector< CScript > manualWinners, std::vector< ProofRef > acceptedWinners, std::vector< ProofRef > rejectedWinners)
A BlockHash is a unqiue identifier for a block.
Definition: blockhash.h:13
StakeContenderIds are unique for each block to ensure that the peer polling for their acceptance has ...
double ComputeProofRewardRank(uint32_t proofScore) const
To make sure the selection is properly weighted according to the proof score, we normalize the conten...