Bitcoin ABC 0.30.10
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
9
10#include <algorithm>
11
12namespace avalanche {
13
14void StakeContenderCache::cleanup(const int requestedMinHeight) {
15 // Do not cleanup past the last promoted height, otherwise we lose cached
16 // remote proof data.
17 const int minHeight = std::min(lastPromotedHeight, requestedMinHeight);
18
19 std::set<BlockHash> hashesToErase;
20 auto &mwHeightView = manualWinners.get<by_blockheight>();
21 for (auto it = mwHeightView.begin();
22 it != mwHeightView.lower_bound(minHeight); it++) {
23 hashesToErase.insert(it->prevblockhash);
24 }
25
26 auto &cHeightView = contenders.get<by_blockheight>();
27 for (auto it = cHeightView.begin();
28 it != cHeightView.lower_bound(minHeight); it++) {
29 hashesToErase.insert(it->prevblockhash);
30 }
31
32 for (const auto &blockhash : hashesToErase) {
33 auto &mwHashView = manualWinners.get<by_prevblockhash>();
34 auto [mwHashBegin, mwHashEnd] = mwHashView.equal_range(blockhash);
35 mwHashView.erase(mwHashBegin, mwHashEnd);
36
37 auto &cHashView = contenders.get<by_prevblockhash>();
38 auto [cHashBegin, cHashEnd] = cHashView.equal_range(blockhash);
39 cHashView.erase(cHashBegin, cHashEnd);
40 }
41}
42
43bool StakeContenderCache::add(const CBlockIndex *pindex, const ProofRef &proof,
44 uint8_t status) {
45 return contenders
46 .emplace(pindex->GetBlockHash(), pindex->nHeight, proof->getId(),
47 status, proof->getPayoutScript(), proof->getScore())
48 .second;
49}
50
52 PeerManager &pm) {
53 // "Promote" past contenders to activeTip and check that those contenders
54 // are still valid proofs to be stake winners. This is done because new
55 // stake contenders are only added when a new proof is seen for the first
56 // time. We need to persist the cached payout scripts and proof scores since
57 // they are not guaranteed to be stored by peerManager.
58 const BlockHash &blockhash = activeTip->GetBlockHash();
59 const int height = activeTip->nHeight;
60 lastPromotedHeight = height;
61 for (auto &contender : contenders) {
62 const ProofId &proofid = contender.proofid;
63 if (pm.isRemoteProof(proofid) &&
64 (pm.isBoundToPeer(proofid) || pm.isDangling(proofid))) {
65 contenders.emplace(blockhash, height, proofid,
67 contender.payoutScriptPubkey, contender.score);
68 }
69 }
70}
71
73 const CBlockIndex *pindex, const std::vector<CScript> &payoutScripts) {
74 const BlockHash &prevblockhash = pindex->GetBlockHash();
75 auto &view = manualWinners.get<by_prevblockhash>();
76 auto it = view.find(prevblockhash);
77 if (it == view.end()) {
78 return manualWinners
79 .emplace(prevblockhash, pindex->nHeight, payoutScripts)
80 .second;
81 }
82 return manualWinners.replace(
83 it, ManualWinners(prevblockhash, pindex->nHeight, payoutScripts));
84}
85
87 auto &view = contenders.get<by_stakecontenderid>();
88 auto it = view.find(contenderId);
89 if (it == view.end()) {
90 return false;
91 }
92
93 return contenders.modify(it, [&](StakeContenderCacheEntry &entry) {
95 });
96}
97
99 auto &view = contenders.get<by_stakecontenderid>();
100 auto it = view.find(contenderId);
101 if (it == view.end()) {
102 return false;
103 }
104
105 return contenders.modify(it, [&](StakeContenderCacheEntry &entry) {
108 });
109}
110
112 auto &view = contenders.get<by_stakecontenderid>();
113 auto it = view.find(contenderId);
114 if (it == view.end()) {
115 return false;
116 }
117
118 return contenders.modify(it, [&](StakeContenderCacheEntry &entry) {
120 });
121}
122
124 auto &view = contenders.get<by_stakecontenderid>();
125 auto it = view.find(contenderId);
126 if (it == view.end()) {
127 return false;
128 }
129
130 return contenders.modify(it, [&](StakeContenderCacheEntry &entry) {
133 });
134}
135
137 BlockHash &prevblockhashout) const {
138 auto &view = contenders.get<by_stakecontenderid>();
139 auto it = view.find(contenderId);
140 if (it == view.end()) {
141 return -1;
142 }
143
144 prevblockhashout = it->prevblockhash;
145
146 // Contender is accepted
147 if (it->isAccepted()) {
148 return 0;
149 }
150
151 // If the contender matches a manual winner, it is accepted.
152 auto &manualWinnersView = manualWinners.get<by_prevblockhash>();
153 auto manualWinnerIt = manualWinnersView.find(it->prevblockhash);
154 if (manualWinnerIt != manualWinners.end()) {
155 for (auto &payoutScript : manualWinnerIt->payoutScripts) {
156 if (payoutScript == it->payoutScriptPubkey) {
157 return 0;
158 }
159 }
160 }
161
162 // Contender is rejected
163 return 1;
164}
165
167 const BlockHash &prevblockhash, size_t maxPollable,
168 std::vector<StakeContenderId> &pollableContenders) const {
169 std::vector<const StakeContenderCacheEntry *> rankedContenders;
170 auto &view = contenders.get<by_prevblockhash>();
171 auto [begin, end] = view.equal_range(prevblockhash);
172 for (auto it = begin; it != end; it++) {
173 rankedContenders.push_back(&(*it));
174 }
175
176 // First sort all contenders with accepted contenders first
177 std::sort(rankedContenders.begin(), rankedContenders.end(),
178 [](const StakeContenderCacheEntry *left,
179 const StakeContenderCacheEntry *right) {
180 if (left->isAccepted() != right->isAccepted()) {
181 // Accepted contenders sort first
182 return left->isAccepted();
183 }
184
185 double leftRank = left->computeRewardRank();
186 double rightRank = right->computeRewardRank();
187 const StakeContenderId &leftContenderId =
188 left->getStakeContenderId();
189 const StakeContenderId &rightContenderId =
190 right->getStakeContenderId();
191 return RewardRankComparator()(leftContenderId, leftRank,
192 left->proofid, rightContenderId,
193 rightRank, right->proofid);
194 });
195
196 // Sort again, only by reward rank, and only up to the max number of
197 // pollable contenders.
198 size_t numPollable = std::min(rankedContenders.size(), maxPollable);
199 std::sort(rankedContenders.begin(), rankedContenders.begin() + numPollable,
200 [](const StakeContenderCacheEntry *left,
201 const StakeContenderCacheEntry *right) {
202 double leftRank = left->computeRewardRank();
203 double rightRank = right->computeRewardRank();
204 const StakeContenderId &leftContenderId =
205 left->getStakeContenderId();
206 const StakeContenderId &rightContenderId =
207 right->getStakeContenderId();
208 return RewardRankComparator()(leftContenderId, leftRank,
209 left->proofid, rightContenderId,
210 rightRank, right->proofid);
211 });
212
213 // Only return up to the maximum number of contenders
214 pollableContenders.clear();
215 pollableContenders.reserve(numPollable);
216 for (size_t i = 0; i < numPollable; i++) {
217 pollableContenders.push_back(
218 rankedContenders[i]->getStakeContenderId());
219 }
220
221 return pollableContenders.size();
222}
223
224bool StakeContenderCache::getWinners(const BlockHash &prevblockhash,
225 std::vector<CScript> &payouts) const {
226 // Winners determined by avalanche are sorted by reward rank
227 std::vector<const StakeContenderCacheEntry *> rankedWinners;
228 auto &view = contenders.get<by_prevblockhash>();
229 auto [begin, end] = view.equal_range(prevblockhash);
230 for (auto it = begin; it != end; it++) {
231 if (it->isInWinnerSet()) {
232 rankedWinners.push_back(&(*it));
233 }
234 }
235
236 std::sort(rankedWinners.begin(), rankedWinners.end(),
237 [](const StakeContenderCacheEntry *left,
238 const StakeContenderCacheEntry *right) {
239 return left->computeRewardRank() < right->computeRewardRank();
240 });
241
242 payouts.clear();
243
244 // Add manual winners first, preserving order
245 auto &manualWinnersView = manualWinners.get<by_prevblockhash>();
246 auto manualWinnerIt = manualWinnersView.find(prevblockhash);
247 if (manualWinnerIt != manualWinners.end()) {
248 payouts.reserve(manualWinnerIt->payoutScripts.size() +
249 rankedWinners.size());
250
251 payouts.insert(payouts.begin(), manualWinnerIt->payoutScripts.begin(),
252 manualWinnerIt->payoutScripts.end());
253 } else {
254 payouts.reserve(rankedWinners.size());
255 }
256
257 // Add ranked winners, preserving reward rank order
258 for (const auto &rankedWinner : rankedWinners) {
259 payouts.push_back(rankedWinner->payoutScriptPubkey);
260 }
261
262 return payouts.size() > 0;
263}
264
265} // 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:146
int nHeight
height of the entry in the chain. The genesis block has height 0
Definition: blockindex.h:38
bool isDangling(const ProofId &proofid) const
bool isRemoteProof(const ProofId &proofid) const
bool isBoundToPeer(const ProofId &proofid) const
bool invalidate(const StakeContenderId &contenderId)
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.
int getVoteStatus(const StakeContenderId &contenderId, BlockHash &prevblockhashout) const
Get contender acceptance state for avalanche voting.
bool finalize(const StakeContenderId &contenderId)
void promoteToBlock(const CBlockIndex *activeTip, PeerManager &pm)
Promote cache entries to a the active chain tip.
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 ...