Bitcoin ABC 0.31.8
P2P Digital Currency
stakecontendercache.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 <blockindex.h>
9#include <logging.h>
10#include <util/strencodings.h>
11
12#include <algorithm>
13
14namespace avalanche {
15
16void StakeContenderCache::cleanup(const int requestedMinHeight) {
17 // Do not cleanup past the last promoted height, otherwise we lose cached
18 // remote proof data.
19 const int minHeight = std::min(lastPromotedHeight, requestedMinHeight);
20
21 std::set<BlockHash> hashesToErase;
22 auto &mwHeightView = manualWinners.get<by_blockheight>();
23 for (auto it = mwHeightView.begin();
24 it != mwHeightView.lower_bound(minHeight); it++) {
25 hashesToErase.insert(it->prevblockhash);
26 }
27
28 auto &cHeightView = contenders.get<by_blockheight>();
29 for (auto it = cHeightView.begin();
30 it != cHeightView.lower_bound(minHeight); it++) {
31 hashesToErase.insert(it->prevblockhash);
32 }
33
34 for (const auto &blockhash : hashesToErase) {
35 auto &mwHashView = manualWinners.get<by_prevblockhash>();
36 auto [mwHashBegin, mwHashEnd] = mwHashView.equal_range(blockhash);
37 mwHashView.erase(mwHashBegin, mwHashEnd);
38
39 auto &cHashView = contenders.get<by_prevblockhash>();
40 auto [cHashBegin, cHashEnd] = cHashView.equal_range(blockhash);
41 cHashView.erase(cHashBegin, cHashEnd);
42 }
43}
44
45bool StakeContenderCache::add(const CBlockIndex *pindex, const ProofRef &proof,
46 uint8_t status) {
47 return contenders
48 .emplace(pindex->GetBlockHash(), pindex->nHeight, proof->getId(),
49 status, proof->getPayoutScript(), proof->getScore())
50 .second;
51}
52
54 const CBlockIndex *activeTip,
55 std::function<bool(const ProofId &proofid)> const &shouldPromote) {
56 // "Promote" past contenders to activeTip and check that those contenders
57 // are still valid proofs to be stake winners. This is done because stake
58 // contenders are only added when a proof is registered in the peerManager.
59 // We need to persist the cached payout scripts and proof scores since they
60 // are not guaranteed to be stored in the event they become remote proofs.
61 const BlockHash &blockhash = activeTip->GetBlockHash();
62 const int height = activeTip->nHeight;
63 lastPromotedHeight = height;
64
65 // Gather entries to promote and then insert them afterwards so we don't
66 // iterate over newly inserted entries.
67 std::vector<StakeContenderCacheEntry> promotedEntries;
68 promotedEntries.reserve(contenders.size());
69 for (auto &contender : contenders) {
70 const ProofId &proofid = contender.proofid;
71 bool promoted = false;
72 if (shouldPromote(proofid)) {
73 promotedEntries.push_back(StakeContenderCacheEntry(
74 blockhash, height, proofid, StakeContenderStatus::UNKNOWN,
75 contender.payoutScriptPubkey, contender.score));
76 promoted = true;
77 }
79 "Contender with proofid %s, payout %s was%s promoted to "
80 "block %s (height %d) (old id %s, next id %s)\n",
81 proofid.ToString(), HexStr(contender.payoutScriptPubkey),
82 promoted ? "" : " NOT", blockhash.ToString(), height,
83 contender.getStakeContenderId().ToString(),
84 StakeContenderId(blockhash, proofid).ToString());
85 }
86 contenders.insert(promotedEntries.begin(), promotedEntries.end());
87}
88
90 const CBlockIndex *pindex, const std::vector<CScript> &payoutScripts) {
91 const BlockHash &prevblockhash = pindex->GetBlockHash();
92 auto &view = manualWinners.get<by_prevblockhash>();
93 auto it = view.find(prevblockhash);
94 if (it == view.end()) {
95 return manualWinners
96 .emplace(prevblockhash, pindex->nHeight, payoutScripts)
97 .second;
98 }
99 return manualWinners.replace(
100 it, ManualWinners(prevblockhash, pindex->nHeight, payoutScripts));
101}
102
104 auto &view = contenders.get<by_stakecontenderid>();
105 auto it = view.find(contenderId);
106 if (it == view.end()) {
107 return false;
108 }
109
110 return contenders.modify(it, [&](StakeContenderCacheEntry &entry) {
112 });
113}
114
116 auto &view = contenders.get<by_stakecontenderid>();
117 auto it = view.find(contenderId);
118 if (it == view.end()) {
119 return false;
120 }
121
122 return contenders.modify(it, [&](StakeContenderCacheEntry &entry) {
125 });
126}
127
129 auto &view = contenders.get<by_stakecontenderid>();
130 auto it = view.find(contenderId);
131 if (it == view.end()) {
132 return false;
133 }
134
135 return contenders.modify(it, [&](StakeContenderCacheEntry &entry) {
137 });
138}
139
141 BlockHash &prevblockhashout) const {
142 auto &view = contenders.get<by_stakecontenderid>();
143 auto it = view.find(contenderId);
144 if (it == view.end()) {
145 return -1;
146 }
147
148 prevblockhashout = it->prevblockhash;
149
150 // Contender is accepted
151 if (it->isAccepted()) {
152 return 0;
153 }
154
155 // If the contender matches a manual winner, it is accepted.
156 auto &manualWinnersView = manualWinners.get<by_prevblockhash>();
157 auto manualWinnerIt = manualWinnersView.find(it->prevblockhash);
158 if (manualWinnerIt != manualWinners.end()) {
159 for (auto &payoutScript : manualWinnerIt->payoutScripts) {
160 if (payoutScript == it->payoutScriptPubkey) {
161 return 0;
162 }
163 }
164 }
165
166 // Contender is rejected
167 return 1;
168}
169
171 const BlockHash &prevblockhash, size_t maxPollable,
172 std::vector<StakeContenderId> &pollableContenders) const {
173 std::vector<const StakeContenderCacheEntry *> rankedContenders;
174 auto &view = contenders.get<by_prevblockhash>();
175 auto [begin, end] = view.equal_range(prevblockhash);
176 for (auto it = begin; it != end; it++) {
177 rankedContenders.push_back(&(*it));
178 }
179
180 // First sort all contenders with accepted contenders first
181 std::sort(rankedContenders.begin(), rankedContenders.end(),
182 [](const StakeContenderCacheEntry *left,
183 const StakeContenderCacheEntry *right) {
184 if (left->isAccepted() != right->isAccepted()) {
185 // Accepted contenders sort first
186 return left->isAccepted();
187 }
188
189 double leftRank = left->computeRewardRank();
190 double rightRank = right->computeRewardRank();
191 const StakeContenderId &leftContenderId =
192 left->getStakeContenderId();
193 const StakeContenderId &rightContenderId =
194 right->getStakeContenderId();
195 return RewardRankComparator()(leftContenderId, leftRank,
196 left->proofid, rightContenderId,
197 rightRank, right->proofid);
198 });
199
200 // Sort again, only by reward rank, and only up to the max number of
201 // pollable contenders.
202 size_t numPollable = std::min(rankedContenders.size(), maxPollable);
203 std::sort(rankedContenders.begin(), rankedContenders.begin() + numPollable,
204 [](const StakeContenderCacheEntry *left,
205 const StakeContenderCacheEntry *right) {
206 double leftRank = left->computeRewardRank();
207 double rightRank = right->computeRewardRank();
208 const StakeContenderId &leftContenderId =
209 left->getStakeContenderId();
210 const StakeContenderId &rightContenderId =
211 right->getStakeContenderId();
212 return RewardRankComparator()(leftContenderId, leftRank,
213 left->proofid, rightContenderId,
214 rightRank, right->proofid);
215 });
216
217 // Only return up to the maximum number of contenders
218 pollableContenders.clear();
219 pollableContenders.reserve(numPollable);
220 for (size_t i = 0; i < numPollable; i++) {
221 pollableContenders.push_back(
222 rankedContenders[i]->getStakeContenderId());
223 }
224
225 return pollableContenders.size();
226}
227
228bool StakeContenderCache::getWinners(
229 const BlockHash &prevblockhash,
230 std::vector<std::pair<ProofId, CScript>> &winners) const {
231 // Winners determined by avalanche are sorted by reward rank
232 std::vector<const StakeContenderCacheEntry *> rankedWinners;
233 auto &view = contenders.get<by_prevblockhash>();
234 auto [begin, end] = view.equal_range(prevblockhash);
235 for (auto it = begin; it != end; it++) {
236 if (it->isInWinnerSet()) {
237 rankedWinners.push_back(&(*it));
238 }
239 }
240
241 std::sort(rankedWinners.begin(), rankedWinners.end(),
242 [](const StakeContenderCacheEntry *left,
243 const StakeContenderCacheEntry *right) {
244 if (left->isAccepted() != right->isAccepted()) {
245 // Accepted contenders sort first
246 return left->isAccepted();
247 }
248
249 double leftRank = left->computeRewardRank();
250 double rightRank = right->computeRewardRank();
251 const StakeContenderId &leftContenderId =
252 left->getStakeContenderId();
253 const StakeContenderId &rightContenderId =
254 right->getStakeContenderId();
255 return RewardRankComparator()(leftContenderId, leftRank,
256 left->proofid, rightContenderId,
257 rightRank, right->proofid);
258 });
259
260 winners.clear();
261
262 // Add manual winners first, preserving order
263 auto &manualWinnersView = manualWinners.get<by_prevblockhash>();
264 auto manualWinnerIt = manualWinnersView.find(prevblockhash);
265 if (manualWinnerIt != manualWinners.end()) {
266 winners.reserve(manualWinnerIt->payoutScripts.size() +
267 rankedWinners.size());
268
269 for (auto &payoutScript : manualWinnerIt->payoutScripts) {
270 winners.push_back({ProofId(), payoutScript});
271 }
272 } else {
273 winners.reserve(rankedWinners.size());
274 }
275
276 // Add ranked winners, preserving reward rank order
277 for (const auto &rankedWinner : rankedWinners) {
278 winners.push_back(
279 {rankedWinner->proofid, rankedWinner->payoutScriptPubkey});
280 }
281
282 return winners.size() > 0;
283}
284
285} // namespace avalanche
The block chain is a tree shaped structure starting with the genesis block at the root,...
Definition: blockindex.h:25
BlockHash GetBlockHash() const
Definition: blockindex.h:130
int nHeight
height of the entry in the chain. The genesis block has height 0
Definition: blockindex.h:38
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 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)
std::string ToString() const
Definition: uint256.h:80
#define LogPrintLevel(category, level,...)
Definition: logging.h:277
@ AVALANCHE
Definition: logging.h:65
std::string ToString(const T &t)
Locale-independent version of std::to_string.
Definition: string.h:108
A BlockHash is a unqiue identifier for a block.
Definition: blockhash.h:13
uint8_t status
ProofId proofid
double computeRewardRank() const
StakeContenderId getStakeContenderId() const
StakeContenderIds are unique for each block to ensure that the peer polling for their acceptance has ...
std::string HexStr(const Span< const uint8_t > s)
Convert a span of bytes to a lower-case hexadecimal string.