LCOV - code coverage report
Current view: top level - src/rpc - budget.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 260 358 72.6 %
Date: 2025-02-23 09:33:43 Functions: 17 18 94.4 %

          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 "chainparams.h"
       7             : #include "budget/budgetmanager.h"
       8             : #include "budget/budgetutil.h"
       9             : #include "db.h"
      10             : #include "evo/deterministicmns.h"
      11             : #include "key_io.h"
      12             : #include "masternode-payments.h"
      13             : #include "masternode-sync.h"
      14             : #include "masternodeconfig.h"
      15             : #include "masternodeman.h"
      16             : #include "messagesigner.h"
      17             : #include "tiertwo/tiertwo_sync_state.h"
      18             : #include "rpc/server.h"
      19             : #include "utilmoneystr.h"
      20             : #ifdef ENABLE_WALLET
      21             : #include "wallet/wallet.h"
      22             : #include "wallet/rpcwallet.h"
      23             : #endif
      24             : 
      25             : #include <univalue.h>
      26             : 
      27         507 : void budgetToJSON(const CBudgetProposal* pbudgetProposal, UniValue& bObj, int nCurrentHeight)
      28             : {
      29         507 :     CTxDestination address1;
      30         507 :     ExtractDestination(pbudgetProposal->GetPayee(), address1);
      31             : 
      32        1521 :     bObj.pushKV("Name", pbudgetProposal->GetName());
      33        1521 :     bObj.pushKV("URL", pbudgetProposal->GetURL());
      34        1014 :     bObj.pushKV("Hash", pbudgetProposal->GetHash().ToString());
      35        1014 :     bObj.pushKV("FeeHash", pbudgetProposal->GetFeeTXHash().ToString());
      36         507 :     bObj.pushKV("BlockStart", (int64_t)pbudgetProposal->GetBlockStart());
      37         507 :     bObj.pushKV("BlockEnd", (int64_t)pbudgetProposal->GetBlockEnd());
      38         507 :     bObj.pushKV("TotalPaymentCount", (int64_t)pbudgetProposal->GetTotalPaymentCount());
      39         507 :     bObj.pushKV("RemainingPaymentCount", (int64_t)pbudgetProposal->GetRemainingPaymentCount(nCurrentHeight));
      40        1014 :     bObj.pushKV("PaymentAddress", EncodeDestination(address1));
      41         507 :     bObj.pushKV("Ratio", pbudgetProposal->GetRatio());
      42         507 :     bObj.pushKV("Yeas", (int64_t)pbudgetProposal->GetYeas());
      43         507 :     bObj.pushKV("Nays", (int64_t)pbudgetProposal->GetNays());
      44         507 :     bObj.pushKV("Abstains", (int64_t)pbudgetProposal->GetAbstains());
      45        1014 :     bObj.pushKV("TotalPayment", ValueFromAmount(pbudgetProposal->GetAmount() * pbudgetProposal->GetTotalPaymentCount()));
      46        1014 :     bObj.pushKV("MonthlyPayment", ValueFromAmount(pbudgetProposal->GetAmount()));
      47         507 :     bObj.pushKV("IsEstablished", pbudgetProposal->IsEstablished());
      48         507 :     bool fValid = pbudgetProposal->IsValid();
      49         507 :     bObj.pushKV("IsValid", fValid);
      50         507 :     if (!fValid)
      51           0 :         bObj.pushKV("IsInvalidReason", pbudgetProposal->IsInvalidReason());
      52        1014 :     bObj.pushKV("Allotted", ValueFromAmount(pbudgetProposal->GetAllotted()));
      53         507 : }
      54             : 
      55          50 : void checkBudgetInputs(const UniValue& params, std::string &strProposalName, std::string &strURL,
      56             :                        int &nPaymentCount, int &nBlockStart, CTxDestination &address, CAmount &nAmount)
      57             : {
      58          50 :     strProposalName = SanitizeString(params[0].get_str());
      59          50 :     if (strProposalName.size() > 20)
      60           2 :         throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid proposal name, limit of 20 characters.");
      61             : 
      62          49 :     strURL = SanitizeString(params[1].get_str());
      63          49 :     std::string strErr;
      64          49 :     if (!validateURL(strURL, strErr))
      65           4 :         throw JSONRPCError(RPC_INVALID_PARAMETER, strErr);
      66             : 
      67          45 :     nPaymentCount = params[2].get_int();
      68          45 :     if (nPaymentCount < 1)
      69           2 :         throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid payment count, must be more than zero.");
      70             : 
      71          44 :     int nMaxPayments = Params().GetConsensus().nMaxProposalPayments;
      72             :     if (nPaymentCount > nMaxPayments) {
      73           1 :         throw JSONRPCError(RPC_INVALID_PARAMETER,
      74           2 :                 strprintf("Invalid payment count, must be <= %d", nMaxPayments));
      75             :     }
      76             : 
      77          43 :     CBlockIndex* pindexPrev = GetChainTip();
      78          43 :     if (!pindexPrev)
      79           0 :         throw JSONRPCError(RPC_IN_WARMUP, "Try again after active chain is loaded");
      80             : 
      81             :     // Start must be in the next budget cycle or later
      82          43 :     const int budgetCycleBlocks = Params().GetConsensus().nBudgetCycleBlocks;
      83          43 :     int pHeight = pindexPrev->nHeight;
      84             : 
      85          43 :     int nBlockMin = pHeight - (pHeight % budgetCycleBlocks) + budgetCycleBlocks;
      86             : 
      87          43 :     nBlockStart = params[3].get_int();
      88          43 :     if ((nBlockStart < nBlockMin) || ((nBlockStart % budgetCycleBlocks) != 0))
      89           2 :         throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid block start - must be a budget cycle block. Next valid block: %d", nBlockMin));
      90             : 
      91          82 :     address = DecodeDestination(params[4].get_str());
      92          41 :     if (!IsValidDestination(address))
      93           2 :         throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid PIVX address");
      94             : 
      95          40 :     nAmount = AmountFromValue(params[5]);
      96          40 :     if (nAmount < 10 * COIN)
      97           2 :         throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid amount - Payment of %s is less than minimum 10 %s allowed", FormatMoney(nAmount), CURRENCY_UNIT));
      98             : 
      99          39 :     const CAmount& nTotalBudget = g_budgetman.GetTotalBudget(nBlockStart);
     100          39 :     if (nAmount > nTotalBudget)
     101           2 :         throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid amount - Payment of %s more than max of %s", FormatMoney(nAmount), FormatMoney(nTotalBudget)));
     102          38 : }
     103             : 
     104          31 : UniValue preparebudget(const JSONRPCRequest& request)
     105             : {
     106          31 :     CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
     107             : 
     108          31 :     if (!EnsureWalletIsAvailable(pwallet, request.fHelp))
     109           0 :         return NullUniValue;
     110             : 
     111          31 :     if (request.fHelp || request.params.size() != 6)
     112           0 :         throw std::runtime_error(
     113             :             "preparebudget \"name\" \"url\" npayments start \"address\" monthly_payment\n"
     114             :             "\nPrepare proposal for network by signing and creating tx\n"
     115             : 
     116             :             "\nArguments:\n"
     117             :             "1. \"name\":        (string, required) Desired proposal name (20 character limit)\n"
     118             :             "2. \"url\":         (string, required) URL of proposal details (64 character limit)\n"
     119             :             "3. npayments:       (numeric, required) Total number of monthly payments\n"
     120             :             "4. start:           (numeric, required) Starting super block height\n"
     121             :             "5. \"address\":     (string, required) PIVX address to send payments to\n"
     122             :             "6. monthly_payment: (numeric, required) Monthly payment amount\n"
     123             : 
     124             :             "\nResult:\n"
     125             :             "\"xxxx\"       (string) proposal fee hash (if successful) or error message (if failed)\n"
     126             : 
     127           0 :             "\nExamples:\n" +
     128           0 :             HelpExampleCli("preparebudget", "\"test-proposal\" \"https://forum.pivx.org/t/test-proposal\" 2 820800 \"D9oc6C3dttUbv8zd7zGNq1qKBGf4ZQ1XEE\" 500") +
     129           0 :             HelpExampleRpc("preparebudget", "\"test-proposal\" \"https://forum.pivx.org/t/test-proposal\" 2 820800 \"D9oc6C3dttUbv8zd7zGNq1qKBGf4ZQ1XEE\" 500"));
     130             : 
     131          81 :     LOCK2(cs_main, pwallet->cs_wallet);
     132             : 
     133          31 :     EnsureWalletIsUnlocked(pwallet);
     134             : 
     135          62 :     std::string strProposalName;
     136          31 :     std::string strURL;
     137          31 :     int nPaymentCount;
     138          31 :     int nBlockStart;
     139          62 :     CTxDestination address;
     140          31 :     CAmount nAmount;
     141             : 
     142          31 :     checkBudgetInputs(request.params, strProposalName, strURL, nPaymentCount, nBlockStart, address, nAmount);
     143             : 
     144             :     // Parse PIVX address
     145          38 :     CScript scriptPubKey = GetScriptForDestination(address);
     146             : 
     147             :     // create transaction 15 minutes into the future, to allow for confirmation time
     148          38 :     CBudgetProposal proposal(strProposalName, strURL, nPaymentCount, scriptPubKey, nAmount, nBlockStart, UINT256_ZERO);
     149          19 :     const uint256& nHash = proposal.GetHash();
     150          19 :     if (!proposal.IsWellFormed(g_budgetman.GetTotalBudget(proposal.GetBlockStart())))
     151           0 :         throw std::runtime_error("Proposal is not valid " + proposal.IsInvalidReason());
     152             : 
     153          19 :     CTransactionRef wtx;
     154             :     // make our change address
     155          38 :     CReserveKey keyChange(pwallet);
     156          19 :     if (!pwallet->CreateBudgetFeeTX(wtx, nHash, keyChange, BUDGET_FEE_TX_OLD)) { // 50 PIV collateral for proposal
     157           0 :         throw std::runtime_error("Error making collateral transaction for proposal. Please check your wallet balance.");
     158             :     }
     159             : 
     160             :     //send the tx to the network
     161          38 :     const CWallet::CommitResult& res = pwallet->CommitTransaction(wtx, keyChange, g_connman.get());
     162          19 :     if (res.status != CWallet::CommitStatus::OK)
     163           0 :         throw JSONRPCError(RPC_WALLET_ERROR, res.ToString());
     164             : 
     165             :     // Store proposal name as a comment
     166          19 :     auto it = pwallet->mapWallet.find(wtx->GetHash());
     167          19 :     assert(it != pwallet->mapWallet.end());
     168          19 :     it->second.SetComment("Proposal: " + strProposalName);
     169             : 
     170          38 :     return wtx->GetHash().ToString();
     171             : }
     172             : 
     173          19 : UniValue submitbudget(const JSONRPCRequest& request)
     174             : {
     175          19 :     if (request.fHelp || request.params.size() != 7)
     176           0 :         throw std::runtime_error(
     177             :             "submitbudget \"name\" \"url\" npayments start \"address\" monthly_payment \"fee_txid\"\n"
     178             :             "\nSubmit proposal to the network\n"
     179             : 
     180             :             "\nArguments:\n"
     181             :             "1. \"name\":         (string, required) Desired proposal name (20 character limit)\n"
     182             :             "2. \"url\":          (string, required) URL of proposal details (64 character limit)\n"
     183             :             "3. npayments:        (numeric, required) Total number of monthly payments\n"
     184             :             "4. start:            (numeric, required) Starting super block height\n"
     185             :             "5. \"address\":      (string, required) PIVX address to send payments to\n"
     186             :             "6. monthly_payment:  (numeric, required) Monthly payment amount\n"
     187             :             "7. \"fee_txid\":     (string, required) Transaction hash from preparebudget command\n"
     188             : 
     189             :             "\nResult:\n"
     190             :             "\"xxxx\"       (string) proposal hash (if successful) or error message (if failed)\n"
     191             : 
     192           0 :             "\nExamples:\n" +
     193           0 :             HelpExampleCli("submitbudget", "\"test-proposal\" \"https://forum.pivx.org/t/test-proposal\" 2 820800 \"D9oc6C3dttUbv8zd7zGNq1qKBGf4ZQ1XEE\" 500") +
     194           0 :             HelpExampleRpc("submitbudget", "\"test-proposal\" \"https://forum.pivx.org/t/test-proposal\" 2 820800 \"D9oc6C3dttUbv8zd7zGNq1qKBGf4ZQ1XEE\" 500"));
     195             : 
     196          19 :     std::string strProposalName;
     197          19 :     std::string strURL;
     198          19 :     int nPaymentCount;
     199          19 :     int nBlockStart;
     200          38 :     CTxDestination address;
     201          19 :     CAmount nAmount;
     202             : 
     203          19 :     checkBudgetInputs(request.params, strProposalName, strURL, nPaymentCount, nBlockStart, address, nAmount);
     204             : 
     205             :     // Parse PIVX address
     206          38 :     CScript scriptPubKey = GetScriptForDestination(address);
     207          19 :     const uint256& hash = ParseHashV(request.params[6], "parameter 1");
     208             : 
     209          19 :     if (!g_tiertwo_sync_state.IsBlockchainSynced()) {
     210           0 :         throw std::runtime_error("Must wait for client to sync with masternode network. Try again in a minute or so.");
     211             :     }
     212             : 
     213             :     // create the proposal in case we're the first to make it
     214          38 :     CBudgetProposal proposal(strProposalName, strURL, nPaymentCount, scriptPubKey, nAmount, nBlockStart, hash);
     215          19 :     if(!g_budgetman.AddProposal(proposal)) {
     216           0 :         std::string strError = strprintf("invalid budget proposal - %s", proposal.IsInvalidReason());
     217           0 :         throw std::runtime_error(strError);
     218             :     }
     219          19 :     proposal.Relay();
     220             : 
     221          57 :     return proposal.GetHash().ToString();
     222             : }
     223             : 
     224           7 : static CBudgetVote::VoteDirection parseVote(const std::string& strVote)
     225             : {
     226           7 :     if (strVote != "yes" && strVote != "no") throw JSONRPCError(RPC_MISC_ERROR, "You can only vote 'yes' or 'no'");
     227           7 :     CBudgetVote::VoteDirection nVote = CBudgetVote::VOTE_ABSTAIN;
     228           7 :     if (strVote == "yes") nVote = CBudgetVote::VOTE_YES;
     229           7 :     if (strVote == "no") nVote = CBudgetVote::VOTE_NO;
     230           7 :     return nVote;
     231             : }
     232             : 
     233           7 : UniValue mnbudgetvote(const JSONRPCRequest& request)
     234             : {
     235          14 :     std::string strCommand;
     236           7 :     if (request.params.size() >= 1) {
     237           7 :         strCommand = request.params[0].get_str();
     238             : 
     239             :         // Backwards compatibility with legacy `mnbudget` command
     240           7 :         if (strCommand == "vote") strCommand = "local";
     241           7 :         if (strCommand == "vote-many") strCommand = "many";
     242           7 :         if (strCommand == "vote-alias") strCommand = "alias";
     243             :     }
     244             : 
     245           7 :     CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
     246             : 
     247          12 :     if (request.fHelp || (request.params.size() == 3 && (strCommand != "local" && strCommand != "many")) || (request.params.size() == 4 && strCommand != "alias") ||
     248          14 :         request.params.size() > 5 || request.params.size() < 3)
     249           0 :         throw std::runtime_error(
     250             :             "mnbudgetvote \"local|many|alias\" \"hash\" \"yes|no\" ( \"alias\" legacy )\n"
     251             :             "\nVote on a budget proposal\n"
     252             :             "\nAfter V6 enforcement, the deterministic masternode system is used by default. Set the \"legacy\" parameter to true to vote with legacy masternodes."
     253             : 
     254             :             "\nArguments:\n"
     255             :             "1. \"mode\"      (string, required) The voting mode. 'local' for voting directly from a masternode, 'many' for voting with a MN controller and casting the same vote for each MN, 'alias' for voting with a MN controller and casting a vote for a single MN\n"
     256             :             "2. \"hash\"      (string, required) The budget proposal hash\n"
     257             :             "3. \"votecast\"  (string, required) Your vote. 'yes' to vote for the proposal, 'no' to vote against\n"
     258             :             "4. \"alias\"     (string, required for 'alias' mode) The MN alias to cast a vote for (for deterministic masternodes it's the hash of the proTx transaction).\n"
     259             :             "5. \"legacy\"    (boolean, optional, default=false) Use the legacy masternode system after deterministic masternodes enforcement.\n"
     260             : 
     261             :             "\nResult:\n"
     262             :             "{\n"
     263             :             "  \"overall\": \"xxxx\",      (string) The overall status message for the vote cast\n"
     264             :             "  \"detail\": [\n"
     265             :             "    {\n"
     266             :             "      \"node\": \"xxxx\",      (string) 'local' or the MN alias\n"
     267             :             "      \"result\": \"xxxx\",    (string) Either 'Success' or 'Failed'\n"
     268             :             "      \"error\": \"xxxx\",     (string) Error message, if vote failed\n"
     269             :             "    }\n"
     270             :             "    ,...\n"
     271             :             "  ]\n"
     272             :             "}\n"
     273             : 
     274           0 :             "\nExamples:\n" +
     275           0 :             HelpExampleCli("mnbudgetvote", "\"alias\" \"ed2f83cedee59a91406f5f47ec4d60bf5a7f9ee6293913c82976bd2d3a658041\" \"yes\" \"4f9de28fca1f0574a217c5d3c59cc51125ec671de82a2f80b6ceb69673115041\"") +
     276           0 :             HelpExampleRpc("mnbudgetvote", "\"alias\" \"ed2f83cedee59a91406f5f47ec4d60bf5a7f9ee6293913c82976bd2d3a658041\" \"yes\" \"4f9de28fca1f0574a217c5d3c59cc51125ec671de82a2f80b6ceb69673115041\""));
     277             : 
     278           7 :     const uint256& hash = ParseHashV(request.params[1], "parameter 1");
     279           7 :     CBudgetVote::VoteDirection nVote = parseVote(request.params[2].get_str());
     280             : 
     281           7 :     bool fLegacyMN = !deterministicMNManager->IsDIP3Enforced() || (request.params.size() > 4 && request.params[4].get_bool());
     282             : 
     283           7 :     if (strCommand == "local") {
     284           0 :         if (!fLegacyMN) {
     285           0 :             throw JSONRPCError(RPC_MISC_ERROR, _("\"local\" vote is no longer available with DMNs. Use \"alias\" from the wallet with the voting key."));
     286             :         }
     287           0 :         return mnLocalBudgetVoteInner(true, hash, false, nVote);
     288             :     }
     289             : 
     290             :     // DMN require wallet with voting key
     291           7 :     if (!fLegacyMN) {
     292           1 :         if (!EnsureWalletIsAvailable(pwallet, false)) {
     293           0 :             return NullUniValue;
     294             :         }
     295           1 :         EnsureWalletIsUnlocked(pwallet);
     296             :     }
     297             : 
     298           7 :     bool isAlias = false;
     299          14 :     if (strCommand == "many" || (isAlias = strCommand == "alias")) {
     300          14 :         Optional<std::string> mnAlias = isAlias ? Optional<std::string>(request.params[3].get_str()) : nullopt;
     301           7 :         return mnBudgetVoteInner(pwallet, fLegacyMN, hash, false, nVote, mnAlias);
     302             :     }
     303             : 
     304           0 :     return NullUniValue;
     305             : }
     306             : 
     307          54 : UniValue getbudgetvotes(const JSONRPCRequest& request)
     308             : {
     309          54 :     if (request.params.size() != 1)
     310           0 :         throw std::runtime_error(
     311             :             "getbudgetvotes \"name\"\n"
     312             :             "\nPrint vote information for a budget proposal\n"
     313             : 
     314             :             "\nArguments:\n"
     315             :             "1. \"name\":      (string, required) Name of the proposal\n"
     316             : 
     317             :             "\nResult:\n"
     318             :             "[\n"
     319             :             "  {\n"
     320             :             "    \"mnId\": \"xxxx-x\",      (string) Masternode's outpoint collateral transaction (hash-n)\n"
     321             :             "    \"nHash\": \"xxxx\",       (string) Hash of the vote\n"
     322             :             "    \"Vote\": \"YES|NO\",      (string) Vote cast ('YES' or 'NO')\n"
     323             :             "    \"nTime\": xxxx,         (numeric) Time in seconds since epoch the vote was cast\n"
     324             :             "    \"fValid\": true|false,  (boolean) 'true' if the vote is valid, 'false' otherwise\n"
     325             :             "  }\n"
     326             :             "  ,...\n"
     327             :             "]\n"
     328             : 
     329           0 :             "\nExamples:\n" +
     330           0 :             HelpExampleCli("getbudgetvotes", "\"test-proposal\"") + HelpExampleRpc("getbudgetvotes", "\"test-proposal\""));
     331             : 
     332          54 :     std::string strProposalName = SanitizeString(request.params[0].get_str());
     333          54 :     const CBudgetProposal* pbudgetProposal = g_budgetman.FindProposalByName(strProposalName);
     334          54 :     if (pbudgetProposal == nullptr) throw std::runtime_error("Unknown proposal name");
     335         108 :     return pbudgetProposal->GetVotesArray();
     336             : }
     337             : 
     338           9 : UniValue getnextsuperblock(const JSONRPCRequest& request)
     339             : {
     340           9 :     if (request.fHelp || request.params.size() != 0)
     341           0 :         throw std::runtime_error(
     342             :             "getnextsuperblock\n"
     343             :             "\nPrint the next super block height\n"
     344             : 
     345             :             "\nResult:\n"
     346             :             "n      (numeric) Block height of the next super block\n"
     347             : 
     348           0 :             "\nExamples:\n" +
     349           0 :             HelpExampleCli("getnextsuperblock", "") + HelpExampleRpc("getnextsuperblock", ""));
     350             : 
     351          18 :     int nChainHeight = WITH_LOCK(cs_main, return chainActive.Height());
     352           9 :     if (nChainHeight < 0) return "unknown";
     353             : 
     354           9 :     const int nBlocksPerCycle = Params().GetConsensus().nBudgetCycleBlocks;
     355           9 :     int nNext = nChainHeight - nChainHeight % nBlocksPerCycle + nBlocksPerCycle;
     356           9 :     return nNext;
     357             : }
     358             : 
     359          24 : UniValue getbudgetprojection(const JSONRPCRequest& request)
     360             : {
     361          24 :     if (request.fHelp || request.params.size() != 0)
     362           0 :         throw std::runtime_error(
     363             :             "getbudgetprojection\n"
     364             :             "\nShow the projection of which proposals will be paid the next cycle\n"
     365             :             "Proposal fee tx time need to be +24hrs old from the current time. (Testnet is 5 mins)\n"
     366             :             "Net Votes needs to be above Masternode Count divided by 10\n"
     367             : 
     368             :             "\nResult:\n"
     369             :             "[\n"
     370             :             "  {\n"
     371             :             "    \"Name\": \"xxxx\",               (string) Proposal Name\n"
     372             :             "    \"URL\": \"xxxx\",                (string) Proposal URL\n"
     373             :             "    \"Hash\": \"xxxx\",               (string) Proposal vote hash\n"
     374             :             "    \"FeeHash\": \"xxxx\",            (string) Proposal fee hash\n"
     375             :             "    \"BlockStart\": n,              (numeric) Proposal starting block\n"
     376             :             "    \"BlockEnd\": n,                (numeric) Proposal ending block\n"
     377             :             "    \"TotalPaymentCount\": n,       (numeric) Number of payments\n"
     378             :             "    \"RemainingPaymentCount\": n,   (numeric) Number of remaining payments\n"
     379             :             "    \"PaymentAddress\": \"xxxx\",     (string) PIVX address of payment\n"
     380             :             "    \"Ratio\": x.xxx,               (numeric) Ratio of yeas vs nays\n"
     381             :             "    \"Yeas\": n,                    (numeric) Number of yea votes\n"
     382             :             "    \"Nays\": n,                    (numeric) Number of nay votes\n"
     383             :             "    \"Abstains\": n,                (numeric) Number of abstains\n"
     384             :             "    \"TotalPayment\": xxx.xxx,      (numeric) Total payment amount in PIV\n"
     385             :             "    \"MonthlyPayment\": xxx.xxx,    (numeric) Monthly payment amount in PIV\n"
     386             :             "    \"IsEstablished\": true|false,  (boolean) Proposal is considered established, 24 hrs after being submitted to network. (Testnet is 5 mins)\n"
     387             :             "    \"IsValid\": true|false,        (boolean) Valid (true) or Invalid (false)\n"
     388             :             "    \"IsInvalidReason\": \"xxxx\",  (string) Error message, if any\n"
     389             :             "    \"Allotted\": xxx.xxx,           (numeric) Amount of PIV allotted in current period\n"
     390             :             "    \"TotalBudgetAllotted\": xxx.xxx (numeric) Total PIV allotted\n"
     391             :             "  }\n"
     392             :             "  ,...\n"
     393             :             "]\n"
     394             : 
     395           0 :             "\nExamples:\n" +
     396           0 :             HelpExampleCli("getbudgetprojection", "") + HelpExampleRpc("getbudgetprojection", ""));
     397             : 
     398          24 :     UniValue ret(UniValue::VARR);
     399          48 :     UniValue resultObj(UniValue::VOBJ);
     400          24 :     CAmount nTotalAllotted = 0;
     401             : 
     402          24 :     std::vector<CBudgetProposal> winningProps = g_budgetman.GetBudget();
     403          44 :     for (const CBudgetProposal& p : winningProps) {
     404          40 :         UniValue bObj(UniValue::VOBJ);
     405          20 :         budgetToJSON(&p, bObj, g_budgetman.GetBestHeight());
     406          20 :         nTotalAllotted += p.GetAllotted();
     407          40 :         bObj.pushKV("TotalBudgetAllotted", ValueFromAmount(nTotalAllotted));
     408          20 :         ret.push_back(bObj);
     409             :     }
     410             : 
     411          48 :     return ret;
     412             : }
     413             : 
     414         131 : UniValue getbudgetinfo(const JSONRPCRequest& request)
     415             : {
     416         131 :     if (request.fHelp || request.params.size() > 1)
     417           0 :         throw std::runtime_error(
     418             :             "getbudgetinfo ( \"name\" )\n"
     419             :             "\nShow current masternode budgets\n"
     420             : 
     421             :             "\nArguments:\n"
     422             :             "1. \"name\"    (string, optional) Proposal name\n"
     423             : 
     424             :             "\nResult:\n"
     425             :             "[\n"
     426             :             "  {\n"
     427             :             "    \"Name\": \"xxxx\",               (string) Proposal Name\n"
     428             :             "    \"URL\": \"xxxx\",                (string) Proposal URL\n"
     429             :             "    \"Hash\": \"xxxx\",               (string) Proposal vote hash\n"
     430             :             "    \"FeeHash\": \"xxxx\",            (string) Proposal fee hash\n"
     431             :             "    \"BlockStart\": n,              (numeric) Proposal starting block\n"
     432             :             "    \"BlockEnd\": n,                (numeric) Proposal ending block\n"
     433             :             "    \"TotalPaymentCount\": n,       (numeric) Number of payments\n"
     434             :             "    \"RemainingPaymentCount\": n,   (numeric) Number of remaining payments\n"
     435             :             "    \"PaymentAddress\": \"xxxx\",     (string) PIVX address of payment\n"
     436             :             "    \"Ratio\": x.xxx,               (numeric) Ratio of yeas vs nays\n"
     437             :             "    \"Yeas\": n,                    (numeric) Number of yea votes\n"
     438             :             "    \"Nays\": n,                    (numeric) Number of nay votes\n"
     439             :             "    \"Abstains\": n,                (numeric) Number of abstains\n"
     440             :             "    \"TotalPayment\": xxx.xxx,      (numeric) Total payment amount in PIV\n"
     441             :             "    \"MonthlyPayment\": xxx.xxx,    (numeric) Monthly payment amount in PIV\n"
     442             :             "    \"IsEstablished\": true|false,  (boolean) Proposal is considered established, 24 hrs after being submitted to network. (5 mins for Testnet)\n"
     443             :             "    \"IsValid\": true|false,        (boolean) Valid (true) or Invalid (false)\n"
     444             :             "    \"IsInvalidReason\": \"xxxx\",      (string) Error message, if any\n"
     445             :             "  }\n"
     446             :             "  ,...\n"
     447             :             "]\n"
     448             : 
     449           0 :             "\nExamples:\n" +
     450           0 :             HelpExampleCli("getbudgetprojection", "") + HelpExampleRpc("getbudgetprojection", ""));
     451             : 
     452         131 :     UniValue ret(UniValue::VARR);
     453         131 :     int nCurrentHeight = g_budgetman.GetBestHeight();
     454             : 
     455         131 :     if (request.params.size() == 1) {
     456         206 :         std::string strProposalName = SanitizeString(request.params[0].get_str());
     457         103 :         const CBudgetProposal* pbudgetProposal = g_budgetman.FindProposalByName(strProposalName);
     458         103 :         if (pbudgetProposal == nullptr) throw std::runtime_error("Unknown proposal name");
     459         206 :         UniValue bObj(UniValue::VOBJ);
     460         103 :         budgetToJSON(pbudgetProposal, bObj, nCurrentHeight);
     461         103 :         ret.push_back(bObj);
     462         103 :         return ret;
     463             :     }
     464             : 
     465         159 :     std::vector<CBudgetProposal*> winningProps = g_budgetman.GetAllProposalsOrdered();
     466         412 :     for (CBudgetProposal* pbudgetProposal : winningProps) {
     467         384 :         if (!pbudgetProposal->IsValid()) continue;
     468             : 
     469         768 :         UniValue bObj(UniValue::VOBJ);
     470         384 :         budgetToJSON(pbudgetProposal, bObj, nCurrentHeight);
     471         384 :         ret.push_back(bObj);
     472             :     }
     473             : 
     474          28 :     return ret;
     475             : }
     476             : 
     477           0 : UniValue mnbudgetrawvote(const JSONRPCRequest& request)
     478             : {
     479           0 :     if (request.fHelp || request.params.size() != 6)
     480           0 :         throw std::runtime_error(
     481             :             "mnbudgetrawvote \"collat_txid\" collat_vout \"hash\" votecast time \"sig\"\n"
     482             :             "\nCompile and relay a proposal vote with provided external signature instead of signing vote internally\n"
     483             : 
     484             :             "\nArguments:\n"
     485             :             "1. \"collat_txid\"   (string, required) Transaction hash for the masternode collateral\n"
     486             :             "2. collat_vout       (numeric, required) Output index for the masternode collateral\n"
     487             :             "3. \"hash\"          (string, required) Budget Proposal hash\n"
     488             :             "4. \"votecast\"      (string, required) Your vote. 'yes' to vote for the proposal, 'no' to vote against\n"
     489             :             "5. time              (numeric, required) Time since epoch in seconds\n"
     490             :             "6. \"sig\"           (string, required) External signature\n"
     491             : 
     492             :             "\nResult:\n"
     493             :             "\"status\"     (string) Vote status or error message\n"
     494             : 
     495           0 :             "\nExamples:\n" +
     496           0 :             HelpExampleCli("mnbudgetrawvote", "") + HelpExampleRpc("mnbudgetrawvote", ""));
     497             : 
     498           0 :     const uint256& hashMnTx = ParseHashV(request.params[0], "mn tx hash");
     499           0 :     int nMnTxIndex = request.params[1].get_int();
     500           0 :     const CTxIn& vin = CTxIn(hashMnTx, nMnTxIndex);
     501             : 
     502           0 :     const uint256& hashProposal = ParseHashV(request.params[2], "Proposal hash");
     503           0 :     CBudgetVote::VoteDirection nVote = parseVote(request.params[3].get_str());
     504             : 
     505           0 :     int64_t nTime = request.params[4].get_int64();
     506           0 :     std::string strSig = request.params[5].get_str();
     507           0 :     bool fInvalid = false;
     508           0 :     std::vector<unsigned char> vchSig = DecodeBase64(strSig.c_str(), &fInvalid);
     509             : 
     510           0 :     if (fInvalid)
     511           0 :         throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Malformed base64 encoding");
     512             : 
     513           0 :     CMasternode* pmn = mnodeman.Find(vin.prevout);
     514           0 :     if (!pmn) {
     515           0 :         return "Failure to find masternode in list : " + vin.ToString();
     516             :     }
     517             : 
     518           0 :     CBudgetVote vote(vin, hashProposal, nVote);
     519           0 :     vote.SetTime(nTime);
     520           0 :     vote.SetVchSig(vchSig);
     521             : 
     522           0 :     if (!vote.CheckSignature(pmn->pubKeyMasternode.GetID())) {
     523           0 :         return "Failure to verify signature.";
     524             :     }
     525             : 
     526           0 :     std::string strError;
     527           0 :     if (g_budgetman.AddAndRelayProposalVote(vote, strError)) {
     528           0 :         return "Voted successfully";
     529             :     } else {
     530           0 :         return "Error voting : " + strError;
     531             :     }
     532             : }
     533             : 
     534           4 : UniValue mnfinalbudgetsuggest(const JSONRPCRequest& request)
     535             : {
     536           4 :     if (request.fHelp || !request.params.empty())
     537           0 :         throw std::runtime_error(
     538             :                 "mnfinalbudgetsuggest\n"
     539             :                 "\nTry to submit a budget finalization\n"
     540           0 :                 "returns the budget hash if it was broadcasted successfully");
     541             : 
     542           4 :     if (!Params().IsRegTestNet()) {
     543           0 :         throw JSONRPCError(RPC_MISC_ERROR, "command available only for RegTest network");
     544             :     }
     545             : 
     546           4 :     const uint256& budgetHash = g_budgetman.SubmitFinalBudget();
     547          12 :     return (budgetHash.IsNull()) ? NullUniValue : budgetHash.ToString();
     548             : }
     549             : 
     550           2 : UniValue createrawmnfinalbudget(const JSONRPCRequest& request)
     551             : {
     552           2 :     if (request.fHelp || request.params.size() > 4)
     553           0 :         throw std::runtime_error(
     554             :                 "createrawmnfinalbudget\n"
     555             :                 "\nTry to submit the raw budget finalization\n"
     556             :                 "returns the budget hash if it was broadcasted successfully"
     557             :                 "\nArguments:\n"
     558             :                 "1. \"budgetname\"    (string, required) finalization name\n"
     559             :                 "2. \"blockstart\"    (numeric, required) superblock height\n"
     560             :                 "3. \"proposals\"     (string, required) A json array of json objects\n"
     561             :                 "     [\n"
     562             :                 "       {\n"
     563             :                 "         \"proposalid\":\"id\",  (string, required) The proposal id\n"
     564             :                 "         \"payee\":n,         (hex, required) The payee script\n"
     565             :                 "         \"amount\":n            (numeric, optional) The payee amount\n"
     566             :                 "       }\n"
     567             :                 "       ,...\n"
     568             :                 "     ]\n"
     569             :                 "4. \"feetxid\"    (string, optional) the transaction fee hash\n"
     570             :                 ""
     571             :                 "\nResult:\n"
     572             :                 "{\n"
     573             :                     "\"result\"     (string) Budget suggest broadcast or error\n"
     574             :                     "\"id\"         (string) id of the fee tx or the finalized budget\n"
     575             :                 "}\n"
     576           0 :                 ); // future: add examples.
     577             : 
     578           2 :     if (!Params().IsRegTestNet()) {
     579           0 :         throw JSONRPCError(RPC_MISC_ERROR, "command available only for RegTest network");
     580             :     }
     581             : 
     582             :     // future: add inputs error checking..
     583           4 :     std::string budName = request.params[0].get_str();
     584           2 :     int nBlockStart = request.params[1].get_int();
     585           4 :     std::vector<CTxBudgetPayment> vecTxBudgetPayments;
     586           2 :     UniValue budgetVec = request.params[2].get_array();
     587           4 :     for (unsigned int idx = 0; idx < budgetVec.size(); idx++) {
     588           2 :         const UniValue& prop = budgetVec[idx].get_obj();
     589           2 :         uint256 propId = ParseHashO(prop, "proposalid");
     590           4 :         std::vector<unsigned char> scriptData(ParseHexO(prop, "payee"));
     591           4 :         CScript payee = CScript(scriptData.begin(), scriptData.end());
     592           2 :         CAmount amount = AmountFromValue(find_value(prop, "amount"));
     593           2 :         vecTxBudgetPayments.emplace_back(propId, payee, amount);
     594             :     }
     595             : 
     596           4 :     Optional<uint256> txFeeId = nullopt;
     597           2 :     if (request.params.size() > 3) {
     598           2 :         txFeeId = ParseHashV(request.params[3], "parameter 4");
     599             :     }
     600             : 
     601           2 :     if (!txFeeId) {
     602           1 :         CFinalizedBudget tempBudget(budName, nBlockStart, vecTxBudgetPayments, UINT256_ZERO);
     603           1 :         const uint256& budgetHash = tempBudget.GetHash();
     604             : 
     605             :         // create fee tx
     606           1 :         CTransactionRef wtx;
     607           2 :         CReserveKey keyChange(vpwallets[0]);
     608           1 :         if (!vpwallets[0]->CreateBudgetFeeTX(wtx, budgetHash, keyChange, BUDGET_FEE_TX)) {
     609           0 :             throw std::runtime_error("Can't make collateral transaction");
     610             :         }
     611             :         // Send the tx to the network
     612           2 :         const CWallet::CommitResult& res = vpwallets[0]->CommitTransaction(wtx, keyChange, g_connman.get());
     613           2 :         UniValue ret(UniValue::VOBJ);
     614           1 :         if (res.status == CWallet::CommitStatus::OK) {
     615           1 :             ret.pushKV("result", "tx_fee_sent");
     616           3 :             ret.pushKV("id", wtx->GetHash().ToString());
     617             :         } else {
     618           0 :             ret.pushKV("result", "error");
     619             :         }
     620           1 :         return ret;
     621             :     }
     622             : 
     623           2 :     UniValue ret(UniValue::VOBJ);
     624             :     // Collateral tx already exists, see if it's mature enough.
     625           2 :     CFinalizedBudget fb(budName, nBlockStart, vecTxBudgetPayments, *txFeeId);
     626           1 :     if (g_budgetman.AddFinalizedBudget(fb)) {
     627           0 :         fb.Relay();
     628           0 :         ret.pushKV("result", "fin_budget_sent");
     629           0 :         ret.pushKV("id", fb.GetHash().ToString());
     630             :     } else {
     631             :         // future: add proper error
     632           2 :         ret.pushKV("result", "error");
     633             :     }
     634           1 :     return ret;
     635             : }
     636             : 
     637          45 : UniValue mnfinalbudget(const JSONRPCRequest& request)
     638             : {
     639          90 :     std::string strCommand;
     640          45 :     if (request.params.size() >= 1)
     641          45 :         strCommand = request.params[0].get_str();
     642             : 
     643          45 :     CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
     644             : 
     645          45 :     if (request.fHelp ||
     646         128 :         (strCommand != "vote-many" && strCommand != "vote" && strCommand != "show" && strCommand != "getvotes"))
     647           0 :         throw std::runtime_error(
     648             :             "mnfinalbudget \"command\"... ( \"passphrase\" )\n"
     649             :             "\nVote or show current budgets\n"
     650             : 
     651             :             "\nAvailable commands:\n"
     652             :             "  vote-many   - Vote on a finalized budget\n"
     653             :             "  vote        - Vote on a finalized budget with local masternode\n"
     654             :             "  show        - Show existing finalized budgets\n"
     655           0 :             "  getvotes     - Get vote information for each finalized budget\n");
     656             : 
     657          87 :     if (strCommand == "vote-many" || strCommand == "vote") {
     658           4 :         if (request.params.size() < 2 || request.params.size() > 3) {
     659           0 :             throw std::runtime_error(strprintf("Correct usage is 'mnfinalbudget %s BUDGET_HASH (fLegacy)'", strCommand));
     660             :         }
     661           4 :         const uint256& hash = ParseHashV(request.params[1], "BUDGET_HASH");
     662           4 :         bool fLegacyMN = !deterministicMNManager->IsDIP3Enforced() || (request.params.size() > 2 && request.params[2].get_bool());
     663             : 
     664             :         // DMN require wallet with operator keys for vote-many
     665           1 :         if (!fLegacyMN && strCommand == "vote-many" && !EnsureWalletIsAvailable(pwallet, false)) {
     666           0 :             return NullUniValue;
     667             :         }
     668             : 
     669           4 :         return (strCommand == "vote-many" ? mnBudgetVoteInner(pwallet, fLegacyMN, hash, true, CBudgetVote::VOTE_YES, nullopt)
     670           7 :                                           : mnLocalBudgetVoteInner(fLegacyMN, hash, true, CBudgetVote::VOTE_YES));
     671             :     }
     672             : 
     673          41 :     if (strCommand == "show") {
     674          41 :         UniValue resultObj(UniValue::VOBJ);
     675             : 
     676          82 :         std::vector<CFinalizedBudget*> winningFbs = g_budgetman.GetFinalizedBudgets();
     677          80 :         for (CFinalizedBudget* finalizedBudget : winningFbs) {
     678          39 :             const uint256& nHash = finalizedBudget->GetHash();
     679          39 :             UniValue bObj(UniValue::VOBJ);
     680          78 :             bObj.pushKV("FeeTX", finalizedBudget->GetFeeTXHash().ToString());
     681          39 :             bObj.pushKV("BlockStart", (int64_t)finalizedBudget->GetBlockStart());
     682          39 :             bObj.pushKV("BlockEnd", (int64_t)finalizedBudget->GetBlockEnd());
     683         117 :             bObj.pushKV("Proposals", finalizedBudget->GetProposalsStr());
     684          39 :             bObj.pushKV("VoteCount", (int64_t)finalizedBudget->GetVoteCount());
     685          78 :             bObj.pushKV("Status", g_budgetman.GetFinalizedBudgetStatus(nHash));
     686             : 
     687          39 :             bool fValid = finalizedBudget->IsValid();
     688          39 :             bObj.pushKV("IsValid", fValid);
     689          39 :             if (!fValid)
     690           0 :                 bObj.pushKV("IsInvalidReason", finalizedBudget->IsInvalidReason());
     691             : 
     692         156 :             std::string strName = strprintf("%s (%s)", finalizedBudget->GetName(), nHash.ToString());
     693          39 :             resultObj.pushKV(strName, bObj);
     694             :         }
     695             : 
     696          41 :         return resultObj;
     697             :     }
     698             : 
     699           0 :     if (strCommand == "getvotes") {
     700           0 :         if (request.params.size() != 2)
     701           0 :             throw std::runtime_error("Correct usage is 'mnbudget getvotes budget-hash'");
     702             : 
     703           0 :         uint256 hash(ParseHashV(request.params[1], "budget-hash"));
     704             : 
     705           0 :         LOCK(g_budgetman.cs_budgets);
     706           0 :         CFinalizedBudget* pfinalBudget = g_budgetman.FindFinalizedBudget(hash);
     707           0 :         if (pfinalBudget == nullptr) return "Unknown budget hash";
     708           0 :         return pfinalBudget->GetVotesObject();
     709             :     }
     710             : 
     711           0 :     return NullUniValue;
     712             : }
     713             : 
     714           2 : UniValue checkbudgets(const JSONRPCRequest& request)
     715             : {
     716           2 :     if (request.fHelp || request.params.size() != 0)
     717           0 :         throw std::runtime_error(
     718             :             "checkbudgets\n"
     719             :             "\nInitiates a budget check cycle manually\n"
     720             : 
     721           0 :             "\nExamples:\n" +
     722           0 :             HelpExampleCli("checkbudgets", "") + HelpExampleRpc("checkbudgets", ""));
     723             : 
     724           2 :     if (!g_tiertwo_sync_state.IsSynced())
     725           0 :         throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "Masternode/Budget sync not finished yet");
     726             : 
     727           2 :     g_budgetman.CheckAndRemove();
     728           2 :     return NullUniValue;
     729             : }
     730             : 
     731           4 : UniValue cleanbudget(const JSONRPCRequest& request)
     732             : {
     733           4 :     if (request.fHelp || request.params.size() > 1)
     734           0 :         throw std::runtime_error(
     735             :                 "cleanbudget ( try_sync )\n"
     736             :                 "\nCleans the budget data manually\n"
     737             :                 "\nArguments:\n"
     738             :                 "1. try_sync          (boolean, optional, default=false) resets tier two sync to a pre-budget data request\n"
     739           0 :                 "\nExamples:\n" +
     740           0 :                 HelpExampleCli("cleanbudget", "") + HelpExampleRpc("cleanbudget", ""));
     741             : 
     742           4 :     g_budgetman.Clear();
     743           4 :     LogPrintf("Budget data cleaned\n");
     744             : 
     745             :     // reset sync if requested
     746           4 :     bool reset = request.params.size() > 0 ? request.params[0].get_bool() : false;
     747           1 :     if (reset) {
     748           1 :         masternodeSync.ClearFulfilledRequest();
     749           1 :         masternodeSync.Reset();
     750           1 :         LogPrintf("Masternode sync restarted\n");
     751             :     }
     752           4 :     return NullUniValue;
     753             : }
     754             : 
     755             : // clang-format off
     756             : static const CRPCCommand commands[] =
     757             : { //  category              name                      actor (function)         okSafe argNames
     758             :   //  --------------------- ------------------------  -----------------------  ------ --------
     759             :     { "budget",             "checkbudgets",           &checkbudgets,           true,  {} },
     760             :     { "budget",             "getbudgetinfo",          &getbudgetinfo,          true,  {"name"} },
     761             :     { "budget",             "getbudgetprojection",    &getbudgetprojection,    true,  {} },
     762             :     { "budget",             "getbudgetvotes",         &getbudgetvotes,         true,  {"name"} },
     763             :     { "budget",             "getnextsuperblock",      &getnextsuperblock,      true,  {} },
     764             :     { "budget",             "mnbudgetrawvote",        &mnbudgetrawvote,        true,  {"collat_txid","collat_vout","hash","votecast","time","sig"} },
     765             :     { "budget",             "mnbudgetvote",           &mnbudgetvote,           true,  {"mode","hash","votecast","alias","legacy"} },
     766             :     { "budget",             "mnfinalbudget",          &mnfinalbudget,          true,  {"command"} },
     767             :     { "budget",             "preparebudget",          &preparebudget,          true,  {"name","url","npayments","start","address","monthly_payment"} },
     768             :     { "budget",             "submitbudget",           &submitbudget,           true,  {"name","url","npayments","start","address","monthly_payment","fee_txid"}  },
     769             : 
     770             :     /** Not shown in help */
     771             :     { "hidden",             "mnfinalbudgetsuggest",   &mnfinalbudgetsuggest,   true,  {} },
     772             :     { "hidden",             "createrawmnfinalbudget", &createrawmnfinalbudget, true,  {"budgetname", "blockstart", "proposals", "feetxid"} },
     773             :     { "hidden",             "cleanbudget",            &cleanbudget,            true,  {"try_sync"} },
     774             : };
     775             : // clang-format on
     776             : 
     777         494 : void RegisterBudgetRPCCommands(CRPCTable &tableRPC)
     778             : {
     779        6916 :     for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++)
     780        6422 :         tableRPC.appendCommand(commands[vcidx].name, &commands[vcidx]);
     781         494 : }

Generated by: LCOV version 1.14