LCOV - code coverage report
Current view: top level - src/governance - object.cpp (source / functions) Hit Total Coverage
Test: test_dash_coverage.info Lines: 160 605 26.4 %
Date: 2026-06-25 07:23:51 Functions: 15 51 29.4 %

          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          56 : bool GetDataValue(const UniValue& objJSON, const std::string& strKey, std::string& strValueRet, std::string& strErrorMessages)
      37             : {
      38             :     try {
      39          56 :         strValueRet = objJSON[strKey].get_str();
      40          56 :         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          56 : }
      48             : 
      49          78 : bool GetDataValue(const UniValue& objJSON, const std::string& strKey, int64_t& nValueRet, std::string& strErrorMessages)
      50             : {
      51             :     try {
      52          78 :         const UniValue& uValue = objJSON[strKey];
      53          78 :         if (uValue.getType() == UniValue::VNUM) {
      54          76 :             nValueRet = uValue.getInt<int64_t>();
      55          76 :             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          78 : }
      64             : 
      65          16 : bool GetDataValue(const UniValue& objJSON, const std::string& strKey, double& dValueRet, std::string& strErrorMessages)
      66             : {
      67             :     try {
      68          16 :         const UniValue& uValue = objJSON[strKey];
      69          16 :         if (uValue.getType() == UniValue::VNUM) {
      70          15 :             dValueRet = uValue.get_real();
      71          15 :             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          16 : }
      80             : 
      81          31 : bool ValidateType(const UniValue& objJSON, std::string& strErrorMessages)
      82             : {
      83             :     int64_t nType;
      84          31 :     if (!GetDataValue(objJSON, "type", nType, strErrorMessages)) {
      85           0 :         strErrorMessages += "type field not found;";
      86           0 :         return false;
      87             :     }
      88             : 
      89          31 :     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          30 :     return true;
      95          31 : }
      96             : 
      97          30 : bool ValidateName(const UniValue& objJSON, std::string& strErrorMessages)
      98             : {
      99          30 :     std::string strName;
     100          30 :     if (!GetDataValue(objJSON, "name", strName, strErrorMessages)) {
     101           0 :         strErrorMessages += "name field not found;";
     102           0 :         return false;
     103             :     }
     104             : 
     105          30 :     if (strName.size() > MAX_NAME_SIZE) {
     106           0 :         strErrorMessages += strprintf("name exceeds %lu characters;", MAX_NAME_SIZE);
     107           0 :         return false;
     108             :     }
     109             : 
     110          30 :     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          29 :     strName = ToLower(strName);
     118             : 
     119          29 :     if (strName.find_first_not_of(strAllowedChars) != std::string::npos) {
     120           5 :         strErrorMessages += "name contains invalid characters;";
     121           5 :         return false;
     122             :     }
     123             : 
     124          24 :     return true;
     125          30 : }
     126             : 
     127          24 : bool ValidateStartEndEpoch(const UniValue& objJSON, bool fCheckExpiration, std::string& strErrorMessages)
     128             : {
     129          24 :     int64_t nStartEpoch = 0;
     130          24 :     int64_t nEndEpoch = 0;
     131             : 
     132          24 :     if (!GetDataValue(objJSON, "start_epoch", nStartEpoch, strErrorMessages)) {
     133           1 :         strErrorMessages += "start_epoch field not found;";
     134           1 :         return false;
     135             :     }
     136             : 
     137          23 :     if (!GetDataValue(objJSON, "end_epoch", nEndEpoch, strErrorMessages)) {
     138           1 :         strErrorMessages += "end_epoch field not found;";
     139           1 :         return false;
     140             :     }
     141             : 
     142          22 :     if (nEndEpoch <= nStartEpoch) {
     143           0 :         strErrorMessages += "end_epoch <= start_epoch;";
     144           0 :         return false;
     145             :     }
     146             : 
     147          22 :     if (fCheckExpiration && nEndEpoch <= GetAdjustedTime()) {
     148           6 :         strErrorMessages += "expired;";
     149           6 :         return false;
     150             :     }
     151             : 
     152          16 :     return true;
     153          24 : }
     154             : 
     155          16 : bool ValidatePaymentAmount(const UniValue& objJSON, std::string& strErrorMessages)
     156             : {
     157          16 :     double dValue = 0.0;
     158             : 
     159          16 :     if (!GetDataValue(objJSON, "payment_amount", dValue, strErrorMessages)) {
     160           1 :         strErrorMessages += "payment_amount field not found;";
     161           1 :         return false;
     162             :     }
     163             : 
     164          15 :     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          15 :     return true;
     174          16 : }
     175             : 
     176          15 : bool ValidatePaymentAddress(const UniValue& objJSON, bool fAllowScript, std::string& strErrorMessages)
     177             : {
     178          15 :     std::string strPaymentAddress;
     179             : 
     180          15 :     if (!GetDataValue(objJSON, "payment_address", strPaymentAddress, strErrorMessages)) {
     181           0 :         strErrorMessages += "payment_address field not found;";
     182           0 :         return false;
     183             :     }
     184             : 
     185          15 :     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          13 :     CTxDestination dest = DecodeDestination(strPaymentAddress);
     191          13 :     if (!IsValidDestination(dest)) {
     192           1 :         strErrorMessages += "payment_address is invalid;";
     193           1 :         return false;
     194             :     }
     195             : 
     196          12 :     const ScriptHash *scriptID = std::get_if<ScriptHash>(&dest);
     197          12 :     if (!fAllowScript && scriptID) {
     198           1 :         strErrorMessages += "script addresses are not supported;";
     199           1 :         return false;
     200             :     }
     201             : 
     202          11 :     return true;
     203          15 : }
     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           8 : bool CheckURL(const std::string& strURLIn)
     212             : {
     213           8 :     std::string strRest(strURLIn);
     214           8 :     std::string::size_type nPos = strRest.find(':');
     215             : 
     216           8 :     if (nPos != std::string::npos) {
     217           8 :         if (nPos < strRest.size()) {
     218           8 :             strRest = strRest.substr(nPos + 1);
     219           8 :         } else {
     220           0 :             strRest = "";
     221             :         }
     222           8 :     }
     223             : 
     224             :     // Process netloc
     225           8 :     if ((strRest.size() > 2) && (strRest.substr(0, 2) == "//")) {
     226             :         static constexpr std::string_view strNetlocDelimiters{"/?#"};
     227             : 
     228           8 :         strRest = strRest.substr(2);
     229             : 
     230           8 :         std::string::size_type nPos2 = strRest.find_first_of(strNetlocDelimiters);
     231             : 
     232           8 :         std::string strNetloc = strRest.substr(0, nPos2);
     233             : 
     234           8 :         if ((strNetloc.find('[') != std::string::npos) && (strNetloc.find(']') == std::string::npos)) {
     235           1 :             return false;
     236             :         }
     237             : 
     238           7 :         if ((strNetloc.find(']') != std::string::npos) && (strNetloc.find('[') == std::string::npos)) {
     239           1 :             return false;
     240             :         }
     241           8 :     }
     242             : 
     243           6 :     return true;
     244           8 : }
     245             : 
     246          11 : bool ValidateURL(const UniValue& objJSON, std::string& strErrorMessages)
     247             : {
     248          11 :     std::string strURL;
     249          11 :     if (!GetDataValue(objJSON, "url", strURL, strErrorMessages)) {
     250           0 :         strErrorMessages += "url field not found;";
     251           0 :         return false;
     252             :     }
     253             : 
     254          11 :     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           8 :     if (strURL.size() < 4U) {
     260           0 :         strErrorMessages += "url too short;";
     261           0 :         return false;
     262             :     }
     263             : 
     264           8 :     if (!CheckURL(strURL)) {
     265           2 :         strErrorMessages += "url invalid;";
     266           2 :         return false;
     267             :     }
     268             : 
     269           6 :     return true;
     270          11 : }
     271             : 
     272          75 : bool ParseProposalJSON(const std::string& strHexData, UniValue& objJSONOut, std::string& strErrorMessages)
     273             : {
     274          75 :     if (strHexData.empty()) return false;
     275             : 
     276          75 :     std::vector<unsigned char> v = ParseHex(strHexData);
     277          75 :     if (v.size() > MAX_DATA_SIZE) {
     278           0 :         strErrorMessages = strprintf("data exceeds %lu characters;", MAX_DATA_SIZE);
     279           0 :         return false;
     280             :     }
     281             : 
     282          75 :     const std::string strJSONData(v.begin(), v.end());
     283          75 :     if (strJSONData.empty()) return false;
     284             : 
     285             :     try {
     286          75 :         UniValue obj(UniValue::VOBJ);
     287          75 :         obj.read(strJSONData);
     288          75 :         if (!obj.isObject()) {
     289          44 :             throw std::runtime_error("Proposal must be a JSON object");
     290             :         }
     291          31 :         objJSONOut = obj;
     292          31 :         return true;
     293          75 :     } 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         119 : }
     300             : 
     301             : } // namespace
     302             : 
     303           0 : std::ostream& operator<<(std::ostream& os, governance_exception_type_enum_t eType)
     304             : {
     305           0 :     switch (eType) {
     306             :     case GOVERNANCE_EXCEPTION_NONE:
     307           0 :         os << "GOVERNANCE_EXCEPTION_NONE";
     308           0 :         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           0 :     return os;
     323             : }
     324             : 
     325           0 : CGovernanceException::CGovernanceException(const std::string& strMessageIn,
     326             :                                            governance_exception_type_enum_t eTypeIn,
     327             :                                            int nNodePenaltyIn) :
     328           0 :     strMessage{strprintf("%s:%s", eTypeIn, strMessageIn)},
     329           0 :     eType{eTypeIn},
     330           0 :     nNodePenalty{nNodePenaltyIn}
     331           0 : {
     332           0 : }
     333             : 
     334           1 : CGovernanceObject::CGovernanceObject()
     335           0 : {
     336             :     // PARSE JSON DATA STORAGE (VCHDATA)
     337             :     LoadData();
     338           0 : }
     339             : 
     340           0 : CGovernanceObject::CGovernanceObject(const uint256& nHashParentIn, int nRevisionIn, int64_t nTimeIn,
     341             :                                      const uint256& nCollateralHashIn, const std::string& strDataHexIn) :
     342           0 :     cs(),
     343           0 :     m_obj{nHashParentIn, nRevisionIn, nTimeIn, nCollateralHashIn, strDataHexIn}
     344           0 : {
     345             :     // PARSE JSON DATA STORAGE (VCHDATA)
     346             :     LoadData();
     347           0 : }
     348             : 
     349           0 : CGovernanceObject::CGovernanceObject(const CGovernanceObject& other) :
     350           0 :     cs(),
     351           0 :     m_obj{other.m_obj},
     352           0 :     nDeletionTime(other.nDeletionTime),
     353           0 :     fCachedLocalValidity(other.fCachedLocalValidity),
     354           0 :     strLocalValidityError(other.strLocalValidityError),
     355           0 :     fCachedFunding(other.fCachedFunding),
     356           0 :     fCachedValid(other.fCachedValid),
     357           0 :     fCachedDelete(other.fCachedDelete),
     358           0 :     fCachedEndorsed(other.fCachedEndorsed),
     359           0 :     fDirtyCache(other.fDirtyCache),
     360           0 :     fExpired(other.fExpired),
     361           0 :     fUnparsable(other.fUnparsable),
     362           0 :     mapCurrentMNVotes(other.mapCurrentMNVotes),
     363           0 :     fileVotes(other.fileVotes)
     364           0 : {
     365           0 : }
     366             : 
     367           0 : bool CGovernanceObject::ProcessVote(CMasternodeMetaMan& mn_metaman, bool fRateChecksEnabled,
     368             :                                     const CDeterministicMNList& tip_mn_list, const CGovernanceVote& vote,
     369             :                                     CGovernanceException& exception)
     370             : {
     371           0 :     assert(mn_metaman.IsValid());
     372             : 
     373           0 :     LOCK(cs);
     374             : 
     375             :     // do not process already known valid votes twice
     376           0 :     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           0 :     auto dmn = tip_mn_list.GetMNByCollateral(vote.GetMasternodeOutpoint());
     385           0 :     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           0 :     auto it = mapCurrentMNVotes.emplace(vote_m_t::value_type(vote.GetMasternodeOutpoint(), vote_rec_t())).first;
     393           0 :     vote_rec_t& voteRecordRef = it->second;
     394           0 :     vote_signal_enum_t eSignal = vote.GetSignal();
     395           0 :     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           0 :     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           0 :     auto it2 = voteRecordRef.mapInstances.emplace(vote_instance_m_t::value_type(int(eSignal), vote_instance_t())).first;
     409           0 :     vote_instance_t& voteInstanceRef = it2->second;
     410             : 
     411             :     // Reject obsolete votes
     412           0 :     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           0 :     } 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           0 :         std::string msg{strprintf("CGovernanceObject::%s -- Invalid vote, same timestamp for the different outcome", __func__)};
     421           0 :         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           0 :             msg += ", rejected";
     425           0 :             LogPrint(BCLog::GOBJECT, "%s\n", msg);
     426           0 :             exception = CGovernanceException(msg, GOVERNANCE_EXCEPTION_NONE);
     427           0 :             return false;
     428             :         }
     429           0 :         msg += ", accepted";
     430           0 :         LogPrint(BCLog::GOBJECT, "%s\n", msg);
     431           0 :     }
     432             : 
     433           0 :     int64_t nNow = GetAdjustedTime();
     434           0 :     int64_t nVoteTimeUpdate = voteInstanceRef.nTime;
     435           0 :     if (fRateChecksEnabled) {
     436           0 :         int64_t nTimeDelta = nNow - voteInstanceRef.nTime;
     437           0 :         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           0 :         nVoteTimeUpdate = nNow;
     446           0 :     }
     447             : 
     448           0 :     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           0 :     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           0 :     mn_metaman.AddGovernanceVote(dmn->proTxHash, vote.GetParentHash());
     461             : 
     462           0 :     voteInstanceRef = vote_instance_t(vote.GetOutcome(), nVoteTimeUpdate, vote.GetTimestamp());
     463           0 :     fileVotes.AddVote(vote);
     464           0 :     fDirtyCache = true;
     465             :     // SEND NOTIFICATION TO SCRIPT/ZMQ
     466           0 :     GetMainSignals().NotifyGovernanceVote(std::make_shared<CDeterministicMNList>(tip_mn_list),
     467           0 :                                           std::make_shared<const CGovernanceVote>(vote), vote.GetHash().ToString());
     468           0 :     uiInterface.NotifyGovernanceChanged();
     469           0 :     return true;
     470           0 : }
     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           1 : uint256 CGovernanceObject::GetHash() const
     528             : {
     529           1 :     return m_obj.GetHash();
     530             : }
     531             : 
     532           0 : uint256 CGovernanceObject::GetDataHash() const
     533             : {
     534           0 :     CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION);
     535           0 :     ss << GetDataAsHexString();
     536             : 
     537           0 :     return ss.GetHash();
     538           0 : }
     539             : 
     540           0 : uint256 CGovernanceObject::GetSignatureHash() const
     541             : {
     542           0 :     return SerializeHash(*this);
     543             : }
     544             : 
     545           0 : void CGovernanceObject::SetMasternodeOutpoint(const COutPoint& outpoint)
     546             : {
     547           0 :     m_obj.masternodeOutpoint = outpoint;
     548           0 : }
     549             : 
     550           0 : void CGovernanceObject::SetSignature(Span<const uint8_t> sig)
     551             : {
     552           0 :     m_obj.vchSig.assign(sig.begin(), sig.end());
     553           0 : }
     554             : 
     555           0 : bool CGovernanceObject::CheckSignature(const CBLSPublicKey& pubKey) const
     556             : {
     557           0 :     CBLSSignature sig;
     558           0 :     sig.SetBytes(m_obj.vchSig, false);
     559           0 :     if (!sig.VerifyInsecure(pubKey, GetSignatureHash(), false)) {
     560           0 :         LogPrintf("CGovernanceObject::CheckSignature -- VerifyInsecure() failed\n");
     561           0 :         return false;
     562             :     }
     563           0 :     return true;
     564           0 : }
     565             : 
     566             : /**
     567             :    Return the actual object from the vchData JSON structure.
     568             : 
     569             :    Returns an empty object on error.
     570             :  */
     571           0 : UniValue CGovernanceObject::GetJSONObject() const
     572             : {
     573           0 :     UniValue obj(UniValue::VOBJ);
     574           0 :     if (m_obj.vchData.empty()) {
     575           0 :         return obj;
     576             :     }
     577             : 
     578           0 :     UniValue objResult(UniValue::VOBJ);
     579           0 :     GetData(objResult);
     580             : 
     581           0 :     if (objResult.isObject()) {
     582           0 :         obj = objResult;
     583           0 :     } 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           0 :     return obj;
     590           0 : }
     591             : 
     592           0 : UniValue CGovernanceObject::GetStateJson(const ChainstateManager& chainman, const CDeterministicMNList& tip_mn_list, const std::string& local_valid_key) const
     593             : {
     594           0 :     UniValue ret(UniValue::VOBJ);
     595           0 :     ret.pushKV("DataHex", GetDataAsHexString());
     596           0 :     ret.pushKV("DataString", GetDataAsPlainString());
     597           0 :     ret.pushKV("Hash", GetHash().ToString());
     598           0 :     ret.pushKV("CollateralHash", GetCollateralHash().ToString());
     599           0 :     ret.pushKV("ObjectType", std23::to_underlying(GetObjectType()));
     600           0 :     ret.pushKV("CreationTime", GetCreationTime());
     601           0 :     if (const COutPoint& outpoint = GetMasternodeOutpoint(); outpoint != COutPoint{}) {
     602           0 :         ret.pushKV("SigningMasternode", outpoint.ToStringShort());
     603           0 :     }
     604           0 :     std::string strError;
     605           0 :     ret.pushKV(local_valid_key, WITH_LOCK(::cs_main, return IsValidLocally(tip_mn_list, chainman, strError, /*fCheckCollateral=*/false)));
     606           0 :     ret.pushKV("IsValidReason", strError.c_str());
     607           0 :     ret.pushKV("fCachedValid", IsSetCachedValid());
     608           0 :     ret.pushKV("fCachedFunding", IsSetCachedFunding());
     609           0 :     ret.pushKV("fCachedDelete", IsSetCachedDelete());
     610           0 :     ret.pushKV("fCachedEndorsed", IsSetCachedEndorsed());
     611           0 :     return ret;
     612           0 : }
     613             : 
     614             : /**
     615             : *   LoadData
     616             : *   --------------------------------------------------------
     617             : *
     618             : *   Attempt to load data from vchData
     619             : *
     620             : */
     621             : 
     622           1 : void CGovernanceObject::LoadData()
     623             : {
     624           1 :     if (m_obj.vchData.empty()) {
     625           1 :         return;
     626             :     }
     627             : 
     628             :     try {
     629             :         // ATTEMPT TO LOAD JSON STRING FROM VCHDATA
     630           0 :         UniValue objResult(UniValue::VOBJ);
     631           0 :         GetData(objResult);
     632           0 :         LogPrint(BCLog::GOBJECT, "CGovernanceObject::LoadData -- GetDataAsPlainString = %s\n", GetDataAsPlainString());
     633           0 :         UniValue obj = GetJSONObject();
     634           0 :         m_obj.type = GovernanceObject(obj["type"].getInt<int>());
     635           0 :     } 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           1 : }
     645             : 
     646             : /**
     647             : *   GetData - Example usage:
     648             : *   --------------------------------------------------------
     649             : *
     650             : *   Decode governance object data into UniValue(VOBJ)
     651             : *
     652             : */
     653             : 
     654           0 : void CGovernanceObject::GetData(UniValue& objResult) const
     655             : {
     656           0 :     UniValue o(UniValue::VOBJ);
     657           0 :     std::string s = GetDataAsPlainString();
     658           0 :     o.read(s);
     659           0 :     objResult = o;
     660           0 : }
     661             : 
     662             : /**
     663             : *   GetData - As
     664             : *   --------------------------------------------------------
     665             : *
     666             : */
     667           0 : std::string CGovernanceObject::GetDataAsHexString() const
     668             : {
     669           0 :     return m_obj.GetDataAsHexString();
     670             : }
     671             : 
     672           0 : std::string CGovernanceObject::GetDataAsPlainString() const
     673             : {
     674           0 :     return m_obj.GetDataAsPlainString();
     675             : }
     676             : 
     677           0 : void CGovernanceObject::UpdateLocalValidity(const CDeterministicMNList& tip_mn_list, const ChainstateManager& chainman)
     678             : {
     679           0 :     AssertLockHeld(::cs_main);
     680             :     // THIS DOES NOT CHECK COLLATERAL, THIS IS CHECKED UPON ORIGINAL ARRIVAL
     681           0 :     fCachedLocalValidity = IsValidLocally(tip_mn_list, chainman, strLocalValidityError, false);
     682           0 : }
     683             : 
     684             : 
     685           0 : bool CGovernanceObject::IsValidLocally(const CDeterministicMNList& tip_mn_list, const ChainstateManager& chainman, std::string& strError, bool fCheckCollateral) const
     686             : {
     687           0 :     bool fMissingConfirmations = false;
     688             : 
     689           0 :     return IsValidLocally(tip_mn_list, chainman, strError, fMissingConfirmations, fCheckCollateral);
     690             : }
     691             : 
     692           0 : bool CGovernanceObject::IsValidLocally(const CDeterministicMNList& tip_mn_list, const ChainstateManager& chainman, std::string& strError, bool& fMissingConfirmations, bool fCheckCollateral) const
     693             : {
     694           0 :     AssertLockHeld(::cs_main);
     695             : 
     696           0 :     fMissingConfirmations = false;
     697             : 
     698           0 :     if (fUnparsable) {
     699           0 :         strError = "Object data unparsable";
     700           0 :         return false;
     701             :     }
     702             : 
     703           0 :     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           0 :         std::string strValidationError;
     709           0 :         if (!governance::ValidateProposal(GetDataAsHexString(), strValidationError, /*fCheckExpiration=*/false)) {
     710           0 :             strError = strprintf("Invalid proposal data, error messages: %s", strValidationError);
     711           0 :             return false;
     712             :         }
     713           0 :         if (fCheckCollateral && !IsCollateralValid(chainman, strError, fMissingConfirmations)) {
     714           0 :             strError = "Invalid proposal collateral";
     715           0 :             return false;
     716             :         }
     717           0 :         return true;
     718           0 :     }
     719             :     case GovernanceObject::TRIGGER: {
     720           0 :         if (!fCheckCollateral) {
     721             :             // nothing else we can check here (yet?)
     722           0 :             return true;
     723             :         }
     724             : 
     725           0 :         std::string strOutpoint = m_obj.masternodeOutpoint.ToStringShort();
     726           0 :         auto dmn = tip_mn_list.GetMNByCollateral(m_obj.masternodeOutpoint);
     727           0 :         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           0 :         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           0 :         return true;
     739           0 :     }
     740             :     default: {
     741           0 :         strError = strprintf("Invalid object type %d", std23::to_underlying(m_obj.type));
     742           0 :         return false;
     743             :     }
     744             :     }
     745           0 : }
     746             : 
     747           0 : CAmount CGovernanceObject::GetMinCollateralFee() const
     748             : {
     749             :     // Only 1 type has a fee for the moment but switch statement allows for future object types
     750           0 :     switch (m_obj.type) {
     751             :         case GovernanceObject::PROPOSAL: {
     752           0 :             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           0 : }
     762             : 
     763           0 : bool CGovernanceObject::IsCollateralValid(const ChainstateManager& chainman, std::string& strError, bool& fMissingConfirmations) const
     764             : {
     765           0 :     AssertLockHeld(::cs_main);
     766             : 
     767           0 :     strError = "";
     768           0 :     fMissingConfirmations = false;
     769           0 :     uint256 nExpectedHash = GetHash();
     770             : 
     771           0 :     CTransactionRef txCollateral;
     772           0 :     uint256 nBlockHash;
     773           0 :     if (g_txindex) {
     774           0 :         g_txindex->FindTx(m_obj.collateralHash, nBlockHash, txCollateral);
     775           0 :     }
     776             : 
     777           0 :     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           0 :     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           0 :     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           0 :     CScript findScript;
     798           0 :     findScript << OP_RETURN << ToByteVector(nExpectedHash);
     799             : 
     800           0 :     CAmount nMinFee = GetMinCollateralFee();
     801             : 
     802           0 :     LogPrint(BCLog::GOBJECT, "CGovernanceObject::IsCollateralValid -- txCollateral->vout.size() = %s, findScript = %s, nMinFee = %lld\n",
     803             :                 txCollateral->vout.size(), HexStr(findScript), nMinFee);
     804             : 
     805           0 :     bool foundOpReturn = false;
     806           0 :     for (const auto& output : txCollateral->vout) {
     807           0 :         LogPrint(BCLog::GOBJECT, "CGovernanceObject::IsCollateralValid -- txout = %s, output.nValue = %lld, output.scriptPubKey = %s\n",
     808             :                     output.ToString(), output.nValue, HexStr(output.scriptPubKey));
     809           0 :         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           0 :         if (output.scriptPubKey == findScript && output.nValue >= nMinFee) {
     815           0 :             foundOpReturn = true;
     816           0 :         }
     817             :     }
     818             : 
     819           0 :     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           0 :     AssertLockHeld(::cs_main);
     828           0 :     int nConfirmationsIn = 0;
     829           0 :     if (nBlockHash != uint256()) {
     830           0 :         const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(nBlockHash);
     831           0 :         if (pindex && chainman.ActiveChain().Contains(pindex)) {
     832           0 :             nConfirmationsIn += chainman.ActiveChain().Height() - pindex->nHeight + 1;
     833           0 :         }
     834           0 :     }
     835             : 
     836           0 :     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           0 :     strError = "valid";
     850           0 :     return true;
     851           0 : }
     852             : 
     853           0 : int CGovernanceObject::CountMatchingVotes(const CDeterministicMNList& tip_mn_list, vote_signal_enum_t eVoteSignalIn, vote_outcome_enum_t eVoteOutcomeIn) const
     854             : {
     855           0 :     LOCK(cs);
     856             : 
     857           0 :     int nCount = 0;
     858           0 :     for (const auto& [outpoint, recVote] : mapCurrentMNVotes) {
     859           0 :         auto it2 = recVote.mapInstances.find(eVoteSignalIn);
     860           0 :         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           0 :             auto dmn = tip_mn_list.GetMNByCollateral(outpoint);
     864           0 :             if (dmn != nullptr) nCount += GetMnType(dmn->nType).voting_weight;
     865           0 :         }
     866             :     }
     867           0 :     return nCount;
     868           0 : }
     869             : 
     870             : /**
     871             : *   Get specific vote counts for each outcome (funding, validity, etc)
     872             : */
     873             : 
     874           0 : int CGovernanceObject::GetAbsoluteYesCount(const CDeterministicMNList& tip_mn_list, vote_signal_enum_t eVoteSignalIn) const
     875             : {
     876           0 :     AssertLockNotHeld(cs);
     877           0 :     return GetYesCount(tip_mn_list, eVoteSignalIn) - GetNoCount(tip_mn_list, eVoteSignalIn);
     878             : }
     879             : 
     880           0 : int CGovernanceObject::GetAbsoluteNoCount(const CDeterministicMNList& tip_mn_list, vote_signal_enum_t eVoteSignalIn) const
     881             : {
     882           0 :     AssertLockNotHeld(cs);
     883           0 :     return GetNoCount(tip_mn_list, eVoteSignalIn) - GetYesCount(tip_mn_list, eVoteSignalIn);
     884             : }
     885             : 
     886           0 : int CGovernanceObject::GetYesCount(const CDeterministicMNList& tip_mn_list, vote_signal_enum_t eVoteSignalIn) const
     887             : {
     888           0 :     AssertLockNotHeld(cs);
     889           0 :     return CountMatchingVotes(tip_mn_list, eVoteSignalIn, VOTE_OUTCOME_YES);
     890             : }
     891             : 
     892           0 : int CGovernanceObject::GetNoCount(const CDeterministicMNList& tip_mn_list, vote_signal_enum_t eVoteSignalIn) const
     893             : {
     894           0 :     AssertLockNotHeld(cs);
     895           0 :     return CountMatchingVotes(tip_mn_list, eVoteSignalIn, VOTE_OUTCOME_NO);
     896             : }
     897             : 
     898           0 : int CGovernanceObject::GetAbstainCount(const CDeterministicMNList& tip_mn_list, vote_signal_enum_t eVoteSignalIn) const
     899             : {
     900           0 :     AssertLockNotHeld(cs);
     901           0 :     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           0 : bool CGovernanceObject::GetCurrentMNVotes(const COutPoint& mnCollateralOutpoint, vote_rec_t& voteRecord) const
     926             : {
     927           0 :     LOCK(cs);
     928             : 
     929           0 :     auto it = mapCurrentMNVotes.find(mnCollateralOutpoint);
     930           0 :     if (it == mapCurrentMNVotes.end()) {
     931           0 :         return false;
     932             :     }
     933           0 :     voteRecord = it->second;
     934           0 :     return true;
     935           0 : }
     936             : 
     937           0 : void CGovernanceObject::UpdateSentinelVariables(const CDeterministicMNList& tip_mn_list)
     938             : {
     939           0 :     AssertLockNotHeld(cs);
     940             : 
     941             :     // CALCULATE MINIMUM SUPPORT LEVELS REQUIRED
     942             : 
     943           0 :     int nWeightedMnCount = (int)tip_mn_list.GetCounts().m_valid_weighted;
     944           0 :     if (nWeightedMnCount == 0) return;
     945             : 
     946             :     // CALCULATE THE MINIMUM VOTE COUNT REQUIRED FOR FULL SIGNAL
     947             : 
     948           0 :     int nAbsVoteReq = std::max(Params().GetConsensus().nGovernanceMinQuorum, nWeightedMnCount / 10);
     949           0 :     int nAbsDeleteReq = std::max(Params().GetConsensus().nGovernanceMinQuorum, (2 * nWeightedMnCount) / 3);
     950             : 
     951             :     // SET SENTINEL FLAGS TO FALSE
     952             : 
     953           0 :     fCachedFunding = false;
     954           0 :     fCachedValid = true; //default to valid
     955           0 :     fCachedEndorsed = false;
     956           0 :     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           0 :     if (GetAbsoluteYesCount(tip_mn_list, VOTE_SIGNAL_FUNDING) >= nAbsVoteReq) fCachedFunding = true;
     962           0 :     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           0 :     if (GetAbsoluteYesCount(tip_mn_list, VOTE_SIGNAL_ENDORSED) >= nAbsVoteReq) fCachedEndorsed = true;
     970             : 
     971           0 :     if (GetAbsoluteNoCount(tip_mn_list, VOTE_SIGNAL_VALID) >= nAbsVoteReq) fCachedValid = false;
     972           0 : }
     973             : 
     974             : namespace governance {
     975          75 : bool ValidateProposal(const std::string& strDataHex, std::string& strErrorOut,
     976             :                       bool fCheckExpiration, bool fAllowScript)
     977             : {
     978          75 :     UniValue objJSON(UniValue::VOBJ);
     979          75 :     if (!ParseProposalJSON(strDataHex, objJSON, strErrorOut)) {
     980          44 :         strErrorOut += "JSON parsing error;";
     981          44 :         return false;
     982             :     }
     983          31 :     if (!ValidateType(objJSON, strErrorOut)) {
     984           1 :         strErrorOut += "Invalid type;";
     985           1 :         return false;
     986             :     }
     987          30 :     if (!ValidateName(objJSON, strErrorOut)) {
     988           6 :         strErrorOut += "Invalid name;";
     989           6 :         return false;
     990             :     }
     991          24 :     if (!ValidateStartEndEpoch(objJSON, fCheckExpiration, strErrorOut)) {
     992           8 :         strErrorOut += "Invalid start:end range;";
     993           8 :         return false;
     994             :     }
     995          16 :     if (!ValidatePaymentAmount(objJSON, strErrorOut)) {
     996           1 :         strErrorOut += "Invalid payment amount;";
     997           1 :         return false;
     998             :     }
     999          15 :     if (!ValidatePaymentAddress(objJSON, fAllowScript, strErrorOut)) {
    1000           4 :         strErrorOut += "Invalid payment address;";
    1001           4 :         return false;
    1002             :     }
    1003          11 :     if (!ValidateURL(objJSON, strErrorOut)) {
    1004           5 :         strErrorOut += "Invalid URL;";
    1005           5 :         return false;
    1006             :     }
    1007           6 :     return true;
    1008          75 : }
    1009             : } // namespace governance

Generated by: LCOV version 1.16