Bitcoin ABC 0.26.3
P2P Digital Currency
Loading...
Searching...
No Matches
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
7#include <script/script.h>
8
10#include <test/util/random.h>
11#include <test/util/setup_common.h>
12
13#include <boost/test/unit_test.hpp>
14
15#include <limits>
16
17using namespace avalanche;
18
20
22 const BlockHash &prevblockhash,
23 std::vector<CScript> manualWinners,
25 std::vector<CScript> winners;
26 size_t expectedSize = manualWinners.size() + avalancheWinners.size();
27 if (expectedSize == 0) {
28 BOOST_CHECK(!cache.getWinners(prevblockhash, winners));
29 return;
30 }
31
32 BOOST_CHECK(cache.getWinners(prevblockhash, winners));
33 BOOST_CHECK_EQUAL(winners.size(), expectedSize);
34
35 // Manual winners are always first and in order
36 for (size_t i = 0; i < manualWinners.size(); i++) {
37 BOOST_CHECK(winners[i] == manualWinners[i]);
38 }
39
40 // Rest of the the winners are only those determined by avalanche.
41 // For each winning payout script, find all avalancheWinners with the same
42 // payout script.
43 std::vector<std::vector<ProofRef>> possibleWinningProofs;
44 for (auto it = std::next(winners.begin(), manualWinners.size());
45 it != winners.end(); it++) {
46 possibleWinningProofs.push_back(std::vector<ProofRef>());
47 for (const auto &proof : avalancheWinners) {
48 if (proof->getPayoutScript() == *it) {
49 possibleWinningProofs.back().push_back(proof);
50 }
51 }
52 BOOST_CHECK(possibleWinningProofs.back().size() > 0);
53 }
55
56 // Verify the winner order such that the best (lowest) reward ranked proof's
57 // payout script is always before payout scripts from proofs with worse
58 // (higher) reward ranks.
59 double previousRank = 0;
61 double lowestRank = std::numeric_limits<double>::max();
62 for (const auto &proof : possibleWinningProofList) {
63 double proofRank = StakeContenderId(prevblockhash, proof->getId())
64 .ComputeProofRewardRank(proof->getScore());
65 if (proofRank < lowestRank) {
67 }
68 }
69
72 }
73}
74
76 const BlockHash &prevblockhash,
77 const ProofRef &proof, int expected) {
79 cache.getVoteStatus(StakeContenderId(prevblockhash, proof->getId())),
80 expected);
81}
82
84 Chainstate &active_chainstate = Assert(m_node.chainman)->ActiveChainstate();
86
87 CBlockIndex *pindex = active_chainstate.m_chain.Tip();
88 const BlockHash &blockhash = pindex->GetBlockHash();
89
90 std::vector<int> initialStatuses = {
91 StakeContenderStatus::UNKNOWN, StakeContenderStatus::ACCEPTED,
92 StakeContenderStatus::IN_WINNER_SET,
93 StakeContenderStatus::ACCEPTED | StakeContenderStatus::IN_WINNER_SET};
96
97 // Unknown contender
98 CheckVoteStatus(cache, blockhash, proof, -1);
99
100 // Add the contender and check its vote after avalanche updates
101 BOOST_CHECK(cache.add(pindex, proof, initialStatus));
102 CheckVoteStatus(cache, blockhash, proof,
103 !(initialStatus & StakeContenderStatus::ACCEPTED));
104
105 cache.accept(StakeContenderId(blockhash, proof->getId()));
106 CheckVoteStatus(cache, blockhash, proof, 0);
107
108 cache.reject(StakeContenderId(blockhash, proof->getId()));
109 CheckVoteStatus(cache, blockhash, proof, 1);
110
111 cache.finalize(StakeContenderId(blockhash, proof->getId()));
112 CheckVoteStatus(cache, blockhash, proof, 0);
113
114 cache.invalidate(StakeContenderId(blockhash, proof->getId()));
115 CheckVoteStatus(cache, blockhash, proof, 1);
116
117 // Add the proof as a manual winner. It should always be accepted.
118 BOOST_CHECK(cache.addWinner(pindex, proof->getPayoutScript()));
119 CheckVoteStatus(cache, blockhash, proof, 0);
120 }
121}
122
124 Chainstate &active_chainstate = Assert(m_node.chainman)->ActiveChainstate();
126
127 std::vector<CScript> manualWinners = {
128 CScript() << OP_TRUE,
129 CScript() << OP_FALSE,
130 };
131
132 std::vector<ProofRef> proofs;
133 for (int i = 0; i < 4; i++) {
136 }
137
138 // Repeat these tests with multiple block hashes to ensure no unintended
139 // modifications are made to other entries
140 CBlockIndex *pindex = active_chainstate.m_chain.Tip();
141 for (int i = 0; i < 5; i++) {
142 const BlockHash &blockhash = pindex->GetBlockHash();
143 CheckWinners(cache, blockhash, {}, {});
144
145 // Add a winner manually
146 BOOST_CHECK(cache.addWinner(pindex, manualWinners[0]));
147 CheckWinners(cache, blockhash, {manualWinners[0]}, {});
148
149 // Before adding contenders, check that vote status is unknown
150 for (int p = 0; p < 4; p++) {
151 CheckVoteStatus(cache, blockhash, proofs[p], -1);
152 }
153
154 // Add some contenders
155 // Local winner
156 BOOST_CHECK(cache.add(pindex, proofs[0],
157 StakeContenderStatus::ACCEPTED |
158 StakeContenderStatus::IN_WINNER_SET));
159 CheckVoteStatus(cache, blockhash, proofs[0], 0);
160
161 // Potential winner other than the local winner
163 cache.add(pindex, proofs[1], StakeContenderStatus::ACCEPTED));
164 CheckVoteStatus(cache, blockhash, proofs[1], 0);
165
166 // Local winner that has been rejected by avalanche so far
168 cache.add(pindex, proofs[2], StakeContenderStatus::IN_WINNER_SET));
169 CheckVoteStatus(cache, blockhash, proofs[2], 1);
170
171 // Some other contender
172 BOOST_CHECK(cache.add(pindex, proofs[3]));
173 CheckVoteStatus(cache, blockhash, proofs[3], 1);
174
175 // Attempting to add duplicates fails, even if status is different than
176 // the successfully added entries.
177 for (const auto &proof : proofs) {
178 BOOST_CHECK(!cache.add(pindex, proof));
180 !cache.add(pindex, proof, StakeContenderStatus::ACCEPTED));
181 BOOST_CHECK(!cache.add(pindex, proof,
182 StakeContenderStatus::ACCEPTED |
183 StakeContenderStatus::IN_WINNER_SET));
185 !cache.add(pindex, proof, StakeContenderStatus::IN_WINNER_SET));
186 }
187
188 CheckWinners(cache, blockhash, {manualWinners[0]},
189 {proofs[0], proofs[2]});
190
191 // Add another manual winner. It always comes before contenders in the
192 // winner set.
193 BOOST_CHECK(cache.addWinner(pindex, manualWinners[1]));
194 CheckWinners(cache, blockhash, manualWinners, {proofs[0], proofs[2]});
195
196 // Adding manual winners with the same payout scripts as contenders in
197 // any state never causes conflicts
198 std::vector<CScript> moreManualWinners = manualWinners;
199 for (const auto &proof : proofs) {
200 const auto &payout = proof->getPayoutScript();
201 BOOST_CHECK(cache.addWinner(pindex, payout));
202 CheckVoteStatus(cache, blockhash, proof, 0);
203 moreManualWinners.push_back(payout);
204 CheckWinners(cache, blockhash, moreManualWinners,
205 {proofs[0], proofs[2]});
206 }
207 CheckWinners(cache, blockhash, moreManualWinners,
208 {proofs[0], proofs[2]});
209
210 // Avalanche accepting all of the contenders does not change the winners
211 // yet
212 for (const auto &proof : proofs) {
213 cache.accept(StakeContenderId(blockhash, proof->getId()));
214 }
215 CheckWinners(cache, blockhash, moreManualWinners,
216 {proofs[0], proofs[2]});
217
218 // Avalanche rejecting all of the contenders does not change the winners
219 // yet
220 for (const auto &proof : proofs) {
221 cache.reject(StakeContenderId(blockhash, proof->getId()));
222 }
223 CheckWinners(cache, blockhash, moreManualWinners,
224 {proofs[0], proofs[2]});
225
226 // Avalanche finalizing a contender already in the winner set makes no
227 // difference
228 cache.finalize(StakeContenderId(blockhash, proofs[0]->getId()));
229 CheckWinners(cache, blockhash, moreManualWinners,
230 {proofs[0], proofs[2]});
231
232 // Avalanche invalidating a contender not in the winner set makes no
233 // difference
234 cache.invalidate(StakeContenderId(blockhash, proofs[3]->getId()));
235 CheckWinners(cache, blockhash, moreManualWinners,
236 {proofs[0], proofs[2]});
237
238 // Avalanche finalizing a contender that wasn't in the winner set before
239 // makes a new winner
240 cache.finalize(StakeContenderId(blockhash, proofs[1]->getId()));
241 CheckWinners(cache, blockhash, moreManualWinners,
242 {proofs[0], proofs[1], proofs[2]});
243
244 // Avalanche invalidating a contender that was in the winner set removes
245 // it
246 cache.invalidate(StakeContenderId(blockhash, proofs[2]->getId()));
247 CheckWinners(cache, blockhash, moreManualWinners,
248 {proofs[0], proofs[1]});
249
250 pindex = pindex->pprev;
251 }
252
253 // All contenders were added as manual winners at some point in this test,
254 // so reflect that here.
255 for (const auto &proof : proofs) {
256 manualWinners.push_back(proof->getPayoutScript());
257 }
258
259 // Sanity check that past cached state was not poisoned
260 pindex = active_chainstate.m_chain.Tip();
261 for (int i = 0; i < 5; i++) {
262 CheckWinners(cache, pindex->GetBlockHash(), manualWinners,
263 {proofs[0], proofs[1]});
264 for (int p = 0; p < 4; p++) {
265 CheckVoteStatus(cache, pindex->GetBlockHash(), proofs[p], 0);
266 }
267 pindex = pindex->pprev;
268 }
269}
270
272 Chainstate &active_chainstate = Assert(m_node.chainman)->ActiveChainstate();
274
275 std::vector<ProofRef> proofs;
276 for (int i = 0; i < 10; i++) {
277 proofs.push_back(
279 }
280
281 CBlockIndex *pindex = active_chainstate.m_chain.Tip();
282 std::vector<BlockHash> blockhashes;
283 for (int i = 0; i < 3; i++) {
284 BlockHash blockhash = pindex->GetBlockHash();
285 blockhashes.push_back(blockhash);
286 for (const auto &proof : proofs) {
287 cache.add(pindex, proof, StakeContenderStatus::IN_WINNER_SET);
288 }
289 CheckWinners(cache, blockhash, {}, proofs);
290 pindex = pindex->pprev;
291 }
292
293 // Cleaning up nonexistant entries has no impact
294 for (int height : {0, 10, 50, 90, 98}) {
295 cache.cleanup(height);
296 CheckWinners(cache, blockhashes[0], {}, proofs);
297 CheckWinners(cache, blockhashes[1], {}, proofs);
298 CheckWinners(cache, blockhashes[2], {}, proofs);
299 }
300
301 // Cleanup oldest block in the cache
302 cache.cleanup(99);
303 CheckWinners(cache, blockhashes[0], {}, proofs);
304 CheckWinners(cache, blockhashes[1], {}, proofs);
305 CheckWinners(cache, blockhashes[2], {}, {});
306
307 // Add only a local winner to the recently cleared block
308 cache.addWinner(active_chainstate.m_chain.Tip()->pprev->pprev, CScript());
309 CheckWinners(cache, blockhashes[0], {}, proofs);
310 CheckWinners(cache, blockhashes[1], {}, proofs);
311 CheckWinners(cache, blockhashes[2], {CScript()}, {});
312
313 // Clean it up again
314 cache.cleanup(99);
315 CheckWinners(cache, blockhashes[0], {}, proofs);
316 CheckWinners(cache, blockhashes[1], {}, proofs);
317 CheckWinners(cache, blockhashes[2], {}, {});
318
319 // Add a local winner to a block with winners already there, then clear it
320 cache.addWinner(active_chainstate.m_chain.Tip()->pprev, CScript());
321 CheckWinners(cache, blockhashes[0], {}, proofs);
322 CheckWinners(cache, blockhashes[1], {CScript()}, proofs);
323 CheckWinners(cache, blockhashes[2], {}, {});
324
325 cache.cleanup(100);
326 CheckWinners(cache, blockhashes[0], {}, proofs);
327 CheckWinners(cache, blockhashes[1], {}, {});
328 CheckWinners(cache, blockhashes[2], {}, {});
329
330 // Invalidate proofs so they are no longer in the winner set
331 for (const auto &proof : proofs) {
332 cache.invalidate(StakeContenderId(blockhashes[0], proof->getId()));
333 }
334 CheckWinners(cache, blockhashes[0], {}, {});
335 BOOST_CHECK(!cache.isEmpty());
336
337 // Clean up the remaining block and the cache should be empty now
338 cache.cleanup(101);
339 BOOST_CHECK(cache.isEmpty());
340 CheckWinners(cache, blockhashes[0], {}, {});
341 CheckWinners(cache, blockhashes[1], {}, {});
342 CheckWinners(cache, blockhashes[2], {}, {});
343
344 // Cleaning up again has no effect
345 cache.cleanup(101);
346 BOOST_CHECK(cache.isEmpty());
347 CheckWinners(cache, blockhashes[0], {}, {});
348 CheckWinners(cache, blockhashes[1], {}, {});
349 CheckWinners(cache, blockhashes[2], {}, {});
350
351 // Add winners back with random states and sanity check that higher heights
352 // clear the cache as we expect.
353 for (int height : {102, 200, 1000, 1000000}) {
354 pindex = active_chainstate.m_chain.Tip();
355 for (size_t i = 0; i < 2; i++) {
356 for (const auto &proof : proofs) {
357 cache.add(pindex, proof, InsecureRandBits(2));
358 cache.addWinner(pindex, CScript());
359 }
360
361 // Sanity check there are some winners
362 std::vector<CScript> winners;
363 BOOST_CHECK(cache.getWinners(blockhashes[i], winners));
364 BOOST_CHECK(winners.size() >= 1);
365 pindex = pindex->pprev;
366 }
367
368 // Cleaning up the cache at a height higher than any block results in an
369 // empty cache and no winners.
370 cache.cleanup(height);
371 BOOST_CHECK(cache.isEmpty());
372 CheckWinners(cache, blockhashes[0], {}, {});
373 CheckWinners(cache, blockhashes[1], {}, {});
374 CheckWinners(cache, blockhashes[2], {}, {});
375 }
376}
377
#define Assert(val)
Identity function.
Definition check.h:84
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:146
Serialized script, used inside transaction inputs and outputs.
Definition script.h:431
Chainstate stores and provides an API to update our local knowledge of the current best chain.
Definition validation.h:693
const ProofId & getId() const
Definition proof.h:169
Cache to track stake contenders for recent blocks.
bool invalidate(const StakeContenderId &contenderId)
bool accept(const StakeContenderId &contenderId)
Helpers to set avalanche state of a contender.
size_t isEmpty() const
For tests.
bool reject(const StakeContenderId &contenderId)
bool addWinner(const CBlockIndex *pindex, const CScript &payoutScript)
Add a proof that should be treated as a winner (already finalized).
int getVoteStatus(const StakeContenderId &contenderId) const
Get contender acceptance state for avalanche voting.
void cleanup(const int minHeight)
bool add(const CBlockIndex *pindex, const ProofRef &proof, uint8_t status=StakeContenderStatus::UNKNOWN)
Add a proof to consider in staking rewards pre-consensus.
bool finalize(const StakeContenderId &contenderId)
bool getWinners(const BlockHash &prevblockhash, std::vector< CScript > &payouts) const
Get payout scripts of the winning proofs.
void push_back(const T &value)
Definition prevector.h:531
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
Implement std::hash so RCUPtr can be used as a key for maps or sets.
Definition rcu.h:257
NodeContext & m_node
#define BOOST_CHECK_EQUAL(v1, v2)
Definition object.cpp:18
#define BOOST_CHECK(expr)
Definition object.cpp:17
T GetRand(T nMax=std::numeric_limits< T >::max()) noexcept
Generate a uniform random integer of type T in the range [0..nMax) nMax defaults to std::numeric_limi...
Definition random.h:85
@ OP_FALSE
Definition script.h:50
@ OP_TRUE
Definition script.h:57
static void CheckVoteStatus(StakeContenderCache &cache, const BlockHash &prevblockhash, const ProofRef &proof, int expected)
static void CheckWinners(StakeContenderCache &cache, const BlockHash &prevblockhash, std::vector< CScript > manualWinners, std::vector< ProofRef > avalancheWinners)
BOOST_AUTO_TEST_CASE(vote_status_tests)
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)
To make sure the selection is properly weighted according to the proof score, we normalize the conten...