LCOV - code coverage report
Current view: top level - src/budget - finalizedbudget.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 168 214 78.5 %
Date: 2025-02-23 09:33:43 Functions: 23 25 92.0 %

          Line data    Source code
       1             : // Copyright (c) 2014-2015 The Dash developers
       2             : // Copyright (c) 2015-2022 The PIVX Core developers
       3             : // Distributed under the MIT/X11 software license, see the accompanying
       4             : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
       5             : 
       6             : #include "budget/finalizedbudget.h"
       7             : 
       8             : #include "masternodeman.h"
       9             : #include "validation.h"
      10             : 
      11          67 : CFinalizedBudget::CFinalizedBudget() :
      12             :         fAutoChecked(false),
      13             :         fValid(true),
      14             :         strInvalid(),
      15             :         mapVotes(),
      16             :         strBudgetName(""),
      17             :         nBlockStart(0),
      18             :         vecBudgetPayments(),
      19             :         nFeeTXHash(UINT256_ZERO),
      20             :         strProposals(""),
      21          67 :         nTime(0)
      22          67 : { }
      23             : 
      24          13 : CFinalizedBudget::CFinalizedBudget(const std::string& name,
      25             :                                    int blockstart,
      26             :                                    const std::vector<CTxBudgetPayment>& vecBudgetPaymentsIn,
      27          13 :                                    const uint256& nfeetxhash):
      28             :         fAutoChecked(false),
      29             :         fValid(true),
      30             :         strInvalid(),
      31             :         mapVotes(),
      32             :         strBudgetName(name),
      33             :         nBlockStart(blockstart),
      34             :         vecBudgetPayments(vecBudgetPaymentsIn),
      35             :         nFeeTXHash(nfeetxhash),
      36             :         strProposals(""),
      37          26 :         nTime(0)
      38          13 : { }
      39             : 
      40          14 : bool CFinalizedBudget::ParseBroadcast(CDataStream& broadcast)
      41             : {
      42          14 :     *this = CFinalizedBudget();
      43          14 :     try {
      44          14 :         broadcast >> LIMITED_STRING(strBudgetName, 20);
      45          14 :         broadcast >> nBlockStart;
      46          14 :         broadcast >> vecBudgetPayments;
      47          14 :         broadcast >> nFeeTXHash;
      48           0 :     } catch (std::exception& e) {
      49           0 :         return error("Unable to deserialize finalized budget broadcast: %s", e.what());
      50             :     }
      51             :     return true;
      52             : }
      53             : 
      54          48 : bool CFinalizedBudget::AddOrUpdateVote(const CFinalizedBudgetVote& vote, std::string& strError)
      55             : {
      56          96 :     const COutPoint& mnId = vote.GetVin().prevout;
      57          48 :     const int64_t voteTime = vote.GetTime();
      58          96 :     std::string strAction = "New vote inserted:";
      59             : 
      60          95 :     if (mapVotes.count(mnId)) {
      61           1 :         const int64_t oldTime = mapVotes[mnId].GetTime();
      62           1 :         if (oldTime > voteTime) {
      63           0 :             strError = strprintf("new vote older than existing vote - %s\n", vote.GetHash().ToString());
      64           0 :             LogPrint(BCLog::MNBUDGET, "%s: %s\n", __func__, strError);
      65           0 :             return false;
      66             :         }
      67           1 :         if (voteTime - oldTime < BUDGET_VOTE_UPDATE_MIN) {
      68           2 :             strError = strprintf("time between votes is too soon - %s - %lli sec < %lli sec\n",
      69           2 :                     vote.GetHash().ToString(), voteTime - oldTime, BUDGET_VOTE_UPDATE_MIN);
      70           1 :             LogPrint(BCLog::MNBUDGET, "%s: %s\n", __func__, strError);
      71           1 :             return false;
      72             :         }
      73           0 :         strAction = "Existing vote updated:";
      74             :     }
      75             : 
      76          47 :     mapVotes[mnId] = vote;
      77          83 :     LogPrint(BCLog::MNBUDGET, "%s: %s %s\n", __func__, strAction.c_str(), vote.GetHash().ToString().c_str());
      78             :     return true;
      79             : }
      80             : 
      81           0 : UniValue CFinalizedBudget::GetVotesObject() const
      82             : {
      83           0 :     UniValue ret(UniValue::VOBJ);
      84           0 :     for (const auto& it: mapVotes) {
      85           0 :         const CFinalizedBudgetVote& vote = it.second;
      86           0 :         ret.pushKV(vote.GetVin().prevout.ToStringShort(), vote.ToJSON());
      87             :     }
      88           0 :     return ret;
      89             : }
      90             : 
      91         129 : void CFinalizedBudget::SetSynced(bool synced)
      92             : {
      93         511 :     for (auto& it: mapVotes) {
      94         382 :         CFinalizedBudgetVote& vote = it.second;
      95         382 :         if (synced) {
      96         257 :             if (vote.IsValid()) vote.SetSynced(true);
      97             :         } else {
      98         382 :             vote.SetSynced(false);
      99             :         }
     100             :     }
     101         129 : }
     102             : 
     103          18 : bool CFinalizedBudget::CheckProposals(const std::map<uint256, CBudgetProposal>& mapWinningProposals) const
     104             : {
     105          18 :     if (mapWinningProposals.empty()) {
     106           2 :         LogPrint(BCLog::MNBUDGET,"%s: No Budget-Proposals found, aborting\n", __func__);
     107           2 :         return false;
     108             :     }
     109             : 
     110          16 :     if (mapWinningProposals.size() != vecBudgetPayments.size()) {
     111           0 :         LogPrint(BCLog::MNBUDGET,"%s: Budget-Proposal length (%ld) doesn't match Budget-Payment length (%ld).\n",
     112             :                 __func__, mapWinningProposals.size(), vecBudgetPayments.size());
     113           0 :         return false;
     114             :     }
     115             : 
     116          32 :     for (unsigned int i = 0; i < vecBudgetPayments.size(); i++) {
     117          32 :         LogPrint(BCLog::MNBUDGET,"%s: Budget-Payments - nProp %d %s\n", __func__, i, vecBudgetPayments[i].nProposalHash.ToString());
     118          48 :         LogPrint(BCLog::MNBUDGET,"%s: Budget-Payments - Payee %d %s\n", __func__, i, HexStr(vecBudgetPayments[i].payee));
     119          16 :         LogPrint(BCLog::MNBUDGET,"%s: Budget-Payments - nAmount %d %lli\n", __func__, i, vecBudgetPayments[i].nAmount);
     120             :     }
     121             : 
     122          32 :     for (const auto& it: mapWinningProposals) {
     123          32 :         LogPrint(BCLog::MNBUDGET,"%s: Budget-Proposals - nProp %s\n", __func__, (it.first).ToString());
     124          48 :         LogPrint(BCLog::MNBUDGET,"%s: Budget-Proposals - Payee %s\n", __func__, HexStr((it.second).GetPayee()));
     125          16 :         LogPrint(BCLog::MNBUDGET,"%s: Budget-Proposals - nAmount %lli\n", __func__, (it.second).GetAmount());
     126             :     }
     127             : 
     128          31 :     for (const CTxBudgetPayment& p : vecBudgetPayments) {
     129          16 :         const auto& it = mapWinningProposals.find(p.nProposalHash);
     130          16 :         if (it == mapWinningProposals.end()) {
     131           2 :             LogPrint(BCLog::MNBUDGET,"%s: Proposal %s not found\n", __func__, p.nProposalHash.ToString());
     132           1 :             return false;
     133             :         }
     134             : 
     135          15 :         const CBudgetProposal& prop = it->second;
     136          15 :         if (p.payee != prop.GetPayee()) {
     137           0 :             LogPrint(BCLog::MNBUDGET,"%s: payee doesn't match %s != %s\n", __func__, HexStr(p.payee), HexStr(prop.GetPayee()));
     138           0 :             return false;
     139             :         }
     140             : 
     141          15 :         if (p.nAmount != prop.GetAmount()) {
     142           0 :             LogPrint(BCLog::MNBUDGET,"%s: payee amount doesn't match %lli != %lli\n", __func__, p.nAmount, prop.GetAmount());
     143           0 :             return false;
     144             :         }
     145             :     }
     146             : 
     147          15 :     LogPrint(BCLog::MNBUDGET,"%s: Finalized Budget Matches! Submitting Vote.\n", __func__);
     148             :     return true;
     149             : }
     150             : 
     151          17 : CAmount CFinalizedBudget::GetTotalPayout() const
     152             : {
     153          17 :     CAmount ret = 0;
     154             : 
     155          34 :     for (auto & vecBudgetPayment : vecBudgetPayments) {
     156          17 :         ret += vecBudgetPayment.nAmount;
     157             :     }
     158             : 
     159          17 :     return ret;
     160             : }
     161             : 
     162          16 : std::vector<uint256> CFinalizedBudget::GetProposalsHashes() const
     163             : {
     164          16 :     std::vector<uint256> vHashes;
     165          32 :     for (const CTxBudgetPayment& budgetPayment : vecBudgetPayments) {
     166          16 :         vHashes.push_back(budgetPayment.nProposalHash);
     167             :     }
     168          16 :     return vHashes;
     169             : }
     170             : 
     171         319 : void CFinalizedBudget::SyncVotes(CNode* pfrom, bool fPartial, int& nInvCount) const
     172             : {
     173        1269 :     for (const auto& it: mapVotes) {
     174         950 :         const CFinalizedBudgetVote& vote = it.second;
     175         950 :         if (vote.IsValid() && (!fPartial || !vote.IsSynced())) {
     176         335 :             pfrom->PushInventory(CInv(MSG_BUDGET_FINALIZED_VOTE, vote.GetHash()));
     177         335 :             nInvCount++;
     178             :         }
     179             :     }
     180         319 : }
     181             : 
     182          17 : bool CFinalizedBudget::CheckStartEnd()
     183             : {
     184          17 :     if (nBlockStart == 0) {
     185           0 :         strInvalid = "Invalid BlockStart == 0";
     186           0 :         return false;
     187             :     }
     188             : 
     189             :     // Must be the correct block for payment to happen (once a month)
     190          17 :     if (nBlockStart % Params().GetConsensus().nBudgetCycleBlocks != 0) {
     191           0 :         strInvalid = "Invalid BlockStart";
     192           0 :         return false;
     193             :     }
     194             : 
     195             :     // The following 2 checks check the same (basically if vecBudgetPayments.size() > 100)
     196          17 :     if (GetBlockEnd() - nBlockStart + 1 > (int) MAX_PROPOSALS_PER_CYCLE) {
     197           0 :         strInvalid = "Invalid BlockEnd";
     198           0 :         return false;
     199             :     }
     200          17 :     if ((int)vecBudgetPayments.size() > (int) MAX_PROPOSALS_PER_CYCLE) {
     201           0 :         strInvalid = "Invalid budget payments count (too many)";
     202           0 :         return false;
     203             :     }
     204             : 
     205             :     return true;
     206             : }
     207             : 
     208          17 : bool CFinalizedBudget::CheckAmount(const CAmount& nTotalBudget)
     209             : {
     210             :     // Can only pay out 10% of the possible coins (min value of coins)
     211          17 :     if (GetTotalPayout() > nTotalBudget) {
     212           0 :         strInvalid = "Invalid Payout (more than max)";
     213           0 :         return false;
     214             :     }
     215             : 
     216             :     return true;
     217             : }
     218             : 
     219          17 : bool CFinalizedBudget::CheckName()
     220             : {
     221          17 :     if (strBudgetName == "") {
     222           0 :         strInvalid = "Invalid Budget Name";
     223           0 :         return false;
     224             :     }
     225             : 
     226             :     return true;
     227             : }
     228             : 
     229         106 : bool CFinalizedBudget::updateExpired(int nCurrentHeight)
     230             : {
     231             :     // Remove finalized budgets 2 * MAX_PROPOSALS_PER_CYCLE blocks after their end
     232         106 :     const int nBlockEnd = GetBlockEnd();
     233         106 :     if (nCurrentHeight >= nBlockEnd + 2 * (int) MAX_PROPOSALS_PER_CYCLE) {
     234           1 :         strInvalid = strprintf("(ends at block %ld) too old and obsolete (current %ld)", nBlockEnd, nCurrentHeight);
     235           1 :         return true;
     236             :     }
     237             : 
     238             :     return false;
     239             : }
     240             : 
     241          17 : bool CFinalizedBudget::IsWellFormed(const CAmount& nTotalBudget)
     242             : {
     243          17 :     return CheckStartEnd() && CheckAmount(nTotalBudget) && CheckName();
     244             : }
     245             : 
     246         106 : bool CFinalizedBudget::UpdateValid(int nCurrentHeight)
     247             : {
     248         106 :     fValid = false;
     249             : 
     250         106 :     if (updateExpired(nCurrentHeight)) {
     251             :         return false;
     252             :     }
     253             : 
     254         105 :     fValid = true;
     255         105 :     strInvalid.clear();
     256         105 :     return true;
     257             : }
     258             : 
     259        5310 : int CFinalizedBudget::GetVoteCount() const
     260             : {
     261        5310 :     int ret = 0;
     262       20069 :     for (const auto& it : mapVotes) {
     263       14759 :         if (it.second.IsValid()) {
     264       10380 :             ret++;
     265             :         }
     266             :     }
     267        5310 :     return ret;
     268             : }
     269             : 
     270           0 : std::vector<uint256> CFinalizedBudget::GetVotesHashes() const
     271             : {
     272           0 :     std::vector<uint256> vRet;
     273           0 :     for (const auto& it: mapVotes) {
     274           0 :         vRet.push_back(it.second.GetHash());
     275             :     }
     276           0 :     return vRet;
     277             : }
     278             : 
     279          27 : bool CFinalizedBudget::IsPaidAlready(const uint256& nProposalHash, const uint256& nBlockHash, int nBlockHeight) const
     280             : {
     281             :     // Remove budget-payments from former/future payment cycles
     282          27 :     int nPaidBlockHeight = 0;
     283          27 :     uint256 nOldProposalHash;
     284             : 
     285          27 :     for(auto it = mapPayment_History.begin(); it != mapPayment_History.end(); /* No incrementation needed */ ) {
     286          27 :         nPaidBlockHeight = (*it).second.second;
     287          27 :         if((nPaidBlockHeight < GetBlockStart()) || (nPaidBlockHeight > GetBlockEnd())) {
     288           0 :             nOldProposalHash = (*it).first;
     289           0 :             LogPrint(BCLog::MNBUDGET, "%s: Budget Proposal %s, Block %d from old cycle deleted\n",
     290             :                     __func__, nOldProposalHash.ToString().c_str(), nPaidBlockHeight);
     291           0 :             it = mapPayment_History.erase(it);
     292             :         } else {
     293          81 :             ++it;
     294             :         }
     295             :     }
     296             : 
     297             :     // Now that we only have payments from the current payment cycle check if this budget was paid already
     298          27 :     if(mapPayment_History.count(nProposalHash) == 0) {
     299             :         // New proposal payment, insert into map for checks with later blocks from this cycle
     300          13 :         mapPayment_History.emplace(std::piecewise_construct,
     301          13 :                                    std::forward_as_tuple(nProposalHash),
     302          13 :                                    std::forward_as_tuple(nBlockHash, nBlockHeight));
     303          33 :         LogPrint(BCLog::MNBUDGET, "%s: Budget Proposal %s, Block %d (%s) added to payment history (size=%d)\n",
     304             :                 __func__, nProposalHash.ToString(), nBlockHeight, nBlockHash.ToString(), mapPayment_History.size());
     305          13 :         return false;
     306             :     }
     307             :     // This budget payment was already checked/paid
     308          14 :     const uint256& nPaidBlockHash = mapPayment_History.at(nProposalHash).first;
     309             : 
     310             :     // If we are checking a different block, and the paid one is on chain
     311             :     // -> reject transaction so it gets paid to a masternode instead
     312          14 :     if (nBlockHash != nPaidBlockHash) {
     313          18 :         LOCK(cs_main);
     314           9 :         CBlockIndex* pindex = LookupBlockIndex(nPaidBlockHash);
     315          11 :         return pindex && chainActive.Contains(pindex);
     316             :     }
     317             : 
     318             :     // Re-checking same block. Not a double payment.
     319             :     return false;
     320             : }
     321             : 
     322          27 : TrxValidationStatus CFinalizedBudget::IsTransactionValid(const CTransaction& txNew, const uint256& nBlockHash, int nBlockHeight) const
     323             : {
     324          27 :     const int nBlockEnd = GetBlockEnd();
     325          27 :     if (nBlockHeight > nBlockEnd) {
     326           0 :         LogPrint(BCLog::MNBUDGET,"%s: Invalid block - height: %d end: %d\n", __func__, nBlockHeight, nBlockEnd);
     327           0 :         return TrxValidationStatus::InValid;
     328             :     }
     329          27 :     if (nBlockHeight < nBlockStart) {
     330           0 :         LogPrint(BCLog::MNBUDGET,"%s: Invalid block - height: %d start: %d\n", __func__, nBlockHeight, nBlockStart);
     331           0 :         return TrxValidationStatus::InValid;
     332             :     }
     333             : 
     334          27 :     const int nCurrentBudgetPayment = nBlockHeight - nBlockStart;
     335          27 :     if (nCurrentBudgetPayment > (int)vecBudgetPayments.size() - 1) {
     336           0 :         LogPrint(BCLog::MNBUDGET,"%s: Invalid last block - current budget payment: %d of %d\n",
     337             :                 __func__, nCurrentBudgetPayment + 1, (int)vecBudgetPayments.size());
     338           0 :         return TrxValidationStatus::InValid;
     339             :     }
     340             : 
     341             :     // Check if this proposal was paid already. If so, pay a masternode instead
     342          27 :     if(IsPaidAlready(vecBudgetPayments[nCurrentBudgetPayment].nProposalHash, nBlockHash, nBlockHeight)) {
     343           0 :         LogPrint(BCLog::MNBUDGET,"%s: Double Budget Payment of %d for proposal %d detected. Paying a masternode instead.\n",
     344             :                 __func__, vecBudgetPayments[nCurrentBudgetPayment].nAmount, vecBudgetPayments[nCurrentBudgetPayment].nProposalHash.GetHex());
     345             :         // No matter what we've found before, stop all checks here. In future releases there might be more than one budget payment
     346             :         // per block, so even if the first one was not paid yet this one disables all budget payments for this block.
     347           0 :         return TrxValidationStatus::DoublePayment;
     348             :     }
     349             : 
     350             :     // Search the payment
     351          27 :     const CScript& scriptExpected = vecBudgetPayments[nCurrentBudgetPayment].payee;
     352          27 :     const CAmount& amountExpected = vecBudgetPayments[nCurrentBudgetPayment].nAmount;
     353             :     // Budget payment is usually the last output of coinstake txes, iterate backwards
     354          33 :     for (auto out = txNew.vout.rbegin(); out != txNew.vout.rend(); ++out) {
     355          86 :         LogPrint(BCLog::MNBUDGET,"%s: nCurrentBudgetPayment=%d, payee=%s == out.scriptPubKey=%s, amount=%ld == out.nValue=%ld\n",
     356             :                 __func__, nCurrentBudgetPayment, HexStr(scriptExpected), HexStr(out->scriptPubKey), amountExpected, out->nValue);
     357          30 :         if (scriptExpected == out->scriptPubKey && amountExpected == out->nValue) {
     358             :             // payment found
     359          38 :             LogPrint(BCLog::MNBUDGET,"%s: Found valid Budget Payment of %d for proposal %d\n",
     360             :                     __func__, amountExpected, vecBudgetPayments[nCurrentBudgetPayment].nProposalHash.GetHex());
     361          24 :             return TrxValidationStatus::Valid;
     362             :         }
     363             :     }
     364             : 
     365             :     // payment not found
     366          30 :     CTxDestination address1;
     367           3 :     ExtractDestination(scriptExpected, address1);
     368           3 :     LogPrint(BCLog::MNBUDGET,"%s: Missing required payment - %s: %d c: %d\n",
     369             :             __func__, EncodeDestination(address1), amountExpected, nCurrentBudgetPayment);
     370           3 :     return TrxValidationStatus::InValid;
     371             : }
     372             : 
     373          39 : bool CFinalizedBudget::GetBudgetPaymentByBlock(int64_t nBlockHeight, CTxBudgetPayment& payment) const
     374             : {
     375          39 :     int i = nBlockHeight - GetBlockStart();
     376          39 :     if (i < 0) return false;
     377          39 :     if (i > (int)vecBudgetPayments.size() - 1) return false;
     378          39 :     payment = vecBudgetPayments[i];
     379          39 :     return true;
     380             : }
     381             : 
     382          41 : bool CFinalizedBudget::GetPayeeAndAmount(int64_t nBlockHeight, CScript& payee, CAmount& nAmount) const
     383             : {
     384          41 :     int i = nBlockHeight - GetBlockStart();
     385          41 :     if (i < 0) return false;
     386          41 :     if (i > (int)vecBudgetPayments.size() - 1) return false;
     387          41 :     payee = vecBudgetPayments[i].payee;
     388          41 :     nAmount = vecBudgetPayments[i].nAmount;
     389          41 :     return true;
     390             : }
     391             : 
     392             : // return broadcast serialization
     393          14 : CDataStream CFinalizedBudget::GetBroadcast() const
     394             : {
     395          14 :     CDataStream broadcast(SER_NETWORK, PROTOCOL_VERSION);
     396          14 :     broadcast.reserve(1000);
     397          14 :     broadcast << LIMITED_STRING(strBudgetName, 20);
     398          14 :     broadcast << nBlockStart;
     399          14 :     broadcast << vecBudgetPayments;
     400          14 :     broadcast << nFeeTXHash;
     401          14 :     return broadcast;
     402             : }
     403             : 
     404             : 
     405           2 : bool CFinalizedBudget::operator>(const CFinalizedBudget& other) const
     406             : {
     407           2 :     const int count = GetVoteCount();
     408           2 :     const int otherCount = other.GetVoteCount();
     409             : 
     410           2 :     if (count == otherCount) return UintToArith256(GetFeeTXHash()) > UintToArith256(other.GetFeeTXHash());
     411             : 
     412           2 :     return count > otherCount;
     413             : }
     414             : 
     415          13 : void CFinalizedBudget::Relay()
     416             : {
     417          13 :     CInv inv(MSG_BUDGET_FINALIZED, GetHash());
     418          13 :     g_connman->RelayInv(inv);
     419          13 : }

Generated by: LCOV version 1.14