Line data Source code
1 : // Copyright (c) 2014-2025 The Dash Core developers
2 : // Distributed under the MIT/X11 software license, see the accompanying
3 : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 :
5 : #include <governance/superblock.h>
6 :
7 : #include <chainparams.h>
8 : #include <core_io.h>
9 : #include <evo/deterministicmns.h>
10 : #include <governance/object.h>
11 : #include <governance/vote.h>
12 : #include <key_io.h>
13 : #include <logging.h>
14 : #include <primitives/transaction.h>
15 : #include <util/std23.h>
16 : #include <util/strencodings.h>
17 : #include <util/time.h>
18 : #include <validation.h>
19 :
20 : #include <univalue.h>
21 :
22 952 : CAmount ParsePaymentAmount(const std::string& strAmount)
23 : {
24 952 : CAmount nAmount = 0;
25 952 : if (strAmount.empty()) {
26 0 : throw std::runtime_error(strprintf("%s -- Amount is empty", __func__));
27 : }
28 952 : if (strAmount.size() > 20) {
29 : // String is much too long, the functions below impose stricter
30 : // requirements
31 0 : throw std::runtime_error(strprintf("%s -- Amount string too long", __func__));
32 : }
33 : // Make sure the string makes sense as an amount
34 : // Note: No spaces allowed
35 : // Also note: No scientific notation
36 952 : size_t pos = strAmount.find_first_not_of("0123456789.");
37 952 : if (pos != std::string::npos) {
38 0 : throw std::runtime_error(strprintf("%s -- Amount string contains invalid character", __func__));
39 : }
40 :
41 952 : pos = strAmount.find('.');
42 952 : if (pos == 0) {
43 : // JSON doesn't allow values to start with a decimal point
44 0 : throw std::runtime_error(strprintf("%s -- Invalid amount string, leading decimal point not allowed", __func__));
45 : }
46 :
47 : // Make sure there's no more than 1 decimal point
48 952 : if ((pos != std::string::npos) && (strAmount.find('.', pos + 1) != std::string::npos)) {
49 0 : throw std::runtime_error(strprintf("%s -- Invalid amount string, too many decimal points", __func__));
50 : }
51 :
52 : // Note this code is taken from AmountFromValue in rpcserver.cpp
53 : // which is used for parsing the amounts in createrawtransaction.
54 952 : if (!ParseFixedPoint(strAmount, 8, &nAmount)) {
55 0 : nAmount = 0;
56 0 : throw std::runtime_error(strprintf("%s -- ParseFixedPoint failed for string \"%s\"", __func__, strAmount));
57 : }
58 952 : if (!MoneyRange(nAmount)) {
59 0 : nAmount = 0;
60 0 : throw std::runtime_error(strprintf("%s -- Invalid amount string, value outside of valid money range", __func__));
61 : }
62 :
63 952 : return nAmount;
64 0 : }
65 :
66 : CSuperblock::
67 0 : CSuperblock() :
68 0 : nGovObjHash(),
69 0 : nBlockHeight(0),
70 0 : nStatus(SeenObjectStatus::Unknown),
71 0 : vecPayments()
72 0 : {
73 0 : }
74 :
75 376 : CSuperblock::CSuperblock(const CGovernanceObject& govObj, uint256& nHash) :
76 188 : nGovObjHash(nHash),
77 188 : nBlockHeight(0),
78 188 : nStatus(SeenObjectStatus::Unknown),
79 188 : vecPayments()
80 188 : {
81 188 : LogPrint(BCLog::GOBJECT, "CSuperblock -- Constructor govobj: %s, nObjectType = %d\n", govObj.GetDataAsPlainString(),
82 : std23::to_underlying(govObj.GetObjectType()));
83 :
84 188 : if (govObj.GetObjectType() != GovernanceObject::TRIGGER) {
85 0 : throw std::runtime_error("CSuperblock: Governance Object not a trigger");
86 : }
87 :
88 188 : UniValue obj = govObj.GetJSONObject();
89 :
90 188 : if (obj["type"].getInt<int>() != std23::to_underlying(GovernanceObject::TRIGGER)) {
91 0 : throw std::runtime_error("CSuperblock: invalid data type");
92 : }
93 :
94 : // FIRST WE GET THE START HEIGHT, THE BLOCK HEIGHT AT WHICH THE PAYMENT SHALL OCCUR
95 188 : nBlockHeight = obj["event_block_height"].getInt<int>();
96 :
97 : // NEXT WE GET THE PAYMENT INFORMATION AND RECONSTRUCT THE PAYMENT VECTOR
98 188 : std::string strAddresses = obj["payment_addresses"].get_str();
99 188 : std::string strAmounts = obj["payment_amounts"].get_str();
100 188 : std::string strProposalHashes = obj["proposal_hashes"].get_str();
101 188 : ParsePaymentSchedule(strAddresses, strAmounts, strProposalHashes);
102 :
103 188 : LogPrint(BCLog::GOBJECT, "CSuperblock -- nBlockHeight = %d, strAddresses = %s, strAmounts = %s, vecPayments.size() = %d\n",
104 : nBlockHeight, strAddresses, strAmounts, vecPayments.size());
105 376 : }
106 :
107 410 : CSuperblock::CSuperblock(int nBlockHeight, std::vector<CGovernancePayment> vecPayments) : nBlockHeight(nBlockHeight), vecPayments(std::move(vecPayments))
108 205 : {
109 205 : nStatus = SeenObjectStatus::Valid; //TODO: Investigate this
110 205 : nGovObjHash = GetHash();
111 410 : }
112 :
113 : /**
114 : * Is Valid Superblock Height
115 : *
116 : * - See if a block at this height can be a superblock
117 : */
118 :
119 450265 : bool CSuperblock::IsValidBlockHeight(int nBlockHeight)
120 : {
121 : // SUPERBLOCKS CAN HAPPEN ONLY after hardfork and only ONCE PER CYCLE
122 567219 : return nBlockHeight >= Params().GetConsensus().nSuperblockStartBlock &&
123 116954 : ((nBlockHeight % Params().GetConsensus().nSuperblockCycle) == 0);
124 : }
125 :
126 208 : void CSuperblock::GetNearestSuperblocksHeights(int nBlockHeight, int& nLastSuperblockRet, int& nNextSuperblockRet)
127 : {
128 208 : const Consensus::Params& consensusParams = Params().GetConsensus();
129 208 : int nSuperblockStartBlock = consensusParams.nSuperblockStartBlock;
130 208 : int nSuperblockCycle = consensusParams.nSuperblockCycle;
131 :
132 : // Get first superblock
133 208 : int nFirstSuperblockOffset = (nSuperblockCycle - nSuperblockStartBlock % nSuperblockCycle) % nSuperblockCycle;
134 208 : int nFirstSuperblock = nSuperblockStartBlock + nFirstSuperblockOffset;
135 :
136 208 : if (nBlockHeight < nFirstSuperblock) {
137 0 : nLastSuperblockRet = 0;
138 0 : nNextSuperblockRet = nFirstSuperblock;
139 0 : } else {
140 208 : nLastSuperblockRet = nBlockHeight - nBlockHeight % nSuperblockCycle;
141 208 : nNextSuperblockRet = nLastSuperblockRet + nSuperblockCycle;
142 : }
143 208 : }
144 :
145 27823 : CAmount CSuperblock::GetPaymentsLimit(const CChain& active_chain, int nBlockHeight)
146 : {
147 27823 : const Consensus::Params& consensusParams = Params().GetConsensus();
148 :
149 27823 : if (!IsValidBlockHeight(nBlockHeight)) {
150 25785 : return 0;
151 : }
152 :
153 2038 : const bool fV20Active{nBlockHeight >= consensusParams.V20Height};
154 :
155 : // min subsidy for high diff networks and vice versa
156 2038 : int nBits = consensusParams.fPowAllowMinDifficultyBlocks ? UintToArith256(consensusParams.powLimit).GetCompact() : 1;
157 : // some part of all blocks issued during the cycle goes to superblock, see GetBlockSubsidy
158 2038 : CAmount nSuperblockPartOfSubsidy = GetSuperblockSubsidyInner(nBits, nBlockHeight - 1, consensusParams, fV20Active);
159 2038 : CAmount nPaymentsLimit = nSuperblockPartOfSubsidy * consensusParams.nSuperblockCycle;
160 2038 : LogPrint(BCLog::GOBJECT, "CSuperblock::GetPaymentsLimit -- Valid superblock height %d, payments max %lld\n", nBlockHeight, nPaymentsLimit);
161 :
162 2038 : return nPaymentsLimit;
163 27823 : }
164 :
165 188 : void CSuperblock::ParsePaymentSchedule(const std::string& strPaymentAddresses, const std::string& strPaymentAmounts, const std::string& strProposalHashes)
166 : {
167 : // SPLIT UP ADDR/AMOUNT STRINGS AND PUT IN VECTORS
168 :
169 188 : const auto vecPaymentAddresses = SplitString(strPaymentAddresses, "|");
170 188 : const auto vecPaymentAmounts = SplitString(strPaymentAmounts, "|");
171 188 : const auto vecProposalHashes = SplitString(strProposalHashes, "|");
172 :
173 : // IF THESE DON'T MATCH, SOMETHING IS WRONG
174 :
175 188 : if (vecPaymentAddresses.size() != vecPaymentAmounts.size() || vecPaymentAddresses.size() != vecProposalHashes.size()) {
176 0 : std::string msg{strprintf("CSuperblock::%s -- Mismatched payments, amounts and proposalHashes", __func__)};
177 0 : LogPrintf("%s\n", msg);
178 0 : throw std::runtime_error(msg);
179 0 : }
180 :
181 188 : if (vecPaymentAddresses.empty()) {
182 0 : std::string msg{strprintf("CSuperblock::%s -- Error no payments", __func__)};
183 0 : LogPrintf("%s\n", msg);
184 0 : throw std::runtime_error(msg);
185 0 : }
186 :
187 : // LOOP THROUGH THE ADDRESSES/AMOUNTS AND CREATE PAYMENTS
188 : /*
189 : ADDRESSES = [ADDR1|2|3|4|5|6]
190 : AMOUNTS = [AMOUNT1|2|3|4|5|6]
191 : */
192 :
193 564 : for (int i = 0; i < (int)vecPaymentAddresses.size(); i++) {
194 376 : CTxDestination dest = DecodeDestination(vecPaymentAddresses[i]);
195 376 : if (!IsValidDestination(dest)) {
196 0 : std::string msg{strprintf("CSuperblock::%s -- Invalid Dash Address: %s", __func__, vecPaymentAddresses[i])};
197 0 : LogPrintf("%s\n", msg);
198 0 : throw std::runtime_error(msg);
199 0 : }
200 :
201 376 : CAmount nAmount = ParsePaymentAmount(vecPaymentAmounts[i]);
202 :
203 376 : uint256 proposalHash;
204 376 : if (!ParseHashStr(vecProposalHashes[i], proposalHash)) {
205 0 : std::string msg{strprintf("CSuperblock::%s -- Invalid proposal hash: %s", __func__, vecProposalHashes[i])};
206 0 : LogPrintf("%s\n", msg);
207 0 : throw std::runtime_error(msg);
208 0 : }
209 :
210 376 : LogPrint(BCLog::GOBJECT, /* Continued */
211 : "CSuperblock::%s -- i = %d, amount string = %s, nAmount = %lld, proposalHash = %s\n", __func__,
212 : i, vecPaymentAmounts[i], nAmount, proposalHash.ToString());
213 :
214 376 : CGovernancePayment payment(dest, nAmount, proposalHash);
215 376 : if (payment.IsValid()) {
216 376 : vecPayments.push_back(payment);
217 376 : } else {
218 0 : vecPayments.clear();
219 0 : std::string msg{strprintf("CSuperblock::%s -- Invalid payment found: address = %s, amount = %d", __func__,
220 0 : EncodeDestination(dest), nAmount)};
221 0 : LogPrintf("%s\n", msg);
222 0 : throw std::runtime_error(msg);
223 0 : }
224 376 : }
225 188 : }
226 :
227 500 : bool CSuperblock::GetPayment(int nPaymentIndex, CGovernancePayment& paymentRet)
228 : {
229 500 : if ((nPaymentIndex < 0) || (nPaymentIndex >= (int)vecPayments.size())) {
230 0 : return false;
231 : }
232 :
233 500 : paymentRet = vecPayments[nPaymentIndex];
234 500 : return true;
235 500 : }
236 :
237 233 : CAmount CSuperblock::GetPaymentsTotalAmount()
238 : {
239 699 : return std23::ranges::fold_left(vecPayments, CAmount{0}, [](CAmount s, const auto& p) { return s + p.nAmount; });
240 : }
241 :
242 : /**
243 : * Is Transaction Valid
244 : *
245 : * - Does this transaction match the superblock?
246 : */
247 :
248 233 : bool CSuperblock::IsValid(const CChain& active_chain, const CTransaction& txNew, int nBlockHeight, CAmount blockReward, bool is_v24)
249 : {
250 : // TODO : LOCK(cs);
251 : // No reason for a lock here now since this method only accesses data
252 : // internal to *this and since CSuperblock's are accessed only through
253 : // shared pointers there's no way our object can get deleted while this
254 : // code is running.
255 233 : if (!IsValidBlockHeight(nBlockHeight)) {
256 0 : LogPrintf("CSuperblock::IsValid -- ERROR: Block invalid, incorrect block height\n");
257 0 : return false;
258 : }
259 :
260 : // CONFIGURE SUPERBLOCK OUTPUTS
261 :
262 233 : int nOutputs = txNew.vout.size();
263 233 : int nPayments = CountPayments();
264 233 : int nMinerAndMasternodePayments = nOutputs - nPayments;
265 :
266 233 : LogPrint(BCLog::GOBJECT, "CSuperblock::IsValid -- nOutputs = %d, nPayments = %d, hash = %s\n", nOutputs, nPayments,
267 : nGovObjHash.ToString());
268 :
269 : // We require an exact match (including order) between the expected
270 : // superblock payments and the payments actually in the block.
271 :
272 233 : if (nMinerAndMasternodePayments < 0) {
273 : // This means the block cannot have all the superblock payments
274 : // so it is not valid.
275 : // TODO: could that be that we just hit coinbase size limit?
276 0 : LogPrintf("CSuperblock::IsValid -- ERROR: Block invalid, too few superblock payments\n");
277 0 : return false;
278 : }
279 :
280 : // payments should not exceed limit
281 233 : CAmount nPaymentsTotalAmount = GetPaymentsTotalAmount();
282 233 : CAmount nPaymentsLimit = GetPaymentsLimit(active_chain, nBlockHeight);
283 233 : if (nPaymentsTotalAmount > nPaymentsLimit) {
284 0 : LogPrintf("CSuperblock::IsValid -- ERROR: Block invalid, payments limit exceeded: payments %lld, limit %lld\n", nPaymentsTotalAmount, nPaymentsLimit);
285 0 : return false;
286 : }
287 :
288 : // miner and masternodes should not get more than they would usually get
289 233 : CAmount nBlockValue = txNew.GetValueOut();
290 233 : if (nBlockValue > blockReward + nPaymentsTotalAmount) {
291 0 : LogPrintf("CSuperblock::IsValid -- ERROR: Block invalid, block value limit exceeded: block %lld, limit %lld\n", nBlockValue, blockReward + nPaymentsTotalAmount);
292 0 : return false;
293 : }
294 :
295 233 : int nVoutIndex = -1;
296 698 : for (int i = 0; i < nPayments; i++) {
297 466 : CGovernancePayment payment;
298 466 : if (!GetPayment(i, payment)) {
299 : // This shouldn't happen so log a warning
300 0 : LogPrintf("CSuperblock::IsValid -- WARNING: Failed to find payment: %d of %d total payments\n", i, nPayments);
301 0 : continue;
302 : }
303 :
304 466 : bool fPaymentMatch = false;
305 :
306 : // From V24 on, start past the previously matched output so each expected
307 : // payment consumes a distinct vout (two adjacent payments with the same
308 : // script and amount must match two separate outputs, not the same one
309 : // twice). Before V24 the scan restarted at the previously matched index
310 : // (inclusive), which is kept for backwards compatibility.
311 : // TODO: After V24 is hardened/finalized so historical duplicate-output
312 : // blocks cannot be encountered, simplify this path to the V24 scan only.
313 466 : const int nVoutStart = is_v24 ? nVoutIndex + 1 : std::max(nVoutIndex, 0);
314 1619 : for (int j = nVoutStart; j < nOutputs; j++) {
315 : // Find superblock payment
316 2083 : fPaymentMatch = ((payment.script == txNew.vout[j].scriptPubKey) &&
317 465 : (payment.nAmount == txNew.vout[j].nValue));
318 :
319 1618 : if (fPaymentMatch) {
320 465 : nVoutIndex = j;
321 465 : break;
322 : }
323 1153 : }
324 :
325 466 : if (!fPaymentMatch) {
326 : // Superblock payment not found!
327 :
328 1 : CTxDestination dest;
329 1 : ExtractDestination(payment.script, dest);
330 1 : LogPrintf("CSuperblock::IsValid -- ERROR: Block invalid: %d payment %d to %s not found\n", i, payment.nAmount, EncodeDestination(dest));
331 :
332 1 : return false;
333 : }
334 466 : }
335 :
336 232 : return true;
337 233 : }
338 :
339 5228 : bool CSuperblock::IsExpired(int heightToTest) const
340 : {
341 : int nExpirationBlocks;
342 : // Executed triggers are kept for another superblock cycle (approximately 1 month for mainnet).
343 : // Other valid triggers are kept for ~1 day only (for mainnet, but no longer than a superblock cycle for other networks).
344 : // Everything else is pruned after ~1h (for mainnet, but no longer than a superblock cycle for other networks).
345 5228 : switch (nStatus) {
346 : case SeenObjectStatus::Executed:
347 1946 : nExpirationBlocks = Params().GetConsensus().nSuperblockCycle;
348 1946 : break;
349 : case SeenObjectStatus::Valid:
350 3282 : nExpirationBlocks = std::min(576, Params().GetConsensus().nSuperblockCycle);
351 3282 : break;
352 : default:
353 0 : nExpirationBlocks = std::min(24, Params().GetConsensus().nSuperblockCycle);
354 0 : break;
355 : }
356 :
357 5228 : int nExpirationBlock = nBlockHeight + nExpirationBlocks;
358 :
359 5228 : LogPrint(BCLog::GOBJECT, "CSuperblock::IsExpired -- nBlockHeight = %d, nExpirationBlock = %d\n", nBlockHeight, nExpirationBlock);
360 :
361 5228 : if (heightToTest > nExpirationBlock) {
362 104 : LogPrint(BCLog::GOBJECT, "CSuperblock::IsExpired -- Outdated trigger found\n");
363 104 : return true;
364 : }
365 :
366 5124 : if (Params().NetworkIDString() != CBaseChainParams::MAIN) {
367 : // NOTE: this can happen on testnet/devnets due to reorgs, should never happen on mainnet
368 5124 : if (heightToTest + Params().GetConsensus().nSuperblockCycle * 2 < nBlockHeight) {
369 0 : LogPrint(BCLog::GOBJECT, "CSuperblock::IsExpired -- Trigger is too far into the future\n");
370 0 : return true;
371 : }
372 5124 : }
373 :
374 5124 : return false;
375 5228 : }
376 :
377 0 : std::vector<uint256> CSuperblock::GetProposalHashes() const
378 : {
379 0 : std::vector<uint256> res;
380 :
381 0 : for (const auto& payment : vecPayments) {
382 0 : res.push_back(payment.proposalHash);
383 : }
384 :
385 0 : return res;
386 0 : }
387 :
388 204 : std::string CSuperblock::GetHexStrData() const
389 : {
390 : // {\"event_block_height\": 879720, \"payment_addresses\": \"yd5KMREs3GLMe6mTJYr3YrH1juwNwrFCfB\", \"payment_amounts\": \"5.00000000\", \"proposal_hashes\": \"485817fddbcab6c55c9a6856dabc8b19ed79548bda8c01712daebc9f74f287f4\", \"type\": 2}
391 :
392 612 : std::string str_addresses = Join(vecPayments, "|", [&](const auto& payment) {
393 408 : CTxDestination dest;
394 408 : ExtractDestination(payment.script, dest);
395 408 : return EncodeDestination(dest);
396 : });
397 612 : std::string str_amounts = Join(vecPayments, "|", [&](const auto& payment) {
398 408 : return ValueFromAmount(payment.nAmount).write();
399 0 : });
400 612 : std::string str_hashes = Join(vecPayments, "|", [&](const auto& payment) { return payment.proposalHash.ToString(); });
401 :
402 204 : std::stringstream ss;
403 204 : ss << "{";
404 204 : ss << "\"event_block_height\": " << nBlockHeight << ", ";
405 204 : ss << "\"payment_addresses\": \"" << str_addresses << "\", ";
406 204 : ss << "\"payment_amounts\": \"" << str_amounts << "\", ";
407 204 : ss << "\"proposal_hashes\": \"" << str_hashes << "\", ";
408 204 : ss << "\"type\":" << 2;
409 204 : ss << "}";
410 :
411 204 : return HexStr(ss.str());
412 204 : }
413 :
414 1908 : CGovernancePayment::CGovernancePayment(const CTxDestination& destIn, CAmount nAmountIn, const uint256& proposalHash) :
415 954 : fValid(false),
416 954 : script(),
417 954 : nAmount(0),
418 954 : proposalHash(proposalHash)
419 954 : {
420 : try {
421 954 : script = GetScriptForDestination(destIn);
422 954 : nAmount = nAmountIn;
423 954 : fValid = true;
424 954 : } catch (std::exception& e) {
425 0 : LogPrintf("CGovernancePayment Payment not valid: destIn = %s, nAmountIn = %d, what = %s\n",
426 : EncodeDestination(destIn), nAmountIn, e.what());
427 0 : } catch (...) {
428 0 : LogPrintf("CGovernancePayment Payment not valid: destIn = %s, nAmountIn = %d\n",
429 : EncodeDestination(destIn), nAmountIn);
430 0 : }
431 1908 : }
432 :
433 : namespace governance {
434 :
435 188 : bool SuperblockManager::AddTrigger(std::shared_ptr<CGovernanceObject> obj, int cachedHeight)
436 : {
437 188 : AssertLockNotHeld(cs_sb);
438 188 : if (!obj) return false;
439 :
440 188 : uint256 nHash = obj->GetHash();
441 :
442 188 : LOCK(cs_sb);
443 188 : if (m_triggers.count(nHash)) {
444 0 : LogPrint(BCLog::GOBJECT, "SuperblockManager::%s -- Already have hash, nHash = %s, size = %s\n", __func__,
445 : nHash.GetHex(), m_triggers.size());
446 0 : return false;
447 : }
448 :
449 188 : CSuperblock_sptr pSuperblock;
450 : try {
451 188 : pSuperblock = std::make_shared<CSuperblock>(*obj, nHash);
452 188 : } catch (std::exception& e) {
453 0 : LogPrintf("SuperblockManager::%s -- Error creating superblock: %s\n", __func__, e.what());
454 0 : return false;
455 0 : } catch (...) {
456 0 : LogPrintf("SuperblockManager::%s -- Unknown Error creating superblock\n", __func__);
457 0 : return false;
458 0 : }
459 :
460 188 : pSuperblock->SetStatus(SeenObjectStatus::Valid);
461 188 : m_triggers.emplace(nHash, TriggerEntry{pSuperblock, std::move(obj)});
462 :
463 188 : return !pSuperblock->IsExpired(cachedHeight);
464 188 : }
465 :
466 20 : void SuperblockManager::RemoveTrigger(const uint256& hash)
467 : {
468 20 : LOCK(cs_sb);
469 20 : m_triggers.erase(hash);
470 20 : }
471 :
472 99040 : void SuperblockManager::Clean(int cachedHeight)
473 : {
474 99040 : AssertLockNotHeld(cs_sb);
475 99040 : LOCK(cs_sb);
476 :
477 99040 : LogPrint(BCLog::GOBJECT, "SuperblockManager::%s -- m_triggers.size() = %d\n", __func__, m_triggers.size());
478 :
479 104080 : for (auto it = m_triggers.begin(); it != m_triggers.end();) {
480 5040 : bool remove = false;
481 20368 : const auto& [sb, obj] = it->second;
482 :
483 5040 : if (!sb) {
484 0 : LogPrint(BCLog::GOBJECT, "SuperblockManager::%s -- nullptr superblock\n", __func__);
485 0 : remove = true;
486 0 : } else {
487 5040 : if (!obj || obj->GetObjectType() != GovernanceObject::TRIGGER) {
488 0 : LogPrint(BCLog::GOBJECT, "SuperblockManager::%s -- Unknown or non-trigger superblock\n", __func__);
489 0 : sb->SetStatus(SeenObjectStatus::ErrorInvalid);
490 0 : }
491 5040 : LogPrint(BCLog::GOBJECT, "SuperblockManager::%s -- superblock status = %d\n", __func__,
492 : std23::to_underlying(sb->GetStatus()));
493 5040 : switch (sb->GetStatus()) {
494 : case SeenObjectStatus::ErrorInvalid:
495 : case SeenObjectStatus::Unknown:
496 0 : LogPrint(BCLog::GOBJECT, "SuperblockManager::%s -- Unknown or invalid trigger found\n", __func__);
497 0 : remove = true;
498 0 : break;
499 : case SeenObjectStatus::Valid:
500 : case SeenObjectStatus::Executed:
501 5040 : LogPrint(BCLog::GOBJECT, "SuperblockManager::%s -- Valid trigger found\n", __func__);
502 5040 : if (sb->IsExpired(cachedHeight)) {
503 104 : if (obj) obj->SetExpired();
504 104 : remove = true;
505 104 : }
506 5040 : break;
507 : default:
508 0 : break;
509 : }
510 : }
511 5040 : LogPrint(BCLog::GOBJECT, "SuperblockManager::%s -- %smarked for removal\n", __func__, remove ? "" : "NOT ");
512 :
513 5040 : if (remove) {
514 104 : std::string strDataAsPlainString = "nullptr";
515 104 : if (obj) {
516 104 : strDataAsPlainString = obj->GetDataAsPlainString();
517 104 : obj->PrepareDeletion(GetTime<std::chrono::seconds>().count());
518 104 : }
519 104 : LogPrint(BCLog::GOBJECT, "SuperblockManager::%s -- Removing trigger object %s\n", __func__,
520 : strDataAsPlainString);
521 104 : it = m_triggers.erase(it);
522 104 : } else {
523 4936 : ++it;
524 : }
525 : }
526 99040 : }
527 :
528 3035 : void SuperblockManager::Clear()
529 : {
530 3035 : LOCK(cs_sb);
531 3035 : m_triggers.clear();
532 3035 : m_loaded = false;
533 3035 : }
534 :
535 71623 : std::vector<CSuperblock_sptr> SuperblockManager::GetActiveTriggers() const
536 : {
537 71623 : LOCK(cs_sb);
538 71623 : std::vector<CSuperblock_sptr> vecResults;
539 71623 : vecResults.reserve(m_triggers.size());
540 75459 : for (const auto& [_, entry] : m_triggers) {
541 3836 : if (entry.sb && entry.obj) {
542 3836 : vecResults.push_back(entry.sb);
543 3836 : }
544 : }
545 71623 : return vecResults;
546 71623 : }
547 :
548 81273 : bool SuperblockManager::GetBestSuperblock(const CDeterministicMNList& tip_mn_list, CSuperblock_sptr& sbRet,
549 : int nBlockHeight) const
550 : {
551 81273 : LOCK(cs_sb);
552 81273 : return GetBestSuperblockInternal(tip_mn_list, sbRet, nBlockHeight);
553 81273 : }
554 :
555 287834 : bool SuperblockManager::GetBestSuperblockInternal(const CDeterministicMNList& tip_mn_list, CSuperblock_sptr& sbRet,
556 : int nBlockHeight) const
557 : {
558 287834 : AssertLockHeld(cs_sb);
559 287834 : if (!CSuperblock::IsValidBlockHeight(nBlockHeight)) {
560 285682 : return false;
561 : }
562 :
563 2152 : int nYesCount = 0;
564 3420 : for (const auto& [_, entry] : m_triggers) {
565 1268 : if (!entry.sb || !entry.obj || nBlockHeight != entry.sb->GetBlockHeight()) {
566 530 : continue;
567 : }
568 738 : int nTempYesCount = entry.obj->GetAbsoluteYesCount(tip_mn_list, VOTE_SIGNAL_FUNDING);
569 738 : if (nTempYesCount > nYesCount) {
570 426 : nYesCount = nTempYesCount;
571 426 : sbRet = entry.sb;
572 426 : }
573 : }
574 2152 : return nYesCount > 0;
575 287834 : }
576 :
577 107224 : bool SuperblockManager::IsSuperblockTriggered(const CDeterministicMNList& tip_mn_list, int nBlockHeight)
578 : {
579 107224 : LogPrint(BCLog::GOBJECT, "IsSuperblockTriggered -- Start nBlockHeight = %d\n", nBlockHeight);
580 107224 : if (!CSuperblock::IsValidBlockHeight(nBlockHeight)) {
581 105126 : return false;
582 : }
583 :
584 2098 : LOCK(cs_sb);
585 :
586 2098 : LogPrint(BCLog::GOBJECT, "IsSuperblockTriggered -- m_triggers.size() = %d\n", m_triggers.size());
587 :
588 3767 : for (const auto& [_, entry] : m_triggers) {
589 479 : if (!entry.sb) {
590 0 : LogPrintf("IsSuperblockTriggered -- Non-superblock found, continuing\n");
591 0 : continue;
592 : }
593 479 : if (!entry.obj) {
594 0 : LogPrintf("IsSuperblockTriggered -- pObj == nullptr, continuing\n");
595 0 : continue;
596 : }
597 :
598 479 : LogPrint(BCLog::GOBJECT, "IsSuperblockTriggered -- data = %s\n", entry.obj->GetDataAsPlainString());
599 :
600 958 : if (nBlockHeight != entry.sb->GetBlockHeight()) {
601 187 : LogPrint(BCLog::GOBJECT, /* Continued */
602 : "IsSuperblockTriggered -- block height doesn't match nBlockHeight = %d, blockStart = %d, "
603 : "continuing\n",
604 : nBlockHeight, entry.sb->GetBlockHeight());
605 187 : continue;
606 : }
607 :
608 292 : entry.obj->UpdateSentinelVariables(tip_mn_list);
609 :
610 292 : if (entry.obj->IsSetCachedFunding()) {
611 247 : LogPrint(BCLog::GOBJECT, "IsSuperblockTriggered -- fCacheFunding = true, returning true\n");
612 247 : return true;
613 : } else {
614 45 : LogPrint(BCLog::GOBJECT, "IsSuperblockTriggered -- fCacheFunding = false, continuing\n");
615 : }
616 : }
617 :
618 1851 : return false;
619 107224 : }
620 :
621 230 : bool SuperblockManager::IsValidSuperblock(const CChain& active_chain, const CDeterministicMNList& tip_mn_list,
622 : const CTransaction& txNew, int nBlockHeight, CAmount blockReward, bool is_v24) const
623 : {
624 230 : LOCK(cs_sb);
625 230 : CSuperblock_sptr pSuperblock;
626 230 : if (GetBestSuperblockInternal(tip_mn_list, pSuperblock, nBlockHeight)) {
627 230 : return pSuperblock->IsValid(active_chain, txNew, nBlockHeight, blockReward, is_v24);
628 : }
629 0 : return false;
630 230 : }
631 :
632 17 : bool SuperblockManager::GetSuperblockPayments(const CDeterministicMNList& tip_mn_list, int nBlockHeight,
633 : std::vector<CTxOut>& voutSuperblockRet) const
634 : {
635 17 : LOCK(cs_sb);
636 :
637 17 : CSuperblock_sptr pSuperblock;
638 17 : if (!GetBestSuperblockInternal(tip_mn_list, pSuperblock, nBlockHeight)) {
639 0 : LogPrint(BCLog::GOBJECT, "GetSuperblockPayments -- Can't find superblock for height %d\n", nBlockHeight);
640 0 : return false;
641 : }
642 :
643 17 : voutSuperblockRet.clear();
644 :
645 : // TODO: How many payments can we add before things blow up?
646 : // Consider at least following limits:
647 : // - max coinbase tx size
648 : // - max "budget" available
649 51 : for (int i = 0; i < pSuperblock->CountPayments(); i++) {
650 34 : CGovernancePayment payment;
651 34 : if (pSuperblock->GetPayment(i, payment)) {
652 34 : voutSuperblockRet.emplace_back(payment.nAmount, payment.script);
653 :
654 34 : CTxDestination dest;
655 34 : ExtractDestination(payment.script, dest);
656 :
657 34 : LogPrint(BCLog::GOBJECT, "GetSuperblockPayments -- NEW Superblock: output %d (addr %s, amount %d.%08d)\n",
658 : i, EncodeDestination(dest), payment.nAmount / COIN, payment.nAmount % COIN);
659 34 : } else {
660 0 : LogPrint(BCLog::GOBJECT, "GetSuperblockPayments -- Payment not found\n");
661 : }
662 34 : }
663 :
664 17 : return true;
665 17 : }
666 :
667 206314 : void SuperblockManager::ExecuteBestSuperblock(const CDeterministicMNList& tip_mn_list, int nBlockHeight)
668 : {
669 206314 : LOCK(cs_sb);
670 206314 : CSuperblock_sptr pSuperblock;
671 206314 : if (GetBestSuperblockInternal(tip_mn_list, pSuperblock, nBlockHeight)) {
672 : // All checks are done in CSuperblock::IsValid via IsBlockValueValid and IsBlockPayeeValid,
673 : // tip wouldn't be updated if anything was wrong. Mark this trigger as executed.
674 98 : pSuperblock->SetExecuted();
675 98 : }
676 206314 : }
677 :
678 : } // namespace governance
|