LCOV - code coverage report
Current view: top level - src/budget - budgetmanager.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 662 904 73.2 %
Date: 2026-08-09 10:51:41 Functions: 63 69 91.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/budgetmanager.h"
       7             : 
       8             : #include "consensus/validation.h"
       9             : #include "evo/deterministicmns.h"
      10             : #include "masternodeman.h"
      11             : #include "netmessagemaker.h"
      12             : #include "tiertwo/tiertwo_sync_state.h"
      13             : #include "tiertwo/netfulfilledman.h"
      14             : #include "util/validation.h"
      15             : #include "validation.h"   // GetTransaction, cs_main
      16             : 
      17             : #ifdef ENABLE_WALLET
      18             : #include "wallet/wallet.h" // future: use interface instead.
      19             : #endif
      20             : 
      21             : 
      22             : #define BUDGET_ORPHAN_VOTES_CLEANUP_SECONDS (60 * 60) // One hour.
      23             : // Request type used in the net requests manager to block peers asking budget sync too often
      24             : static const std::string BUDGET_SYNC_REQUEST_RECV = "budget-sync-recv";
      25             : 
      26             : CBudgetManager g_budgetman;
      27             : 
      28             : // Used to check both proposals and finalized-budgets collateral txes
      29             : bool CheckCollateral(const uint256& nTxCollateralHash, const uint256& nExpectedHash, std::string& strError, int64_t& nTime, int nCurrentHeight, bool fBudgetFinalization);
      30             : 
      31         293 : void CBudgetManager::ReloadMapSeen()
      32             : {
      33         879 :     const auto reloadSeenMap = [](auto& mutex1, auto& mutex2, const auto& mapBudgets, auto& mapSeen, auto& mapOrphans) {
      34        1172 :         LOCK2(mutex1, mutex2);
      35         586 :         mapSeen.clear();
      36         586 :         mapOrphans.clear();
      37         586 :         for (const auto& b : mapBudgets) {
      38           0 :             for (const auto& it : b.second.mapVotes) {
      39           0 :                 const auto& vote = it.second;
      40           0 :                 if (vote.IsValid()) {
      41           0 :                     mapSeen.emplace(vote.GetHash(), vote);
      42             :                 }
      43             :             }
      44             :         }
      45         586 :     };
      46             : 
      47         293 :     reloadSeenMap(cs_proposals, cs_votes, mapProposals, mapSeenProposalVotes, mapOrphanProposalVotes);
      48         293 :     reloadSeenMap(cs_budgets, cs_finalizedvotes, mapFinalizedBudgets, mapSeenFinalizedBudgetVotes, mapOrphanFinalizedBudgetVotes);
      49         293 : }
      50             : 
      51          25 : void CBudgetManager::CheckOrphanVotes()
      52             : {
      53          25 :     {
      54          50 :         LOCK2(cs_proposals, cs_votes);
      55          25 :         for (auto itOrphanVotes = mapOrphanProposalVotes.begin(); itOrphanVotes != mapOrphanProposalVotes.end();) {
      56           0 :             auto itProposal = mapProposals.find(itOrphanVotes->first);
      57           0 :             if (itProposal != mapProposals.end()) {
      58             :                 // Proposal found.
      59           0 :                 CBudgetProposal* bp = &(itProposal->second);
      60             :                 // Try to add orphan votes
      61           0 :                 for (const CBudgetVote& vote : itOrphanVotes->second.first) {
      62           0 :                     std::string strError;
      63           0 :                     if (!bp->AddOrUpdateVote(vote, strError)) {
      64           0 :                         LogPrint(BCLog::MNBUDGET, "Unable to add orphan vote for proposal: %s\n", strError);
      65             :                     }
      66             :                 }
      67             :                 // Remove entry from the map
      68           0 :                 itOrphanVotes = mapOrphanProposalVotes.erase(itOrphanVotes);
      69             :             } else {
      70           0 :                 ++itOrphanVotes;
      71             :             }
      72             :         }
      73             :     }
      74             : 
      75          25 :     {
      76          50 :         LOCK2(cs_budgets, cs_finalizedvotes);
      77          25 :         for (auto itOrphanVotes = mapOrphanFinalizedBudgetVotes.begin(); itOrphanVotes != mapOrphanFinalizedBudgetVotes.end();) {
      78           0 :             auto itFinalBudget = mapFinalizedBudgets.find(itOrphanVotes->first);
      79           0 :             if (itFinalBudget != mapFinalizedBudgets.end()) {
      80             :                 // Finalized budget found.
      81           0 :                 CFinalizedBudget* fb = &(itFinalBudget->second);
      82             :                 // Try to add orphan votes
      83           0 :                 for (const CFinalizedBudgetVote& vote : itOrphanVotes->second.first) {
      84           0 :                     std::string strError;
      85           0 :                     if (!fb->AddOrUpdateVote(vote, strError)) {
      86           0 :                         LogPrint(BCLog::MNBUDGET, "Unable to add orphan vote for final budget: %s\n", strError);
      87             :                     }
      88             :                 }
      89             :                 // Remove entry from the map
      90           0 :                 itOrphanVotes = mapOrphanFinalizedBudgetVotes.erase(itOrphanVotes);
      91             :             } else {
      92           0 :                 ++itOrphanVotes;
      93             :             }
      94             :         }
      95             :     }
      96             : 
      97          25 :     LogPrint(BCLog::MNBUDGET,"%s: Done\n", __func__);
      98          25 : }
      99             : 
     100           2 : uint256 CBudgetManager::SubmitFinalBudget()
     101             : {
     102           2 :     static int nSubmittedHeight = 0; // height at which final budget was submitted last time
     103           2 :     int nCurrentHeight = GetBestHeight();
     104             : 
     105           2 :     const int nBlocksPerCycle = Params().GetConsensus().nBudgetCycleBlocks;
     106           2 :     int nBlockStart = nCurrentHeight - nCurrentHeight % nBlocksPerCycle + nBlocksPerCycle;
     107           2 :     if (nSubmittedHeight >= nBlockStart){
     108           0 :         LogPrint(BCLog::MNBUDGET,"%s: nSubmittedHeight(=%ld) < nBlockStart(=%ld) condition not fulfilled.\n",
     109             :                 __func__, nSubmittedHeight, nBlockStart);
     110           0 :         return UINT256_ZERO;
     111             :     }
     112             : 
     113             :      // Submit final budget during the last 2 days (2880 blocks) before payment for Mainnet, about 9 minutes (9 blocks) for Testnet
     114           2 :     int finalizationWindow = ((nBlocksPerCycle / 30) * 2);
     115             : 
     116           2 :     if (Params().IsTestnet()) {
     117             :         // NOTE: 9 blocks for testnet is way to short to have any masternode submit an automatic vote on the finalized(!) budget,
     118             :         //       because those votes are only submitted/relayed once every 56 blocks in CFinalizedBudget::AutoCheck()
     119             : 
     120           0 :         finalizationWindow = 64; // 56 + 4 finalization confirmations + 4 minutes buffer for propagation
     121             :     }
     122             : 
     123           2 :     int nFinalizationStart = nBlockStart - finalizationWindow;
     124             : 
     125           2 :     int nOffsetToStart = nFinalizationStart - nCurrentHeight;
     126             : 
     127           2 :     if (nBlockStart - nCurrentHeight > finalizationWindow) {
     128           0 :         LogPrint(BCLog::MNBUDGET,"%s: Too early for finalization. Current block is %ld, next Superblock is %ld.\n", __func__, nCurrentHeight, nBlockStart);
     129           0 :         LogPrint(BCLog::MNBUDGET,"%s: First possible block for finalization: %ld. Last possible block for finalization: %ld. " /* Continued */
     130             :                 "You have to wait for %ld block(s) until Budget finalization will be possible\n", __func__, nFinalizationStart, nBlockStart, nOffsetToStart);
     131           0 :         return UINT256_ZERO;
     132             :     }
     133             : 
     134           2 :     std::vector<CBudgetProposal> vBudgetProposals = GetBudget();
     135           4 :     std::string strBudgetName = "main";
     136           4 :     std::vector<CTxBudgetPayment> vecTxBudgetPayments;
     137             : 
     138           4 :     for (const auto& p : vBudgetProposals) {
     139           4 :         CTxBudgetPayment txBudgetPayment;
     140           2 :         txBudgetPayment.nProposalHash = p.GetHash();
     141           2 :         txBudgetPayment.payee = p.GetPayee();
     142           2 :         txBudgetPayment.nAmount = p.GetAllotted();
     143           2 :         vecTxBudgetPayments.push_back(txBudgetPayment);
     144             :     }
     145             : 
     146           2 :     if (vecTxBudgetPayments.size() < 1) {
     147           0 :         LogPrint(BCLog::MNBUDGET,"%s: Found No Proposals For Period\n", __func__);
     148           0 :         return UINT256_ZERO;
     149             :     }
     150             : 
     151           4 :     CFinalizedBudget tempBudget(strBudgetName, nBlockStart, vecTxBudgetPayments, UINT256_ZERO);
     152           2 :     const uint256& budgetHash = tempBudget.GetHash();
     153           2 :     if (HaveFinalizedBudget(budgetHash)) {
     154           0 :         LogPrint(BCLog::MNBUDGET,"%s: Budget already exists - %s\n", __func__, budgetHash.ToString());
     155           0 :         nSubmittedHeight = nCurrentHeight;
     156           0 :         return UINT256_ZERO;
     157             :     }
     158             : 
     159             :     // See if collateral tx exists
     160           2 :     if (!mapUnconfirmedFeeTx.count(budgetHash)) {
     161             :         // create the collateral tx, send it to the network and return
     162           1 :         CTransactionRef wtx;
     163             :         // Get our change address
     164           1 :         if (vpwallets.empty() || !vpwallets[0]) {
     165           0 :             LogPrint(BCLog::MNBUDGET,"%s: Wallet not found\n", __func__);
     166           0 :             return UINT256_ZERO;
     167             :         }
     168             :         // Exit if wallet is locked
     169           1 :         if (vpwallets[0]->IsLocked()) {
     170           0 :             LogPrint(BCLog::MNBUDGET, "%s: Wallet is locked, can't make collateral transaction.\n", __func__);
     171           0 :             return UINT256_ZERO;
     172             :         }
     173           2 :         CReserveKey keyChange(vpwallets[0]);
     174           1 :         if (!vpwallets[0]->CreateBudgetFeeTX(wtx, budgetHash, keyChange, BUDGET_FEE_TX)) {
     175           0 :             LogPrint(BCLog::MNBUDGET,"%s: Can't make collateral transaction\n", __func__);
     176           0 :             return UINT256_ZERO;
     177             :         }
     178             :         // Send the tx to the network
     179           2 :         const CWallet::CommitResult& res = vpwallets[0]->CommitTransaction(wtx, keyChange, g_connman.get());
     180           1 :         if (res.status == CWallet::CommitStatus::OK) {
     181           1 :             const uint256& collateraltxid = wtx->GetHash();
     182           1 :             mapUnconfirmedFeeTx.emplace(budgetHash, collateraltxid);
     183           2 :             LogPrint(BCLog::MNBUDGET,"%s: Collateral sent. txid: %s\n", __func__, collateraltxid.ToString());
     184           1 :             return budgetHash;
     185             :         }
     186           0 :         return UINT256_ZERO;
     187             :     }
     188             : 
     189             :     // Collateral tx already exists, see if it's mature enough.
     190           2 :     CFinalizedBudget fb(strBudgetName, nBlockStart, vecTxBudgetPayments, mapUnconfirmedFeeTx.at(budgetHash));
     191           1 :     if (!AddFinalizedBudget(fb)) {
     192           0 :         return UINT256_ZERO;
     193             :     }
     194           1 :     fb.Relay();
     195           1 :     nSubmittedHeight = nCurrentHeight;
     196           2 :     LogPrint(BCLog::MNBUDGET,"%s: Done! %s\n", __func__, budgetHash.ToString());
     197           1 :     return budgetHash;
     198             : }
     199             : 
     200           5 : void CBudgetManager::SetBudgetProposalsStr(CFinalizedBudget& finalizedBudget) const
     201             : {
     202           5 :     const std::vector<uint256>& vHashes = finalizedBudget.GetProposalsHashes();
     203          10 :     std::string strProposals = "";
     204           5 :     {
     205           5 :         LOCK(cs_proposals);
     206          10 :         for (const uint256& hash: vHashes) {
     207          15 :             const std::string token = (mapProposals.count(hash) ? mapProposals.at(hash).GetName() : hash.ToString());
     208          10 :             strProposals += (strProposals == "" ? "" : ", ") + token;
     209             :         }
     210             :     }
     211          15 :     finalizedBudget.SetProposalsStr(strProposals);
     212           5 : }
     213             : 
     214           4 : std::string CBudgetManager::GetFinalizedBudgetStatus(const uint256& nHash) const
     215             : {
     216           4 :     CFinalizedBudget fb;
     217           4 :     if (!GetFinalizedBudget(nHash, fb))
     218           0 :         return strprintf("ERROR: cannot find finalized budget %s\n", nHash.ToString());
     219             : 
     220           8 :     std::string retBadHashes = "";
     221           8 :     std::string retBadPayeeOrAmount = "";
     222           4 :     int nBlockStart = fb.GetBlockStart();
     223           4 :     int nBlockEnd = fb.GetBlockEnd();
     224             : 
     225           8 :     for (int nBlockHeight = nBlockStart; nBlockHeight <= nBlockEnd; nBlockHeight++) {
     226           8 :         CTxBudgetPayment budgetPayment;
     227           4 :         if (!fb.GetBudgetPaymentByBlock(nBlockHeight, budgetPayment)) {
     228           0 :             LogPrint(BCLog::MNBUDGET,"%s: Couldn't find budget payment for block %lld\n", __func__, nBlockHeight);
     229           0 :             continue;
     230             :         }
     231             : 
     232           8 :         CBudgetProposal bp;
     233           4 :         if (!GetProposal(budgetPayment.nProposalHash, bp)) {
     234           0 :             retBadHashes += (retBadHashes == "" ? "" : ", ") + budgetPayment.nProposalHash.ToString();
     235           0 :             continue;
     236             :         }
     237             : 
     238           8 :         if (bp.GetPayee() != budgetPayment.payee || bp.GetAmount() != budgetPayment.nAmount) {
     239           0 :             retBadPayeeOrAmount += (retBadPayeeOrAmount == "" ? "" : ", ") + budgetPayment.nProposalHash.ToString();
     240             :         }
     241             :     }
     242             : 
     243           8 :     if (retBadHashes == "" && retBadPayeeOrAmount == "") return "OK";
     244             : 
     245           0 :     if (retBadHashes != "") retBadHashes = "Unknown proposal(s) hash! Check this proposal(s) before voting: " + retBadHashes;
     246           0 :     if (retBadPayeeOrAmount != "") retBadPayeeOrAmount = "Budget payee/nAmount doesn't match our proposal(s)! "+ retBadPayeeOrAmount;
     247             : 
     248           0 :     return retBadHashes + " -- " + retBadPayeeOrAmount;
     249             : }
     250             : 
     251           6 : bool CBudgetManager::AddFinalizedBudget(CFinalizedBudget& finalizedBudget, CNode* pfrom)
     252             : {
     253           6 :     AssertLockNotHeld(cs_budgets);    // need to lock cs_main here (CheckCollateral)
     254           6 :     const uint256& nHash = finalizedBudget.GetHash();
     255             : 
     256          18 :     if (WITH_LOCK(cs_budgets, return mapFinalizedBudgets.count(nHash))) {
     257           0 :         LogPrint(BCLog::MNBUDGET,"%s: finalized budget %s already added\n", __func__, nHash.ToString());
     258           0 :         return false;
     259             :     }
     260             : 
     261           6 :     if (!finalizedBudget.IsWellFormed(GetTotalBudget(finalizedBudget.GetBlockStart()))) {
     262           0 :         LogPrint(BCLog::MNBUDGET,"%s: invalid finalized budget: %s %s\n", __func__, nHash.ToString(), finalizedBudget.IsInvalidLogStr());
     263           0 :         return false;
     264             :     }
     265             : 
     266          12 :     std::string strError;
     267           6 :     int nCurrentHeight = GetBestHeight();
     268           6 :     const uint256& feeTxId = finalizedBudget.GetFeeTXHash();
     269           6 :     if (!CheckCollateral(feeTxId, nHash, strError, finalizedBudget.nTime, nCurrentHeight, true)) {
     270           0 :         LogPrint(BCLog::MNBUDGET,"%s: invalid finalized budget (%s) collateral id=%s - %s\n",
     271             :                 __func__, nHash.ToString(), feeTxId.ToString(), strError);
     272           6 :         finalizedBudget.SetStrInvalid(strError);
     273             :         return false;
     274             :     }
     275             : 
     276             :     // update expiration
     277           6 :     if (!finalizedBudget.UpdateValid(nCurrentHeight)) {
     278           0 :         LogPrint(BCLog::MNBUDGET,"%s: invalid finalized budget: %s %s\n", __func__, nHash.ToString(), finalizedBudget.IsInvalidLogStr());
     279           0 :         return false;
     280             :     }
     281             : 
     282             :     // Compare budget payments with existent proposals, don't care on the order, just verify proposals existence.
     283           6 :     std::vector<CBudgetProposal> vBudget = GetBudget();
     284          12 :     std::map<uint256, CBudgetProposal> mapWinningProposals;
     285          12 :     for (const CBudgetProposal& p: vBudget) { mapWinningProposals.emplace(p.GetHash(), p); }
     286           6 :     if (!finalizedBudget.CheckProposals(mapWinningProposals)) {
     287           2 :         finalizedBudget.SetStrInvalid("Invalid proposals");
     288           1 :         LogPrint(BCLog::MNBUDGET,"%s: Budget finalization does not match with winning proposals\n", __func__);
     289             :         // just for now (until v6), request proposals and budget sync in case we are missing them
     290           1 :         if (pfrom) {
     291           0 :             CNetMsgMaker maker(pfrom->GetSendVersion());
     292             :             // First, request single proposals that we don't have.
     293           0 :             for (const auto& propId : finalizedBudget.GetProposalsHashes()) {
     294           0 :                 if (!g_budgetman.HaveProposal(propId)) {
     295           0 :                     g_connman->PushMessage(pfrom, maker.Make(NetMsgType::BUDGETVOTESYNC, propId));
     296             :                 }
     297             :             }
     298             : 
     299             :             // Second a full budget sync for missing votes and the budget finalization that we are rejecting here.
     300             :             // Note: this will not make any effect on peers with version <= 70923 as they, invalidly, are blocking
     301             :             // follow-up budget sync request for the entire node life cycle.
     302           0 :             uint256 n;
     303           0 :             g_connman->PushMessage(pfrom, maker.Make(NetMsgType::BUDGETVOTESYNC, n));
     304             :         }
     305           1 :         return false;
     306             :     }
     307             : 
     308             :     // Add budget finalization.
     309           5 :     SetBudgetProposalsStr(finalizedBudget);
     310           5 :     ForceAddFinalizedBudget(nHash, feeTxId, finalizedBudget);
     311             : 
     312          20 :     LogPrint(BCLog::MNBUDGET,"%s: finalized budget %s [%s (%s)] added\n",
     313             :             __func__, nHash.ToString(), finalizedBudget.GetName(), finalizedBudget.GetProposalsStr());
     314             :     return true;
     315             : }
     316             : 
     317          10 : void CBudgetManager::ForceAddFinalizedBudget(const uint256& nHash, const uint256& feeTxId, const CFinalizedBudget& finalizedBudget)
     318             : {
     319          10 :     LOCK(cs_budgets);
     320          10 :     mapFinalizedBudgets.emplace(nHash, finalizedBudget);
     321             :     // Add to feeTx index
     322          10 :     mapFeeTxToBudget.emplace(feeTxId, nHash);
     323             :     // Remove the budget from the unconfirmed map, if it was there
     324          19 :     if (mapUnconfirmedFeeTx.count(nHash))
     325          10 :         mapUnconfirmedFeeTx.erase(nHash);
     326          10 : }
     327             : 
     328          24 : bool CBudgetManager::AddProposal(CBudgetProposal& budgetProposal)
     329             : {
     330          24 :     AssertLockNotHeld(cs_proposals);    // need to lock cs_main here (CheckCollateral)
     331          24 :     const uint256& nHash = budgetProposal.GetHash();
     332             : 
     333          72 :     if (WITH_LOCK(cs_proposals, return mapProposals.count(nHash))) {
     334           0 :         LogPrint(BCLog::MNBUDGET,"%s: proposal %s already added\n", __func__, nHash.ToString());
     335           0 :         return false;
     336             :     }
     337             : 
     338          24 :     if (!budgetProposal.IsWellFormed(GetTotalBudget(budgetProposal.GetBlockStart()))) {
     339           0 :         LogPrint(BCLog::MNBUDGET,"%s: Invalid budget proposal %s %s\n", __func__, nHash.ToString(), budgetProposal.IsInvalidLogStr());
     340           0 :         return false;
     341             :     }
     342             : 
     343          48 :     std::string strError;
     344          24 :     int nCurrentHeight = GetBestHeight();
     345          24 :     const uint256& feeTxId = budgetProposal.GetFeeTXHash();
     346          24 :     if (!CheckCollateral(feeTxId, nHash, strError, budgetProposal.nTime, nCurrentHeight, false)) {
     347           0 :         LogPrint(BCLog::MNBUDGET,"%s: invalid budget proposal (%s) collateral id=%s - %s\n",
     348             :                 __func__, nHash.ToString(), feeTxId.ToString(), strError);
     349          24 :         budgetProposal.SetStrInvalid(strError);
     350             :         return false;
     351             :     }
     352             : 
     353             :     // update expiration / heavily-downvoted
     354          24 :     int mnCount = mnodeman.CountEnabled();
     355          24 :     if (!budgetProposal.UpdateValid(nCurrentHeight, mnCount)) {
     356           0 :         LogPrint(BCLog::MNBUDGET,"%s: Invalid budget proposal %s %s\n", __func__, nHash.ToString(), budgetProposal.IsInvalidLogStr());
     357           0 :         return false;
     358             :     }
     359             : 
     360          24 :     {
     361          24 :         LOCK(cs_proposals);
     362          24 :         mapProposals.emplace(nHash, budgetProposal);
     363             :         // Add to feeTx index
     364          24 :         mapFeeTxToProposal.emplace(feeTxId, nHash);
     365             :     }
     366          72 :     LogPrint(BCLog::MNBUDGET,"%s: budget proposal %s [%s] added\n", __func__, nHash.ToString(), budgetProposal.GetName());
     367             : 
     368             :     return true;
     369             : }
     370             : 
     371         274 : void CBudgetManager::CheckAndRemove()
     372             : {
     373         274 :     int nCurrentHeight = GetBestHeight();
     374         274 :     std::map<uint256, CFinalizedBudget> tmpMapFinalizedBudgets;
     375         274 :     std::map<uint256, CBudgetProposal> tmpMapProposals;
     376             : 
     377             :     // Get MN count, used for the heavily down-voted check
     378         274 :     int mnCount = mnodeman.CountEnabled();
     379             : 
     380             :     // Check Proposals first
     381         274 :     {
     382         274 :         LOCK(cs_proposals);
     383         274 :         LogPrint(BCLog::MNBUDGET, "%s: mapProposals cleanup - size before: %d\n", __func__, mapProposals.size());
     384         317 :         for (auto& it: mapProposals) {
     385          43 :             CBudgetProposal* pbudgetProposal = &(it.second);
     386          43 :             if (!pbudgetProposal->UpdateValid(nCurrentHeight, mnCount)) {
     387           0 :                 LogPrint(BCLog::MNBUDGET,"%s: Invalid budget proposal %s %s\n", __func__, (it.first).ToString(), pbudgetProposal->IsInvalidLogStr());
     388          43 :                 mapFeeTxToProposal.erase(pbudgetProposal->GetFeeTXHash());
     389             :             } else {
     390         172 :                  LogPrint(BCLog::MNBUDGET,"%s: Found valid budget proposal: %s %s\n", __func__,
     391             :                           pbudgetProposal->GetName(), pbudgetProposal->GetFeeTXHash().ToString());
     392          86 :                  tmpMapProposals.emplace(pbudgetProposal->GetHash(), *pbudgetProposal);
     393             :             }
     394             :         }
     395             :         // Remove invalid entries by overwriting complete map
     396         274 :         mapProposals.swap(tmpMapProposals);
     397         274 :         LogPrint(BCLog::MNBUDGET, "%s: mapProposals cleanup - size after: %d\n", __func__, mapProposals.size());
     398             :     }
     399             : 
     400             :     // Then check finalized budgets
     401         274 :     {
     402         274 :         LOCK(cs_budgets);
     403         274 :         LogPrint(BCLog::MNBUDGET, "%s: mapFinalizedBudgets cleanup - size before: %d\n", __func__, mapFinalizedBudgets.size());
     404         279 :         for (auto& it: mapFinalizedBudgets) {
     405           5 :             CFinalizedBudget* pfinalizedBudget = &(it.second);
     406           5 :             if (!pfinalizedBudget->UpdateValid(nCurrentHeight)) {
     407           0 :                 LogPrint(BCLog::MNBUDGET,"%s: Invalid finalized budget %s %s\n", __func__, (it.first).ToString(), pfinalizedBudget->IsInvalidLogStr());
     408           5 :                 mapFeeTxToBudget.erase(pfinalizedBudget->GetFeeTXHash());
     409             :             } else {
     410          20 :                 LogPrint(BCLog::MNBUDGET,"%s: Found valid finalized budget: %s %s\n", __func__,
     411             :                           pfinalizedBudget->GetName(), pfinalizedBudget->GetFeeTXHash().ToString());
     412          10 :                 tmpMapFinalizedBudgets.emplace(pfinalizedBudget->GetHash(), *pfinalizedBudget);
     413             :             }
     414             :         }
     415             :         // Remove invalid entries by overwriting complete map
     416         274 :         mapFinalizedBudgets = tmpMapFinalizedBudgets;
     417         274 :         LogPrint(BCLog::MNBUDGET, "%s: mapFinalizedBudgets cleanup - size after: %d\n", __func__, mapFinalizedBudgets.size());
     418             :     }
     419             :     // Masternodes vote on valid ones
     420         274 :     VoteOnFinalizedBudgets();
     421         274 : }
     422             : 
     423       54872 : void CBudgetManager::RemoveByFeeTxId(const uint256& feeTxId)
     424             : {
     425       54872 :     {
     426       54872 :         LOCK(cs_proposals);
     427             :         // Is this collateral related to a proposal?
     428       54872 :         const auto& it = mapFeeTxToProposal.find(feeTxId);
     429       54872 :         if (it != mapFeeTxToProposal.end()) {
     430             :             // Remove proposal
     431           0 :             CBudgetProposal* p = FindProposal(it->second);
     432           0 :             if (p) {
     433           0 :                 LogPrintf("%s: Removing proposal %s (collateral disconnected, id=%s)\n", __func__, p->GetName(), feeTxId.ToString());
     434           0 :                 {
     435             :                     // Erase seen/orphan votes
     436           0 :                     LOCK(cs_votes);
     437           0 :                     for (const auto& vote: p->GetVotes()) {
     438           0 :                         const uint256& hash{vote.second.GetHash()};
     439           0 :                         mapSeenProposalVotes.erase(hash);
     440           0 :                         mapOrphanProposalVotes.erase(hash);
     441             :                     }
     442             :                 }
     443             :                 // Erase proposal object
     444           0 :                 mapProposals.erase(it->second);
     445             :             }
     446             :             // Remove from collateral index
     447           0 :             mapFeeTxToProposal.erase(it);
     448           0 :             return;
     449             :         }
     450             :     }
     451       54872 :     {
     452      109744 :         LOCK(cs_budgets);
     453             :         // Is this collateral related to a finalized budget?
     454       54872 :         const auto& it = mapFeeTxToBudget.find(feeTxId);
     455       54872 :         if (it != mapFeeTxToBudget.end()) {
     456             :             // Remove finalized budget
     457           0 :             CFinalizedBudget* b = FindFinalizedBudget(it->second);
     458           0 :             if (b) {
     459           0 :                 LogPrintf("%s: Removing finalized budget %s (collateral disconnected, id=%s)\n", __func__, b->GetName(), feeTxId.ToString());
     460           0 :                 {
     461             :                     // Erase seen/orphan votes
     462           0 :                     LOCK(cs_finalizedvotes);
     463           0 :                     for (const uint256& hash: b->GetVotesHashes()) {
     464           0 :                         mapSeenFinalizedBudgetVotes.erase(hash);
     465           0 :                         mapOrphanFinalizedBudgetVotes.erase(hash);
     466             :                     }
     467             :                 }
     468             :                 // Erase finalized budget object
     469           0 :                 mapFinalizedBudgets.erase(it->second);
     470             :             }
     471             :             // Remove from collateral index
     472           0 :             mapFeeTxToBudget.erase(it);
     473             :         }
     474             :     }
     475             : }
     476             : 
     477        2110 : CBudgetManager::HighestFinBudget CBudgetManager::GetBudgetWithHighestVoteCount(int chainHeight) const
     478             : {
     479        2110 :     LOCK(cs_budgets);
     480        2110 :     int highestVoteCount = 0;
     481        2110 :     const CFinalizedBudget* pHighestBudget = nullptr;
     482        4030 :     for (const auto& it: mapFinalizedBudgets) {
     483        1920 :         const CFinalizedBudget* pfinalizedBudget = &(it.second);
     484        1920 :         int voteCount = pfinalizedBudget->GetVoteCount();
     485        3686 :         if (voteCount > highestVoteCount &&
     486        1920 :             chainHeight >= pfinalizedBudget->GetBlockStart() &&
     487         301 :             chainHeight <= pfinalizedBudget->GetBlockEnd()) {
     488             :             pHighestBudget = pfinalizedBudget;
     489             :             highestVoteCount = voteCount;
     490             :         }
     491             :     }
     492        4220 :     return {pHighestBudget, highestVoteCount};
     493             : }
     494             : 
     495        2056 : int CBudgetManager::GetHighestVoteCount(int chainHeight) const
     496             : {
     497        2056 :     const auto& highestBudFin = GetBudgetWithHighestVoteCount(chainHeight);
     498        2056 :     return (highestBudFin.m_budget_fin ? highestBudFin.m_vote_count : -1);
     499             : }
     500             : 
     501        1182 : bool CBudgetManager::GetPayeeAndAmount(int chainHeight, CScript& payeeRet, CAmount& nAmountRet) const
     502             : {
     503        1182 :     int nCountThreshold;
     504        1182 :     if (!IsBudgetPaymentBlock(chainHeight, nCountThreshold))
     505             :         return false;
     506             : 
     507          33 :     const auto& highestBudFin = GetBudgetWithHighestVoteCount(chainHeight);
     508          33 :     const CFinalizedBudget* pfb = highestBudFin.m_budget_fin;
     509          33 :     return pfb && pfb->GetPayeeAndAmount(chainHeight, payeeRet, nAmountRet) && highestBudFin.m_vote_count > nCountThreshold;
     510             : }
     511             : 
     512         849 : bool CBudgetManager::GetExpectedPayeeAmount(int chainHeight, CAmount& nAmountRet) const
     513             : {
     514         849 :     CScript payeeRet;
     515         849 :     return GetPayeeAndAmount(chainHeight, payeeRet, nAmountRet);
     516             : }
     517             : 
     518         333 : bool CBudgetManager::FillBlockPayee(CMutableTransaction& txCoinbase, CMutableTransaction& txCoinstake, const int nHeight, bool fProofOfStake) const
     519             : {
     520         333 :     if (nHeight <= 0) return false;
     521             : 
     522         666 :     CScript payee;
     523         333 :     CAmount nAmount = 0;
     524             : 
     525         333 :     if (!GetPayeeAndAmount(nHeight, payee, nAmount))
     526             :         return false;
     527             : 
     528           7 :     CAmount blockValue = GetBlockValue(nHeight);
     529             : 
     530             :     // Starting from PIVX v6.0 masternode and budgets are paid in the coinbase tx of PoS blocks
     531           9 :     const bool fPayCoinstake = fProofOfStake &&
     532           2 :                                !Params().GetConsensus().NetworkUpgradeActive(nHeight, Consensus::UPGRADE_V6_0);
     533             : 
     534           7 :     if (fProofOfStake) {
     535           2 :         if (fPayCoinstake) {
     536           2 :             unsigned int i = txCoinstake.vout.size();
     537           2 :             txCoinstake.vout.resize(i + 1);
     538           2 :             txCoinstake.vout[i].scriptPubKey = payee;
     539           2 :             txCoinstake.vout[i].nValue = nAmount;
     540             :         } else {
     541           0 :             txCoinbase.vout.resize(1);
     542           0 :             txCoinbase.vout[0].scriptPubKey = payee;
     543           0 :             txCoinbase.vout[0].nValue = nAmount;
     544             :         }
     545             :     } else {
     546             :         //miners get the full amount on these blocks
     547           5 :         txCoinbase.vout[0].nValue = blockValue;
     548           5 :         txCoinbase.vout.resize(2);
     549             : 
     550             :         //these are super blocks, so their value can be much larger than normal
     551           5 :         txCoinbase.vout[1].scriptPubKey = payee;
     552           5 :         txCoinbase.vout[1].nValue = nAmount;
     553             :     }
     554             : 
     555         340 :     CTxDestination address;
     556           7 :     ExtractDestination(payee, address);
     557           9 :     LogPrint(BCLog::MNBUDGET,"%s: Budget payment to %s for %lld\n", __func__, EncodeDestination(address), nAmount);
     558           7 :     return true;
     559             : }
     560             : 
     561         274 : void CBudgetManager::VoteOnFinalizedBudgets()
     562             : {
     563             :     // function called only from initialized masternodes
     564         274 :     if (!fMasterNode) {
     565         261 :         LogPrint(BCLog::MNBUDGET,"%s: Not a masternode\n", __func__);
     566         271 :         return;
     567             :     }
     568             : 
     569             :     // Do this 1 in 4 blocks -- spread out the voting activity
     570             :     // -- this function is only called every fourteenth block, so this is really 1 in 56 blocks
     571          13 :     if (GetRandInt(4) != 0) {
     572           9 :         LogPrint(BCLog::MNBUDGET,"%s: waiting\n", __func__);
     573           9 :         return;
     574             :     }
     575             : 
     576             :     // Get the active masternode (operator) key
     577           7 :     CTxIn mnVin;
     578           7 :     Optional<CKey> mnKey{nullopt};
     579           7 :     CBLSSecretKey blsKey;
     580           4 :     if (!GetActiveMasternodeKeys(mnVin, mnKey, blsKey)) {
     581           1 :         return;
     582             :     }
     583             : 
     584           7 :     std::vector<CBudgetProposal> vBudget = GetBudget();
     585           4 :     if (vBudget.empty()) {
     586           1 :         LogPrint(BCLog::MNBUDGET,"%s: No proposal can be finalized\n", __func__);
     587           1 :         return;
     588             :     }
     589             : 
     590           6 :     std::map<uint256, CBudgetProposal> mapWinningProposals;
     591           6 :     for (const CBudgetProposal& p: vBudget) {
     592           6 :         mapWinningProposals.emplace(p.GetHash(), p);
     593             :     }
     594             :     // Vector containing the hash of finalized budgets to sign
     595           6 :     std::vector<uint256> vBudgetHashes;
     596           3 :     {
     597           3 :         LOCK(cs_budgets);
     598           4 :         for (auto& it: mapFinalizedBudgets) {
     599           1 :             CFinalizedBudget* pfb = &(it.second);
     600             :             // we only need to check this once
     601           1 :             if (pfb->IsAutoChecked()) continue;
     602           1 :             pfb->SetAutoChecked(true);
     603             :             //only vote for exact matches
     604           1 :             if (strBudgetMode == "auto") {
     605             :                 // compare budget payments with winning proposals
     606           1 :                 if (!pfb->CheckProposals(mapWinningProposals)) {
     607           0 :                     continue;
     608             :                 }
     609             :             }
     610             :             // exact match found. add budget hash to sign it later.
     611           1 :             vBudgetHashes.emplace_back(pfb->GetHash());
     612             :         }
     613             :     }
     614             : 
     615             :     // Sign finalized budgets
     616           4 :     for (const uint256& budgetHash: vBudgetHashes) {
     617           1 :         CFinalizedBudgetVote vote(mnVin, budgetHash);
     618           1 :         if (mnKey != nullopt) {
     619             :             // Legacy MN
     620           2 :             if (!vote.Sign(*mnKey, mnKey->GetPubKey().GetID())) {
     621           0 :                 LogPrintf("%s: Failure to sign budget %s\n", __func__, budgetHash.ToString());
     622           0 :                 continue;
     623             :             }
     624             :         } else {
     625             :             // DMN
     626           0 :             if (!vote.Sign(blsKey)) {
     627           0 :                 LogPrintf("%s: Failure to sign budget %s with DMN\n", __func__, budgetHash.ToString());
     628           0 :                 continue;
     629             :             }
     630             :         }
     631           1 :         std::string strError = "";
     632           1 :         if (!UpdateFinalizedBudget(vote, nullptr, strError)) {
     633           1 :             LogPrintf("%s: Error submitting vote - %s\n", __func__, strError);
     634           2 :             continue;
     635             :         }
     636           0 :         LogPrint(BCLog::MNBUDGET, "%s: new finalized budget vote signed: %s\n", __func__, vote.GetHash().ToString());
     637           0 :         AddSeenFinalizedBudgetVote(vote);
     638           0 :         vote.Relay();
     639             :     }
     640             : }
     641             : 
     642           0 : CFinalizedBudget* CBudgetManager::FindFinalizedBudget(const uint256& nHash)
     643             : {
     644           0 :     AssertLockHeld(cs_budgets);
     645           0 :     auto it = mapFinalizedBudgets.find(nHash);
     646           0 :     return it != mapFinalizedBudgets.end() ? &(it->second) : nullptr;
     647             : }
     648             : 
     649          22 : const CBudgetProposal* CBudgetManager::FindProposalByName(const std::string& strProposalName) const
     650             : {
     651          22 :     LOCK(cs_proposals);
     652             : 
     653          22 :     int64_t nYesCountMax = std::numeric_limits<int64_t>::min();
     654          22 :     const CBudgetProposal* pbudgetProposal = nullptr;
     655             : 
     656         344 :     for (const auto& it: mapProposals) {
     657         322 :         const CBudgetProposal& proposal = it.second;
     658         322 :         int64_t nYesCount = proposal.GetYeas() - proposal.GetNays();
     659         966 :         if (proposal.GetName() == strProposalName && nYesCount > nYesCountMax) {
     660          22 :             pbudgetProposal = &proposal;
     661          22 :             nYesCountMax = nYesCount;
     662             :         }
     663             :     }
     664             : 
     665          44 :     return pbudgetProposal;
     666             : }
     667             : 
     668           0 : CBudgetProposal* CBudgetManager::FindProposal(const uint256& nHash)
     669             : {
     670           0 :     AssertLockHeld(cs_proposals);
     671           0 :     auto it = mapProposals.find(nHash);
     672           0 :     return it != mapProposals.end() ? &(it->second) : nullptr;
     673             : }
     674             : 
     675           4 : bool CBudgetManager::GetProposal(const uint256& nHash, CBudgetProposal& bp) const
     676             : {
     677           8 :     LOCK(cs_proposals);
     678           4 :     auto it = mapProposals.find(nHash);
     679           4 :     if (it == mapProposals.end()) return false;
     680           4 :     bp = it->second;
     681             :     return true;
     682             : }
     683             : 
     684           4 : bool CBudgetManager::GetFinalizedBudget(const uint256& nHash, CFinalizedBudget& fb) const
     685             : {
     686           8 :     LOCK(cs_budgets);
     687           4 :     auto it = mapFinalizedBudgets.find(nHash);
     688           4 :     if (it == mapFinalizedBudgets.end()) return false;
     689           4 :     fb = it->second;
     690             :     return true;
     691             : }
     692             : 
     693        2056 : bool CBudgetManager::IsBudgetPaymentBlock(int nBlockHeight, int& nCountThreshold) const
     694             : {
     695        2056 :     int nHighestCount = GetHighestVoteCount(nBlockHeight);
     696        2056 :     int nCountEnabled = mnodeman.CountEnabled();
     697        2056 :     int nFivePercent = nCountEnabled / 20;
     698             :     // threshold for highest finalized budgets (highest vote count - 10% of active masternodes)
     699        2056 :     nCountThreshold = nHighestCount - (nCountEnabled / 10);
     700             :     // reduce the threshold if there are less than 10 enabled masternodes
     701        2056 :     if (nCountThreshold == nHighestCount) nCountThreshold--;
     702             : 
     703        2056 :     LogPrint(BCLog::MNBUDGET,"%s: nHighestCount: %lli, 5%% of Masternodes: %lli.\n",
     704             :             __func__, nHighestCount, nFivePercent);
     705             : 
     706             :     // If budget doesn't have 5% of the network votes, then we should pay a masternode instead
     707        2056 :     return (nHighestCount > nFivePercent);
     708             : }
     709             : 
     710         853 : bool CBudgetManager::IsBudgetPaymentBlock(int nBlockHeight) const
     711             : {
     712         853 :     int nCountThreshold;
     713         853 :     return IsBudgetPaymentBlock(nBlockHeight, nCountThreshold);
     714             : }
     715             : 
     716          21 : TrxValidationStatus CBudgetManager::IsTransactionValid(const CTransaction& txNew, const uint256& nBlockHash, int nBlockHeight) const
     717             : {
     718          21 :     int nCountThreshold = 0;
     719          21 :     if (!IsBudgetPaymentBlock(nBlockHeight, nCountThreshold)) {
     720             :         // If budget doesn't have 5% of the network votes, then we should pay a masternode instead
     721             :         return TrxValidationStatus::InValid;
     722             :     }
     723             : 
     724             :     // check the highest finalized budgets (- 10% to assist in consensus)
     725          21 :     bool fThreshold = false;
     726          21 :     {
     727          21 :         LOCK(cs_budgets);
     728             :         // Get the finalized budget with the highest amount of votes..
     729          21 :         const auto& highestBudFin = GetBudgetWithHighestVoteCount(nBlockHeight);
     730          21 :         const CFinalizedBudget* highestVotesBudget = highestBudFin.m_budget_fin;
     731          21 :         if (highestVotesBudget) {
     732             :             // Need to surpass the threshold
     733          21 :             if (highestBudFin.m_vote_count > nCountThreshold) {
     734          21 :                 fThreshold = true;
     735          21 :                 if (highestVotesBudget->IsTransactionValid(txNew, nBlockHash, nBlockHeight) ==
     736             :                     TrxValidationStatus::Valid) {
     737          36 :                     return TrxValidationStatus::Valid;
     738             :                 }
     739             :             }
     740             :             // tx not valid
     741           3 :             LogPrint(BCLog::MNBUDGET, "%s: ignoring budget. Out of range or tx not valid.\n", __func__);
     742             :         }
     743             :     }
     744             : 
     745             :     // If not enough masternodes autovoted for any of the finalized budgets or if none of the txs
     746             :     // are valid, we should pay a masternode instead
     747           3 :     return fThreshold ? TrxValidationStatus::InValid : TrxValidationStatus::VoteThreshold;
     748             : }
     749             : 
     750          19 : std::vector<CBudgetProposal*> CBudgetManager::GetAllProposalsOrdered()
     751             : {
     752          19 :     LOCK(cs_proposals);
     753          19 :     std::vector<CBudgetProposal*> vBudgetProposalRet;
     754         128 :     for (auto& it: mapProposals) {
     755         109 :         CBudgetProposal* pbudgetProposal = &(it.second);
     756         109 :         RemoveStaleVotesOnProposal(pbudgetProposal);
     757         109 :         vBudgetProposalRet.push_back(pbudgetProposal);
     758             :     }
     759          19 :     std::sort(vBudgetProposalRet.begin(), vBudgetProposalRet.end(), CBudgetProposal::PtrHigherYes);
     760          38 :     return vBudgetProposalRet;
     761             : }
     762             : 
     763          17 : std::vector<CBudgetProposal> CBudgetManager::GetBudget()
     764             : {
     765          34 :     LOCK(cs_proposals);
     766             : 
     767          17 :     int nHeight = GetBestHeight();
     768          17 :     if (nHeight <= 0)
     769           0 :         return {};
     770             : 
     771             :     // ------- Get proposals ordered by votes (highest to lowest)
     772          34 :     std::vector<CBudgetProposal*> vProposalsOrdered = GetAllProposalsOrdered();
     773             : 
     774             :     // ------- Grab The Budgets In Order
     775          34 :     std::vector<CBudgetProposal> vBudgetProposalsRet;
     776          17 :     CAmount nBudgetAllocated = 0;
     777             : 
     778          17 :     const int nBlocksPerCycle = Params().GetConsensus().nBudgetCycleBlocks;
     779          17 :     int nBlockStart = nHeight - nHeight % nBlocksPerCycle + nBlocksPerCycle;
     780          17 :     int nBlockEnd = nBlockStart + nBlocksPerCycle - 1;
     781          17 :     int mnCount = mnodeman.CountEnabled();
     782          17 :     CAmount nTotalBudget = GetTotalBudget(nBlockStart);
     783             : 
     784          94 :     for (CBudgetProposal* pbudgetProposal: vProposalsOrdered) {
     785         231 :         LogPrint(BCLog::MNBUDGET,"%s: Processing Budget %s\n", __func__, pbudgetProposal->GetName());
     786             :         //prop start/end should be inside this period
     787          77 :         if (pbudgetProposal->IsPassing(nBlockStart, nBlockEnd, mnCount)) {
     788          16 :             LogPrint(BCLog::MNBUDGET,"%s:  -   Check 1 passed: valid=%d | %ld <= %ld | %ld >= %ld | Yeas=%d Nays=%d Count=%d | established=%d\n",
     789             :                     __func__, pbudgetProposal->IsValid(), pbudgetProposal->GetBlockStart(), nBlockStart, pbudgetProposal->GetBlockEnd(),
     790             :                     nBlockEnd, pbudgetProposal->GetYeas(), pbudgetProposal->GetNays(), mnCount / 10, pbudgetProposal->IsEstablished());
     791             : 
     792          16 :             if (pbudgetProposal->GetAmount() + nBudgetAllocated <= nTotalBudget) {
     793          16 :                 pbudgetProposal->SetAllotted(pbudgetProposal->GetAmount());
     794          16 :                 nBudgetAllocated += pbudgetProposal->GetAmount();
     795          16 :                 vBudgetProposalsRet.emplace_back(*pbudgetProposal);
     796          16 :                 LogPrint(BCLog::MNBUDGET,"%s:  -     Check 2 passed: Budget added\n", __func__);
     797             :             } else {
     798           0 :                 pbudgetProposal->SetAllotted(0);
     799           0 :                 LogPrint(BCLog::MNBUDGET,"%s:  -     Check 2 failed: no amount allotted\n", __func__);
     800             :             }
     801             : 
     802             :         } else {
     803          61 :             LogPrint(BCLog::MNBUDGET,"%s:  -   Check 1 failed: valid=%d | %ld <= %ld | %ld >= %ld | Yeas=%d Nays=%d Count=%d | established=%d\n",
     804             :                     __func__, pbudgetProposal->IsValid(), pbudgetProposal->GetBlockStart(), nBlockStart, pbudgetProposal->GetBlockEnd(),
     805             :                     nBlockEnd, pbudgetProposal->GetYeas(), pbudgetProposal->GetNays(), mnodeman.CountEnabled() / 10,
     806             :                     pbudgetProposal->IsEstablished());
     807             :         }
     808             : 
     809             :     }
     810             : 
     811          17 :     return vBudgetProposalsRet;
     812             : }
     813             : 
     814           6 : std::vector<CFinalizedBudget*> CBudgetManager::GetFinalizedBudgets()
     815             : {
     816           6 :     LOCK(cs_budgets);
     817             : 
     818           6 :     std::vector<CFinalizedBudget*> vFinalizedBudgetsRet;
     819             : 
     820             :     // ------- Grab The Budgets In Order
     821          12 :     for (auto& it: mapFinalizedBudgets) {
     822           6 :         vFinalizedBudgetsRet.push_back(&(it.second));
     823             :     }
     824           6 :     std::sort(vFinalizedBudgetsRet.begin(), vFinalizedBudgetsRet.end(), CFinalizedBudget::PtrGreater);
     825             : 
     826          12 :     return vFinalizedBudgetsRet;
     827             : }
     828             : 
     829           0 : std::string CBudgetManager::GetRequiredPaymentsString(int nBlockHeight)
     830             : {
     831           0 :     LOCK(cs_budgets);
     832             : 
     833           0 :     std::string ret = "unknown-budget";
     834             : 
     835           0 :     std::map<uint256, CFinalizedBudget>::iterator it = mapFinalizedBudgets.begin();
     836           0 :     while (it != mapFinalizedBudgets.end()) {
     837           0 :         CFinalizedBudget* pfinalizedBudget = &((*it).second);
     838           0 :         if (nBlockHeight >= pfinalizedBudget->GetBlockStart() && nBlockHeight <= pfinalizedBudget->GetBlockEnd()) {
     839           0 :             CTxBudgetPayment payment;
     840           0 :             if (pfinalizedBudget->GetBudgetPaymentByBlock(nBlockHeight, payment)) {
     841           0 :                 if (ret == "unknown-budget") {
     842           0 :                     ret = payment.nProposalHash.ToString();
     843             :                 } else {
     844           0 :                     ret += ",";
     845           0 :                     ret += payment.nProposalHash.ToString();
     846             :                 }
     847             :             } else {
     848           0 :                 LogPrint(BCLog::MNBUDGET,"%s:  Couldn't find budget payment for block %d\n", __func__, nBlockHeight);
     849             :             }
     850             :         }
     851             : 
     852           0 :         ++it;
     853             :     }
     854             : 
     855           0 :     return ret;
     856             : }
     857             : 
     858       23018 : CAmount CBudgetManager::GetTotalBudget(int nHeight)
     859             : {
     860             :     // 100% of block reward after V5.5 upgrade
     861       23018 :     CAmount nSubsidy = GetBlockValue(nHeight);
     862             : 
     863             :     // 20% of block reward prior to V5.5 upgrade
     864       23018 :     if (nHeight <= Params().GetConsensus().vUpgrades[Consensus::UPGRADE_V5_5].nActivationHeight) {
     865       20487 :         nSubsidy /= 5;
     866             :     }
     867             : 
     868             :     // multiplied by the number of blocks in a cycle (144 on testnet, 30*1440 on mainnet)
     869       23018 :     return nSubsidy * Params().GetConsensus().nBudgetCycleBlocks;
     870             : }
     871             : 
     872          17 : void CBudgetManager::AddSeenProposalVote(const CBudgetVote& vote)
     873             : {
     874          17 :     LOCK(cs_votes);
     875          34 :     mapSeenProposalVotes.emplace(vote.GetHash(), vote);
     876          17 : }
     877             : 
     878          11 : void CBudgetManager::AddSeenFinalizedBudgetVote(const CFinalizedBudgetVote& vote)
     879             : {
     880          11 :     LOCK(cs_finalizedvotes);
     881          22 :     mapSeenFinalizedBudgetVotes.emplace(vote.GetHash(), vote);
     882          11 : }
     883             : 
     884         152 : void CBudgetManager::RemoveStaleVotesOnProposal(CBudgetProposal* prop)
     885             : {
     886         152 :     AssertLockHeld(cs_proposals);
     887         304 :     LogPrint(BCLog::MNBUDGET, "Cleaning proposal votes for %s. Before: YES=%d, NO=%d\n",
     888             :             prop->GetName(), prop->GetYeas(), prop->GetNays());
     889             : 
     890         152 :     auto it = prop->mapVotes.begin();
     891         221 :     while (it != prop->mapVotes.end()) {
     892          69 :         auto mnList = deterministicMNManager->GetListAtChainTip();
     893         138 :         auto dmn = mnList.GetMNByCollateral(it->first);
     894          69 :         if (dmn) {
     895           7 :             (*it).second.SetValid(!dmn->IsPoSeBanned());
     896             :         } else {
     897             :             // -- Legacy System (!TODO: remove after enforcement) --
     898          62 :             CMasternode* pmn = mnodeman.Find(it->first);
     899         128 :             (*it).second.SetValid(pmn && pmn->IsEnabled());
     900             :         }
     901          69 :         ++it;
     902             :     }
     903             : 
     904         304 :     LogPrint(BCLog::MNBUDGET, "Cleaned proposal votes for %s. After: YES=%d, NO=%d\n",
     905             :             prop->GetName(), prop->GetYeas(), prop->GetNays());
     906         152 : }
     907             : 
     908           5 : void CBudgetManager::RemoveStaleVotesOnFinalBudget(CFinalizedBudget* fbud)
     909             : {
     910           5 :     AssertLockHeld(cs_budgets);
     911          15 :     LogPrint(BCLog::MNBUDGET, "Cleaning finalized budget votes for [%s (%s)]. Before: %d\n",
     912             :             fbud->GetName(), fbud->GetProposalsStr(), fbud->GetVoteCount());
     913             : 
     914           5 :     auto it = fbud->mapVotes.begin();
     915          16 :     while (it != fbud->mapVotes.end()) {
     916          11 :         auto mnList = deterministicMNManager->GetListAtChainTip();
     917          22 :         auto dmn = mnList.GetMNByCollateral(it->first);
     918          11 :         if (dmn) {
     919           1 :             (*it).second.SetValid(!dmn->IsPoSeBanned());
     920             :         } else {
     921             :             // -- Legacy System (!TODO: remove after enforcement) --
     922          10 :             CMasternode* pmn = mnodeman.Find(it->first);
     923          22 :             (*it).second.SetValid(pmn && pmn->IsEnabled());
     924             :         }
     925          11 :         ++it;
     926             :     }
     927          15 :     LogPrint(BCLog::MNBUDGET, "Cleaned finalized budget votes for [%s (%s)]. After: %d\n",
     928             :             fbud->GetName(), fbud->GetProposalsStr(), fbud->GetVoteCount());
     929           5 : }
     930             : 
     931          20 : CDataStream CBudgetManager::GetProposalVoteSerialized(const uint256& voteHash) const
     932             : {
     933          20 :     LOCK(cs_votes);
     934          20 :     CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
     935          20 :     ss.reserve(1000);
     936          20 :     ss << mapSeenProposalVotes.at(voteHash);
     937          40 :     return ss;
     938             : }
     939             : 
     940          52 : CDataStream CBudgetManager::GetProposalSerialized(const uint256& propHash) const
     941             : {
     942          52 :     LOCK(cs_proposals);
     943         104 :     return mapProposals.at(propHash).GetBroadcast();
     944             : }
     945             : 
     946          13 : CDataStream CBudgetManager::GetFinalizedBudgetVoteSerialized(const uint256& voteHash) const
     947             : {
     948          13 :     LOCK(cs_finalizedvotes);
     949          13 :     CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
     950          13 :     ss.reserve(1000);
     951          13 :     ss << mapSeenFinalizedBudgetVotes.at(voteHash);
     952          26 :     return ss;
     953             : }
     954             : 
     955           6 : CDataStream CBudgetManager::GetFinalizedBudgetSerialized(const uint256& budgetHash) const
     956             : {
     957           6 :     LOCK(cs_budgets);
     958          12 :     return mapFinalizedBudgets.at(budgetHash).GetBroadcast();
     959             : }
     960             : 
     961           0 : bool CBudgetManager::AddAndRelayProposalVote(const CBudgetVote& vote, std::string& strError)
     962             : {
     963           0 :     if (UpdateProposal(vote, nullptr, strError)) {
     964           0 :         AddSeenProposalVote(vote);
     965           0 :         vote.Relay();
     966           0 :         return true;
     967             :     }
     968             :     return false;
     969             : }
     970             : 
     971       21970 : void CBudgetManager::UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload)
     972             : {
     973       24999 :     if (g_tiertwo_sync_state.GetSyncPhase() <= MASTERNODE_SYNC_BUDGET) return;
     974             : 
     975        3250 :     if (strBudgetMode == "suggest") { //suggest the budget we see
     976           0 :         SubmitFinalBudget();
     977             :     }
     978             : 
     979        3250 :     int nCurrentHeight = GetBestHeight();
     980             :     //this function should be called 1/14 blocks, allowing up to 100 votes per day on all proposals
     981        3250 :     if (nCurrentHeight % 14 != 0) return;
     982             : 
     983             :     // incremental sync with our peers
     984         221 :     if (g_tiertwo_sync_state.IsSynced()) {
     985         221 :         LogPrint(BCLog::MNBUDGET,"%s:  incremental sync started\n", __func__);
     986             :         // Once every 7 days, try to relay the complete budget data
     987         221 :         if (GetRandInt(Params().IsRegTestNet() ? 2 : 720) == 0) {
     988          99 :             ResetSync();
     989             :         }
     990             : 
     991         221 :         CBudgetManager* manager = this;
     992         221 :         g_connman->ForEachNode([manager](CNode* pnode){
     993        1205 :             if (pnode->nVersion >= ActiveProtocol())
     994        1205 :                 manager->Sync(pnode, true);
     995        1205 :         });
     996         221 :         MarkSynced();
     997             :     }
     998             : 
     999             :     // remove expired/heavily downvoted budgets
    1000         221 :     CheckAndRemove();
    1001             : 
    1002         221 :     {
    1003         221 :         LOCK(cs_proposals);
    1004         221 :         LogPrint(BCLog::MNBUDGET,"%s:  mapProposals cleanup - size: %d\n", __func__, mapProposals.size());
    1005         264 :         for (auto& it: mapProposals) {
    1006          43 :             RemoveStaleVotesOnProposal(&it.second);
    1007             :         }
    1008             :     }
    1009         221 :     {
    1010         221 :         LOCK(cs_budgets);
    1011         221 :         LogPrint(BCLog::MNBUDGET,"%s:  mapFinalizedBudgets cleanup - size: %d\n", __func__, mapFinalizedBudgets.size());
    1012         226 :         for (auto& it: mapFinalizedBudgets) {
    1013           5 :             RemoveStaleVotesOnFinalBudget(&it.second);
    1014             :         }
    1015             :     }
    1016             : 
    1017         221 :     int64_t now = GetTime();
    1018         663 :     const auto cleanOrphans = [now](auto& mutex, auto& mapOrphans, auto& mapSeen) {
    1019         442 :         LOCK(mutex);
    1020         442 :         for (auto it = mapOrphans.begin() ; it != mapOrphans.end();) {
    1021           0 :             int64_t lastReceivedVoteTime = it->second.second;
    1022           0 :             if (lastReceivedVoteTime + BUDGET_ORPHAN_VOTES_CLEANUP_SECONDS < now) {
    1023             :                 // Clean seen votes
    1024           0 :                 for (const auto& voteIt : it->second.first) {
    1025           0 :                     mapSeen.erase(voteIt.GetHash());
    1026             :                 }
    1027             :                 // Remove proposal orphan votes
    1028           0 :                 it = mapOrphans.erase(it);
    1029             :             } else {
    1030         442 :                 it++;
    1031             :             }
    1032             :         }
    1033         663 :     };
    1034             : 
    1035             :     // Clean orphan proposal votes if no parent arrived after an hour.
    1036         221 :     cleanOrphans(cs_votes, mapOrphanProposalVotes, mapSeenProposalVotes);
    1037             :     // Clean orphan budget votes if no parent arrived after an hour.
    1038         221 :     cleanOrphans(cs_finalizedvotes, mapOrphanFinalizedBudgetVotes, mapSeenFinalizedBudgetVotes);
    1039             : 
    1040             :     // Once every 2 weeks (1/14 * 1/1440), clean the seen maps
    1041         221 :     if (g_tiertwo_sync_state.IsSynced() && GetRandInt(1440) == 0) {
    1042           0 :         ReloadMapSeen();
    1043             :     }
    1044             : 
    1045         221 :     LogPrint(BCLog::MNBUDGET,"%s:  PASSED\n", __func__);
    1046             : }
    1047             : 
    1048         239 : int CBudgetManager::ProcessBudgetVoteSync(const uint256& nProp, CNode* pfrom)
    1049             : {
    1050         478 :     if (nProp.IsNull()) {
    1051         711 :         LOCK2(cs_budgets, cs_proposals);
    1052         237 :         if (!(pfrom->addr.IsRFC1918() || pfrom->addr.IsLocal())) {
    1053           0 :             if (g_netfulfilledman.HasFulfilledRequest(pfrom->addr, BUDGET_SYNC_REQUEST_RECV)) {
    1054           0 :                 LogPrint(BCLog::MASTERNODE, "budgetsync - peer %i already asked for budget sync\n", pfrom->GetId());
    1055             :                 // let's not be so hard with the node for now.
    1056           0 :                 return 10;
    1057             :             }
    1058             :         }
    1059             :     }
    1060             : 
    1061         478 :     if (nProp.IsNull()) Sync(pfrom, false /* fPartial */);
    1062           2 :     else SyncSingleItem(pfrom, nProp);
    1063         239 :     LogPrint(BCLog::MNBUDGET, "mnvs - Sent Masternode votes to peer %i\n", pfrom->GetId());
    1064             :     return 0;
    1065             : }
    1066             : 
    1067          21 : int CBudgetManager::ProcessProposal(CBudgetProposal& proposal)
    1068             : {
    1069          21 :     const uint256& nHash = proposal.GetHash();
    1070          21 :     if (HaveProposal(nHash)) {
    1071           0 :         g_tiertwo_sync_state.AddedBudgetItem(nHash);
    1072           0 :         return 0;
    1073             :     }
    1074          21 :     if (!AddProposal(proposal)) {
    1075             :         return 0;
    1076             :     }
    1077             : 
    1078             :     // Relay only if we are synchronized
    1079             :     // Makes no sense to relay proposals to the peers from where we are syncing them.
    1080          21 :     if (g_tiertwo_sync_state.IsSynced()) proposal.Relay();
    1081          21 :     g_tiertwo_sync_state.AddedBudgetItem(nHash);
    1082             : 
    1083          42 :     LogPrint(BCLog::MNBUDGET, "mprop (new) %s\n", nHash.ToString());
    1084             :     //We might have active votes for this proposal that are valid now
    1085          21 :     CheckOrphanVotes();
    1086             :     return 0;
    1087             : }
    1088             : 
    1089          17 : bool CBudgetManager::ProcessProposalVote(CBudgetVote& vote, CNode* pfrom, CValidationState& state)
    1090             : {
    1091          17 :     const uint256& voteID = vote.GetHash();
    1092             : 
    1093          17 :     if (HaveSeenProposalVote(voteID)) {
    1094           0 :         g_tiertwo_sync_state.AddedBudgetItem(voteID);
    1095           0 :         return false;
    1096             :     }
    1097             : 
    1098          34 :     std::string err;
    1099          17 :     if (vote.GetTime() > GetTime() + (60 * 60)) {
    1100           0 :         err = strprintf("new vote is too far ahead of current time - %s - nTime %lli - Max Time %lli\n",
    1101           0 :                              vote.GetHash().ToString(), vote.GetTime(), GetTime() + (60 * 60));
    1102           0 :         return state.Invalid(false, REJECT_INVALID, "bad-mvote", err);
    1103             :     }
    1104             : 
    1105          34 :     const CTxIn& voteVin = vote.GetVin();
    1106             : 
    1107             :     // See if this vote was signed with a deterministic masternode
    1108          34 :     auto mnList = deterministicMNManager->GetListAtChainTip();
    1109          34 :     auto dmn = mnList.GetMNByCollateral(voteVin.prevout);
    1110          17 :     if (dmn) {
    1111           2 :         const std::string& mn_protx_id = dmn->proTxHash.ToString();
    1112             : 
    1113           1 :         if (dmn->IsPoSeBanned()) {
    1114           0 :             err = strprintf("masternode (%s) not valid or PoSe banned", mn_protx_id);
    1115           0 :             return state.DoS(0, false, REJECT_INVALID, "bad-mvote", false, err);
    1116             :         }
    1117             : 
    1118           1 :         AddSeenProposalVote(vote);
    1119             : 
    1120           1 :         if (!vote.CheckSignature(dmn->pdmnState->keyIDVoting)) {
    1121           0 :             err = strprintf("invalid mvote sig from dmn: %s", mn_protx_id);
    1122           0 :             return state.DoS(100, false, REJECT_INVALID, "bad-mvote-sig", false, err);
    1123             :         }
    1124             : 
    1125           1 :         if (!UpdateProposal(vote, pfrom, err)) {
    1126           0 :             return state.DoS(0, false, REJECT_INVALID, "bad-mvote", false, strprintf("%s (%s)", err, mn_protx_id));
    1127             :         }
    1128             : 
    1129             :         // Relay only if we are synchronized
    1130             :         // Makes no sense to relay votes to the peers from where we are syncing them.
    1131           1 :         if (g_tiertwo_sync_state.IsSynced()) vote.Relay();
    1132           1 :         g_tiertwo_sync_state.AddedBudgetItem(voteID);
    1133           3 :         LogPrint(BCLog::MNBUDGET, "mvote - new vote (%s) for proposal %s from dmn %s\n",
    1134             :                 voteID.ToString(), vote.GetProposalHash().ToString(), mn_protx_id);
    1135           1 :         return true;
    1136             :     }
    1137             : 
    1138             :     // -- Legacy System (!TODO: remove after enforcement) --
    1139             : 
    1140          16 :     CMasternode* pmn = mnodeman.Find(voteVin.prevout);
    1141          16 :     if (!pmn) {
    1142           0 :         err = strprintf("unknown masternode - vin: %s", voteVin.prevout.ToString());
    1143             :         // Ask for MN only if we finished syncing the MN list.
    1144           0 :         if (pfrom && g_tiertwo_sync_state.IsMasternodeListSynced()) mnodeman.AskForMN(pfrom, voteVin);
    1145           0 :         return state.DoS(0, false, REJECT_INVALID, "bad-mvote", false, err);
    1146             :     }
    1147             : 
    1148          16 :     if (!pmn->IsEnabled()) {
    1149           0 :         return state.DoS(0, false, REJECT_INVALID, "bad-mvote", false, "masternode not valid");
    1150             :     }
    1151             : 
    1152          16 :     AddSeenProposalVote(vote);
    1153             : 
    1154          16 :     if (!vote.CheckSignature(pmn->pubKeyMasternode.GetID())) {
    1155           0 :         if (g_tiertwo_sync_state.IsSynced()) {
    1156           0 :             err = strprintf("signature from masternode %s invalid", voteVin.prevout.ToString());
    1157           0 :             return state.DoS(20, false, REJECT_INVALID, "bad-mvote-sig", false, err);
    1158             :         }
    1159             :         return false;
    1160             :     }
    1161             : 
    1162          16 :     if (!UpdateProposal(vote, pfrom, err)) {
    1163           0 :         return state.DoS(0, false, REJECT_INVALID, "bad-mvote", false, err);
    1164             :     }
    1165             : 
    1166             :     // Relay only if we are synchronized
    1167             :     // Makes no sense to relay votes to the peers from where we are syncing them.
    1168          16 :     if (g_tiertwo_sync_state.IsSynced()) vote.Relay();
    1169          16 :     g_tiertwo_sync_state.AddedBudgetItem(voteID);
    1170          64 :     LogPrint(BCLog::MNBUDGET, "mvote - new vote (%s) for proposal %s from dmn %s\n",
    1171             :             voteID.ToString(), vote.GetProposalHash().ToString(), voteVin.prevout.ToString());
    1172             :     return true;
    1173             : }
    1174             : 
    1175           4 : int CBudgetManager::ProcessFinalizedBudget(CFinalizedBudget& finalbudget, CNode* pfrom)
    1176             : {
    1177             : 
    1178           4 :     const uint256& nHash = finalbudget.GetHash();
    1179           4 :     if (HaveFinalizedBudget(nHash)) {
    1180           0 :         g_tiertwo_sync_state.AddedBudgetItem(nHash);
    1181           0 :         return 0;
    1182             :     }
    1183           4 :     if (!AddFinalizedBudget(finalbudget, pfrom)) {
    1184             :         return 0;
    1185             :     }
    1186             : 
    1187             :     // Relay only if we are synchronized
    1188             :     // Makes no sense to relay finalizations to the peers from where we are syncing them.
    1189           4 :     if (g_tiertwo_sync_state.IsSynced()) finalbudget.Relay();
    1190           4 :     g_tiertwo_sync_state.AddedBudgetItem(nHash);
    1191             : 
    1192           8 :     LogPrint(BCLog::MNBUDGET, "fbs (new) %s\n", nHash.ToString());
    1193             :     //we might have active votes for this budget that are now valid
    1194           4 :     CheckOrphanVotes();
    1195             :     return 0;
    1196             : }
    1197             : 
    1198          11 : bool CBudgetManager::ProcessFinalizedBudgetVote(CFinalizedBudgetVote& vote, CNode* pfrom, CValidationState& state)
    1199             : {
    1200          11 :     const uint256& voteID = vote.GetHash();
    1201             : 
    1202          11 :     if (HaveSeenFinalizedBudgetVote(voteID)) {
    1203           0 :         g_tiertwo_sync_state.AddedBudgetItem(voteID);
    1204           0 :         return false;
    1205             :     }
    1206             : 
    1207          22 :     std::string err;
    1208          11 :     if (vote.GetTime() > GetTime() + (60 * 60)) {
    1209           0 :         err = strprintf("new vote is too far ahead of current time - %s - nTime %lli - Max Time %lli\n",
    1210           0 :                              vote.GetHash().ToString(), vote.GetTime(), GetTime() + (60 * 60));
    1211           0 :         return state.Invalid(false, REJECT_INVALID, "bad-fbvote", err);
    1212             :     }
    1213             : 
    1214          22 :     const CTxIn& voteVin = vote.GetVin();
    1215             : 
    1216             :     // See if this vote was signed with a deterministic masternode
    1217          22 :     auto mnList = deterministicMNManager->GetListAtChainTip();
    1218          22 :     auto dmn = mnList.GetMNByCollateral(voteVin.prevout);
    1219          11 :     if (dmn) {
    1220           2 :         const std::string& mn_protx_id = dmn->proTxHash.ToString();
    1221             : 
    1222           1 :         if (dmn->IsPoSeBanned()) {
    1223           0 :             err = strprintf("masternode (%s) not valid or PoSe banned", mn_protx_id);
    1224           0 :             return state.DoS(0, false, REJECT_INVALID, "bad-fbvote", false, err);
    1225             :         }
    1226             : 
    1227           1 :         AddSeenFinalizedBudgetVote(vote);
    1228             : 
    1229           1 :         if (!vote.CheckSignature(dmn->pdmnState->pubKeyOperator.Get())) {
    1230           0 :             err = strprintf("invalid fbvote sig from dmn: %s", mn_protx_id);
    1231           0 :             return state.DoS(100, false, REJECT_INVALID, "bad-fbvote-sig", false, err);
    1232             :         }
    1233             : 
    1234           1 :         if (!UpdateFinalizedBudget(vote, pfrom, err)) {
    1235           0 :             return state.DoS(0, false, REJECT_INVALID, "bad-fbvote", false, strprintf("%s (%s)", err, mn_protx_id));
    1236             :         }
    1237             : 
    1238             :         // Relay only if we are synchronized
    1239             :         // Makes no sense to relay votes to the peers from where we are syncing them.
    1240           1 :         if (g_tiertwo_sync_state.IsSynced()) vote.Relay();
    1241           1 :         g_tiertwo_sync_state.AddedBudgetItem(voteID);
    1242           3 :         LogPrint(BCLog::MNBUDGET, "fbvote - new vote (%s) for budget %s from dmn %s\n",
    1243             :                 voteID.ToString(), vote.GetBudgetHash().ToString(), mn_protx_id);
    1244           1 :         return true;
    1245             :     }
    1246             : 
    1247             :     // -- Legacy System (!TODO: remove after enforcement) --
    1248          10 :     CMasternode* pmn = mnodeman.Find(voteVin.prevout);
    1249          10 :     if (!pmn) {
    1250           0 :         err = strprintf("unknown masternode - vin: %s", voteVin.prevout.ToString());
    1251             :         // Ask for MN only if we finished syncing the MN list.
    1252           0 :         if (pfrom && g_tiertwo_sync_state.IsMasternodeListSynced()) mnodeman.AskForMN(pfrom, voteVin);
    1253           0 :         return state.DoS(0, false, REJECT_INVALID, "bad-fbvote", false, err);
    1254             :     }
    1255             : 
    1256          10 :     if (!pmn->IsEnabled()) {
    1257           0 :         return state.DoS(0, false, REJECT_INVALID, "bad-fbvote", false, "masternode not valid");
    1258             :     }
    1259             : 
    1260          10 :     AddSeenFinalizedBudgetVote(vote);
    1261             : 
    1262          10 :     if (!vote.CheckSignature(pmn->pubKeyMasternode.GetID())) {
    1263           0 :         if (g_tiertwo_sync_state.IsSynced()) {
    1264           0 :             err = strprintf("signature from masternode %s invalid", voteVin.prevout.ToString());
    1265           0 :             return state.DoS(20, false, REJECT_INVALID, "bad-fbvote-sig", false, err);
    1266             :         }
    1267             :         return false;
    1268             :     }
    1269             : 
    1270          10 :     if (!UpdateFinalizedBudget(vote, pfrom, err)) {
    1271           0 :         return state.DoS(0, false, REJECT_INVALID, "bad-fbvote", false, err);
    1272             :     }
    1273             : 
    1274             :     // Relay only if we are synchronized
    1275             :     // Makes no sense to relay votes to the peers from where we are syncing them.
    1276          10 :     if (g_tiertwo_sync_state.IsSynced()) vote.Relay();
    1277          10 :     g_tiertwo_sync_state.AddedBudgetItem(voteID);
    1278          40 :     LogPrint(BCLog::MNBUDGET, "fbvote - new vote (%s) for budget %s from mn %s\n",
    1279             :             voteID.ToString(), vote.GetBudgetHash().ToString(), voteVin.prevout.ToString());
    1280             :     return true;
    1281             : }
    1282             : 
    1283       51037 : bool CBudgetManager::ProcessMessage(CNode* pfrom, std::string& strCommand, CDataStream& vRecv, int& banScore)
    1284             : {
    1285       51037 :     banScore = ProcessMessageInner(pfrom, strCommand, vRecv);
    1286       51037 :     return banScore == 0;
    1287             : }
    1288             : 
    1289       51037 : int CBudgetManager::ProcessMessageInner(CNode* pfrom, std::string& strCommand, CDataStream& vRecv)
    1290             : {
    1291       51037 :     if (!g_tiertwo_sync_state.IsBlockchainSynced()) return 0;
    1292             : 
    1293       51036 :     if (strCommand == NetMsgType::BUDGETVOTESYNC) {
    1294             :         // Masternode vote sync
    1295         239 :         uint256 nProp;
    1296         239 :         vRecv >> nProp;
    1297         239 :         return ProcessBudgetVoteSync(nProp, pfrom);
    1298             :     }
    1299             : 
    1300       50797 :     if (strCommand == NetMsgType::BUDGETPROPOSAL) {
    1301             :         // Masternode Proposal
    1302          42 :         CBudgetProposal proposal;
    1303          21 :         if (!proposal.ParseBroadcast(vRecv)) {
    1304             :             return 20;
    1305             :         }
    1306          21 :         {
    1307             :             // Clear inv request
    1308          21 :             LOCK(cs_main);
    1309          21 :             g_connman->RemoveAskFor(proposal.GetHash(), MSG_BUDGET_PROPOSAL);
    1310             :         }
    1311          21 :         return ProcessProposal(proposal);
    1312             :     }
    1313             : 
    1314       50776 :     if (strCommand == NetMsgType::BUDGETVOTE) {
    1315          24 :         CBudgetVote vote;
    1316          12 :         vRecv >> vote;
    1317          12 :         vote.SetValid(true);
    1318             : 
    1319          12 :         {
    1320             :             // Clear inv request
    1321          12 :             LOCK(cs_main);
    1322          12 :             g_connman->RemoveAskFor(vote.GetHash(), MSG_BUDGET_VOTE);
    1323             :         }
    1324             : 
    1325          24 :         CValidationState state;
    1326          12 :         if (!ProcessProposalVote(vote, pfrom, state)) {
    1327           0 :             int nDos = 0;
    1328           0 :             if (state.IsInvalid(nDos)) {
    1329           0 :                 LogPrint(BCLog::MNBUDGET, "%s: %s\n", __func__, FormatStateMessage(state));
    1330             :             }
    1331           0 :             return nDos;
    1332             :         }
    1333             :         return 0;
    1334             :     }
    1335             : 
    1336       50764 :     if (strCommand == NetMsgType::FINALBUDGET) {
    1337             :         // Finalized Budget Suggestion
    1338           8 :         CFinalizedBudget finalbudget;
    1339           4 :         if (!finalbudget.ParseBroadcast(vRecv)) {
    1340             :             return 20;
    1341             :         }
    1342           4 :         {
    1343             :             // Clear inv request
    1344           4 :             LOCK(cs_main);
    1345           4 :             g_connman->RemoveAskFor(finalbudget.GetHash(), MSG_BUDGET_FINALIZED);
    1346             :         }
    1347           4 :         return ProcessFinalizedBudget(finalbudget, pfrom);
    1348             :     }
    1349             : 
    1350       50760 :     if (strCommand == NetMsgType::FINALBUDGETVOTE) {
    1351          16 :         CFinalizedBudgetVote vote;
    1352           8 :         vRecv >> vote;
    1353           8 :         vote.SetValid(true);
    1354             : 
    1355           8 :         {
    1356             :             // Clear inv request
    1357           8 :             LOCK(cs_main);
    1358           8 :             g_connman->RemoveAskFor(vote.GetHash(), MSG_BUDGET_FINALIZED_VOTE);
    1359             :         }
    1360             : 
    1361          16 :         CValidationState state;
    1362           8 :         if (!ProcessFinalizedBudgetVote(vote, pfrom, state)) {
    1363           0 :             int nDos = 0;
    1364           0 :             if (state.IsInvalid(nDos)) {
    1365           0 :                 LogPrint(BCLog::MNBUDGET, "%s: %s\n", __func__, FormatStateMessage(state));
    1366             :             }
    1367           0 :             return nDos;
    1368             :         }
    1369             :         return 0;
    1370             :     }
    1371             : 
    1372             :     // nothing was done
    1373             :     return 0;
    1374             : }
    1375             : 
    1376         613 : void CBudgetManager::SetSynced(bool synced)
    1377             : {
    1378         613 :     {
    1379         613 :         LOCK(cs_proposals);
    1380         692 :         for (auto& it: mapProposals) {
    1381          79 :             CBudgetProposal* pbudgetProposal = &(it.second);
    1382          79 :             if (pbudgetProposal && pbudgetProposal->IsValid()) {
    1383             :                 //mark votes
    1384          79 :                 pbudgetProposal->SetSynced(synced);
    1385             :             }
    1386             :         }
    1387             :     }
    1388         613 :     {
    1389         613 :         LOCK(cs_budgets);
    1390         621 :         for (auto& it: mapFinalizedBudgets) {
    1391           8 :             CFinalizedBudget* pfinalizedBudget = &(it.second);
    1392           8 :             if (pfinalizedBudget && pfinalizedBudget->IsValid()) {
    1393             :                 //mark votes
    1394           8 :                 pfinalizedBudget->SetSynced(synced);
    1395             :             }
    1396             :         }
    1397             :     }
    1398         613 : }
    1399             : 
    1400             : template<typename T>
    1401           3 : static bool relayItemIfFound(const uint256& itemHash, CNode* pfrom, RecursiveMutex& cs, std::map<uint256, T>& map, const char* type)
    1402             : {
    1403           3 :     CNetMsgMaker msgMaker(pfrom->GetSendVersion());
    1404           6 :     LOCK(cs);
    1405           3 :     const auto& it = map.find(itemHash);
    1406           3 :     if (it == map.end()) return false;
    1407           2 :     T* item = &(it->second);
    1408           2 :     if (!item->IsValid()) return true; // don't broadcast invalid items
    1409           2 :     g_connman->PushMessage(pfrom, msgMaker.Make(type, item->GetBroadcast()));
    1410           2 :     int nInvCount = 1;
    1411           2 :     item->SyncVotes(pfrom, false /* fPartial */, nInvCount);
    1412           2 :     LogPrint(BCLog::MNBUDGET, "%s: single %s sent %d items\n", __func__, type, nInvCount);
    1413             :     return true;
    1414             : }
    1415             : 
    1416             : template<typename T>
    1417        2884 : static void relayInventoryItems(CNode* pfrom, RecursiveMutex& cs, std::map<uint256, T>& map, bool fPartial, GetDataMsg invType, const int mn_sync_budget_type)
    1418             : {
    1419        2884 :     CNetMsgMaker msgMaker(pfrom->GetSendVersion());
    1420        2884 :     int nInvCount = 0;
    1421             :     {
    1422        5768 :         LOCK(cs);
    1423        3038 :         for (auto& it: map) {
    1424         154 :             T* item = &(it.second);
    1425         154 :             if (item && item->IsValid()) {
    1426         154 :                 pfrom->PushInventory(CInv(invType, item->GetHash()));
    1427         154 :                 nInvCount++;
    1428         154 :                 item->SyncVotes(pfrom, fPartial, nInvCount);
    1429             :             }
    1430             :         }
    1431             :     }
    1432        2884 :     g_connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::SYNCSTATUSCOUNT, mn_sync_budget_type, nInvCount));
    1433        2884 :     LogPrint(BCLog::MNBUDGET, "%s: sent %d items\n", __func__, nInvCount);
    1434        2884 : }
    1435             : 
    1436           2 : void CBudgetManager::SyncSingleItem(CNode* pfrom, const uint256& nProp)
    1437             : {
    1438           4 :     if (nProp.IsNull()) return;
    1439             :     // Try first to relay a proposal
    1440           2 :     if (relayItemIfFound<CBudgetProposal>(nProp, pfrom, cs_proposals, mapProposals, NetMsgType::BUDGETPROPOSAL)) {
    1441             :         return;
    1442             :     }
    1443             :     // Try now to relay a finalization
    1444           1 :     if (relayItemIfFound<CFinalizedBudget>(nProp, pfrom, cs_budgets, mapFinalizedBudgets, NetMsgType::FINALBUDGET)) {
    1445             :         return;
    1446             :     }
    1447           0 :     LogPrint(BCLog::MNBUDGET, "%s: single request budget item not found\n", __func__);
    1448             : }
    1449             : 
    1450             : 
    1451        1442 : void CBudgetManager::Sync(CNode* pfrom, bool fPartial)
    1452             : {
    1453             :     // Full budget sync request.
    1454        1442 :     relayInventoryItems<CBudgetProposal>(pfrom, cs_proposals, mapProposals, fPartial, MSG_BUDGET_PROPOSAL, MASTERNODE_SYNC_BUDGET_PROP);
    1455        1442 :     relayInventoryItems<CFinalizedBudget>(pfrom, cs_budgets, mapFinalizedBudgets, fPartial, MSG_BUDGET_FINALIZED, MASTERNODE_SYNC_BUDGET_FIN);
    1456             : 
    1457        1442 :     if (!fPartial) {
    1458             :         // We are not going to answer full budget sync requests for an hour (chainparams.FulfilledRequestExpireTime()).
    1459             :         // The remote peer can still do single prop and mnv sync requests if needed.
    1460         237 :         g_netfulfilledman.AddFulfilledRequest(pfrom->addr, BUDGET_SYNC_REQUEST_RECV);
    1461             :     }
    1462        1442 : }
    1463             : 
    1464             : template<typename T>
    1465           0 : static void TryAppendOrphanVoteMap(const T& vote,
    1466             :                                    const uint256& parentHash,
    1467             :                                    std::map<uint256, std::pair<std::vector<T>, int64_t>>& mapOrphan,
    1468             :                                    std::map<uint256, T>& mapSeen)
    1469             : {
    1470           0 :     if (mapOrphan.size() > ORPHAN_VOTES_CACHE_LIMIT) {
    1471             :         // future: notify user about this
    1472           0 :         mapSeen.erase(vote.GetHash());
    1473             :     } else {
    1474             :         // Append orphan vote
    1475           0 :         const auto& it = mapOrphan.find(parentHash);
    1476           0 :         if (it != mapOrphan.end()) {
    1477             :             // Check size limit and erase it from the seen map if we already passed it
    1478           0 :             if (it->second.first.size() > ORPHAN_VOTES_CACHE_LIMIT) {
    1479             :                 // future: check if the MN already voted and replace vote
    1480           0 :                 mapSeen.erase(vote.GetHash());
    1481             :             } else {
    1482           0 :                 it->second.first.emplace_back(vote);
    1483           0 :                 it->second.second = GetTime();
    1484             :             }
    1485             :         } else {
    1486           0 :             mapOrphan.emplace(parentHash, std::make_pair<std::vector<T>, int64_t>({vote}, GetTime()));
    1487             :         }
    1488             :     }
    1489           0 : }
    1490             : 
    1491          17 : bool CBudgetManager::UpdateProposal(const CBudgetVote& vote, CNode* pfrom, std::string& strError)
    1492             : {
    1493          34 :     LOCK(cs_proposals);
    1494             : 
    1495          17 :     const uint256& nProposalHash = vote.GetProposalHash();
    1496          17 :     const auto& itProposal = mapProposals.find(nProposalHash);
    1497          17 :     if (itProposal == mapProposals.end()) {
    1498           0 :         if (pfrom) {
    1499             :             // only ask for missing items after our syncing process is complete --
    1500             :             //   otherwise we'll think a full sync succeeded when they return a result
    1501           0 :             if (!g_tiertwo_sync_state.IsSynced()) return false;
    1502             : 
    1503           0 :             LogPrint(BCLog::MNBUDGET,"%s: Unknown proposal %d, asking for source proposal\n", __func__, nProposalHash.ToString());
    1504           0 :             {
    1505           0 :                 LOCK(cs_votes);
    1506           0 :                 TryAppendOrphanVoteMap<CBudgetVote>(vote, nProposalHash, mapOrphanProposalVotes, mapSeenProposalVotes);
    1507             :             }
    1508             : 
    1509           0 :             if (!g_netfulfilledman.HasItemRequest(pfrom->addr, nProposalHash)) {
    1510           0 :                 g_connman->PushMessage(pfrom, CNetMsgMaker(pfrom->GetSendVersion()).Make(NetMsgType::BUDGETVOTESYNC, nProposalHash));
    1511           0 :                 g_netfulfilledman.AddItemRequest(pfrom->addr, nProposalHash);
    1512             :             }
    1513             :         }
    1514             : 
    1515          17 :         strError = "Proposal not found!";
    1516             :         return false;
    1517             :     }
    1518             : 
    1519             :     // Add or update vote
    1520          17 :     return itProposal->second.AddOrUpdateVote(vote, strError);
    1521             : }
    1522             : 
    1523          12 : bool CBudgetManager::UpdateFinalizedBudget(const CFinalizedBudgetVote& vote, CNode* pfrom, std::string& strError)
    1524             : {
    1525          24 :     LOCK(cs_budgets);
    1526             : 
    1527          12 :     const uint256& nBudgetHash = vote.GetBudgetHash();
    1528          12 :     if (!mapFinalizedBudgets.count(nBudgetHash)) {
    1529           0 :         if (pfrom) {
    1530             :             // only ask for missing items after our syncing process is complete --
    1531             :             //   otherwise we'll think a full sync succeeded when they return a result
    1532           0 :             if (!g_tiertwo_sync_state.IsSynced()) return false;
    1533             : 
    1534           0 :             LogPrint(BCLog::MNBUDGET,"%s: Unknown Finalized Proposal %s, asking for source budget\n", __func__, nBudgetHash.ToString());
    1535           0 :             {
    1536           0 :                 LOCK(cs_finalizedvotes);
    1537           0 :                 TryAppendOrphanVoteMap<CFinalizedBudgetVote>(vote, nBudgetHash, mapOrphanFinalizedBudgetVotes, mapSeenFinalizedBudgetVotes);
    1538             :             }
    1539             : 
    1540           0 :             if (!g_netfulfilledman.HasItemRequest(pfrom->addr, nBudgetHash)) {
    1541           0 :                 g_connman->PushMessage(pfrom, CNetMsgMaker(pfrom->GetSendVersion()).Make(NetMsgType::BUDGETVOTESYNC, nBudgetHash));
    1542           0 :                 g_netfulfilledman.AddItemRequest(pfrom->addr, nBudgetHash);
    1543             :             }
    1544             :         }
    1545             : 
    1546           0 :         strError = "Finalized Budget " + nBudgetHash.ToString() +  " not found!";
    1547           0 :         return false;
    1548             :     }
    1549          24 :     LogPrint(BCLog::MNBUDGET,"%s: Finalized Proposal %s added\n", __func__, nBudgetHash.ToString());
    1550          12 :     return mapFinalizedBudgets[nBudgetHash].AddOrUpdateVote(vote, strError);
    1551             : }
    1552             : 
    1553         129 : std::string CBudgetManager::ToString() const
    1554             : {
    1555         258 :     unsigned int nProposals = WITH_LOCK(cs_proposals, return mapProposals.size(); );
    1556         258 :     unsigned int nBudgets = WITH_LOCK(cs_budgets, return mapFinalizedBudgets.size(); );
    1557             : 
    1558         129 :     unsigned int nSeenVotes = 0, nOrphanVotes = 0;
    1559         129 :     {
    1560         129 :         LOCK(cs_votes);
    1561         129 :         nSeenVotes = mapSeenProposalVotes.size();
    1562         129 :         nOrphanVotes = mapOrphanProposalVotes.size();
    1563             :     }
    1564             : 
    1565         129 :     unsigned int nSeenFinalizedVotes = 0, nOrphanFinalizedVotes = 0;
    1566         129 :     {
    1567         129 :         LOCK(cs_finalizedvotes);
    1568         129 :         nSeenFinalizedVotes = mapSeenFinalizedBudgetVotes.size();
    1569         129 :         nOrphanFinalizedVotes = mapOrphanFinalizedBudgetVotes.size();
    1570             :     }
    1571             : 
    1572         129 :     return strprintf("Proposals: %d - Finalized Budgets: %d - "
    1573             :             "Proposal Votes: %d (orphan: %d) - "
    1574             :             "Finalized Budget Votes: %d (orphan: %d)",
    1575             :             nProposals, nBudgets,
    1576         129 :             nSeenVotes, nOrphanVotes, nSeenFinalizedVotes, nOrphanFinalizedVotes);
    1577             : }
    1578             : 
    1579             : 
    1580             : /*
    1581             :  * Check Collateral
    1582             :  */
    1583          30 : bool CheckCollateralConfs(const uint256& nTxCollateralHash, int nCurrentHeight, int nProposalHeight, std::string& strError)
    1584             : {
    1585          30 :     const int nRequiredConfs = Params().GetConsensus().nBudgetFeeConfirmations;
    1586          30 :     const int nConf = nCurrentHeight - nProposalHeight + 1;
    1587             : 
    1588          30 :     if (nConf < nRequiredConfs) {
    1589           0 :         strError = strprintf("Collateral requires at least %d confirmations - %d confirmations (current height: %d, fee tx height: %d)",
    1590           0 :                 nRequiredConfs, nConf, nCurrentHeight, nProposalHeight);
    1591           0 :         LogPrint(BCLog::MNBUDGET,"%s: %s\n", __func__, strError);
    1592           0 :         return false;
    1593             :     }
    1594             :     return true;
    1595             : }
    1596             : 
    1597          30 : bool CheckCollateral(const uint256& nTxCollateralHash, const uint256& nExpectedHash, std::string& strError, int64_t& nTime, int nCurrentHeight, bool fBudgetFinalization)
    1598             : {
    1599          30 :     CTransactionRef txCollateral;
    1600          30 :     uint256 nBlockHash;
    1601          30 :     if (!GetTransaction(nTxCollateralHash, txCollateral, nBlockHash, true)) {
    1602           0 :         strError = strprintf("Can't find collateral tx %s", nTxCollateralHash.ToString());
    1603           0 :         return false;
    1604             :     }
    1605             : 
    1606          30 :     if (txCollateral->vout.size() < 1) return false;
    1607          30 :     if (txCollateral->nLockTime != 0) return false;
    1608             : 
    1609          60 :     CScript findScript;
    1610          30 :     findScript << OP_RETURN << ToByteVector(nExpectedHash);
    1611             : 
    1612          30 :     bool foundOpReturn = false;
    1613          45 :     for (const CTxOut &o : txCollateral->vout) {
    1614          45 :         if (!o.scriptPubKey.IsPayToPublicKeyHash() && !o.scriptPubKey.IsUnspendable()) {
    1615           0 :             strError = strprintf("Invalid Script %s", txCollateral->ToString());
    1616           0 :             return false;
    1617             :         }
    1618          45 :         if (fBudgetFinalization) {
    1619             :             // Collateral for budget finalization
    1620             :             // Note: there are still old valid budgets out there, but the check for the new 5 PIV finalization collateral
    1621             :             //       will also cover the old 50 PIV finalization collateral.
    1622          40 :             LogPrint(BCLog::MNBUDGET, "Final Budget: o.scriptPubKey(%s) == findScript(%s) ?\n", HexStr(o.scriptPubKey), HexStr(findScript));
    1623           8 :             if (o.scriptPubKey == findScript) {
    1624           6 :                 LogPrint(BCLog::MNBUDGET, "Final Budget: o.nValue(%ld) >= BUDGET_FEE_TX(%ld) ?\n", o.nValue, BUDGET_FEE_TX);
    1625           6 :                 if(o.nValue >= BUDGET_FEE_TX) {
    1626             :                     foundOpReturn = true;
    1627             :                     break;
    1628             :                 }
    1629             :             }
    1630             :         } else {
    1631             :             // Collateral for normal budget proposal
    1632         185 :             LogPrint(BCLog::MNBUDGET, "Normal Budget: o.scriptPubKey(%s) == findScript(%s) ?\n", HexStr(o.scriptPubKey), HexStr(findScript));
    1633          37 :             if (o.scriptPubKey == findScript) {
    1634          24 :                 LogPrint(BCLog::MNBUDGET, "Normal Budget: o.nValue(%ld) >= PROPOSAL_FEE_TX(%ld) ?\n", o.nValue, PROPOSAL_FEE_TX);
    1635          24 :                 if(o.nValue >= PROPOSAL_FEE_TX) {
    1636             :                     foundOpReturn = true;
    1637             :                     break;
    1638             :                 }
    1639             :             }
    1640             :         }
    1641             :     }
    1642             : 
    1643          30 :     if (!foundOpReturn) {
    1644           0 :         strError = strprintf("Couldn't find opReturn %s in %s", nExpectedHash.ToString(), txCollateral->ToString());
    1645           0 :         return false;
    1646             :     }
    1647             : 
    1648             :     // Retrieve block height (checking that it's in the active chain) and time
    1649             :     // both get set in CBudgetProposal/CFinalizedBudget by the caller (AddProposal/AddFinalizedBudget)
    1650          60 :     if (nBlockHash.IsNull()) {
    1651           0 :         strError = strprintf("Collateral transaction %s is unconfirmed", nTxCollateralHash.ToString());
    1652           0 :         return false;
    1653             :     }
    1654          30 :     nTime = 0;
    1655          30 :     int nProposalHeight = 0;
    1656          30 :     {
    1657          30 :         LOCK(cs_main);
    1658          30 :         CBlockIndex* pindex = LookupBlockIndex(nBlockHash);
    1659          60 :         if (pindex && chainActive.Contains(pindex)) {
    1660          30 :             nProposalHeight = pindex->nHeight;
    1661          30 :             nTime = pindex->nTime;
    1662             :         }
    1663             :     }
    1664             : 
    1665          30 :     if (!nProposalHeight) {
    1666           0 :         strError = strprintf("Collateral transaction %s not in Active chain", nTxCollateralHash.ToString());
    1667           0 :         return false;
    1668             :     }
    1669             : 
    1670          30 :     return CheckCollateralConfs(nTxCollateralHash, nCurrentHeight, nProposalHeight, strError);
    1671             : }

Generated by: LCOV version 1.14