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 0 : CAmount ParsePaymentAmount(const std::string& strAmount)
23 : {
24 0 : CAmount nAmount = 0;
25 0 : if (strAmount.empty()) {
26 0 : throw std::runtime_error(strprintf("%s -- Amount is empty", __func__));
27 : }
28 0 : 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 0 : size_t pos = strAmount.find_first_not_of("0123456789.");
37 0 : if (pos != std::string::npos) {
38 0 : throw std::runtime_error(strprintf("%s -- Amount string contains invalid character", __func__));
39 : }
40 :
41 0 : pos = strAmount.find('.');
42 0 : 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 0 : 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 0 : 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 0 : 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 0 : 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 0 : CSuperblock::CSuperblock(const CGovernanceObject& govObj, uint256& nHash) :
76 0 : nGovObjHash(nHash),
77 0 : nBlockHeight(0),
78 0 : nStatus(SeenObjectStatus::Unknown),
79 0 : vecPayments()
80 0 : {
81 0 : LogPrint(BCLog::GOBJECT, "CSuperblock -- Constructor govobj: %s, nObjectType = %d\n", govObj.GetDataAsPlainString(),
82 : std23::to_underlying(govObj.GetObjectType()));
83 :
84 0 : if (govObj.GetObjectType() != GovernanceObject::TRIGGER) {
85 0 : throw std::runtime_error("CSuperblock: Governance Object not a trigger");
86 : }
87 :
88 0 : UniValue obj = govObj.GetJSONObject();
89 :
90 0 : 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 0 : nBlockHeight = obj["event_block_height"].getInt<int>();
96 :
97 : // NEXT WE GET THE PAYMENT INFORMATION AND RECONSTRUCT THE PAYMENT VECTOR
98 0 : std::string strAddresses = obj["payment_addresses"].get_str();
99 0 : std::string strAmounts = obj["payment_amounts"].get_str();
100 0 : std::string strProposalHashes = obj["proposal_hashes"].get_str();
101 0 : ParsePaymentSchedule(strAddresses, strAmounts, strProposalHashes);
102 :
103 0 : LogPrint(BCLog::GOBJECT, "CSuperblock -- nBlockHeight = %d, strAddresses = %s, strAmounts = %s, vecPayments.size() = %d\n",
104 : nBlockHeight, strAddresses, strAmounts, vecPayments.size());
105 0 : }
106 :
107 2 : CSuperblock::CSuperblock(int nBlockHeight, std::vector<CGovernancePayment> vecPayments) : nBlockHeight(nBlockHeight), vecPayments(std::move(vecPayments))
108 1 : {
109 1 : nStatus = SeenObjectStatus::Valid; //TODO: Investigate this
110 1 : nGovObjHash = GetHash();
111 2 : }
112 :
113 : /**
114 : * Is Valid Superblock Height
115 : *
116 : * - See if a block at this height can be a superblock
117 : */
118 :
119 28050 : bool CSuperblock::IsValidBlockHeight(int nBlockHeight)
120 : {
121 : // SUPERBLOCKS CAN HAPPEN ONLY after hardfork and only ONCE PER CYCLE
122 32487 : return nBlockHeight >= Params().GetConsensus().nSuperblockStartBlock &&
123 4437 : ((nBlockHeight % Params().GetConsensus().nSuperblockCycle) == 0);
124 : }
125 :
126 0 : void CSuperblock::GetNearestSuperblocksHeights(int nBlockHeight, int& nLastSuperblockRet, int& nNextSuperblockRet)
127 : {
128 0 : const Consensus::Params& consensusParams = Params().GetConsensus();
129 0 : int nSuperblockStartBlock = consensusParams.nSuperblockStartBlock;
130 0 : int nSuperblockCycle = consensusParams.nSuperblockCycle;
131 :
132 : // Get first superblock
133 0 : int nFirstSuperblockOffset = (nSuperblockCycle - nSuperblockStartBlock % nSuperblockCycle) % nSuperblockCycle;
134 0 : int nFirstSuperblock = nSuperblockStartBlock + nFirstSuperblockOffset;
135 :
136 0 : if (nBlockHeight < nFirstSuperblock) {
137 0 : nLastSuperblockRet = 0;
138 0 : nNextSuperblockRet = nFirstSuperblock;
139 0 : } else {
140 0 : nLastSuperblockRet = nBlockHeight - nBlockHeight % nSuperblockCycle;
141 0 : nNextSuperblockRet = nLastSuperblockRet + nSuperblockCycle;
142 : }
143 0 : }
144 :
145 1766 : CAmount CSuperblock::GetPaymentsLimit(const CChain& active_chain, int nBlockHeight)
146 : {
147 1766 : const Consensus::Params& consensusParams = Params().GetConsensus();
148 :
149 1766 : if (!IsValidBlockHeight(nBlockHeight)) {
150 1677 : return 0;
151 : }
152 :
153 89 : const bool fV20Active{nBlockHeight >= consensusParams.V20Height};
154 :
155 : // min subsidy for high diff networks and vice versa
156 89 : 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 89 : CAmount nSuperblockPartOfSubsidy = GetSuperblockSubsidyInner(nBits, nBlockHeight - 1, consensusParams, fV20Active);
159 89 : CAmount nPaymentsLimit = nSuperblockPartOfSubsidy * consensusParams.nSuperblockCycle;
160 89 : LogPrint(BCLog::GOBJECT, "CSuperblock::GetPaymentsLimit -- Valid superblock height %d, payments max %lld\n", nBlockHeight, nPaymentsLimit);
161 :
162 89 : return nPaymentsLimit;
163 1766 : }
164 :
165 0 : 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 0 : const auto vecPaymentAddresses = SplitString(strPaymentAddresses, "|");
170 0 : const auto vecPaymentAmounts = SplitString(strPaymentAmounts, "|");
171 0 : const auto vecProposalHashes = SplitString(strProposalHashes, "|");
172 :
173 : // IF THESE DON'T MATCH, SOMETHING IS WRONG
174 :
175 0 : 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 0 : 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 0 : for (int i = 0; i < (int)vecPaymentAddresses.size(); i++) {
194 0 : CTxDestination dest = DecodeDestination(vecPaymentAddresses[i]);
195 0 : 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 0 : CAmount nAmount = ParsePaymentAmount(vecPaymentAmounts[i]);
202 :
203 0 : uint256 proposalHash;
204 0 : 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 0 : 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 0 : CGovernancePayment payment(dest, nAmount, proposalHash);
215 0 : if (payment.IsValid()) {
216 0 : vecPayments.push_back(payment);
217 0 : } 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 0 : }
225 0 : }
226 :
227 6 : bool CSuperblock::GetPayment(int nPaymentIndex, CGovernancePayment& paymentRet)
228 : {
229 6 : if ((nPaymentIndex < 0) || (nPaymentIndex >= (int)vecPayments.size())) {
230 0 : return false;
231 : }
232 :
233 6 : paymentRet = vecPayments[nPaymentIndex];
234 6 : return true;
235 6 : }
236 :
237 3 : CAmount CSuperblock::GetPaymentsTotalAmount()
238 : {
239 9 : 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 3 : 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 3 : 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 3 : int nOutputs = txNew.vout.size();
263 3 : int nPayments = CountPayments();
264 3 : int nMinerAndMasternodePayments = nOutputs - nPayments;
265 :
266 3 : 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 3 : 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 3 : CAmount nPaymentsTotalAmount = GetPaymentsTotalAmount();
282 3 : CAmount nPaymentsLimit = GetPaymentsLimit(active_chain, nBlockHeight);
283 3 : 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 3 : CAmount nBlockValue = txNew.GetValueOut();
290 3 : 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 3 : int nVoutIndex = -1;
296 8 : for (int i = 0; i < nPayments; i++) {
297 6 : CGovernancePayment payment;
298 6 : 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 6 : 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 6 : const int nVoutStart = is_v24 ? nVoutIndex + 1 : std::max(nVoutIndex, 0);
314 9 : for (int j = nVoutStart; j < nOutputs; j++) {
315 : // Find superblock payment
316 13 : fPaymentMatch = ((payment.script == txNew.vout[j].scriptPubKey) &&
317 5 : (payment.nAmount == txNew.vout[j].nValue));
318 :
319 8 : if (fPaymentMatch) {
320 5 : nVoutIndex = j;
321 5 : break;
322 : }
323 3 : }
324 :
325 6 : 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 6 : }
335 :
336 2 : return true;
337 3 : }
338 :
339 0 : 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 0 : switch (nStatus) {
346 : case SeenObjectStatus::Executed:
347 0 : nExpirationBlocks = Params().GetConsensus().nSuperblockCycle;
348 0 : break;
349 : case SeenObjectStatus::Valid:
350 0 : nExpirationBlocks = std::min(576, Params().GetConsensus().nSuperblockCycle);
351 0 : break;
352 : default:
353 0 : nExpirationBlocks = std::min(24, Params().GetConsensus().nSuperblockCycle);
354 0 : break;
355 : }
356 :
357 0 : int nExpirationBlock = nBlockHeight + nExpirationBlocks;
358 :
359 0 : LogPrint(BCLog::GOBJECT, "CSuperblock::IsExpired -- nBlockHeight = %d, nExpirationBlock = %d\n", nBlockHeight, nExpirationBlock);
360 :
361 0 : if (heightToTest > nExpirationBlock) {
362 0 : LogPrint(BCLog::GOBJECT, "CSuperblock::IsExpired -- Outdated trigger found\n");
363 0 : return true;
364 : }
365 :
366 0 : if (Params().NetworkIDString() != CBaseChainParams::MAIN) {
367 : // NOTE: this can happen on testnet/devnets due to reorgs, should never happen on mainnet
368 0 : 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 0 : }
373 :
374 0 : return false;
375 0 : }
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 0 : 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 0 : std::string str_addresses = Join(vecPayments, "|", [&](const auto& payment) {
393 0 : CTxDestination dest;
394 0 : ExtractDestination(payment.script, dest);
395 0 : return EncodeDestination(dest);
396 : });
397 0 : std::string str_amounts = Join(vecPayments, "|", [&](const auto& payment) {
398 0 : return ValueFromAmount(payment.nAmount).write();
399 0 : });
400 0 : std::string str_hashes = Join(vecPayments, "|", [&](const auto& payment) { return payment.proposalHash.ToString(); });
401 :
402 0 : std::stringstream ss;
403 0 : ss << "{";
404 0 : ss << "\"event_block_height\": " << nBlockHeight << ", ";
405 0 : ss << "\"payment_addresses\": \"" << str_addresses << "\", ";
406 0 : ss << "\"payment_amounts\": \"" << str_amounts << "\", ";
407 0 : ss << "\"proposal_hashes\": \"" << str_hashes << "\", ";
408 0 : ss << "\"type\":" << 2;
409 0 : ss << "}";
410 :
411 0 : return HexStr(ss.str());
412 0 : }
413 :
414 4 : CGovernancePayment::CGovernancePayment(const CTxDestination& destIn, CAmount nAmountIn, const uint256& proposalHash) :
415 2 : fValid(false),
416 2 : script(),
417 2 : nAmount(0),
418 2 : proposalHash(proposalHash)
419 2 : {
420 : try {
421 2 : script = GetScriptForDestination(destIn);
422 2 : nAmount = nAmountIn;
423 2 : fValid = true;
424 2 : } 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 4 : }
432 :
433 : namespace governance {
434 :
435 0 : bool SuperblockManager::AddTrigger(std::shared_ptr<CGovernanceObject> obj, int cachedHeight)
436 : {
437 0 : AssertLockNotHeld(cs_sb);
438 0 : if (!obj) return false;
439 :
440 0 : uint256 nHash = obj->GetHash();
441 :
442 0 : LOCK(cs_sb);
443 0 : 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 0 : CSuperblock_sptr pSuperblock;
450 : try {
451 0 : pSuperblock = std::make_shared<CSuperblock>(*obj, nHash);
452 0 : } 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 0 : pSuperblock->SetStatus(SeenObjectStatus::Valid);
461 0 : m_triggers.emplace(nHash, TriggerEntry{pSuperblock, std::move(obj)});
462 :
463 0 : return !pSuperblock->IsExpired(cachedHeight);
464 0 : }
465 :
466 0 : void SuperblockManager::RemoveTrigger(const uint256& hash)
467 : {
468 0 : LOCK(cs_sb);
469 0 : m_triggers.erase(hash);
470 0 : }
471 :
472 0 : void SuperblockManager::Clean(int cachedHeight)
473 : {
474 0 : AssertLockNotHeld(cs_sb);
475 0 : LOCK(cs_sb);
476 :
477 0 : LogPrint(BCLog::GOBJECT, "SuperblockManager::%s -- m_triggers.size() = %d\n", __func__, m_triggers.size());
478 :
479 0 : for (auto it = m_triggers.begin(); it != m_triggers.end();) {
480 0 : bool remove = false;
481 0 : const auto& [sb, obj] = it->second;
482 :
483 0 : if (!sb) {
484 0 : LogPrint(BCLog::GOBJECT, "SuperblockManager::%s -- nullptr superblock\n", __func__);
485 0 : remove = true;
486 0 : } else {
487 0 : 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 0 : LogPrint(BCLog::GOBJECT, "SuperblockManager::%s -- superblock status = %d\n", __func__,
492 : std23::to_underlying(sb->GetStatus()));
493 0 : 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 0 : LogPrint(BCLog::GOBJECT, "SuperblockManager::%s -- Valid trigger found\n", __func__);
502 0 : if (sb->IsExpired(cachedHeight)) {
503 0 : if (obj) obj->SetExpired();
504 0 : remove = true;
505 0 : }
506 0 : break;
507 : default:
508 0 : break;
509 : }
510 : }
511 0 : LogPrint(BCLog::GOBJECT, "SuperblockManager::%s -- %smarked for removal\n", __func__, remove ? "" : "NOT ");
512 :
513 0 : if (remove) {
514 0 : std::string strDataAsPlainString = "nullptr";
515 0 : if (obj) {
516 0 : strDataAsPlainString = obj->GetDataAsPlainString();
517 0 : obj->PrepareDeletion(GetTime<std::chrono::seconds>().count());
518 0 : }
519 0 : LogPrint(BCLog::GOBJECT, "SuperblockManager::%s -- Removing trigger object %s\n", __func__,
520 : strDataAsPlainString);
521 0 : it = m_triggers.erase(it);
522 0 : } else {
523 0 : ++it;
524 : }
525 : }
526 0 : }
527 :
528 178 : void SuperblockManager::Clear()
529 : {
530 178 : LOCK(cs_sb);
531 178 : m_triggers.clear();
532 178 : m_loaded = false;
533 178 : }
534 :
535 0 : std::vector<CSuperblock_sptr> SuperblockManager::GetActiveTriggers() const
536 : {
537 0 : LOCK(cs_sb);
538 0 : std::vector<CSuperblock_sptr> vecResults;
539 0 : vecResults.reserve(m_triggers.size());
540 0 : for (const auto& [_, entry] : m_triggers) {
541 0 : if (entry.sb && entry.obj) {
542 0 : vecResults.push_back(entry.sb);
543 0 : }
544 : }
545 0 : return vecResults;
546 0 : }
547 :
548 0 : bool SuperblockManager::GetBestSuperblock(const CDeterministicMNList& tip_mn_list, CSuperblock_sptr& sbRet,
549 : int nBlockHeight) const
550 : {
551 0 : LOCK(cs_sb);
552 0 : return GetBestSuperblockInternal(tip_mn_list, sbRet, nBlockHeight);
553 0 : }
554 :
555 0 : bool SuperblockManager::GetBestSuperblockInternal(const CDeterministicMNList& tip_mn_list, CSuperblock_sptr& sbRet,
556 : int nBlockHeight) const
557 : {
558 0 : AssertLockHeld(cs_sb);
559 0 : if (!CSuperblock::IsValidBlockHeight(nBlockHeight)) {
560 0 : return false;
561 : }
562 :
563 0 : int nYesCount = 0;
564 0 : for (const auto& [_, entry] : m_triggers) {
565 0 : if (!entry.sb || !entry.obj || nBlockHeight != entry.sb->GetBlockHeight()) {
566 0 : continue;
567 : }
568 0 : int nTempYesCount = entry.obj->GetAbsoluteYesCount(tip_mn_list, VOTE_SIGNAL_FUNDING);
569 0 : if (nTempYesCount > nYesCount) {
570 0 : nYesCount = nTempYesCount;
571 0 : sbRet = entry.sb;
572 0 : }
573 : }
574 0 : return nYesCount > 0;
575 0 : }
576 :
577 24517 : bool SuperblockManager::IsSuperblockTriggered(const CDeterministicMNList& tip_mn_list, int nBlockHeight)
578 : {
579 24517 : LogPrint(BCLog::GOBJECT, "IsSuperblockTriggered -- Start nBlockHeight = %d\n", nBlockHeight);
580 24517 : if (!CSuperblock::IsValidBlockHeight(nBlockHeight)) {
581 24474 : return false;
582 : }
583 :
584 43 : LOCK(cs_sb);
585 :
586 43 : LogPrint(BCLog::GOBJECT, "IsSuperblockTriggered -- m_triggers.size() = %d\n", m_triggers.size());
587 :
588 43 : for (const auto& [_, entry] : m_triggers) {
589 0 : if (!entry.sb) {
590 0 : LogPrintf("IsSuperblockTriggered -- Non-superblock found, continuing\n");
591 0 : continue;
592 : }
593 0 : if (!entry.obj) {
594 0 : LogPrintf("IsSuperblockTriggered -- pObj == nullptr, continuing\n");
595 0 : continue;
596 : }
597 :
598 0 : LogPrint(BCLog::GOBJECT, "IsSuperblockTriggered -- data = %s\n", entry.obj->GetDataAsPlainString());
599 :
600 0 : if (nBlockHeight != entry.sb->GetBlockHeight()) {
601 0 : LogPrint(BCLog::GOBJECT, /* Continued */
602 : "IsSuperblockTriggered -- block height doesn't match nBlockHeight = %d, blockStart = %d, "
603 : "continuing\n",
604 : nBlockHeight, entry.sb->GetBlockHeight());
605 0 : continue;
606 : }
607 :
608 0 : entry.obj->UpdateSentinelVariables(tip_mn_list);
609 :
610 0 : if (entry.obj->IsSetCachedFunding()) {
611 0 : LogPrint(BCLog::GOBJECT, "IsSuperblockTriggered -- fCacheFunding = true, returning true\n");
612 0 : return true;
613 : } else {
614 0 : LogPrint(BCLog::GOBJECT, "IsSuperblockTriggered -- fCacheFunding = false, continuing\n");
615 : }
616 : }
617 :
618 43 : return false;
619 24517 : }
620 :
621 0 : 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 0 : LOCK(cs_sb);
625 0 : CSuperblock_sptr pSuperblock;
626 0 : if (GetBestSuperblockInternal(tip_mn_list, pSuperblock, nBlockHeight)) {
627 0 : return pSuperblock->IsValid(active_chain, txNew, nBlockHeight, blockReward, is_v24);
628 : }
629 0 : return false;
630 0 : }
631 :
632 0 : bool SuperblockManager::GetSuperblockPayments(const CDeterministicMNList& tip_mn_list, int nBlockHeight,
633 : std::vector<CTxOut>& voutSuperblockRet) const
634 : {
635 0 : LOCK(cs_sb);
636 :
637 0 : CSuperblock_sptr pSuperblock;
638 0 : 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 0 : 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 0 : for (int i = 0; i < pSuperblock->CountPayments(); i++) {
650 0 : CGovernancePayment payment;
651 0 : if (pSuperblock->GetPayment(i, payment)) {
652 0 : voutSuperblockRet.emplace_back(payment.nAmount, payment.script);
653 :
654 0 : CTxDestination dest;
655 0 : ExtractDestination(payment.script, dest);
656 :
657 0 : 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 0 : } else {
660 0 : LogPrint(BCLog::GOBJECT, "GetSuperblockPayments -- Payment not found\n");
661 : }
662 0 : }
663 :
664 0 : return true;
665 0 : }
666 :
667 0 : void SuperblockManager::ExecuteBestSuperblock(const CDeterministicMNList& tip_mn_list, int nBlockHeight)
668 : {
669 0 : LOCK(cs_sb);
670 0 : CSuperblock_sptr pSuperblock;
671 0 : 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 0 : pSuperblock->SetExecuted();
675 0 : }
676 0 : }
677 :
678 : } // namespace governance
|