LCOV - code coverage report
Current view: top level - src/budget - budgetutil.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 58 139 41.7 %
Date: 2026-08-09 10:51:41 Functions: 8 15 53.3 %

          Line data    Source code
       1             : // Copyright (c) 2021 The PIVX Core developers
       2             : // Distributed under the MIT software license, see the accompanying
       3             : // file COPYING or https://www.opensource.org/licenses/mit-license.php.
       4             : 
       5             : #include "budget/budgetutil.h"
       6             : 
       7             : #include "budget/budgetmanager.h"
       8             : #include "masternodeman.h"
       9             : #include "masternodeconfig.h"
      10             : #include "util/validation.h"
      11             : 
      12             : #ifdef ENABLE_WALLET
      13             : #include "wallet/wallet.h" // future: use interface instead.
      14             : #endif
      15             : 
      16             : 
      17           8 : static UniValue packRetStatus(const std::string& nodeType, const std::string& result, const std::string& error)
      18             : {
      19           8 :     UniValue statusObj(UniValue::VOBJ);
      20           8 :     statusObj.pushKV("node", nodeType);
      21           8 :     statusObj.pushKV("result", result);
      22           8 :     statusObj.pushKV("error", error);
      23           8 :     return statusObj;
      24             : }
      25             : 
      26           0 : static UniValue packErrorRetStatus(const std::string& nodeType, const std::string& error)
      27             : {
      28           0 :     return packRetStatus(nodeType, "failed", error);
      29             : }
      30             : 
      31           7 : static UniValue packVoteReturnValue(const UniValue& details, int success, int failed)
      32             : {
      33           7 :     UniValue returnObj(UniValue::VOBJ);
      34          14 :     returnObj.pushKV("overall", strprintf("Voted successfully %d time(s) and failed %d time(s).", success, failed));
      35           7 :     returnObj.pushKV("detail", details);
      36           7 :     return returnObj;
      37             : }
      38             : 
      39             : // key, alias and collateral outpoint of a masternode. Struct used to sign proposal/budget votes
      40             : struct MnKeyData
      41             : {
      42             :     std::string mnAlias;
      43             :     const COutPoint* collateralOut;
      44             : 
      45             :     MnKeyData() = delete;
      46           8 :     MnKeyData(const std::string& _mnAlias, const COutPoint* _collateralOut, const CKey& _key):
      47             :         mnAlias(_mnAlias),
      48             :         collateralOut(_collateralOut),
      49             :         key(_key),
      50           8 :         use_bls(false)
      51           8 :     {}
      52           0 :     MnKeyData(const std::string& _mnAlias, const COutPoint* _collateralOut, const CBLSSecretKey& _key):
      53             :         mnAlias(_mnAlias),
      54             :         collateralOut(_collateralOut),
      55             :         blsKey(_key),
      56           0 :         use_bls(true)
      57           0 :     {}
      58             : 
      59           8 :     bool Sign(CSignedMessage* msg) const
      60             :     {
      61           8 :         return use_bls ? msg->Sign(blsKey)
      62           8 :                        : msg->Sign(key, key.GetPubKey().GetID());
      63             :     }
      64             : 
      65             : private:
      66             :     CKey key;
      67             :     CBLSSecretKey blsKey;
      68             :     bool use_bls;   // whether to use a CKey (mbv) or blsKey (fbv, mnw) to sign
      69             : };
      70             : 
      71             : typedef std::list<MnKeyData> mnKeyList;
      72             : 
      73           5 : static UniValue voteProposal(const uint256& propHash, const CBudgetVote::VoteDirection& nVote,
      74             :                              const mnKeyList& mnKeys, UniValue resultsObj, int failed)
      75             : {
      76           5 :     int success = 0;
      77          10 :     for (const auto& k : mnKeys) {
      78          15 :         CBudgetVote vote(CTxIn(*k.collateralOut), propHash, nVote);
      79           5 :         if (!k.Sign(&vote)) {
      80           0 :             resultsObj.push_back(packErrorRetStatus(k.mnAlias, "Failure to sign."));
      81           0 :             failed++;
      82           0 :             continue;
      83             :         }
      84           5 :         CValidationState state;
      85           5 :         if (!g_budgetman.ProcessProposalVote(vote, nullptr, state)) {
      86           0 :             resultsObj.push_back(packErrorRetStatus(k.mnAlias, FormatStateMessage(state)));
      87           0 :             failed++;
      88           0 :             continue;
      89             :         }
      90           5 :         resultsObj.push_back(packRetStatus(k.mnAlias, "success", ""));
      91           5 :         success++;
      92             :     }
      93             : 
      94           5 :     return packVoteReturnValue(resultsObj, success, failed);
      95             : }
      96             : 
      97           2 : static UniValue voteFinalBudget(const uint256& budgetHash,
      98             :                                 const mnKeyList& mnKeys, UniValue resultsObj, int failed)
      99             : {
     100           2 :     int success = 0;
     101           5 :     for (const auto& k : mnKeys) {
     102           9 :         CFinalizedBudgetVote vote(CTxIn(*k.collateralOut), budgetHash);
     103           3 :         if (!k.Sign(&vote)) {
     104           0 :             resultsObj.push_back(packErrorRetStatus(k.mnAlias, "Failure to sign."));
     105           0 :             failed++;
     106           0 :             continue;
     107             :         }
     108           3 :         CValidationState state;
     109           3 :         if (!g_budgetman.ProcessFinalizedBudgetVote(vote, nullptr, state)) {
     110           0 :             resultsObj.push_back(packErrorRetStatus(k.mnAlias, FormatStateMessage(state)));
     111           0 :             failed++;
     112           0 :             continue;
     113             :         }
     114           3 :         resultsObj.push_back(packRetStatus(k.mnAlias, "success", ""));
     115           3 :         success++;
     116             :     }
     117             : 
     118           2 :     return packVoteReturnValue(resultsObj, success, failed);
     119             : }
     120             : 
     121             : // Legacy masternodes
     122           7 : static mnKeyList getMNKeys(const Optional<std::string>& mnAliasFilter,
     123             :                            UniValue& resultsObj, int& failed)
     124             : {
     125           7 :     mnKeyList mnKeys;
     126          19 :     for (const CMasternodeConfig::CMasternodeEntry& mne : masternodeConfig.getEntries()) {
     127          12 :         if (mnAliasFilter && *mnAliasFilter != mne.getAlias()) continue;
     128          16 :         CKey mnKey; CPubKey mnPubKey;
     129           8 :         const std::string& mnAlias = mne.getAlias();
     130           8 :         if (!CMessageSigner::GetKeysFromSecret(mne.getPrivKey(), mnKey, mnPubKey)) {
     131           0 :             resultsObj.push_back(packErrorRetStatus(mnAlias, "Could not get key from masternode.conf"));
     132           0 :             failed++;
     133           4 :             continue;
     134             :         }
     135           8 :         CMasternode* pmn = mnodeman.Find(mnPubKey);
     136           8 :         if (!pmn) {
     137           0 :             resultsObj.push_back(packErrorRetStatus(mnAlias, "Can't find masternode by pubkey"));
     138           0 :             failed++;
     139           0 :             continue;
     140             :         }
     141          16 :         mnKeys.emplace_back(mnAlias, &pmn->vin.prevout, mnKey);
     142             :     }
     143           7 :     return mnKeys;
     144             : }
     145             : 
     146           0 : static mnKeyList getMNKeysForActiveMasternode(UniValue& resultsObj)
     147             : {
     148             :     // local node must be a masternode
     149           0 :     if (!fMasterNode) {
     150           0 :         throw std::runtime_error(_("This is not a masternode. 'local' option disabled."));
     151             :     }
     152             : 
     153           0 :     if (activeMasternode.vin == nullopt) {
     154           0 :         throw std::runtime_error(_("Active Masternode not initialized."));
     155             :     }
     156             : 
     157           0 :     CKey mnKey; CPubKey mnPubKey;
     158           0 :     activeMasternode.GetKeys(mnKey, mnPubKey);
     159           0 :     CMasternode* pmn = mnodeman.Find(mnPubKey);
     160           0 :     if (!pmn) {
     161           0 :         resultsObj.push_back(packErrorRetStatus("local", "Can't find masternode by pubkey"));
     162           0 :         return mnKeyList();
     163             :     }
     164             : 
     165           0 :     return {MnKeyData("local", &pmn->vin.prevout, mnKey)};
     166             : }
     167             : 
     168             : // Deterministic masternodes
     169           0 : static mnKeyList getDMNVotingKeys(CWallet* const pwallet, const Optional<std::string>& mnAliasFilter, bool fFinal, UniValue& resultsObj, int& failed)
     170             : {
     171           0 :     if (!pwallet) {
     172           0 :         throw std::runtime_error( "Wallet (with voting key) not found.");
     173             :     }
     174             : 
     175           0 :     auto mnList = deterministicMNManager->GetListAtChainTip();
     176             : 
     177           0 :     CDeterministicMNCPtr mnFilter{nullptr};
     178           0 :     if (mnAliasFilter) {
     179             :         // vote with a single masternode (identified by ProTx)
     180           0 :         const uint256& proTxHash = uint256S(*mnAliasFilter);
     181           0 :         mnFilter = mnList.GetValidMN(proTxHash);
     182           0 :         if (!mnFilter) {
     183           0 :             resultsObj.push_back(packErrorRetStatus(*mnAliasFilter, "Invalid or unknown proTxHash"));
     184           0 :             failed++;
     185           0 :             return {};
     186             :         }
     187             :     }
     188             : 
     189           0 :     mnKeyList mnKeys;
     190           0 :     mnList.ForEachMN(true, [&](const CDeterministicMNCPtr& dmn) {
     191           0 :         bool filtered = mnFilter && dmn->proTxHash == mnFilter->proTxHash;
     192           0 :         if (!mnFilter || filtered) {
     193           0 :             if (fFinal) {
     194             :                 // We should never get here. BLS operator key (for active mn) is needed.
     195           0 :                 throw std::runtime_error("Finalized budget voting is allowed only locally, from the masternode");
     196             :             }
     197             :             // Get voting key from the wallet
     198           0 :             LOCK(pwallet->cs_wallet);
     199           0 :             CKey mnKey;
     200           0 :             if (pwallet->GetKey(dmn->pdmnState->keyIDVoting, mnKey)) {
     201           0 :                 mnKeys.emplace_back(dmn->proTxHash.ToString(), &dmn->collateralOutpoint, mnKey);
     202           0 :             } else if (filtered) {
     203           0 :                 resultsObj.push_back(packErrorRetStatus(*mnAliasFilter, strprintf(
     204             :                                         "Private key for voting address %s not known by this wallet",
     205           0 :                                         EncodeDestination(dmn->pdmnState->keyIDVoting)))
     206             :                                     );
     207           0 :                 failed++;
     208             :             }
     209             :         }
     210           0 :     });
     211             : 
     212           0 :     return mnKeys;
     213             : }
     214             : 
     215           0 : static mnKeyList getDMNKeysForActiveMasternode(UniValue& resultsObj)
     216             : {
     217             :     // local node must be a masternode
     218           0 :     if (!activeMasternodeManager) {
     219           0 :         throw std::runtime_error(_("This is not a deterministic masternode. 'local' option disabled."));
     220             :     }
     221             : 
     222           0 :     CBLSSecretKey sk; CDeterministicMNCPtr dmn;
     223           0 :     auto res = activeMasternodeManager->GetOperatorKey(sk, dmn);
     224           0 :     if (!res) {
     225           0 :         resultsObj.push_back(packErrorRetStatus("local", res.getError()));
     226           0 :         return {};
     227             :     }
     228             : 
     229           0 :     return {MnKeyData("local", &dmn->collateralOutpoint, sk)};
     230             : }
     231             : 
     232             : // vote on proposal (finalized budget, if fFinal=true) with all possible keys or a single mn (mnAliasFilter)
     233             : // Note: for DMNs only proposal voting is allowed with the voting key
     234             : // (finalized budget voting requires the operator BLS key)
     235           7 : UniValue mnBudgetVoteInner(CWallet* const pwallet, bool fLegacyMN, const uint256& budgetHash, bool fFinal,
     236             :                                   const CBudgetVote::VoteDirection& nVote, const Optional<std::string>& mnAliasFilter)
     237             : {
     238           7 :     if (fFinal && !fLegacyMN) {
     239           0 :         throw std::runtime_error("Finalized budget voting is allowed only locally, from the masternode");
     240             :     }
     241           7 :     UniValue resultsObj(UniValue::VARR);
     242           7 :     int failed = 0;
     243             : 
     244           7 :     mnKeyList mnKeys = fLegacyMN ? getMNKeys(mnAliasFilter, resultsObj, failed)
     245          14 :                                  : getDMNVotingKeys(pwallet, mnAliasFilter, fFinal, resultsObj, failed);
     246             : 
     247           7 :     if (mnKeys.empty()) {
     248           0 :         return packVoteReturnValue(resultsObj, 0, failed);
     249             :     }
     250             : 
     251           7 :     return (fFinal ? voteFinalBudget(budgetHash, mnKeys, resultsObj, failed)
     252           7 :                    : voteProposal(budgetHash, nVote, mnKeys, resultsObj, failed));
     253             : }
     254             : 
     255             : // vote on proposal (finalized budget, if fFinal=true) with the active local masternode
     256             : // Note: for DMNs only finalized budget voting is allowed with the operator key
     257             : // (proposal voting requires the voting key)
     258           0 : UniValue mnLocalBudgetVoteInner(bool fLegacyMN, const uint256& budgetHash, bool fFinal,
     259             :                                        const CBudgetVote::VoteDirection& nVote)
     260             : {
     261           0 :     UniValue resultsObj(UniValue::VARR);
     262             : 
     263           0 :     mnKeyList mnKeys = fLegacyMN ? getMNKeysForActiveMasternode(resultsObj)
     264           0 :                                  : getDMNKeysForActiveMasternode(resultsObj);
     265             : 
     266           0 :     if (mnKeys.empty()) {
     267           0 :         return packVoteReturnValue(resultsObj, 0, 1);
     268             :     }
     269             : 
     270           0 :     return (fFinal ? voteFinalBudget(budgetHash, mnKeys, resultsObj, 0)
     271           0 :                    : voteProposal(budgetHash, nVote, mnKeys, resultsObj, 0));
     272             : }

Generated by: LCOV version 1.14