Bitcoin ABC 0.33.11
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,
49 pindex->GetBlockTime(), proof->getId(), status,
50 proof->getPayoutScript(), proof->getScore())
51 .second;
52}
53
55 const CBlockIndex *activeTip,
56 std::function<bool(const ProofId &proofid)> const &shouldPromote) {
57 // "Promote" past contenders to activeTip and check that those contenders
58 // are still valid proofs to be stake winners. This is done because stake
59 // contenders are only added when a proof is registered in the peerManager.
60 // We need to persist the cached payout scripts and proof scores since they
61 // are not guaranteed to be stored in the event they become remote proofs.
62 const BlockHash &blockhash = activeTip->GetBlockHash();
63 const int height = activeTip->nHeight;
64 const int64_t blocktime = activeTip->GetBlockTime();
65 lastPromotedHeight = height;
66
67 // Gather entries to promote and then insert them afterwards so we don't
68 // iterate over newly inserted entries.
69 std::vector<StakeContenderCacheEntry> promotedEntries;
70 promotedEntries.reserve(contenders.size());
71 for (auto &contender : contenders) {
72 const ProofId &proofid = contender.proofid;
73 bool promoted = false;
74 if (shouldPromote(proofid)) {
75 promotedEntries.push_back(StakeContenderCacheEntry(
76 blockhash, height, blocktime, proofid,
77 StakeContenderStatus::UNKNOWN, contender.payoutScriptPubkey,
78 contender.score));
79 promoted = true;
80 }
82 "Contender with proofid %s, payout %s was%s promoted to "
83 "block %s (height %d) (old id %s, next id %s)\n",
84 proofid.ToString(), HexStr(contender.payoutScriptPubkey),
85 promoted ? "" : " NOT", blockhash.ToString(), height,
86 contender.getStakeContenderId().ToString(),
87 StakeContenderId(blockhash, proofid).ToString());
88 }
89 contenders.insert(promotedEntries.begin(), promotedEntries.end());
90}
91
93 const CBlockIndex *pindex, const std::vector<CScript> &payoutScripts) {
94 const BlockHash &prevblockhash = pindex->GetBlockHash();
95 auto &view = manualWinners.get<by_prevblockhash>();
96 auto it = view.find(prevblockhash);
97 if (it == view.end()) {
98 return manualWinners
99 .emplace(prevblockhash, pindex->nHeight, payoutScripts)
100 .second;
101 }
102 return manualWinners.replace(
103 it, ManualWinners(prevblockhash, pindex->nHeight, payoutScripts));
104}
105
107 auto &view = contenders.get<by_stakecontenderid>();
108 auto it = view.find(contenderId);
109 if (it == view.end()) {
110 return false;
111 }
112
113 return contenders.modify(it, [&](StakeContenderCacheEntry &entry) {
115 });
116}
117
119 auto &view = contenders.get<by_stakecontenderid>();
120 auto it = view.find(contenderId);
121 if (it == view.end()) {
122 return false;
123 }
124
125 return contenders.modify(it, [&](StakeContenderCacheEntry &entry) {
128 });
129}
130
132 auto &view = contenders.get<by_stakecontenderid>();
133 auto it = view.find(contenderId);
134 if (it == view.end()) {
135 return false;
136 }
137
138 return contenders.modify(it, [&](StakeContenderCacheEntry &entry) {
140 });
141}
142
143std::optional<StakeContenderCacheInfo> StakeContenderCache::getContenderInfo(
144 const StakeContenderId &contenderId) const {
145 auto &view = contenders.get<by_stakecontenderid>();
146 auto it = view.find(contenderId);
147 if (it == view.end()) {
148 return std::nullopt;
149 }
150
151 StakeContenderCacheInfo contenderInfo{
152 it->prevblockhash,
153 it->prevblocktime,
154 it->proofid,
155 1,
156 };
157
158 if (it->isAccepted()) {
159 contenderInfo.voteStatus = 0;
160 return contenderInfo;
161 }
162
163 auto &manualWinnersView = manualWinners.get<by_prevblockhash>();
164 auto manualWinnerIt = manualWinnersView.find(it->prevblockhash);
165 if (manualWinnerIt != manualWinners.end()) {
166 for (auto &payoutScript : manualWinnerIt->payoutScripts) {
167 if (payoutScript == it->payoutScriptPubkey) {
168 contenderInfo.voteStatus = 0;
169 return contenderInfo;
170 }
171 }
172 }
173
174 return contenderInfo;
175}
176
178 BlockHash &prevblockhashout) const {
179 const auto contenderInfo = getContenderInfo(contenderId);
180 if (!contenderInfo) {
181 return -1;
182 }
183
184 prevblockhashout = contenderInfo->prevblockhash;
185 return contenderInfo->voteStatus;
186}
187
189 const BlockHash &prevblockhash, size_t maxPollable,
190 std::vector<StakeContenderId> &pollableContenders) const {
191 std::vector<const StakeContenderCacheEntry *> rankedContenders;
192 auto &view = contenders.get<by_prevblockhash>();
193 auto [begin, end] = view.equal_range(prevblockhash);
194 for (auto it = begin; it != end; it++) {
195 rankedContenders.push_back(&(*it));
196 }
197
198 // First sort all contenders with accepted contenders first
199 std::sort(rankedContenders.begin(), rankedContenders.end(),
200 [](const StakeContenderCacheEntry *left,
201 const StakeContenderCacheEntry *right) {
202 if (left->isAccepted() != right->isAccepted()) {
203 // Accepted contenders sort first
204 return left->isAccepted();
205 }
206
207 double leftRank = left->computeRewardRank();
208 double rightRank = right->computeRewardRank();
209 const StakeContenderId &leftContenderId =
210 left->getStakeContenderId();
211 const StakeContenderId &rightContenderId =
212 right->getStakeContenderId();
213 return RewardRankComparator()(leftContenderId, leftRank,
214 left->proofid, rightContenderId,
215 rightRank, right->proofid);
216 });
217
218 // Sort again, only by reward rank, and only up to the max number of
219 // pollable contenders.
220 size_t numPollable = std::min(rankedContenders.size(), maxPollable);
221 std::sort(rankedContenders.begin(), rankedContenders.begin() + numPollable,
222 [](const StakeContenderCacheEntry *left,
223 const StakeContenderCacheEntry *right) {
224 double leftRank = left->computeRewardRank();
225 double rightRank = right->computeRewardRank();
226 const StakeContenderId &leftContenderId =
227 left->getStakeContenderId();
228 const StakeContenderId &rightContenderId =
229 right->getStakeContenderId();
230 return RewardRankComparator()(leftContenderId, leftRank,
231 left->proofid, rightContenderId,
232 rightRank, right->proofid);
233 });
234
235 // Only return up to the maximum number of contenders
236 pollableContenders.clear();
237 pollableContenders.reserve(numPollable);
238 for (size_t i = 0; i < numPollable; i++) {
239 pollableContenders.push_back(
240 rankedContenders[i]->getStakeContenderId());
241 }
242
243 return pollableContenders.size();
244}
245
246bool StakeContenderCache::getWinners(
247 const BlockHash &prevblockhash,
248 std::vector<std::pair<ProofId, CScript>> &winners) const {
249 // Winners determined by avalanche are sorted by reward rank
250 std::vector<const StakeContenderCacheEntry *> rankedWinners;
251 auto &view = contenders.get<by_prevblockhash>();
252 auto [begin, end] = view.equal_range(prevblockhash);
253 for (auto it = begin; it != end; it++) {
254 if (it->isInWinnerSet()) {
255 rankedWinners.push_back(&(*it));
256 }
257 }
258
259 std::sort(rankedWinners.begin(), rankedWinners.end(),
260 [](const StakeContenderCacheEntry *left,
261 const StakeContenderCacheEntry *right) {
262 if (left->isAccepted() != right->isAccepted()) {
263 // Accepted contenders sort first
264 return left->isAccepted();
265 }
266
267 double leftRank = left->computeRewardRank();
268 double rightRank = right->computeRewardRank();
269 const StakeContenderId &leftContenderId =
270 left->getStakeContenderId();
271 const StakeContenderId &rightContenderId =
272 right->getStakeContenderId();
273 return RewardRankComparator()(leftContenderId, leftRank,
274 left->proofid, rightContenderId,
275 rightRank, right->proofid);
276 });
277
278 winners.clear();
279
280 // Add manual winners first, preserving order
281 auto &manualWinnersView = manualWinners.get<by_prevblockhash>();
282 auto manualWinnerIt = manualWinnersView.find(prevblockhash);
283 if (manualWinnerIt != manualWinners.end()) {
284 winners.reserve(manualWinnerIt->payoutScripts.size() +
285 rankedWinners.size());
286
287 for (auto &payoutScript : manualWinnerIt->payoutScripts) {
288 winners.push_back({ProofId(), payoutScript});
289 }
290 } else {
291 winners.reserve(rankedWinners.size());
292 }
293
294 // Add ranked winners, preserving reward rank order
295 for (const auto &rankedWinner : rankedWinners) {
296 winners.push_back(
297 {rankedWinner->proofid, rankedWinner->payoutScriptPubkey});
298 }
299
300 return winners.size() > 0;
301}
302
303} // namespace avalanche
The block chain is a tree shaped structure starting with the genesis block at the root,...
Definition: blockindex.h:25
int64_t GetBlockTime() const
Definition: blockindex.h:160
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.
std::optional< StakeContenderCacheInfo > getContenderInfo(const StakeContenderId &contenderId) const
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
std::string HexStr(const Span< const uint8_t > s)
Convert a span of bytes to a lower-case hexadecimal string.
Definition: hex_base.cpp:30
#define LogTrace(category,...)
Definition: logging.h:448
@ AVALANCHE
Definition: logging.h:91
static std::string ToString(const CService &ip)
Definition: db.h:36
A BlockHash is a unqiue identifier for a block.
Definition: blockhash.h:13
uint8_t status
ProofId proofid
double computeRewardRank() const
StakeContenderId getStakeContenderId() const
Cache to track stake contenders for recent blocks.
StakeContenderIds are unique for each block to ensure that the peer polling for their acceptance has ...