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/object.h>
6 :
7 : #include <bls/bls.h>
8 : #include <evo/deterministicmns.h>
9 : #include <masternode/meta.h>
10 : #include <masternode/sync.h>
11 :
12 : #include <chainparams.h>
13 : #include <index/txindex.h>
14 : #include <key_io.h>
15 : #include <logging.h>
16 : #include <node/interface_ui.h>
17 : #include <timedata.h>
18 : #include <tinyformat.h>
19 : #include <util/std23.h>
20 : #include <util/strencodings.h>
21 : #include <util/time.h>
22 : #include <validation.h>
23 : #include <validationinterface.h>
24 :
25 : #include <univalue.h>
26 :
27 : #include <algorithm>
28 : #include <iostream>
29 : #include <string>
30 :
31 : namespace {
32 :
33 : constexpr size_t MAX_DATA_SIZE = 512;
34 : constexpr size_t MAX_NAME_SIZE = 40;
35 :
36 18354 : bool GetDataValue(const UniValue& objJSON, const std::string& strKey, std::string& strValueRet, std::string& strErrorMessages)
37 : {
38 : try {
39 18354 : strValueRet = objJSON[strKey].get_str();
40 18354 : return true;
41 0 : } catch (std::exception& e) {
42 0 : strErrorMessages += std::string(e.what()) + std::string(";");
43 0 : } catch (...) {
44 0 : strErrorMessages += "Unknown exception;";
45 0 : }
46 0 : return false;
47 18354 : }
48 :
49 18852 : bool GetDataValue(const UniValue& objJSON, const std::string& strKey, int64_t& nValueRet, std::string& strErrorMessages)
50 : {
51 : try {
52 18852 : const UniValue& uValue = objJSON[strKey];
53 18852 : if (uValue.getType() == UniValue::VNUM) {
54 18850 : nValueRet = uValue.getInt<int64_t>();
55 18850 : return true;
56 : }
57 2 : } catch (std::exception& e) {
58 0 : strErrorMessages += std::string(e.what()) + std::string(";");
59 0 : } catch (...) {
60 0 : strErrorMessages += "Unknown exception;";
61 0 : }
62 2 : return false;
63 18852 : }
64 :
65 6036 : bool GetDataValue(const UniValue& objJSON, const std::string& strKey, double& dValueRet, std::string& strErrorMessages)
66 : {
67 : try {
68 6036 : const UniValue& uValue = objJSON[strKey];
69 6036 : if (uValue.getType() == UniValue::VNUM) {
70 6035 : dValueRet = uValue.get_real();
71 6035 : return true;
72 : }
73 1 : } catch (std::exception& e) {
74 0 : strErrorMessages += std::string(e.what()) + std::string(";");
75 0 : } catch (...) {
76 0 : strErrorMessages += "Unknown exception;";
77 0 : }
78 1 : return false;
79 6036 : }
80 :
81 6289 : bool ValidateType(const UniValue& objJSON, std::string& strErrorMessages)
82 : {
83 : int64_t nType;
84 6289 : if (!GetDataValue(objJSON, "type", nType, strErrorMessages)) {
85 0 : strErrorMessages += "type field not found;";
86 0 : return false;
87 : }
88 :
89 6289 : if (nType != std23::to_underlying(GovernanceObject::PROPOSAL)) {
90 1 : strErrorMessages += strprintf("type is not %d;", std23::to_underlying(GovernanceObject::PROPOSAL));
91 1 : return false;
92 : }
93 :
94 6288 : return true;
95 6289 : }
96 :
97 6288 : bool ValidateName(const UniValue& objJSON, std::string& strErrorMessages)
98 : {
99 6288 : std::string strName;
100 6288 : if (!GetDataValue(objJSON, "name", strName, strErrorMessages)) {
101 0 : strErrorMessages += "name field not found;";
102 0 : return false;
103 : }
104 :
105 6288 : if (strName.size() > MAX_NAME_SIZE) {
106 0 : strErrorMessages += strprintf("name exceeds %lu characters;", MAX_NAME_SIZE);
107 0 : return false;
108 : }
109 :
110 6288 : if (strName.empty()) {
111 1 : strErrorMessages += "name cannot be empty;";
112 1 : return false;
113 : }
114 :
115 : static constexpr std::string_view strAllowedChars{"-_abcdefghijklmnopqrstuvwxyz0123456789"};
116 :
117 6287 : strName = ToLower(strName);
118 :
119 6287 : if (strName.find_first_not_of(strAllowedChars) != std::string::npos) {
120 5 : strErrorMessages += "name contains invalid characters;";
121 5 : return false;
122 : }
123 :
124 6282 : return true;
125 6288 : }
126 :
127 6282 : bool ValidateStartEndEpoch(const UniValue& objJSON, bool fCheckExpiration, std::string& strErrorMessages)
128 : {
129 6282 : int64_t nStartEpoch = 0;
130 6282 : int64_t nEndEpoch = 0;
131 :
132 6282 : if (!GetDataValue(objJSON, "start_epoch", nStartEpoch, strErrorMessages)) {
133 1 : strErrorMessages += "start_epoch field not found;";
134 1 : return false;
135 : }
136 :
137 6281 : if (!GetDataValue(objJSON, "end_epoch", nEndEpoch, strErrorMessages)) {
138 1 : strErrorMessages += "end_epoch field not found;";
139 1 : return false;
140 : }
141 :
142 6280 : if (nEndEpoch <= nStartEpoch) {
143 0 : strErrorMessages += "end_epoch <= start_epoch;";
144 0 : return false;
145 : }
146 :
147 6280 : if (fCheckExpiration && nEndEpoch <= GetAdjustedTime()) {
148 244 : strErrorMessages += "expired;";
149 244 : return false;
150 : }
151 :
152 6036 : return true;
153 6282 : }
154 :
155 6036 : bool ValidatePaymentAmount(const UniValue& objJSON, std::string& strErrorMessages)
156 : {
157 6036 : double dValue = 0.0;
158 :
159 6036 : if (!GetDataValue(objJSON, "payment_amount", dValue, strErrorMessages)) {
160 1 : strErrorMessages += "payment_amount field not found;";
161 1 : return false;
162 : }
163 :
164 6035 : if (dValue <= 0.0) {
165 0 : strErrorMessages += "payment_amount is negative;";
166 0 : return false;
167 : }
168 :
169 : // TODO: Should check for an amount which exceeds the budget but this is
170 : // currently difficult because start and end epochs are defined in terms of
171 : // clock time instead of block height.
172 :
173 6035 : return true;
174 6036 : }
175 :
176 6035 : bool ValidatePaymentAddress(const UniValue& objJSON, bool fAllowScript, std::string& strErrorMessages)
177 : {
178 6035 : std::string strPaymentAddress;
179 :
180 6035 : if (!GetDataValue(objJSON, "payment_address", strPaymentAddress, strErrorMessages)) {
181 0 : strErrorMessages += "payment_address field not found;";
182 0 : return false;
183 : }
184 :
185 6035 : if (std::find_if(strPaymentAddress.begin(), strPaymentAddress.end(), IsSpace) != strPaymentAddress.end()) {
186 2 : strErrorMessages += "payment_address can't have whitespaces;";
187 2 : return false;
188 : }
189 :
190 6033 : CTxDestination dest = DecodeDestination(strPaymentAddress);
191 6033 : if (!IsValidDestination(dest)) {
192 1 : strErrorMessages += "payment_address is invalid;";
193 1 : return false;
194 : }
195 :
196 6032 : const ScriptHash *scriptID = std::get_if<ScriptHash>(&dest);
197 6032 : if (!fAllowScript && scriptID) {
198 1 : strErrorMessages += "script addresses are not supported;";
199 1 : return false;
200 : }
201 :
202 6031 : return true;
203 6035 : }
204 :
205 : /*
206 : The purpose of this function is to replicate the behavior of the
207 : Python urlparse function used by sentinel (urlparse.py). This function
208 : should return false whenever urlparse raises an exception and true
209 : otherwise.
210 : */
211 6028 : bool CheckURL(const std::string& strURLIn)
212 : {
213 6028 : std::string strRest(strURLIn);
214 6028 : std::string::size_type nPos = strRest.find(':');
215 :
216 6028 : if (nPos != std::string::npos) {
217 6028 : if (nPos < strRest.size()) {
218 6028 : strRest = strRest.substr(nPos + 1);
219 6028 : } else {
220 0 : strRest = "";
221 : }
222 6028 : }
223 :
224 : // Process netloc
225 6028 : if ((strRest.size() > 2) && (strRest.substr(0, 2) == "//")) {
226 : static constexpr std::string_view strNetlocDelimiters{"/?#"};
227 :
228 6028 : strRest = strRest.substr(2);
229 :
230 6028 : std::string::size_type nPos2 = strRest.find_first_of(strNetlocDelimiters);
231 :
232 6028 : std::string strNetloc = strRest.substr(0, nPos2);
233 :
234 6028 : if ((strNetloc.find('[') != std::string::npos) && (strNetloc.find(']') == std::string::npos)) {
235 1 : return false;
236 : }
237 :
238 6027 : if ((strNetloc.find(']') != std::string::npos) && (strNetloc.find('[') == std::string::npos)) {
239 1 : return false;
240 : }
241 6028 : }
242 :
243 6026 : return true;
244 6028 : }
245 :
246 6031 : bool ValidateURL(const UniValue& objJSON, std::string& strErrorMessages)
247 : {
248 6031 : std::string strURL;
249 6031 : if (!GetDataValue(objJSON, "url", strURL, strErrorMessages)) {
250 0 : strErrorMessages += "url field not found;";
251 0 : return false;
252 : }
253 :
254 6031 : if (std::find_if(strURL.begin(), strURL.end(), IsSpace) != strURL.end()) {
255 3 : strErrorMessages += "url can't have whitespaces;";
256 3 : return false;
257 : }
258 :
259 6028 : if (strURL.size() < 4U) {
260 0 : strErrorMessages += "url too short;";
261 0 : return false;
262 : }
263 :
264 6028 : if (!CheckURL(strURL)) {
265 2 : strErrorMessages += "url invalid;";
266 2 : return false;
267 : }
268 :
269 6026 : return true;
270 6031 : }
271 :
272 6333 : bool ParseProposalJSON(const std::string& strHexData, UniValue& objJSONOut, std::string& strErrorMessages)
273 : {
274 6333 : if (strHexData.empty()) return false;
275 :
276 6333 : std::vector<unsigned char> v = ParseHex(strHexData);
277 6333 : if (v.size() > MAX_DATA_SIZE) {
278 0 : strErrorMessages = strprintf("data exceeds %lu characters;", MAX_DATA_SIZE);
279 0 : return false;
280 : }
281 :
282 6333 : const std::string strJSONData(v.begin(), v.end());
283 6333 : if (strJSONData.empty()) return false;
284 :
285 : try {
286 6333 : UniValue obj(UniValue::VOBJ);
287 6333 : obj.read(strJSONData);
288 6333 : if (!obj.isObject()) {
289 44 : throw std::runtime_error("Proposal must be a JSON object");
290 : }
291 6289 : objJSONOut = obj;
292 6289 : return true;
293 6333 : } catch (std::exception& e) {
294 44 : strErrorMessages += std::string(e.what()) + std::string(";");
295 44 : } catch (...) {
296 0 : strErrorMessages += "Unknown exception;";
297 44 : }
298 44 : return false;
299 6377 : }
300 :
301 : } // namespace
302 :
303 1520 : std::ostream& operator<<(std::ostream& os, governance_exception_type_enum_t eType)
304 : {
305 1520 : switch (eType) {
306 : case GOVERNANCE_EXCEPTION_NONE:
307 1520 : os << "GOVERNANCE_EXCEPTION_NONE";
308 1520 : break;
309 : case GOVERNANCE_EXCEPTION_WARNING:
310 0 : os << "GOVERNANCE_EXCEPTION_WARNING";
311 0 : break;
312 : case GOVERNANCE_EXCEPTION_PERMANENT_ERROR:
313 0 : os << "GOVERNANCE_EXCEPTION_PERMANENT_ERROR";
314 0 : break;
315 : case GOVERNANCE_EXCEPTION_TEMPORARY_ERROR:
316 0 : os << "GOVERNANCE_EXCEPTION_TEMPORARY_ERROR";
317 0 : break;
318 : case GOVERNANCE_EXCEPTION_INTERNAL_ERROR:
319 0 : os << "GOVERNANCE_EXCEPTION_INTERNAL_ERROR";
320 0 : break;
321 : }
322 1520 : return os;
323 : }
324 :
325 3040 : CGovernanceException::CGovernanceException(const std::string& strMessageIn,
326 : governance_exception_type_enum_t eTypeIn,
327 : int nNodePenaltyIn) :
328 1520 : strMessage{strprintf("%s:%s", eTypeIn, strMessageIn)},
329 1520 : eType{eTypeIn},
330 1520 : nNodePenalty{nNodePenaltyIn}
331 3040 : {
332 3040 : }
333 :
334 889 : CGovernanceObject::CGovernanceObject()
335 248 : {
336 : // PARSE JSON DATA STORAGE (VCHDATA)
337 : LoadData();
338 248 : }
339 :
340 532 : CGovernanceObject::CGovernanceObject(const uint256& nHashParentIn, int nRevisionIn, int64_t nTimeIn,
341 : const uint256& nCollateralHashIn, const std::string& strDataHexIn) :
342 266 : cs(),
343 266 : m_obj{nHashParentIn, nRevisionIn, nTimeIn, nCollateralHashIn, strDataHexIn}
344 266 : {
345 : // PARSE JSON DATA STORAGE (VCHDATA)
346 : LoadData();
347 266 : }
348 :
349 124836 : CGovernanceObject::CGovernanceObject(const CGovernanceObject& other) :
350 62520 : cs(),
351 62520 : m_obj{other.m_obj},
352 62520 : nDeletionTime(other.nDeletionTime),
353 62520 : fCachedLocalValidity(other.fCachedLocalValidity),
354 62520 : strLocalValidityError(other.strLocalValidityError),
355 62520 : fCachedFunding(other.fCachedFunding),
356 62520 : fCachedValid(other.fCachedValid),
357 62520 : fCachedDelete(other.fCachedDelete),
358 62520 : fCachedEndorsed(other.fCachedEndorsed),
359 62520 : fDirtyCache(other.fDirtyCache),
360 62520 : fExpired(other.fExpired),
361 62520 : fUnparsable(other.fUnparsable),
362 62520 : mapCurrentMNVotes(other.mapCurrentMNVotes),
363 62520 : fileVotes(other.fileVotes)
364 62316 : {
365 124836 : }
366 :
367 1500 : bool CGovernanceObject::ProcessVote(CMasternodeMetaMan& mn_metaman, bool fRateChecksEnabled,
368 : const CDeterministicMNList& tip_mn_list, const CGovernanceVote& vote,
369 : CGovernanceException& exception)
370 : {
371 1500 : assert(mn_metaman.IsValid());
372 :
373 1500 : LOCK(cs);
374 :
375 : // do not process already known valid votes twice
376 1500 : if (fileVotes.HasVote(vote.GetHash())) {
377 : // nothing to do here, not an error
378 0 : std::string msg{strprintf("CGovernanceObject::%s -- Already known valid vote", __func__)};
379 0 : LogPrint(BCLog::GOBJECT, "%s\n", msg);
380 0 : exception = CGovernanceException(msg, GOVERNANCE_EXCEPTION_NONE);
381 0 : return false;
382 0 : }
383 :
384 1500 : auto dmn = tip_mn_list.GetMNByCollateral(vote.GetMasternodeOutpoint());
385 1500 : if (!dmn) {
386 0 : std::string msg{strprintf("CGovernanceObject::%s -- Masternode %s not found", __func__,
387 0 : vote.GetMasternodeOutpoint().ToStringShort())};
388 0 : exception = CGovernanceException(msg, GOVERNANCE_EXCEPTION_PERMANENT_ERROR, 20);
389 0 : return false;
390 0 : }
391 :
392 1500 : auto it = mapCurrentMNVotes.emplace(vote_m_t::value_type(vote.GetMasternodeOutpoint(), vote_rec_t())).first;
393 1500 : vote_rec_t& voteRecordRef = it->second;
394 1500 : vote_signal_enum_t eSignal = vote.GetSignal();
395 1500 : if (eSignal == VOTE_SIGNAL_NONE) {
396 0 : std::string msg{strprintf("CGovernanceObject::%s -- Vote signal: none", __func__)};
397 0 : LogPrint(BCLog::GOBJECT, "%s\n", msg);
398 0 : exception = CGovernanceException(msg, GOVERNANCE_EXCEPTION_WARNING);
399 0 : return false;
400 0 : }
401 1500 : if (eSignal < VOTE_SIGNAL_NONE || eSignal >= VOTE_SIGNAL_UNKNOWN) {
402 0 : std::string msg{strprintf("CGovernanceObject::%s -- Unsupported vote signal: %s", __func__,
403 0 : CGovernanceVoting::ConvertSignalToString(vote.GetSignal()))};
404 0 : LogPrintf("%s\n", msg);
405 0 : exception = CGovernanceException(msg, GOVERNANCE_EXCEPTION_PERMANENT_ERROR, 20);
406 0 : return false;
407 0 : }
408 1500 : auto it2 = voteRecordRef.mapInstances.emplace(vote_instance_m_t::value_type(int(eSignal), vote_instance_t())).first;
409 1500 : vote_instance_t& voteInstanceRef = it2->second;
410 :
411 : // Reject obsolete votes
412 1500 : if (vote.GetTimestamp() < voteInstanceRef.nCreationTime) {
413 0 : std::string msg{strprintf("CGovernanceObject::%s -- Obsolete vote", __func__)};
414 0 : LogPrint(BCLog::GOBJECT, "%s\n", msg);
415 0 : exception = CGovernanceException(msg, GOVERNANCE_EXCEPTION_NONE);
416 0 : return false;
417 1500 : } else if (vote.GetTimestamp() == voteInstanceRef.nCreationTime) {
418 : // Someone is doing something fishy, there can be no two votes from the same masternode
419 : // with the same timestamp for the same object and signal and yet different hash/outcome.
420 20 : std::string msg{strprintf("CGovernanceObject::%s -- Invalid vote, same timestamp for the different outcome", __func__)};
421 20 : if (vote.GetOutcome() < voteInstanceRef.eOutcome) {
422 : // This is an arbitrary comparison, we have to agree on some way
423 : // to pick the "winning" vote.
424 20 : msg += ", rejected";
425 20 : LogPrint(BCLog::GOBJECT, "%s\n", msg);
426 20 : exception = CGovernanceException(msg, GOVERNANCE_EXCEPTION_NONE);
427 20 : return false;
428 : }
429 0 : msg += ", accepted";
430 0 : LogPrint(BCLog::GOBJECT, "%s\n", msg);
431 20 : }
432 :
433 1480 : int64_t nNow = GetAdjustedTime();
434 1480 : int64_t nVoteTimeUpdate = voteInstanceRef.nTime;
435 1480 : if (fRateChecksEnabled) {
436 1480 : int64_t nTimeDelta = nNow - voteInstanceRef.nTime;
437 1480 : if (nTimeDelta < GOVERNANCE_UPDATE_MIN) {
438 0 : std::string msg{strprintf("CGovernanceObject::%s -- Masternode voting too often, MN outpoint = %s, "
439 : "governance object hash = %s, time delta = %d",
440 0 : __func__, vote.GetMasternodeOutpoint().ToStringShort(), GetHash().ToString(), nTimeDelta)};
441 0 : LogPrint(BCLog::GOBJECT, "%s\n", msg);
442 0 : exception = CGovernanceException(msg, GOVERNANCE_EXCEPTION_TEMPORARY_ERROR);
443 0 : return false;
444 0 : }
445 1480 : nVoteTimeUpdate = nNow;
446 1480 : }
447 :
448 1480 : bool onlyVotingKeyAllowed = m_obj.type == GovernanceObject::PROPOSAL && vote.GetSignal() == VOTE_SIGNAL_FUNDING;
449 :
450 : // Finally check that the vote is actually valid (done last because of cost of signature verification)
451 1480 : if (!vote.IsValid(tip_mn_list, onlyVotingKeyAllowed)) {
452 0 : std::string msg{strprintf("CGovernanceObject::%s -- Invalid vote, MN outpoint = %s, governance object hash = %s, "
453 : "vote hash = %s",
454 0 : __func__, vote.GetMasternodeOutpoint().ToStringShort(), GetHash().ToString(), vote.GetHash().ToString())};
455 0 : LogPrintf("%s\n", msg);
456 0 : exception = CGovernanceException(msg, GOVERNANCE_EXCEPTION_PERMANENT_ERROR, 20);
457 0 : return false;
458 0 : }
459 :
460 1480 : mn_metaman.AddGovernanceVote(dmn->proTxHash, vote.GetParentHash());
461 :
462 1480 : voteInstanceRef = vote_instance_t(vote.GetOutcome(), nVoteTimeUpdate, vote.GetTimestamp());
463 1480 : fileVotes.AddVote(vote);
464 1480 : fDirtyCache = true;
465 : // SEND NOTIFICATION TO SCRIPT/ZMQ
466 2960 : GetMainSignals().NotifyGovernanceVote(std::make_shared<CDeterministicMNList>(tip_mn_list),
467 1480 : std::make_shared<const CGovernanceVote>(vote), vote.GetHash().ToString());
468 1480 : uiInterface.NotifyGovernanceChanged();
469 1480 : return true;
470 1500 : }
471 :
472 0 : void CGovernanceObject::ClearMasternodeVotes(const CDeterministicMNList& tip_mn_list)
473 : {
474 0 : LOCK(cs);
475 :
476 0 : auto it = mapCurrentMNVotes.begin();
477 0 : while (it != mapCurrentMNVotes.end()) {
478 0 : if (!tip_mn_list.HasMNByCollateral(it->first)) {
479 0 : fileVotes.RemoveVotesFromMasternode(it->first);
480 0 : mapCurrentMNVotes.erase(it++);
481 0 : fDirtyCache = true;
482 0 : } else {
483 0 : ++it;
484 : }
485 : }
486 0 : }
487 :
488 0 : std::set<uint256> CGovernanceObject::RemoveInvalidVotes(const CDeterministicMNList& tip_mn_list, const COutPoint& mnOutpoint)
489 : {
490 0 : LOCK(cs);
491 :
492 0 : auto it = mapCurrentMNVotes.find(mnOutpoint);
493 0 : if (it == mapCurrentMNVotes.end()) {
494 : // don't even try as we don't have any votes from this MN
495 0 : return {};
496 : }
497 :
498 0 : auto removedVotes = fileVotes.RemoveInvalidVotes(tip_mn_list, mnOutpoint, m_obj.type == GovernanceObject::PROPOSAL);
499 0 : if (removedVotes.empty()) {
500 0 : return {};
501 : }
502 :
503 0 : auto nParentHash = GetHash();
504 0 : for (auto jt = it->second.mapInstances.begin(); jt != it->second.mapInstances.end(); ) {
505 0 : CGovernanceVote tmpVote(mnOutpoint, nParentHash, (vote_signal_enum_t)jt->first, jt->second.eOutcome);
506 0 : tmpVote.SetTime(jt->second.nCreationTime);
507 0 : if (removedVotes.count(tmpVote.GetHash())) {
508 0 : jt = it->second.mapInstances.erase(jt);
509 0 : } else {
510 0 : ++jt;
511 : }
512 0 : }
513 0 : if (it->second.mapInstances.empty()) {
514 0 : mapCurrentMNVotes.erase(it);
515 0 : }
516 :
517 0 : std::string removedStr;
518 0 : for (const auto& h : removedVotes) {
519 0 : removedStr += strprintf(" %s\n", h.ToString());
520 : }
521 0 : LogPrintf("CGovernanceObject::%s -- Removed %d invalid votes for %s from MN %s:\n%s", __func__, removedVotes.size(), nParentHash.ToString(), mnOutpoint.ToString(), removedStr); /* Continued */
522 0 : fDirtyCache = true;
523 :
524 0 : return removedVotes;
525 0 : }
526 :
527 43100 : uint256 CGovernanceObject::GetHash() const
528 : {
529 43100 : return m_obj.GetHash();
530 : }
531 :
532 1184 : uint256 CGovernanceObject::GetDataHash() const
533 : {
534 1184 : CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION);
535 1184 : ss << GetDataAsHexString();
536 :
537 1184 : return ss.GetHash();
538 0 : }
539 :
540 408 : uint256 CGovernanceObject::GetSignatureHash() const
541 : {
542 408 : return SerializeHash(*this);
543 : }
544 :
545 32 : void CGovernanceObject::SetMasternodeOutpoint(const COutPoint& outpoint)
546 : {
547 32 : m_obj.masternodeOutpoint = outpoint;
548 32 : }
549 :
550 32 : void CGovernanceObject::SetSignature(Span<const uint8_t> sig)
551 : {
552 32 : m_obj.vchSig.assign(sig.begin(), sig.end());
553 32 : }
554 :
555 376 : bool CGovernanceObject::CheckSignature(const CBLSPublicKey& pubKey) const
556 : {
557 376 : CBLSSignature sig;
558 376 : sig.SetBytes(m_obj.vchSig, false);
559 376 : if (!sig.VerifyInsecure(pubKey, GetSignatureHash(), false)) {
560 0 : LogPrintf("CGovernanceObject::CheckSignature -- VerifyInsecure() failed\n");
561 0 : return false;
562 : }
563 376 : return true;
564 376 : }
565 :
566 : /**
567 : Return the actual object from the vchData JSON structure.
568 :
569 : Returns an empty object on error.
570 : */
571 1030 : UniValue CGovernanceObject::GetJSONObject() const
572 : {
573 1030 : UniValue obj(UniValue::VOBJ);
574 1030 : if (m_obj.vchData.empty()) {
575 0 : return obj;
576 : }
577 :
578 1030 : UniValue objResult(UniValue::VOBJ);
579 1030 : GetData(objResult);
580 :
581 1030 : if (objResult.isObject()) {
582 1030 : obj = objResult;
583 1030 : } else {
584 0 : const std::vector<UniValue>& arr1 = objResult.getValues();
585 0 : const std::vector<UniValue>& arr2 = arr1.at(0).getValues();
586 0 : obj = arr2.at(1);
587 : }
588 :
589 1030 : return obj;
590 1030 : }
591 :
592 18048 : UniValue CGovernanceObject::GetStateJson(const ChainstateManager& chainman, const CDeterministicMNList& tip_mn_list, const std::string& local_valid_key) const
593 : {
594 18048 : UniValue ret(UniValue::VOBJ);
595 18048 : ret.pushKV("DataHex", GetDataAsHexString());
596 18048 : ret.pushKV("DataString", GetDataAsPlainString());
597 18048 : ret.pushKV("Hash", GetHash().ToString());
598 18048 : ret.pushKV("CollateralHash", GetCollateralHash().ToString());
599 18048 : ret.pushKV("ObjectType", std23::to_underlying(GetObjectType()));
600 18048 : ret.pushKV("CreationTime", GetCreationTime());
601 18048 : if (const COutPoint& outpoint = GetMasternodeOutpoint(); outpoint != COutPoint{}) {
602 17846 : ret.pushKV("SigningMasternode", outpoint.ToStringShort());
603 17846 : }
604 18048 : std::string strError;
605 36096 : ret.pushKV(local_valid_key, WITH_LOCK(::cs_main, return IsValidLocally(tip_mn_list, chainman, strError, /*fCheckCollateral=*/false)));
606 18048 : ret.pushKV("IsValidReason", strError.c_str());
607 18048 : ret.pushKV("fCachedValid", IsSetCachedValid());
608 18048 : ret.pushKV("fCachedFunding", IsSetCachedFunding());
609 18048 : ret.pushKV("fCachedDelete", IsSetCachedDelete());
610 18048 : ret.pushKV("fCachedEndorsed", IsSetCachedEndorsed());
611 18048 : return ret;
612 18048 : }
613 :
614 : /**
615 : * LoadData
616 : * --------------------------------------------------------
617 : *
618 : * Attempt to load data from vchData
619 : *
620 : */
621 :
622 907 : void CGovernanceObject::LoadData()
623 : {
624 907 : if (m_obj.vchData.empty()) {
625 641 : return;
626 : }
627 :
628 : try {
629 : // ATTEMPT TO LOAD JSON STRING FROM VCHDATA
630 266 : UniValue objResult(UniValue::VOBJ);
631 266 : GetData(objResult);
632 266 : LogPrint(BCLog::GOBJECT, "CGovernanceObject::LoadData -- GetDataAsPlainString = %s\n", GetDataAsPlainString());
633 266 : UniValue obj = GetJSONObject();
634 266 : m_obj.type = GovernanceObject(obj["type"].getInt<int>());
635 266 : } catch (std::exception& e) {
636 0 : fUnparsable = true;
637 0 : LogPrintf("%s\n", strprintf("CGovernanceObject::LoadData -- Error parsing JSON, e.what() = %s", e.what()));
638 : return;
639 0 : } catch (...) {
640 0 : fUnparsable = true;
641 0 : LogPrintf("%s\n", strprintf("CGovernanceObject::LoadData -- Unknown Error parsing JSON"));
642 : return;
643 0 : }
644 907 : }
645 :
646 : /**
647 : * GetData - Example usage:
648 : * --------------------------------------------------------
649 : *
650 : * Decode governance object data into UniValue(VOBJ)
651 : *
652 : */
653 :
654 1296 : void CGovernanceObject::GetData(UniValue& objResult) const
655 : {
656 1296 : UniValue o(UniValue::VOBJ);
657 1296 : std::string s = GetDataAsPlainString();
658 1296 : o.read(s);
659 1296 : objResult = o;
660 1296 : }
661 :
662 : /**
663 : * GetData - As
664 : * --------------------------------------------------------
665 : *
666 : */
667 25428 : std::string CGovernanceObject::GetDataAsHexString() const
668 : {
669 25428 : return m_obj.GetDataAsHexString();
670 : }
671 :
672 20785 : std::string CGovernanceObject::GetDataAsPlainString() const
673 : {
674 20785 : return m_obj.GetDataAsPlainString();
675 : }
676 :
677 592 : void CGovernanceObject::UpdateLocalValidity(const CDeterministicMNList& tip_mn_list, const ChainstateManager& chainman)
678 : {
679 592 : AssertLockHeld(::cs_main);
680 : // THIS DOES NOT CHECK COLLATERAL, THIS IS CHECKED UPON ORIGINAL ARRIVAL
681 592 : fCachedLocalValidity = IsValidLocally(tip_mn_list, chainman, strLocalValidityError, false);
682 592 : }
683 :
684 :
685 19014 : bool CGovernanceObject::IsValidLocally(const CDeterministicMNList& tip_mn_list, const ChainstateManager& chainman, std::string& strError, bool fCheckCollateral) const
686 : {
687 19014 : bool fMissingConfirmations = false;
688 :
689 19014 : return IsValidLocally(tip_mn_list, chainman, strError, fMissingConfirmations, fCheckCollateral);
690 : }
691 :
692 19282 : bool CGovernanceObject::IsValidLocally(const CDeterministicMNList& tip_mn_list, const ChainstateManager& chainman, std::string& strError, bool& fMissingConfirmations, bool fCheckCollateral) const
693 : {
694 19282 : AssertLockHeld(::cs_main);
695 :
696 19282 : fMissingConfirmations = false;
697 :
698 19282 : if (fUnparsable) {
699 0 : strError = "Object data unparsable";
700 0 : return false;
701 : }
702 :
703 19282 : switch (m_obj.type) {
704 : case GovernanceObject::PROPOSAL: {
705 : // Note: It's ok to have expired proposals
706 : // they are going to be cleared by CGovernanceManager::CheckAndRemove()
707 : // TODO: should they be tagged as "expired" to skip vote downloading?
708 580 : std::string strValidationError;
709 580 : if (!governance::ValidateProposal(GetDataAsHexString(), strValidationError, /*fCheckExpiration=*/false)) {
710 0 : strError = strprintf("Invalid proposal data, error messages: %s", strValidationError);
711 0 : return false;
712 : }
713 580 : if (fCheckCollateral && !IsCollateralValid(chainman, strError, fMissingConfirmations)) {
714 0 : strError = "Invalid proposal collateral";
715 0 : return false;
716 : }
717 580 : return true;
718 580 : }
719 : case GovernanceObject::TRIGGER: {
720 18702 : if (!fCheckCollateral) {
721 : // nothing else we can check here (yet?)
722 18326 : return true;
723 : }
724 :
725 376 : std::string strOutpoint = m_obj.masternodeOutpoint.ToStringShort();
726 376 : auto dmn = tip_mn_list.GetMNByCollateral(m_obj.masternodeOutpoint);
727 376 : if (!dmn) {
728 0 : strError = "Failed to find Masternode by UTXO, missing masternode=" + strOutpoint;
729 0 : return false;
730 : }
731 :
732 : // Check that we have a valid MN signature
733 376 : if (!CheckSignature(dmn->pdmnState->pubKeyOperator.Get())) {
734 0 : strError = "Invalid masternode signature for: " + strOutpoint + ", pubkey = " + dmn->pdmnState->pubKeyOperator.ToString();
735 0 : return false;
736 : }
737 :
738 376 : return true;
739 376 : }
740 : default: {
741 0 : strError = strprintf("Invalid object type %d", std23::to_underlying(m_obj.type));
742 0 : return false;
743 : }
744 : }
745 19282 : }
746 :
747 266 : CAmount CGovernanceObject::GetMinCollateralFee() const
748 : {
749 : // Only 1 type has a fee for the moment but switch statement allows for future object types
750 266 : switch (m_obj.type) {
751 : case GovernanceObject::PROPOSAL: {
752 266 : return GOVERNANCE_PROPOSAL_FEE_TX;
753 : }
754 : case GovernanceObject::TRIGGER: {
755 0 : return 0;
756 : }
757 : default: {
758 0 : return MAX_MONEY;
759 : }
760 : }
761 266 : }
762 :
763 224 : bool CGovernanceObject::IsCollateralValid(const ChainstateManager& chainman, std::string& strError, bool& fMissingConfirmations) const
764 : {
765 224 : AssertLockHeld(::cs_main);
766 :
767 224 : strError = "";
768 224 : fMissingConfirmations = false;
769 224 : uint256 nExpectedHash = GetHash();
770 :
771 224 : CTransactionRef txCollateral;
772 224 : uint256 nBlockHash;
773 224 : if (g_txindex) {
774 224 : g_txindex->FindTx(m_obj.collateralHash, nBlockHash, txCollateral);
775 224 : }
776 :
777 224 : if (!txCollateral) {
778 0 : strError = strprintf("Can't find collateral tx %s", m_obj.collateralHash.ToString());
779 0 : LogPrintf("CGovernanceObject::IsCollateralValid -- %s\n", strError);
780 0 : return false;
781 : }
782 :
783 224 : if (nBlockHash == uint256()) {
784 0 : strError = strprintf("Collateral tx %s is not mined yet", txCollateral->ToString());
785 0 : LogPrintf("CGovernanceObject::IsCollateralValid -- %s\n", strError);
786 0 : return false;
787 : }
788 :
789 224 : if (txCollateral->vout.empty()) {
790 0 : strError = "tx vout is empty";
791 0 : LogPrintf("CGovernanceObject::IsCollateralValid -- %s\n", strError);
792 0 : return false;
793 : }
794 :
795 : // LOOK FOR SPECIALIZED GOVERNANCE SCRIPT (PROOF OF BURN)
796 :
797 224 : CScript findScript;
798 224 : findScript << OP_RETURN << ToByteVector(nExpectedHash);
799 :
800 224 : CAmount nMinFee = GetMinCollateralFee();
801 :
802 224 : LogPrint(BCLog::GOBJECT, "CGovernanceObject::IsCollateralValid -- txCollateral->vout.size() = %s, findScript = %s, nMinFee = %lld\n",
803 : txCollateral->vout.size(), HexStr(findScript), nMinFee);
804 :
805 224 : bool foundOpReturn = false;
806 672 : for (const auto& output : txCollateral->vout) {
807 448 : LogPrint(BCLog::GOBJECT, "CGovernanceObject::IsCollateralValid -- txout = %s, output.nValue = %lld, output.scriptPubKey = %s\n",
808 : output.ToString(), output.nValue, HexStr(output.scriptPubKey));
809 448 : if (!output.scriptPubKey.IsPayToPublicKeyHash() && !output.scriptPubKey.IsUnspendable()) {
810 0 : strError = strprintf("Invalid Script %s", txCollateral->ToString());
811 0 : LogPrintf("CGovernanceObject::IsCollateralValid -- %s\n", strError);
812 0 : return false;
813 : }
814 448 : if (output.scriptPubKey == findScript && output.nValue >= nMinFee) {
815 224 : foundOpReturn = true;
816 224 : }
817 : }
818 :
819 224 : if (!foundOpReturn) {
820 0 : strError = strprintf("Couldn't find opReturn %s in %s", nExpectedHash.ToString(), txCollateral->ToString());
821 0 : LogPrintf("CGovernanceObject::IsCollateralValid -- %s\n", strError);
822 0 : return false;
823 : }
824 :
825 : // GET CONFIRMATIONS FOR TRANSACTION
826 :
827 224 : AssertLockHeld(::cs_main);
828 224 : int nConfirmationsIn = 0;
829 224 : if (nBlockHash != uint256()) {
830 224 : const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(nBlockHash);
831 224 : if (pindex && chainman.ActiveChain().Contains(pindex)) {
832 224 : nConfirmationsIn += chainman.ActiveChain().Height() - pindex->nHeight + 1;
833 224 : }
834 224 : }
835 :
836 224 : if (nConfirmationsIn < GOVERNANCE_FEE_CONFIRMATIONS) {
837 0 : strError = strprintf("Collateral requires at least %d confirmations to be relayed throughout the network (it has only %d)", GOVERNANCE_FEE_CONFIRMATIONS, nConfirmationsIn);
838 0 : if (nConfirmationsIn >= GOVERNANCE_MIN_RELAY_FEE_CONFIRMATIONS) {
839 0 : fMissingConfirmations = true;
840 0 : strError += ", pre-accepted -- waiting for required confirmations";
841 0 : } else {
842 0 : strError += ", rejected -- try again later";
843 : }
844 0 : LogPrintf("CGovernanceObject::IsCollateralValid -- %s\n", strError);
845 :
846 0 : return false;
847 : }
848 :
849 224 : strError = "valid";
850 224 : return true;
851 224 : }
852 :
853 106862 : int CGovernanceObject::CountMatchingVotes(const CDeterministicMNList& tip_mn_list, vote_signal_enum_t eVoteSignalIn, vote_outcome_enum_t eVoteOutcomeIn) const
854 : {
855 106862 : LOCK(cs);
856 :
857 106862 : int nCount = 0;
858 767382 : for (const auto& [outpoint, recVote] : mapCurrentMNVotes) {
859 476654 : auto it2 = recVote.mapInstances.find(eVoteSignalIn);
860 476654 : if (it2 != recVote.mapInstances.end() && it2->second.eOutcome == eVoteOutcomeIn) {
861 : // 4x times weight vote for EvoNode owners.
862 : // No need to check if v19 is active since no EvoNode are allowed to register before v19s
863 367732 : auto dmn = tip_mn_list.GetMNByCollateral(outpoint);
864 183866 : if (dmn != nullptr) nCount += GetMnType(dmn->nType).voting_weight;
865 183866 : }
866 : }
867 106862 : return nCount;
868 106862 : }
869 :
870 : /**
871 : * Get specific vote counts for each outcome (funding, validity, etc)
872 : */
873 :
874 24410 : int CGovernanceObject::GetAbsoluteYesCount(const CDeterministicMNList& tip_mn_list, vote_signal_enum_t eVoteSignalIn) const
875 : {
876 24410 : AssertLockNotHeld(cs);
877 24410 : return GetYesCount(tip_mn_list, eVoteSignalIn) - GetNoCount(tip_mn_list, eVoteSignalIn);
878 : }
879 :
880 1184 : int CGovernanceObject::GetAbsoluteNoCount(const CDeterministicMNList& tip_mn_list, vote_signal_enum_t eVoteSignalIn) const
881 : {
882 1184 : AssertLockNotHeld(cs);
883 1184 : return GetNoCount(tip_mn_list, eVoteSignalIn) - GetYesCount(tip_mn_list, eVoteSignalIn);
884 : }
885 :
886 44152 : int CGovernanceObject::GetYesCount(const CDeterministicMNList& tip_mn_list, vote_signal_enum_t eVoteSignalIn) const
887 : {
888 44152 : AssertLockNotHeld(cs);
889 44152 : return CountMatchingVotes(tip_mn_list, eVoteSignalIn, VOTE_OUTCOME_YES);
890 : }
891 :
892 44152 : int CGovernanceObject::GetNoCount(const CDeterministicMNList& tip_mn_list, vote_signal_enum_t eVoteSignalIn) const
893 : {
894 44152 : AssertLockNotHeld(cs);
895 44152 : return CountMatchingVotes(tip_mn_list, eVoteSignalIn, VOTE_OUTCOME_NO);
896 : }
897 :
898 18558 : int CGovernanceObject::GetAbstainCount(const CDeterministicMNList& tip_mn_list, vote_signal_enum_t eVoteSignalIn) const
899 : {
900 18558 : AssertLockNotHeld(cs);
901 18558 : return CountMatchingVotes(tip_mn_list, eVoteSignalIn, VOTE_OUTCOME_ABSTAIN);
902 : }
903 :
904 0 : CGovernanceObject::UniqueVoterCount CGovernanceObject::GetUniqueVoterCount(const CDeterministicMNList& tip_mn_list, vote_signal_enum_t eVoteSignalIn) const
905 : {
906 0 : LOCK(cs);
907 0 : UniqueVoterCount result;
908 0 : for (const auto& [outpoint, recVote] : mapCurrentMNVotes) {
909 0 : if (recVote.mapInstances.count(eVoteSignalIn) == 0) {
910 0 : continue;
911 : }
912 0 : auto dmn = tip_mn_list.GetMNByCollateral(outpoint);
913 0 : if (!dmn) {
914 0 : continue;
915 : }
916 0 : if (dmn->nType == MnType::Evo) {
917 0 : ++result.m_evo;
918 0 : } else {
919 0 : ++result.m_regular;
920 : }
921 0 : }
922 : return result;
923 0 : }
924 :
925 599 : bool CGovernanceObject::GetCurrentMNVotes(const COutPoint& mnCollateralOutpoint, vote_rec_t& voteRecord) const
926 : {
927 599 : LOCK(cs);
928 :
929 599 : auto it = mapCurrentMNVotes.find(mnCollateralOutpoint);
930 599 : if (it == mapCurrentMNVotes.end()) {
931 156 : return false;
932 : }
933 443 : voteRecord = it->second;
934 443 : return true;
935 599 : }
936 :
937 1184 : void CGovernanceObject::UpdateSentinelVariables(const CDeterministicMNList& tip_mn_list)
938 : {
939 1184 : AssertLockNotHeld(cs);
940 :
941 : // CALCULATE MINIMUM SUPPORT LEVELS REQUIRED
942 :
943 1184 : int nWeightedMnCount = (int)tip_mn_list.GetCounts().m_valid_weighted;
944 1184 : if (nWeightedMnCount == 0) return;
945 :
946 : // CALCULATE THE MINIMUM VOTE COUNT REQUIRED FOR FULL SIGNAL
947 :
948 1184 : int nAbsVoteReq = std::max(Params().GetConsensus().nGovernanceMinQuorum, nWeightedMnCount / 10);
949 1184 : int nAbsDeleteReq = std::max(Params().GetConsensus().nGovernanceMinQuorum, (2 * nWeightedMnCount) / 3);
950 :
951 : // SET SENTINEL FLAGS TO FALSE
952 :
953 1184 : fCachedFunding = false;
954 1184 : fCachedValid = true; //default to valid
955 1184 : fCachedEndorsed = false;
956 1184 : fDirtyCache = false;
957 :
958 : // SET SENTINEL FLAGS TO TRUE IF MINIMUM SUPPORT LEVELS ARE REACHED
959 : // ARE ANY OF THESE FLAGS CURRENTLY ACTIVATED?
960 :
961 1184 : if (GetAbsoluteYesCount(tip_mn_list, VOTE_SIGNAL_FUNDING) >= nAbsVoteReq) fCachedFunding = true;
962 1184 : if ((GetAbsoluteYesCount(tip_mn_list, VOTE_SIGNAL_DELETE) >= nAbsDeleteReq) && !fCachedDelete) {
963 0 : fCachedDelete = true;
964 0 : LOCK(cs);
965 0 : if (nDeletionTime == 0) {
966 0 : nDeletionTime = GetTime<std::chrono::seconds>().count();
967 0 : }
968 0 : }
969 1184 : if (GetAbsoluteYesCount(tip_mn_list, VOTE_SIGNAL_ENDORSED) >= nAbsVoteReq) fCachedEndorsed = true;
970 :
971 1184 : if (GetAbsoluteNoCount(tip_mn_list, VOTE_SIGNAL_VALID) >= nAbsVoteReq) fCachedValid = false;
972 1184 : }
973 :
974 : namespace governance {
975 6333 : bool ValidateProposal(const std::string& strDataHex, std::string& strErrorOut,
976 : bool fCheckExpiration, bool fAllowScript)
977 : {
978 6333 : UniValue objJSON(UniValue::VOBJ);
979 6333 : if (!ParseProposalJSON(strDataHex, objJSON, strErrorOut)) {
980 44 : strErrorOut += "JSON parsing error;";
981 44 : return false;
982 : }
983 6289 : if (!ValidateType(objJSON, strErrorOut)) {
984 1 : strErrorOut += "Invalid type;";
985 1 : return false;
986 : }
987 6288 : if (!ValidateName(objJSON, strErrorOut)) {
988 6 : strErrorOut += "Invalid name;";
989 6 : return false;
990 : }
991 6282 : if (!ValidateStartEndEpoch(objJSON, fCheckExpiration, strErrorOut)) {
992 246 : strErrorOut += "Invalid start:end range;";
993 246 : return false;
994 : }
995 6036 : if (!ValidatePaymentAmount(objJSON, strErrorOut)) {
996 1 : strErrorOut += "Invalid payment amount;";
997 1 : return false;
998 : }
999 6035 : if (!ValidatePaymentAddress(objJSON, fAllowScript, strErrorOut)) {
1000 4 : strErrorOut += "Invalid payment address;";
1001 4 : return false;
1002 : }
1003 6031 : if (!ValidateURL(objJSON, strErrorOut)) {
1004 5 : strErrorOut += "Invalid URL;";
1005 5 : return false;
1006 : }
1007 6026 : return true;
1008 6333 : }
1009 : } // namespace governance
|