LCOV - code coverage report
Current view: top level - src/budget - budgetproposal.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 122 159 76.7 %
Date: 2025-02-23 09:33:43 Functions: 26 27 96.3 %

          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/budgetproposal.h"
       7             : #include "chainparams.h"
       8             : #include "script/standard.h"
       9             : #include "utilstrencodings.h"
      10             : 
      11         369 : CBudgetProposal::CBudgetProposal():
      12             :         nAllotted(0),
      13             :         fValid(true),
      14             :         strInvalid(""),
      15             :         strProposalName("unknown"),
      16             :         strURL(""),
      17             :         nBlockStart(0),
      18             :         nBlockEnd(0),
      19             :         address(),
      20             :         nAmount(0),
      21             :         nFeeTXHash(UINT256_ZERO),
      22         369 :         nTime(0)
      23         369 : {}
      24             : 
      25          38 : CBudgetProposal::CBudgetProposal(const std::string& name,
      26             :                                  const std::string& url,
      27             :                                  int paycount,
      28             :                                  const CScript& payee,
      29             :                                  const CAmount& amount,
      30             :                                  int blockstart,
      31          38 :                                  const uint256& nfeetxhash):
      32             :         nAllotted(0),
      33             :         fValid(true),
      34             :         strInvalid(""),
      35             :         strProposalName(name),
      36             :         strURL(url),
      37             :         nBlockStart(blockstart),
      38             :         address(payee),
      39          38 :         nAmount(amount),
      40             :         nFeeTXHash(nfeetxhash),
      41         114 :         nTime(0)
      42             : {
      43             :     // calculate the expiration block
      44          38 :     nBlockEnd = nBlockStart + (Params().GetConsensus().nBudgetCycleBlocks + 1)  * paycount;
      45          38 : }
      46             : 
      47             : // initialize from network broadcast message
      48         165 : bool CBudgetProposal::ParseBroadcast(CDataStream& broadcast)
      49             : {
      50         165 :     *this = CBudgetProposal();
      51         165 :     try {
      52         165 :         broadcast >> LIMITED_STRING(strProposalName, 20);
      53         165 :         broadcast >> LIMITED_STRING(strURL, 64);
      54         165 :         broadcast >> nTime;
      55         165 :         broadcast >> nBlockStart;
      56         165 :         broadcast >> nBlockEnd;
      57         165 :         broadcast >> nAmount;
      58         165 :         broadcast >> address;
      59         165 :         broadcast >> nFeeTXHash;
      60           0 :     } catch (std::exception& e) {
      61           0 :         return error("Unable to deserialize proposal broadcast: %s", e.what());
      62             :     }
      63             :     return true;
      64             : }
      65             : 
      66        5155 : void CBudgetProposal::SyncVotes(CNode* pfrom, bool fPartial, int& nInvCount) const
      67             : {
      68        6158 :     for (const auto& it: mapVotes) {
      69        1003 :         const CBudgetVote& vote = it.second;
      70        1003 :         if (vote.IsValid() && (!fPartial || !vote.IsSynced())) {
      71         356 :             pfrom->PushInventory(CInv(MSG_BUDGET_VOTE, vote.GetHash()));
      72         356 :             nInvCount++;
      73             :         }
      74             :     }
      75        5155 : }
      76             : 
      77        1412 : bool CBudgetProposal::IsHeavilyDownvoted(int mnCount)
      78             : {
      79        1412 :     if (GetNays() - GetYeas() > 3 * mnCount / 10) {
      80           0 :         strInvalid = "Heavily Downvoted";
      81           0 :         return true;
      82             :     }
      83             :     return false;
      84             : }
      85             : 
      86         203 : bool CBudgetProposal::CheckStartEnd()
      87             : {
      88             :     // block start must be a superblock
      89         406 :     if (nBlockStart < 0 ||
      90         203 :             nBlockStart % Params().GetConsensus().nBudgetCycleBlocks != 0) {
      91           0 :         strInvalid = "Invalid nBlockStart";
      92           0 :         return false;
      93             :     }
      94             : 
      95         203 :     if (nBlockEnd < nBlockStart) {
      96           0 :         strInvalid = "Invalid nBlockEnd (end before start)";
      97           0 :         return false;
      98             :     }
      99             : 
     100         203 :     if (GetTotalPaymentCount() > Params().GetConsensus().nMaxProposalPayments) {
     101           0 :         strInvalid = "Invalid payment count";
     102           0 :         return false;
     103             :     }
     104             : 
     105             :     return true;
     106             : }
     107             : 
     108         203 : bool CBudgetProposal::CheckAmount(const CAmount& nTotalBudget)
     109             : {
     110             :     // check minimum amount
     111         203 :     if (nAmount < PROPOSAL_MIN_AMOUNT) {
     112           0 :         strInvalid = "Invalid nAmount (too low)";
     113           0 :         return false;
     114             :     }
     115             : 
     116             :     // check maximum amount
     117             :     // can only pay out 10% of the possible coins (min value of coins)
     118         203 :     if (nAmount > nTotalBudget) {
     119           0 :         strInvalid = "Invalid nAmount (too high)";
     120           0 :         return false;
     121             :     }
     122             : 
     123             :     return true;
     124             : }
     125             : 
     126         203 : bool CBudgetProposal::CheckAddress()
     127             : {
     128             :     // !TODO: There might be an issue with multisig in the coinbase on mainnet
     129             :     // we will add support for it in a future release.
     130         203 :     if (address.IsPayToScriptHash()) {
     131           0 :         strInvalid = "Multisig is not currently supported.";
     132           0 :         return false;
     133             :     }
     134             : 
     135             :     // Check address
     136         406 :     CTxDestination dest;
     137         203 :     if (!ExtractDestination(address, dest, false)) {
     138           0 :         strInvalid = "Invalid script";
     139             :         return false;
     140             :     }
     141         203 :     if (!IsValidDestination(dest)) {
     142         203 :         strInvalid = "Invalid recipient address";
     143             :         return false;
     144             :     }
     145             : 
     146             :     return true;
     147             : }
     148             : 
     149             : /* TODO: Add this to IsWellFormed() for the next hard-fork
     150             :  * This will networkly reject malformed proposal names and URLs
     151             :  */
     152           0 : bool CBudgetProposal::CheckStrings()
     153             : {
     154           0 :     if (strProposalName != SanitizeString(strProposalName)) {
     155           0 :         strInvalid = "Proposal name contains illegal characters.";
     156           0 :         return false;
     157             :     }
     158           0 :     if (strURL != SanitizeString(strURL)) {
     159           0 :         strInvalid = "Proposal URL contains illegal characters.";
     160             :     }
     161             : }
     162             : 
     163         203 : bool CBudgetProposal::IsWellFormed(const CAmount& nTotalBudget)
     164             : {
     165         203 :     return CheckStartEnd() && CheckAmount(nTotalBudget) && CheckAddress();
     166             : }
     167             : 
     168        1637 : bool CBudgetProposal::updateExpired(int nCurrentHeight)
     169             : {
     170        1637 :     if (IsExpired(nCurrentHeight)) {
     171           0 :         strInvalid = "Proposal expired";
     172           0 :         return true;
     173             :     }
     174             :     return false;
     175             : }
     176             : 
     177        1637 : bool CBudgetProposal::UpdateValid(int nCurrentHeight, int mnCount)
     178             : {
     179        1637 :     fValid = false;
     180             : 
     181             :     // Never kill a proposal before the first superblock
     182        1637 :     if (nCurrentHeight > nBlockStart && IsHeavilyDownvoted(mnCount)) {
     183             :         return false;
     184             :     }
     185             : 
     186        1637 :     if (updateExpired(nCurrentHeight)) {
     187             :         return false;
     188             :     }
     189             : 
     190        1637 :     fValid = true;
     191        1637 :     strInvalid.clear();
     192        1637 :     return true;
     193             : }
     194             : 
     195        1089 : bool CBudgetProposal::IsEstablished() const
     196             : {
     197        1089 :     return nTime < GetAdjustedTime() - Params().GetConsensus().nProposalEstablishmentTime;
     198             : }
     199             : 
     200         539 : bool CBudgetProposal::IsPassing(int nBlockStartBudget, int nBlockEndBudget, int mnCount) const
     201             : {
     202         539 :     if (!fValid)
     203             :         return false;
     204             : 
     205         539 :     if (this->nBlockStart > nBlockStartBudget)
     206             :         return false;
     207             : 
     208         539 :     if (this->nBlockEnd < nBlockEndBudget)
     209             :         return false;
     210             : 
     211         539 :     if (GetYeas() - GetNays() <= mnCount / 10)
     212             :         return false;
     213             : 
     214          43 :     if (!IsEstablished())
     215           0 :         return false;
     216             : 
     217             :     return true;
     218             : }
     219             : 
     220        1637 : bool CBudgetProposal::IsExpired(int nCurrentHeight) const
     221             : {
     222        1637 :     return nBlockEnd < nCurrentHeight;
     223             : }
     224             : 
     225          45 : bool CBudgetProposal::AddOrUpdateVote(const CBudgetVote& vote, std::string& strError)
     226             : {
     227          90 :     std::string strAction = "New vote inserted:";
     228          90 :     const COutPoint& mnId = vote.GetVin().prevout;
     229          45 :     const int64_t voteTime = vote.GetTime();
     230             : 
     231          90 :     if (mapVotes.count(mnId)) {
     232           0 :         const int64_t& oldTime = mapVotes[mnId].GetTime();
     233           0 :         if (oldTime > voteTime) {
     234           0 :             strError = strprintf("new vote older than existing vote - %s\n", vote.GetHash().ToString());
     235           0 :             LogPrint(BCLog::MNBUDGET, "%s: %s\n", __func__, strError);
     236           0 :             return false;
     237             :         }
     238           0 :         if (voteTime - oldTime < BUDGET_VOTE_UPDATE_MIN) {
     239           0 :             strError = strprintf("time between votes is too soon - %s - %lli sec < %lli sec\n",
     240           0 :                     vote.GetHash().ToString(), voteTime - oldTime, BUDGET_VOTE_UPDATE_MIN);
     241           0 :             LogPrint(BCLog::MNBUDGET, "%s: %s\n", __func__, strError);
     242           0 :             return false;
     243             :         }
     244           0 :         strAction = "Existing vote updated:";
     245             :     }
     246             : 
     247          45 :     mapVotes[mnId] = vote;
     248          90 :     LogPrint(BCLog::MNBUDGET, "%s: %s %s\n", __func__, strAction.c_str(), vote.GetHash().ToString().c_str());
     249             : 
     250             :     return true;
     251             : }
     252             : 
     253          54 : UniValue CBudgetProposal::GetVotesArray() const
     254             : {
     255          54 :     UniValue ret(UniValue::VARR);
     256         192 :     for (const auto& it: mapVotes) {
     257         138 :         ret.push_back(it.second.ToJSON());
     258             :     }
     259          54 :     return ret;
     260             : }
     261             : 
     262        2097 : void CBudgetProposal::SetSynced(bool synced)
     263             : {
     264        2521 :     for (auto& it: mapVotes) {
     265         424 :         CBudgetVote& vote = it.second;
     266         424 :         if (synced) {
     267         289 :             if (vote.IsValid()) vote.SetSynced(true);
     268             :         } else {
     269         424 :             vote.SetSynced(false);
     270             :         }
     271             :     }
     272        2097 : }
     273             : 
     274         507 : double CBudgetProposal::GetRatio() const
     275             : {
     276         507 :     int yeas = GetYeas();
     277         507 :     int nays = GetNays();
     278             : 
     279         507 :     if (yeas + nays == 0) return 0.0f;
     280             : 
     281          44 :     return ((double)(yeas) / (double)(yeas + nays));
     282             : }
     283             : 
     284       37861 : int CBudgetProposal::GetVoteCount(CBudgetVote::VoteDirection vd) const
     285             : {
     286       37861 :     int ret = 0;
     287       45231 :     for (const auto& it : mapVotes) {
     288        7370 :         const CBudgetVote& vote = it.second;
     289        7370 :         if (vote.GetDirection() == vd && vote.IsValid())
     290        3265 :             ret++;
     291             :     }
     292       37861 :     return ret;
     293             : }
     294             : 
     295        1724 : int CBudgetProposal::GetBlockStartCycle() const
     296             : {
     297             :     //end block is half way through the next cycle (so the proposal will be removed much after the payment is sent)
     298        1724 :     return GetBlockCycle(nBlockStart);
     299             : }
     300             : 
     301        2231 : int CBudgetProposal::GetBlockCycle(int nHeight)
     302             : {
     303        2231 :     return nHeight - nHeight % Params().GetConsensus().nBudgetCycleBlocks;
     304             : }
     305             : 
     306        2231 : int CBudgetProposal::GetBlockEndCycle() const
     307             : {
     308        2231 :     return nBlockEnd;
     309             : 
     310             : }
     311             : 
     312        1724 : int CBudgetProposal::GetTotalPaymentCount() const
     313             : {
     314        1724 :     return (GetBlockEndCycle() - GetBlockStartCycle()) / Params().GetConsensus().nBudgetCycleBlocks;
     315             : }
     316             : 
     317         507 : int CBudgetProposal::GetRemainingPaymentCount(int nCurrentHeight) const
     318             : {
     319             :     // If the proposal is already finished (passed the end block cycle), the payments value will be negative
     320         507 :     int nPayments = (GetBlockEndCycle() - GetBlockCycle(nCurrentHeight)) / Params().GetConsensus().nBudgetCycleBlocks - 1;
     321             :     // Take the lowest value
     322         507 :     return std::min(nPayments, GetTotalPaymentCount());
     323             : }
     324             : 
     325             : // return broadcast serialization
     326         165 : CDataStream CBudgetProposal::GetBroadcast() const
     327             : {
     328         165 :     CDataStream broadcast(SER_NETWORK, PROTOCOL_VERSION);
     329         165 :     broadcast.reserve(1000);
     330         165 :     broadcast << LIMITED_STRING(strProposalName, 20);
     331         165 :     broadcast << LIMITED_STRING(strURL, 64);
     332         165 :     broadcast << nTime;
     333         165 :     broadcast << nBlockStart;
     334         165 :     broadcast << nBlockEnd;
     335         165 :     broadcast << nAmount;
     336         165 :     broadcast << address;
     337         165 :     broadcast << nFeeTXHash;
     338         165 :     return broadcast;
     339             : }
     340             : 
     341         168 : void CBudgetProposal::Relay()
     342             : {
     343         168 :     CInv inv(MSG_BUDGET_PROPOSAL, GetHash());
     344         168 :     g_connman->RelayInv(inv);
     345         168 : }
     346             : 

Generated by: LCOV version 1.14