LCOV - code coverage report
Current view: top level - src/rpc - governance.cpp (source / functions) Hit Total Coverage
Test: test_dash_coverage.info Lines: 179 647 27.7 %
Date: 2026-06-25 07:23:51 Functions: 20 47 42.6 %

          Line data    Source code
       1             : // Copyright (c) 2014-2025 The Dash Core developers
       2             : // Distributed under the MIT/X11 software license, see the accompanying
       3             : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
       4             : 
       5             : #include <active/context.h>
       6             : #include <active/masternode.h>
       7             : #include <evo/deterministicmns.h>
       8             : #include <governance/common.h>
       9             : #include <governance/governance.h>
      10             : #include <governance/object.h>
      11             : #include <governance/superblock.h>
      12             : #include <governance/vote.h>
      13             : #include <masternode/sync.h>
      14             : 
      15             : #include <chainparams.h>
      16             : #include <core_io.h>
      17             : #include <index/txindex.h>
      18             : #include <node/context.h>
      19             : #include <rpc/server.h>
      20             : #include <rpc/server_util.h>
      21             : #include <rpc/util.h>
      22             : #include <timedata.h>
      23             : #include <util/check.h>
      24             : #include <util/strencodings.h>
      25             : #include <validation.h>
      26             : #include <wallet/rpc/util.h>
      27             : 
      28             : #ifdef ENABLE_WALLET
      29             : #include <wallet/spend.h>
      30             : #include <wallet/wallet.h>
      31             : #endif // ENABLE_WALLET
      32             : 
      33             : using node::NodeContext;
      34             : #ifdef ENABLE_WALLET
      35             : using wallet::CWallet;
      36             : using wallet::GetWalletForJSONRPCRequest;
      37             : using wallet::HELP_REQUIRING_PASSPHRASE;
      38             : using wallet::isminetype;
      39             : #endif // ENABLE_WALLET
      40             : 
      41          92 : static RPCHelpMan gobject_count()
      42             : {
      43          92 :     const auto json_help{CGovernanceManager::GetJsonHelp(/*key=*/"", /*optional=*/false)};
      44         184 :     return RPCHelpMan{"gobject count",
      45          92 :         "Count governance objects and votes\n",
      46         184 :         {
      47          92 :             {"mode", RPCArg::Type::STR, RPCArg::DefaultHint{"json"}, "Output format: json (\"json\") or string in free form (\"all\")"},
      48             :         },
      49         276 :         {
      50          92 :             RPCResult{"for mode = json", json_help.m_type, json_help.m_key_name, json_help.m_description, json_help.m_inner},
      51          92 :             RPCResult{"for mode = all", RPCResult::Type::STR, "", "Human-friendly summary string for proposals and votes"},
      52             :         },
      53          92 :         RPCExamples{""},
      54          92 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
      55             : {
      56           0 :     std::string strMode{"json"};
      57             : 
      58           0 :     if (!request.params[0].isNull()) {
      59           0 :         strMode = request.params[0].get_str();
      60           0 :     }
      61             : 
      62           0 :     if (strMode != "json" && strMode != "all")
      63           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER, "mode can be 'json' or 'all'");
      64             : 
      65           0 :     const NodeContext& node = EnsureAnyNodeContext(request.context);
      66           0 :     CHECK_NONFATAL(node.govman);
      67           0 :     return strMode == "json" ? node.govman->ToJson() : node.govman->ToString();
      68           0 : },
      69             :     };
      70          92 : }
      71             : 
      72             : // DEBUG : TEST DESERIALIZATION OF GOVERNANCE META DATA
      73          92 : static RPCHelpMan gobject_deserialize()
      74             : {
      75         184 :     return RPCHelpMan{"gobject deserialize",
      76          92 :         "Deserialize governance object from hex string to JSON\n",
      77         184 :         {
      78          92 :             {"hex_data", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "data in hex string form"},
      79             :         },
      80          92 :         RPCResult{RPCResult::Type::STR, "", "JSON string of the deserialized governance object"},
      81          92 :         RPCExamples{""},
      82          92 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
      83             : {
      84           0 :     std::string strHex = request.params[0].get_str();
      85             : 
      86           0 :     std::vector<unsigned char> v = ParseHex(strHex);
      87           0 :     std::string s(v.begin(), v.end());
      88             : 
      89           0 :     UniValue u(UniValue::VOBJ);
      90           0 :     u.read(s);
      91             : 
      92           0 :     return u.write().c_str();
      93           0 : },
      94             :     };
      95           0 : }
      96             : 
      97             : // VALIDATE A GOVERNANCE OBJECT PRIOR TO SUBMISSION
      98          92 : static RPCHelpMan gobject_check()
      99             : {
     100         184 :     return RPCHelpMan{"gobject check",
     101          92 :         "Validate governance object data (proposal only)\n",
     102         184 :         {
     103          92 :             {"hex_data", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "data in hex string format"},
     104             :         },
     105         184 :         RPCResult{"if object is valid",
     106          92 :             RPCResult::Type::OBJ, "", "",
     107         184 :             {
     108          92 :                 {RPCResult::Type::STR, "Object status", "OK"},
     109             :             }
     110             :         },
     111          92 :         RPCExamples{""},
     112          92 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
     113             : {
     114             :     // ASSEMBLE NEW GOVERNANCE OBJECT FROM USER PARAMETERS
     115             : 
     116           0 :     uint256 hashParent;
     117             : 
     118           0 :     int nRevision = 1;
     119             : 
     120           0 :     int64_t nTime = GetAdjustedTime();
     121           0 :     std::string strDataHex = request.params[0].get_str();
     122             : 
     123           0 :     CGovernanceObject govobj(hashParent, nRevision, nTime, uint256(), strDataHex);
     124             : 
     125           0 :     if (govobj.GetObjectType() == GovernanceObject::PROPOSAL) {
     126           0 :         std::string strValidationError;
     127           0 :         if (!governance::ValidateProposal(strDataHex, strValidationError)) {
     128           0 :             throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid proposal data, error messages: " + strValidationError);
     129             :         }
     130           0 :     } else {
     131           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid object type, only proposals can be validated");
     132             :     }
     133             : 
     134           0 :     UniValue objResult(UniValue::VOBJ);
     135             : 
     136           0 :     objResult.pushKV("Object status", "OK");
     137             : 
     138           0 :     return objResult;
     139           0 : },
     140             :     };
     141           0 : }
     142             : 
     143             : #ifdef ENABLE_WALLET
     144             : // PREPARE THE GOVERNANCE OBJECT BY CREATING A COLLATERAL TRANSACTION
     145           0 : static RPCHelpMan gobject_prepare()
     146             : {
     147           0 :     return RPCHelpMan{"gobject prepare",
     148             :         "Prepare governance object by signing and creating tx\n"
     149           0 :         + HELP_REQUIRING_PASSPHRASE,
     150           0 :         {
     151           0 :             {"parent-hash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "hash of the parent object, \"0\" is root"},
     152           0 :             {"revision", RPCArg::Type::NUM, RPCArg::Optional::NO, "object revision in the system"},
     153           0 :             {"time", RPCArg::Type::NUM, RPCArg::Optional::NO, "time this object was created"},
     154           0 :             {"data-hex", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "data in hex string form"},
     155           0 :             {"use-IS", RPCArg::Type::BOOL, RPCArg::Default{false}, "Deprecated and ignored"},
     156           0 :             {"outputHash", RPCArg::Type::STR_HEX, RPCArg::Default{""}, "the single output to submit the proposal fee from"},
     157           0 :             {"outputIndex", RPCArg::Type::NUM, RPCArg::Default{0}, "The output index."},
     158             :         },
     159           0 :         {RPCResult{"if object valid", RPCResult::Type::STR_HEX, "", "The collateral transaction id (txid)"}},
     160           0 :         RPCExamples{""},
     161           0 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
     162             : {
     163           0 :     std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
     164           0 :     if (!wallet) return UniValue::VNULL;
     165             : 
     166           0 :     EnsureWalletIsUnlocked(*wallet);
     167             : 
     168             :     // ASSEMBLE NEW GOVERNANCE OBJECT FROM USER PARAMETERS
     169             : 
     170           0 :     uint256 hashParent;
     171             : 
     172             :     // -- attach to root node (root node doesn't really exist, but has a hash of zero)
     173           0 :     if (request.params[0].get_str() == "0") {
     174           0 :         hashParent = uint256();
     175           0 :     } else {
     176           0 :         hashParent = ParseHashV(request.params[0], "parent-hash");
     177             :     }
     178             : 
     179           0 :     int nRevision = request.params[1].getInt<int>();
     180           0 :     int64_t nTime = request.params[2].getInt<int64_t>();
     181           0 :     std::string strDataHex = request.params[3].get_str();
     182             : 
     183             :     // CREATE A NEW COLLATERAL TRANSACTION FOR THIS SPECIFIC OBJECT
     184             : 
     185           0 :     CGovernanceObject govobj(hashParent, nRevision, nTime, uint256(), strDataHex);
     186             : 
     187             :     // This command is dangerous because it consumes 5 DASH irreversibly.
     188             :     // If params are lost, it's very hard to bruteforce them and yet
     189             :     // users ignore all instructions on dashcentral etc. and do not save them...
     190             :     // Let's log them here and hope users do not mess with debug.log
     191           0 :     LogPrintf("gobject_prepare -- params: %s %s %s %s, data: %s, hash: %s\n",
     192             :                 request.params[0].getValStr(), request.params[1].getValStr(),
     193             :                 request.params[2].getValStr(), request.params[3].getValStr(),
     194             :                 govobj.GetDataAsPlainString(), govobj.GetHash().ToString());
     195             : 
     196           0 :     if (govobj.GetObjectType() == GovernanceObject::PROPOSAL) {
     197           0 :         std::string strValidationError;
     198           0 :         if (!governance::ValidateProposal(strDataHex, strValidationError)) {
     199           0 :             throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid proposal data, error messages: " + strValidationError);
     200             :         }
     201           0 :     }
     202             : 
     203           0 :     if (govobj.GetObjectType() == GovernanceObject::TRIGGER) {
     204           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER, "Trigger objects need not be prepared (however only masternodes can create them)");
     205             :     }
     206             : 
     207           0 :     if (g_txindex) {
     208           0 :         g_txindex->BlockUntilSyncedToCurrentChain();
     209           0 :     }
     210             : 
     211           0 :     LOCK(wallet->cs_wallet);
     212             : 
     213           0 :     const NodeContext& node = EnsureAnyNodeContext(request.context);
     214           0 :     const ChainstateManager& chainman = EnsureChainman(node);
     215             : 
     216             :     {
     217           0 :         LOCK(cs_main);
     218           0 :         std::string strError;
     219           0 :         if (!govobj.IsValidLocally(CHECK_NONFATAL(node.dmnman)->GetListAtChainTip(), chainman, strError, false))
     220           0 :             throw JSONRPCError(RPC_INTERNAL_ERROR, "Governance object is not valid - " + govobj.GetHash().ToString() + " - " + strError);
     221           0 :     }
     222             : 
     223             :     // If specified, spend this outpoint as the proposal fee
     224           0 :     COutPoint outpoint;
     225           0 :     outpoint.SetNull();
     226           0 :     if (!request.params[5].isNull() && !request.params[6].isNull()) {
     227           0 :         uint256 collateralHash(ParseHashV(request.params[5], "outputHash"));
     228           0 :         int32_t collateralIndex = request.params[6].getInt<int>();
     229           0 :         if (collateralHash.IsNull() || collateralIndex < 0) {
     230           0 :             throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("invalid hash or index: %s-%d", collateralHash.ToString(), collateralIndex));
     231             :         }
     232           0 :         outpoint = COutPoint(collateralHash, (uint32_t)collateralIndex);
     233           0 :     }
     234             : 
     235           0 :     CTransactionRef tx;
     236             : 
     237           0 :     if (!GenBudgetSystemCollateralTx(*wallet, tx, govobj.GetHash(), govobj.GetMinCollateralFee(), outpoint)) {
     238           0 :         std::string err = "Error making collateral transaction for governance object. Please check your wallet balance and make sure your wallet is unlocked.";
     239           0 :         if (!request.params[5].isNull() && !request.params[6].isNull()) {
     240           0 :             err += "Please verify your specified output is valid and is enough for the combined proposal fee and transaction fee.";
     241           0 :         }
     242           0 :         throw JSONRPCError(RPC_INTERNAL_ERROR, err);
     243           0 :     }
     244             : 
     245           0 :     if (!wallet->WriteGovernanceObject(Governance::Object{hashParent, nRevision, nTime, tx->GetHash(), strDataHex})) {
     246           0 :         throw JSONRPCError(RPC_INTERNAL_ERROR, "WriteGovernanceObject failed");
     247             :     }
     248             : 
     249             :     // -- send the tx to the network
     250           0 :     wallet->CommitTransaction(tx, {}, {});
     251             : 
     252           0 :     LogPrint(BCLog::GOBJECT, "gobject_prepare -- GetDataAsPlainString = %s, hash = %s, txid = %s\n",
     253             :                 govobj.GetDataAsPlainString(), govobj.GetHash().ToString(), tx->GetHash().ToString());
     254             : 
     255           0 :     return tx->GetHash().ToString();
     256           0 : },
     257             :     };
     258           0 : }
     259             : 
     260           0 : static RPCHelpMan gobject_list_prepared()
     261             : {
     262           0 :     return RPCHelpMan{"gobject list-prepared",
     263             :         "Returns a list of governance objects prepared by this wallet with \"gobject prepare\" sorted by their creation time.\n"
     264           0 :         + HELP_REQUIRING_PASSPHRASE,
     265           0 :         {
     266           0 :             {"count", RPCArg::Type::NUM, RPCArg::Default{10}, "Maximum number of objects to return."},
     267             :         },
     268           0 :         RPCResult{
     269           0 :             RPCResult::Type::ARR, "", "list of governance objects",
     270           0 :             {
     271           0 :                 Governance::Object::GetJsonHelp(/*key=*/"", /*optional=*/false)
     272             :             }
     273             :         },
     274           0 :         RPCExamples{""},
     275           0 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
     276             : {
     277           0 :     std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
     278           0 :     if (!wallet) return UniValue::VNULL;
     279           0 :     EnsureWalletIsUnlocked(*wallet);
     280             : 
     281           0 :     int64_t nCount = request.params.empty() ? 10 : request.params[0].getInt<int64_t>();
     282           0 :     if (nCount < 0) {
     283           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative count");
     284             :     }
     285             :     // Get a list of all prepared governance objects stored in the wallet
     286           0 :     LOCK(wallet->cs_wallet);
     287           0 :     std::vector<const Governance::Object*> vecObjects = wallet->GetGovernanceObjects();
     288             :     // Sort the vector by the object creation time/hex data
     289           0 :     std::sort(vecObjects.begin(), vecObjects.end(), [](const Governance::Object* a, const Governance::Object* b) {
     290           0 :         if (a->time != b->time) return a->time < b->time;
     291           0 :         return a->GetDataAsHexString() < b->GetDataAsHexString();
     292           0 :     });
     293             : 
     294           0 :     UniValue jsonArray(UniValue::VARR);
     295           0 :     for (auto it = vecObjects.begin() + std::max<int>(0, vecObjects.size() - nCount); it != vecObjects.end(); ++it) {
     296           0 :         jsonArray.push_back((*it)->ToJson());
     297           0 :     }
     298             : 
     299           0 :     return jsonArray;
     300           0 : },
     301             :     };
     302           0 : }
     303             : #endif // ENABLE_WALLET
     304             : 
     305             : // AFTER COLLATERAL TRANSACTION HAS MATURED USER CAN SUBMIT GOVERNANCE OBJECT TO PROPAGATE NETWORK
     306             : /*
     307             :     ------ Example Governance Item ------
     308             : 
     309             :     gobject submit 6e622bb41bad1fb18e7f23ae96770aeb33129e18bd9efe790522488e580a0a03 0 1 1464292854 "beer-reimbursement" 5b5b22636f6e7472616374222c207b2270726f6a6563745f6e616d65223a20225c22626565722d7265696d62757273656d656e745c22222c20227061796d656e745f61646472657373223a20225c225879324c4b4a4a64655178657948726e34744744514238626a6876464564615576375c22222c2022656e645f64617465223a202231343936333030343030222c20226465736372697074696f6e5f75726c223a20225c227777772e646173687768616c652e6f72672f702f626565722d7265696d62757273656d656e745c22222c2022636f6e74726163745f75726c223a20225c22626565722d7265696d62757273656d656e742e636f6d2f3030312e7064665c22222c20227061796d656e745f616d6f756e74223a20223233342e323334323232222c2022676f7665726e616e63655f6f626a6563745f6964223a2037342c202273746172745f64617465223a202231343833323534303030227d5d5d1
     310             : */
     311          92 : static RPCHelpMan gobject_submit()
     312             : {
     313         184 :     return RPCHelpMan{"gobject submit",
     314          92 :         "Submit governance object to network\n",
     315         552 :         {
     316          92 :             {"parent-hash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "hash of the parent object, \"0\" is root"},
     317          92 :             {"revision", RPCArg::Type::NUM, RPCArg::Optional::NO, "object revision in the system"},
     318          92 :             {"time", RPCArg::Type::NUM, RPCArg::Optional::NO, "time this object was created"},
     319          92 :             {"data-hex", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "data in hex string form"},
     320          92 :             {"fee-txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "txid of the corresponding proposal fee transaction"},
     321             :         },
     322          92 :         RPCResult{RPCResult::Type::STR_HEX, "hash", "Hash of the submitted governance object"},
     323          92 :         RPCExamples{""},
     324          92 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
     325             : {
     326           0 :     const NodeContext& node = EnsureAnyNodeContext(request.context);
     327           0 :     const ChainstateManager& chainman = EnsureChainman(node);
     328             : 
     329           0 :     if(!node.mn_sync->IsBlockchainSynced()) {
     330           0 :         throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "Must wait for client to sync with masternode network. Try again in a minute or so.");
     331             :     }
     332             : 
     333           0 :     auto mnList = CHECK_NONFATAL(node.dmnman)->GetListAtChainTip();
     334             : 
     335           0 :     if (node.active_ctx) {
     336           0 :         const auto& mn_activeman{*node.active_ctx->nodeman};
     337           0 :         const bool fMnFound = mnList.HasValidMNByCollateral(mn_activeman.GetOutPoint());
     338           0 :         LogPrint(BCLog::GOBJECT, /* Continued */
     339             :                  "gobject_submit -- pubKeyOperator = %s, outpoint = %s, params.size() = %lld, fMnFound = %d\n",
     340             :                  mn_activeman.GetPubKey().ToString(false), mn_activeman.GetOutPoint().ToStringShort(),
     341             :                  request.params.size(), fMnFound);
     342           0 :     } else {
     343           0 :         LogPrint(BCLog::GOBJECT, "gobject_submit -- pubKeyOperator = N/A, outpoint = N/A, params.size() = %lld, fMnFound = %d\n",
     344             :                  request.params.size(),
     345             :                  false);
     346             :     }
     347             : 
     348             :     // ASSEMBLE NEW GOVERNANCE OBJECT FROM USER PARAMETERS
     349             : 
     350           0 :     uint256 txidFee;
     351             : 
     352           0 :     if (!request.params[4].isNull()) {
     353           0 :         txidFee = ParseHashV(request.params[4], "fee-txid");
     354           0 :     }
     355           0 :     uint256 hashParent;
     356           0 :     if (request.params[0].get_str() == "0") { // attach to root node (root node doesn't really exist, but has a hash of zero)
     357           0 :         hashParent = uint256();
     358           0 :     } else {
     359           0 :         hashParent = ParseHashV(request.params[0], "parent-hash");
     360             :     }
     361             : 
     362             :     // GET THE PARAMETERS FROM USER
     363             : 
     364           0 :     int nRevision = request.params[1].getInt<int>();
     365           0 :     int64_t nTime = request.params[2].getInt<int64_t>();
     366           0 :     std::string strDataHex = request.params[3].get_str();
     367             : 
     368           0 :     CGovernanceObject govobj(hashParent, nRevision, nTime, txidFee, strDataHex);
     369             : 
     370           0 :     LogPrint(BCLog::GOBJECT, "gobject_submit -- GetDataAsPlainString = %s, hash = %s, txid = %s\n",
     371             :                 govobj.GetDataAsPlainString(), govobj.GetHash().ToString(), txidFee.ToString());
     372             : 
     373           0 :     if (govobj.GetObjectType() == GovernanceObject::TRIGGER) {
     374           0 :         LogPrintf("govobject(submit) -- Object submission rejected because submission of trigger is disabled\n");
     375           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER, "Submission of triggers is not available");
     376             :     }
     377             : 
     378           0 :     if (govobj.GetObjectType() == GovernanceObject::PROPOSAL) {
     379           0 :         std::string strValidationError;
     380           0 :         if (!governance::ValidateProposal(strDataHex, strValidationError)) {
     381           0 :             throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid proposal data, error messages: " + strValidationError);
     382             :         }
     383           0 :     }
     384             : 
     385           0 :     std::string strHash = govobj.GetHash().ToString();
     386             : 
     387           0 :     const CTxMemPool& mempool = EnsureMemPool(node);
     388             :     bool fMissingConfirmations;
     389             :     {
     390           0 :         if (g_txindex) {
     391           0 :             g_txindex->BlockUntilSyncedToCurrentChain();
     392           0 :         }
     393             : 
     394           0 :         LOCK2(cs_main, mempool.cs);
     395             : 
     396           0 :         std::string strError;
     397           0 :         if (!govobj.IsValidLocally(node.dmnman->GetListAtChainTip(), chainman, strError, fMissingConfirmations, true) && !fMissingConfirmations) {
     398           0 :             LogPrintf("gobject(submit) -- Object submission rejected because object is not valid - hash = %s, strError = %s\n", strHash, strError);
     399           0 :             throw JSONRPCError(RPC_INTERNAL_ERROR, "Governance object is not valid - " + strHash + " - " + strError);
     400             :         }
     401           0 :     }
     402             : 
     403             :     // RELAY THIS OBJECT
     404             :     // Reject if rate check fails but don't update buffer
     405           0 :     if (!CHECK_NONFATAL(node.govman)->MasternodeRateCheck(govobj)) {
     406           0 :         LogPrintf("gobject(submit) -- Object submission rejected because of rate check failure - hash = %s\n", strHash);
     407           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER, "Object creation rate limit exceeded");
     408             :     }
     409             : 
     410           0 :     LogPrintf("gobject(submit) -- Adding locally created governance object - %s\n", strHash);
     411             : 
     412           0 :     if (fMissingConfirmations) {
     413           0 :         node.govman->AddPostponedObject(govobj);
     414           0 :         node.govman->RelayObject(govobj);
     415           0 :     } else {
     416           0 :         node.govman->AddGovernanceObject(govobj, "<local>");
     417             :     }
     418             : 
     419           0 :     return govobj.GetHash().ToString();
     420           0 : },
     421             :     };
     422           0 : }
     423             : 
     424             : #ifdef ENABLE_WALLET
     425           0 : static UniValue VoteWithMasternodes(const JSONRPCRequest& request, const CWallet& wallet,
     426             :                              const std::map<uint256, CKeyID>& votingKeys,
     427             :                              const uint256& hash, vote_signal_enum_t eVoteSignal,
     428             :                              vote_outcome_enum_t eVoteOutcome)
     429             : {
     430           0 :     const NodeContext& node = EnsureAnyNodeContext(request.context);
     431           0 :     CHECK_NONFATAL(node.govman);
     432             :     {
     433           0 :         auto pGovObj = node.govman->FindConstGovernanceObject(hash);
     434           0 :         if (!pGovObj) {
     435           0 :             throw JSONRPCError(RPC_INVALID_PARAMETER, "Governance object not found");
     436             :         }
     437           0 :     }
     438             : 
     439           0 :     int nSuccessful = 0;
     440           0 :     int nFailed = 0;
     441             : 
     442           0 :     auto mnList = CHECK_NONFATAL(node.dmnman)->GetListAtChainTip();
     443             : 
     444           0 :     UniValue resultsObj(UniValue::VOBJ);
     445             : 
     446           0 :     for (const auto& [proTxHash, keyID] : votingKeys) {
     447           0 :         UniValue statusObj(UniValue::VOBJ);
     448             : 
     449           0 :         auto dmn = mnList.GetValidMN(proTxHash);
     450           0 :         if (!dmn) {
     451           0 :             nFailed++;
     452           0 :             statusObj.pushKV("result", "failed");
     453           0 :             statusObj.pushKV("errorMessage", "Can't find masternode by proTxHash");
     454           0 :             resultsObj.pushKV(proTxHash.ToString(), statusObj);
     455           0 :             continue;
     456             :         }
     457             : 
     458           0 :         CGovernanceVote vote(dmn->collateralOutpoint, hash, eVoteSignal, eVoteOutcome);
     459             : 
     460           0 :         if (!wallet.SignGovernanceVote(keyID, vote) || !vote.CheckSignature(keyID)) {
     461           0 :             nFailed++;
     462           0 :             statusObj.pushKV("result", "failed");
     463           0 :             statusObj.pushKV("errorMessage", "Failure to sign.");
     464           0 :             resultsObj.pushKV(proTxHash.ToString(), statusObj);
     465           0 :             continue;
     466             :         }
     467             : 
     468           0 :         CGovernanceException exception;
     469           0 :         CConnman& connman = EnsureConnman(node);
     470           0 :         if (node.govman->ProcessVoteAndRelay(vote, exception, connman)) {
     471           0 :             nSuccessful++;
     472           0 :             statusObj.pushKV("result", "success");
     473           0 :         } else {
     474           0 :             nFailed++;
     475           0 :             statusObj.pushKV("result", "failed");
     476           0 :             statusObj.pushKV("errorMessage", exception.GetMessage());
     477             :         }
     478             : 
     479           0 :         resultsObj.pushKV(proTxHash.ToString(), statusObj);
     480           0 :     }
     481             : 
     482           0 :     UniValue returnObj(UniValue::VOBJ);
     483           0 :     returnObj.pushKV("overall", strprintf("Voted successfully %d time(s) and failed %d time(s).", nSuccessful, nFailed));
     484           0 :     returnObj.pushKV("detail", resultsObj);
     485             : 
     486           0 :     return returnObj;
     487           0 : }
     488             : 
     489           0 : static bool CheckWalletOwnsKey(const CWallet& wallet, const CKeyID& keyid)
     490             : {
     491           0 :     const CScript script{GetScriptForDestination(PKHash(keyid))};
     492           0 :     LOCK(wallet.cs_wallet);
     493             : 
     494           0 :     return wallet.IsMine(script) == isminetype::ISMINE_SPENDABLE;
     495           0 : }
     496             : 
     497             : namespace {
     498         146 : const RPCResult vote_results{
     499         146 :     RPCResult::Type::OBJ, "", "",
     500         438 :         {
     501         146 :             {RPCResult::Type::STR, "overall", "Total number of successful and failed votes"},
     502         292 :             {RPCResult::Type::OBJ, "detail", "Detailed information for each vote",
     503         292 :             {
     504         292 :                 {RPCResult::Type::OBJ, "protx", "ProTx of masternode for voting",
     505         438 :                 {
     506         146 :                     {RPCResult::Type::STR, "result", "Result of voting: {success|failed}"},
     507         146 :                     {RPCResult::Type::STR, "errorMessage", /*optional=*/true, "Error message if failed"},
     508             :                 }},
     509             :             }},
     510             :         },
     511             : };
     512             : } // anonymous namespace
     513             : 
     514           0 : static RPCHelpMan gobject_vote_many()
     515             : {
     516           0 :     return RPCHelpMan{"gobject vote-many",
     517             :         "Vote on a governance object by all masternodes for which the voting key is present in the local wallet\n"
     518           0 :         + HELP_REQUIRING_PASSPHRASE,
     519           0 :         {
     520           0 :             {"governance-hash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "hash of the governance object"},
     521           0 :             {"vote", RPCArg::Type::STR, RPCArg::Optional::NO, "vote, possible values: [funding|valid|delete|endorsed]"},
     522           0 :             {"vote-outcome", RPCArg::Type::STR, RPCArg::Optional::NO, "vote outcome, possible values: [yes|no|abstain]"},
     523             :         },
     524           0 :         vote_results,
     525           0 :         RPCExamples{""},
     526           0 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
     527             : {
     528           0 :     const std::shared_ptr<const CWallet> wallet = GetWalletForJSONRPCRequest(request);
     529           0 :     if (!wallet) return UniValue::VNULL;
     530             : 
     531           0 :     const NodeContext& node = EnsureAnyNodeContext(request.context);
     532             : 
     533           0 :     uint256 hash(ParseHashV(request.params[0], "Object hash"));
     534           0 :     std::string strVoteSignal = request.params[1].get_str();
     535           0 :     std::string strVoteOutcome = request.params[2].get_str();
     536             : 
     537           0 :     vote_signal_enum_t eVoteSignal = CGovernanceVoting::ConvertVoteSignal(strVoteSignal);
     538           0 :     if (eVoteSignal == VOTE_SIGNAL_NONE) {
     539           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER,
     540           0 :                            "Invalid vote signal. Please use one of the following: "
     541             :                            "(funding|valid|delete|endorsed)");
     542             :     }
     543             : 
     544           0 :     vote_outcome_enum_t eVoteOutcome = CGovernanceVoting::ConvertVoteOutcome(strVoteOutcome);
     545           0 :     if (eVoteOutcome == VOTE_OUTCOME_NONE) {
     546           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid vote outcome. Please use one of the following: 'yes', 'no' or 'abstain'");
     547             :     }
     548             : 
     549           0 :     EnsureWalletIsUnlocked(*wallet);
     550             : 
     551           0 :     std::map<uint256, CKeyID> votingKeys;
     552             : 
     553           0 :     auto mnList = CHECK_NONFATAL(node.dmnman)->GetListAtChainTip();
     554           0 :     mnList.ForEachMN(/*onlyValid=*/true, [&](const auto& dmn) {
     555           0 :         const bool is_mine = CheckWalletOwnsKey(*wallet, dmn.pdmnState->keyIDVoting);
     556           0 :         if (is_mine) {
     557           0 :             votingKeys.emplace(dmn.proTxHash, dmn.pdmnState->keyIDVoting);
     558           0 :         }
     559           0 :     });
     560             : 
     561           0 :     return VoteWithMasternodes(request, *wallet, votingKeys, hash, eVoteSignal, eVoteOutcome);
     562           0 : },
     563             :     };
     564           0 : }
     565             : 
     566           0 : static RPCHelpMan gobject_vote_alias()
     567             : {
     568           0 :     return RPCHelpMan{"gobject vote-alias",
     569             :         "Vote on a governance object by masternode's voting key (if present in local wallet)\n"
     570           0 :         + HELP_REQUIRING_PASSPHRASE,
     571           0 :         {
     572           0 :             {"governance-hash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "hash of the governance object"},
     573           0 :             {"vote", RPCArg::Type::STR, RPCArg::Optional::NO, "vote, possible values: [funding|valid|delete|endorsed]"},
     574           0 :             {"vote-outcome", RPCArg::Type::STR, RPCArg::Optional::NO, "vote outcome, possible values: [yes|no|abstain]"},
     575           0 :             {"protx-hash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "masternode's proTxHash"},
     576             :         },
     577           0 :         vote_results,
     578           0 :         RPCExamples{""},
     579           0 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
     580             : {
     581           0 :     const std::shared_ptr<const CWallet> wallet = GetWalletForJSONRPCRequest(request);
     582           0 :     if (!wallet) return UniValue::VNULL;
     583             : 
     584           0 :     const NodeContext& node = EnsureAnyNodeContext(request.context);
     585             : 
     586           0 :     uint256 hash(ParseHashV(request.params[0], "Object hash"));
     587           0 :     std::string strVoteSignal = request.params[1].get_str();
     588           0 :     std::string strVoteOutcome = request.params[2].get_str();
     589             : 
     590           0 :     vote_signal_enum_t eVoteSignal = CGovernanceVoting::ConvertVoteSignal(strVoteSignal);
     591           0 :     if (eVoteSignal == VOTE_SIGNAL_NONE) {
     592           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER,
     593           0 :                            "Invalid vote signal. Please use one of the following: "
     594             :                            "(funding|valid|delete|endorsed)");
     595             :     }
     596             : 
     597           0 :     vote_outcome_enum_t eVoteOutcome = CGovernanceVoting::ConvertVoteOutcome(strVoteOutcome);
     598           0 :     if (eVoteOutcome == VOTE_OUTCOME_NONE) {
     599           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid vote outcome. Please use one of the following: 'yes', 'no' or 'abstain'");
     600             :     }
     601             : 
     602           0 :     EnsureWalletIsUnlocked(*wallet);
     603             : 
     604           0 :     uint256 proTxHash(ParseHashV(request.params[3], "protx-hash"));
     605           0 :     auto dmn = CHECK_NONFATAL(node.dmnman)->GetListAtChainTip().GetValidMN(proTxHash);
     606           0 :     if (!dmn) {
     607           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid or unknown proTxHash");
     608             :     }
     609             : 
     610             : 
     611           0 :     const bool is_mine = CheckWalletOwnsKey(*wallet, dmn->pdmnState->keyIDVoting);
     612           0 :     if (!is_mine) {
     613           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Private key for voting address %s not known by wallet", EncodeDestination(PKHash(dmn->pdmnState->keyIDVoting))));
     614             :     }
     615             : 
     616           0 :     std::map<uint256, CKeyID> votingKeys;
     617           0 :     votingKeys.emplace(proTxHash, dmn->pdmnState->keyIDVoting);
     618             : 
     619           0 :     return VoteWithMasternodes(request, *wallet, votingKeys, hash, eVoteSignal, eVoteOutcome);
     620           0 : },
     621             :     };
     622           0 : }
     623             : #endif
     624             : 
     625           0 : static UniValue ListObjects(CGovernanceManager& govman, const CDeterministicMNList& tip_mn_list, const ChainstateManager& chainman,
     626             :                             const std::string& strCachedSignal, const std::string& strType, int nStartTime)
     627             : {
     628           0 :     if (g_txindex) {
     629           0 :         g_txindex->BlockUntilSyncedToCurrentChain();
     630           0 :     }
     631             : 
     632           0 :     std::vector<CGovernanceObject> objs;
     633           0 :     govman.GetAllNewerThan(objs, nStartTime);
     634           0 :     govman.UpdateLastDiffTime(GetTime());
     635             : 
     636           0 :     UniValue objResult(UniValue::VOBJ);
     637           0 :     for (const auto& govObj : objs) {
     638           0 :         if (strCachedSignal == "valid" && !govObj.IsSetCachedValid()) continue;
     639           0 :         if (strCachedSignal == "funding" && !govObj.IsSetCachedFunding()) continue;
     640           0 :         if (strCachedSignal == "delete" && !govObj.IsSetCachedDelete()) continue;
     641           0 :         if (strCachedSignal == "endorsed" && !govObj.IsSetCachedEndorsed()) continue;
     642             : 
     643           0 :         if (strType == "proposals" && govObj.GetObjectType() != GovernanceObject::PROPOSAL) continue;
     644           0 :         if (strType == "triggers" && govObj.GetObjectType() != GovernanceObject::TRIGGER) continue;
     645             : 
     646           0 :         UniValue entry{govObj.GetStateJson(chainman, tip_mn_list, /*local_valid_key=*/"fBlockchainValidity")};
     647           0 :         UniValue votes_funding{govObj.GetVotesJson(tip_mn_list, VOTE_SIGNAL_FUNDING)};
     648           0 :         entry.pushKV("AbsoluteYesCount", votes_funding["AbsoluteYesCount"]);
     649           0 :         entry.pushKV("YesCount", votes_funding["YesCount"]);
     650           0 :         entry.pushKV("NoCount", votes_funding["NoCount"]);
     651           0 :         entry.pushKV("AbstainCount", votes_funding["AbstainCount"]);
     652           0 :         objResult.pushKV(govObj.GetHash().ToString(), entry);
     653           0 :     }
     654             : 
     655           0 :     return objResult;
     656           0 : }
     657             : 
     658         184 : static RPCResult ListObjectsHelp()
     659             : {
     660         184 :     auto ret = CGovernanceObject::GetStateJsonHelp(/*key=*/"", /*optional=*/false, /*local_valid_key=*/"fBlockchainValidity");
     661         184 :     auto mod_inner = ret.m_inner;
     662         920 :     for (const auto& result : CGovernanceObject::GetVotesJsonHelp(/*key=*/"", /*optional=*/false).m_inner) {
     663         736 :         mod_inner.push_back(result);
     664             :     }
     665         184 :     return RPCResult{ret.m_type, ret.m_key_name, ret.m_description, mod_inner};
     666         184 : }
     667             : 
     668         184 : static RPCHelpMan gobject_list_helper(const bool make_a_diff)
     669             : {
     670         184 :     const std::string command{make_a_diff ? "gobject diff" : "gobject list"};
     671         184 :     const std::string description{make_a_diff ? "List differences since last diff or list\n"
     672             :             : "List governance objects (can be filtered by signal and/or object type)\n"};
     673             : 
     674         368 :     return RPCHelpMan{command,
     675         184 :         description,
     676         552 :         {
     677         184 :             {"signal", RPCArg::Type::STR, RPCArg::Default{"valid"}, "cached signal, possible values: [valid|funding|delete|endorsed|all]"},
     678         184 :             {"type", RPCArg::Type::STR, RPCArg::Default{"all"}, "object type, possible values: [proposals|triggers|all]"},
     679             :         },
     680         552 :         {
     681         368 :             RPCResult{"If request is valid",
     682         184 :                 RPCResult::Type::OBJ, "hash", "Object details", {ListObjectsHelp()},
     683             :             },
     684         368 :             RPCResult{"If request is invalid",
     685         184 :                 RPCResult::Type::STR, "", "Error string"
     686             :             },
     687             :         },
     688         184 :         RPCExamples{""},
     689         184 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
     690             : {
     691           0 :     std::string strCachedSignal = "valid";
     692           0 :     if (!request.params[0].isNull()) {
     693           0 :         strCachedSignal = request.params[0].get_str();
     694           0 :     }
     695           0 :     if (strCachedSignal != "valid" && strCachedSignal != "funding" && strCachedSignal != "delete" && strCachedSignal != "endorsed" && strCachedSignal != "all")
     696           0 :         return "Invalid signal, should be 'valid', 'funding', 'delete', 'endorsed' or 'all'";
     697             : 
     698           0 :     std::string strType = "all";
     699           0 :     if (!request.params[1].isNull()) {
     700           0 :         strType = request.params[1].get_str();
     701           0 :     }
     702           0 :     if (strType != "proposals" && strType != "triggers" && strType != "all")
     703           0 :         return "Invalid type, should be 'proposals', 'triggers' or 'all'";
     704             : 
     705           0 :     const NodeContext& node = EnsureAnyNodeContext(request.context);
     706           0 :     const ChainstateManager& chainman = EnsureChainman(node);
     707             : 
     708           0 :     const int64_t last_time = make_a_diff ? node.govman->GetLastDiffTime() : 0;
     709           0 :     return ListObjects(*CHECK_NONFATAL(node.govman), CHECK_NONFATAL(node.dmnman)->GetListAtChainTip(), chainman,
     710           0 :                        strCachedSignal, strType, last_time);
     711           0 : },
     712             :     };
     713         184 : }
     714             : 
     715          92 : static RPCHelpMan gobject_list()
     716             : {
     717          92 :     return gobject_list_helper(false);
     718             : }
     719             : 
     720          92 : static RPCHelpMan gobject_diff()
     721             : {
     722          92 :     return gobject_list_helper(true);
     723             : }
     724             : 
     725          92 : static RPCResult gobject_get_help()
     726             : {
     727          92 :     auto ret = CGovernanceObject::GetStateJsonHelp(/*key=*/"", /*optional=*/false, /*local_valid_key=*/"fLocalValidity");
     728          92 :     auto mod_inner = ret.m_inner;
     729          92 :     mod_inner.push_back({RPCResult::Type::OBJ, "FundingResult", "Funding vote details", {CGovernanceObject::GetVotesJsonHelp(/*key=*/"", /*optional=*/false)}});
     730          92 :     mod_inner.push_back({RPCResult::Type::OBJ, "ValidResult", "Object validity vote details", {CGovernanceObject::GetVotesJsonHelp(/*key=*/"", /*optional=*/false)}});
     731          92 :     mod_inner.push_back({RPCResult::Type::OBJ, "DeleteResult", "Delete vote details", {CGovernanceObject::GetVotesJsonHelp(/*key=*/"", /*optional=*/false)}});
     732          92 :     mod_inner.push_back({RPCResult::Type::OBJ, "EndorsedResult", "Endorsed vote details", {CGovernanceObject::GetVotesJsonHelp(/*key=*/"", /*optional=*/false)}});
     733          92 :     return RPCResult{ret.m_type, ret.m_key_name, ret.m_description, mod_inner};
     734          92 : }
     735             : 
     736          92 : static RPCHelpMan gobject_get()
     737             : {
     738         184 :     return RPCHelpMan{"gobject get",
     739          92 :         "Get governance object by hash\n",
     740         184 :         {
     741          92 :             {"governance-hash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "object id"},
     742             :         },
     743          92 :         gobject_get_help(),
     744          92 :         RPCExamples{""},
     745          92 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
     746             : {
     747           0 :     if (g_txindex) {
     748           0 :         g_txindex->BlockUntilSyncedToCurrentChain();
     749           0 :     }
     750             : 
     751           0 :     const NodeContext& node = EnsureAnyNodeContext(request.context);
     752           0 :     const ChainstateManager& chainman = EnsureChainman(node);
     753             : 
     754           0 :     uint256 hash(ParseHashV(request.params[0], "GovObj hash"));
     755           0 :     auto pGovObj = CHECK_NONFATAL(node.govman)->FindConstGovernanceObject(hash);
     756           0 :     if (pGovObj == nullptr) {
     757           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER, "Unknown governance object");
     758             :     }
     759             : 
     760           0 :     auto tip_mn_list = CHECK_NONFATAL(node.dmnman)->GetListAtChainTip();
     761           0 :     UniValue ret{pGovObj->GetStateJson(chainman, tip_mn_list, /*local_valid_key=*/"fLocalValidity")};
     762           0 :     ret.pushKV("FundingResult", pGovObj->GetVotesJson(tip_mn_list, VOTE_SIGNAL_FUNDING));
     763           0 :     ret.pushKV("ValidResult", pGovObj->GetVotesJson(tip_mn_list, VOTE_SIGNAL_VALID));
     764           0 :     ret.pushKV("DeleteResult", pGovObj->GetVotesJson(tip_mn_list, VOTE_SIGNAL_DELETE));
     765           0 :     ret.pushKV("EndorsedResult", pGovObj->GetVotesJson(tip_mn_list, VOTE_SIGNAL_ENDORSED));
     766           0 :     return ret;
     767           0 : },
     768             :     };
     769           0 : }
     770             : 
     771             : // GET VOTES FOR SPECIFIC GOVERNANCE OBJECT
     772          92 : static RPCHelpMan gobject_getcurrentvotes()
     773             : {
     774         184 :     return RPCHelpMan{"gobject getcurrentvotes",
     775          92 :         "Get only current (tallying) votes for a governance object hash (does not include old votes)\n",
     776         368 :         {
     777          92 :             {"governance-hash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "object id"},
     778          92 :             {"txid", RPCArg::Type::STR_HEX, RPCArg::Default{""}, "masternode collateral txid"},
     779          92 :             {"vout", RPCArg::Type::STR, RPCArg::Default{""}, "masternode collateral output index, required if <txid> present"},
     780             :         },
     781          92 :         RPCResult{
     782          92 :             RPCResult::Type::OBJ_DYN, "", "Keys are hashes of vote, values are votes",
     783         184 :             {
     784          92 :                 {RPCResult::Type::STR, "votes", "Human-friendly presentation of vote"},
     785             :             },
     786             :         },
     787          92 :         RPCExamples{""},
     788          92 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
     789             : {
     790             :     // COLLECT PARAMETERS FROM USER
     791             : 
     792           0 :     uint256 hash(ParseHashV(request.params[0], "Governance hash"));
     793             : 
     794           0 :     COutPoint mnCollateralOutpoint;
     795           0 :     if (!request.params[1].isNull() && !request.params[2].isNull()) {
     796           0 :         uint256 txid(ParseHashV(request.params[1], "Masternode Collateral hash"));
     797           0 :         std::string strVout = request.params[2].get_str();
     798           0 :         mnCollateralOutpoint = COutPoint(txid, LocaleIndependentAtoi<uint32_t>(strVout));
     799           0 :     }
     800             : 
     801             :     // FIND OBJECT USER IS LOOKING FOR
     802           0 :     const NodeContext& node = EnsureAnyNodeContext(request.context);
     803             : 
     804           0 :     CHECK_NONFATAL(node.govman);
     805           0 :     auto pGovObj = node.govman->FindConstGovernanceObject(hash);
     806             : 
     807           0 :     if (pGovObj == nullptr) {
     808           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER, "Unknown governance-hash");
     809             :     }
     810             : 
     811             :     // REPORT RESULTS TO USER
     812             : 
     813           0 :     UniValue bResult(UniValue::VOBJ);
     814             : 
     815             :     // GET MATCHING VOTES BY HASH, THEN SHOW USERS VOTE INFORMATION
     816           0 :     std::vector<CGovernanceVote> vecVotes = node.govman->GetCurrentVotes(hash, mnCollateralOutpoint);
     817           0 :     for (const auto& vote : vecVotes) {
     818           0 :         bResult.pushKV(vote.GetHash().ToString(), vote.ToString(CHECK_NONFATAL(node.dmnman)->GetListAtChainTip()));
     819             :     }
     820             : 
     821           0 :     return bResult;
     822           0 : },
     823             :     };
     824           0 : }
     825             : 
     826          92 : static RPCHelpMan gobject()
     827             : {
     828         184 :     return RPCHelpMan{"gobject",
     829          92 :         "Set of commands to manage governance objects.\n"
     830             :         "\nAvailable commands:\n"
     831             :         "  check              - Validate governance object data (proposal only)\n"
     832             : #ifdef ENABLE_WALLET
     833             :         "  prepare            - Prepare governance object by signing and creating tx\n"
     834             :         "  list-prepared      - Returns a list of governance objects prepared by this wallet with \"gobject prepare\"\n"
     835             : #endif // ENABLE_WALLET
     836             :         "  submit             - Submit governance object to network\n"
     837             :         "  deserialize        - Deserialize governance object from hex string to JSON\n"
     838             :         "  count              - Count governance objects and votes (additional param: 'json' or 'all', default: 'json')\n"
     839             :         "  get                - Get governance object by hash\n"
     840             :         "  getcurrentvotes    - Get only current (tallying) votes for a governance object hash (does not include old votes)\n"
     841             :         "  list               - List governance objects (can be filtered by signal and/or object type)\n"
     842             :         "  diff               - List differences since last diff\n"
     843             : #ifdef ENABLE_WALLET
     844             :         "  vote-alias         - Vote on a governance object by masternode proTxHash\n"
     845             :         "  vote-many          - Vote on a governance object by all masternodes for which the voting key is in the wallet\n"
     846             : #endif // ENABLE_WALLET
     847             :         ,
     848         184 :         {
     849          92 :             {"command", RPCArg::Type::STR, RPCArg::Optional::NO, "The command to execute"},
     850             :         },
     851          92 :         RPCResult{RPCResult::Type::NONE, "", ""},
     852          92 :         RPCExamples{""},
     853          92 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
     854             : {
     855           0 :     throw JSONRPCError(RPC_INVALID_PARAMETER, "Must be a valid command");
     856           0 : },
     857             :     };
     858           0 : }
     859             : 
     860          92 : static RPCHelpMan voteraw()
     861             : {
     862         184 :     return RPCHelpMan{"voteraw",
     863          92 :         "Compile and relay a governance vote with provided external signature instead of signing vote internally\n",
     864         736 :         {
     865          92 :             {"mn-collateral-tx-hash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, ""},
     866          92 :             {"mn-collateral-tx-index", RPCArg::Type::NUM, RPCArg::Optional::NO, ""},
     867          92 :             {"governance-hash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, ""},
     868          92 :             {"vote-signal", RPCArg::Type::STR, RPCArg::Optional::NO, ""},
     869          92 :             {"vote-outcome", RPCArg::Type::STR, RPCArg::Optional::NO, "yes|no|abstain"},
     870          92 :             {"time", RPCArg::Type::NUM, RPCArg::Optional::NO, ""},
     871          92 :             {"vote-sig", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, ""},
     872             :         },
     873          92 :         RPCResult{RPCResult::Type::STR, "", "Result of voting"},
     874          92 :         RPCExamples{""},
     875          92 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
     876             : {
     877           0 :     uint256 hashMnCollateralTx(ParseHashV(request.params[0], "mn collateral tx hash"));
     878           0 :     int nMnCollateralTxIndex = request.params[1].getInt<int>();
     879           0 :     COutPoint outpoint = COutPoint(hashMnCollateralTx, nMnCollateralTxIndex);
     880             : 
     881           0 :     uint256 hashGovObj(ParseHashV(request.params[2], "Governance hash"));
     882           0 :     std::string strVoteSignal = request.params[3].get_str();
     883           0 :     std::string strVoteOutcome = request.params[4].get_str();
     884             : 
     885           0 :     vote_signal_enum_t eVoteSignal = CGovernanceVoting::ConvertVoteSignal(strVoteSignal);
     886           0 :     if (eVoteSignal == VOTE_SIGNAL_NONE)  {
     887           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER,
     888           0 :                            "Invalid vote signal. Please use one of the following: "
     889             :                            "(funding|valid|delete|endorsed)");
     890             :     }
     891             : 
     892           0 :     vote_outcome_enum_t eVoteOutcome = CGovernanceVoting::ConvertVoteOutcome(strVoteOutcome);
     893           0 :     if (eVoteOutcome == VOTE_OUTCOME_NONE) {
     894           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid vote outcome. Please use one of the following: 'yes', 'no' or 'abstain'");
     895             :     }
     896             : 
     897           0 :     const NodeContext& node = EnsureAnyNodeContext(request.context);
     898           0 :     CHECK_NONFATAL(node.govman);
     899           0 :     GovernanceObject govObjType = [&]() {
     900           0 :         auto pGovObj = node.govman->FindConstGovernanceObject(hashGovObj);
     901           0 :         if (!pGovObj) {
     902           0 :             throw JSONRPCError(RPC_INVALID_PARAMETER, "Governance object not found");
     903             :         }
     904           0 :         return pGovObj->GetObjectType();
     905           0 :     }();
     906             : 
     907             : 
     908           0 :     int64_t nTime = request.params[5].getInt<int64_t>();
     909           0 :     std::string strSig = request.params[6].get_str();
     910           0 :     auto opt_vchSig = DecodeBase64(strSig);
     911             : 
     912           0 :     if (!opt_vchSig.has_value()) {
     913           0 :         throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Malformed base64 encoding");
     914             :     }
     915             : 
     916           0 :     const auto tip_mn_list = CHECK_NONFATAL(node.dmnman)->GetListAtChainTip();
     917           0 :     auto dmn = tip_mn_list.GetValidMNByCollateral(outpoint);
     918             : 
     919           0 :     if (!dmn) {
     920           0 :         throw JSONRPCError(RPC_INTERNAL_ERROR, "Failure to find masternode in list : " + outpoint.ToStringShort());
     921             :     }
     922             : 
     923           0 :     CGovernanceVote vote(outpoint, hashGovObj, eVoteSignal, eVoteOutcome);
     924           0 :     vote.SetTime(nTime);
     925           0 :     vote.SetSignature(opt_vchSig.value());
     926             : 
     927           0 :     bool onlyVotingKeyAllowed = govObjType == GovernanceObject::PROPOSAL && vote.GetSignal() == VOTE_SIGNAL_FUNDING;
     928             : 
     929           0 :     if (!vote.IsValid(tip_mn_list, onlyVotingKeyAllowed)) {
     930           0 :         throw JSONRPCError(RPC_INTERNAL_ERROR, "Failure to verify vote.");
     931             :     }
     932             : 
     933           0 :     CConnman& connman = EnsureConnman(node);
     934             : 
     935           0 :     CGovernanceException exception;
     936           0 :     if (node.govman->ProcessVoteAndRelay(vote, exception, connman)) {
     937           0 :         return "Voted successfully";
     938             :     } else {
     939           0 :         throw JSONRPCError(RPC_INTERNAL_ERROR, "Error voting : " + exception.GetMessage());
     940             :     }
     941           0 : },
     942             :     };
     943           0 : }
     944             : 
     945          92 : static RPCHelpMan getgovernanceinfo()
     946             : {
     947         184 :     return RPCHelpMan{"getgovernanceinfo",
     948          92 :         "Returns an object containing governance parameters.\n",
     949          92 :         {},
     950          92 :         RPCResult{
     951          92 :             RPCResult::Type::OBJ, "", "",
     952         828 :             {
     953          92 :                 {RPCResult::Type::NUM, "governanceminquorum", "the absolute minimum number of votes needed to trigger a governance action"},
     954          92 :                 {RPCResult::Type::NUM, "proposalfee", "the collateral transaction fee which must be paid to create a proposal in " + CURRENCY_UNIT + ""},
     955          92 :                 {RPCResult::Type::NUM, "superblockcycle", "the number of blocks between superblocks"},
     956          92 :                 {RPCResult::Type::NUM, "superblockmaturitywindow", "the superblock trigger creation window"},
     957          92 :                 {RPCResult::Type::NUM, "lastsuperblock", "the block number of the last superblock"},
     958          92 :                 {RPCResult::Type::NUM, "nextsuperblock", "the block number of the next superblock"},
     959          92 :                 {RPCResult::Type::NUM, "fundingthreshold", "the number of absolute yes votes required for a proposal to be passing"},
     960          92 :                 {RPCResult::Type::NUM, "governancebudget", "the governance budget for the next superblock in " + CURRENCY_UNIT + ""},
     961             :             }},
     962          92 :         RPCExamples{
     963          92 :             HelpExampleCli("getgovernanceinfo", "")
     964          92 :     + HelpExampleRpc("getgovernanceinfo", "")
     965             :         },
     966          92 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
     967             : {
     968             : 
     969           0 :     int nLastSuperblock = 0, nNextSuperblock = 0;
     970             : 
     971           0 :     const NodeContext& node = EnsureAnyNodeContext(request.context);
     972           0 :     const ChainstateManager& chainman = EnsureAnyChainman(request.context);
     973             : 
     974           0 :     const auto* pindex = WITH_LOCK(cs_main, return chainman.ActiveChain().Tip());
     975           0 :     int nBlockHeight = pindex->nHeight;
     976             : 
     977           0 :     CSuperblock::GetNearestSuperblocksHeights(nBlockHeight, nLastSuperblock, nNextSuperblock);
     978             : 
     979           0 :     UniValue obj(UniValue::VOBJ);
     980           0 :     obj.pushKV("governanceminquorum", Params().GetConsensus().nGovernanceMinQuorum);
     981           0 :     obj.pushKV("proposalfee", ValueFromAmount(GOVERNANCE_PROPOSAL_FEE_TX));
     982           0 :     obj.pushKV("superblockcycle", Params().GetConsensus().nSuperblockCycle);
     983           0 :     obj.pushKV("superblockmaturitywindow", Params().GetConsensus().nSuperblockMaturityWindow);
     984           0 :     obj.pushKV("lastsuperblock", nLastSuperblock);
     985           0 :     obj.pushKV("nextsuperblock", nNextSuperblock);
     986           0 :     obj.pushKV("fundingthreshold", CHECK_NONFATAL(node.dmnman)->GetListAtChainTip().GetCounts().m_valid_weighted / 10);
     987           0 :     obj.pushKV("governancebudget", ValueFromAmount(CSuperblock::GetPaymentsLimit(chainman.ActiveChain(), nNextSuperblock)));
     988             : 
     989           0 :     return obj;
     990           0 : },
     991             :     };
     992           0 : }
     993             : 
     994          92 : static RPCHelpMan getsuperblockbudget()
     995             : {
     996         184 :     return RPCHelpMan{"getsuperblockbudget",
     997          92 :         "\nReturns the absolute maximum sum of superblock payments allowed.\n",
     998         184 :         {
     999          92 :             {"index", RPCArg::Type::NUM, RPCArg::Optional::NO, "The block index"},
    1000             :         },
    1001          92 :         RPCResult{
    1002          92 :             RPCResult::Type::NUM, "n", "The absolute maximum sum of superblock payments allowed, in " + CURRENCY_UNIT
    1003             :         },
    1004          92 :         RPCExamples{
    1005          92 :             HelpExampleCli("getsuperblockbudget", "1000")
    1006          92 :     + HelpExampleRpc("getsuperblockbudget", "1000")
    1007             :         },
    1008          92 :         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
    1009             : {
    1010           0 :     int nBlockHeight = request.params[0].getInt<int>();
    1011           0 :     if (nBlockHeight < 0) {
    1012           0 :         throw JSONRPCError(RPC_INVALID_PARAMETER, "Block height out of range");
    1013             :     }
    1014             : 
    1015           0 :     const ChainstateManager& chainman = EnsureAnyChainman(request.context);
    1016           0 :     return ValueFromAmount(CSuperblock::GetPaymentsLimit(chainman.ActiveChain(), nBlockHeight));
    1017           0 : },
    1018             :     };
    1019           0 : }
    1020             : 
    1021             : #ifdef ENABLE_WALLET
    1022           0 : Span<const CRPCCommand> GetWalletGovernanceRPCCommands()
    1023             : {
    1024           0 :     static const CRPCCommand commands[]{
    1025           0 :         {"dash", &gobject_prepare},
    1026           0 :         {"dash", &gobject_list_prepared},
    1027           0 :         {"dash", &gobject_vote_many},
    1028           0 :         {"dash", &gobject_vote_alias},
    1029             :     };
    1030           0 :     return commands;
    1031           0 : }
    1032             : #endif // ENABLE_WALLET
    1033             : 
    1034         178 : void RegisterGovernanceRPCCommands(CRPCTable &t)
    1035             : {
    1036         730 :     static const CRPCCommand commands[]{
    1037          46 :         {"dash", &getgovernanceinfo},
    1038          46 :         {"dash", &getsuperblockbudget},
    1039          46 :         {"dash", &gobject},
    1040          46 :         {"dash", &gobject_count},
    1041          46 :         {"dash", &gobject_deserialize},
    1042          46 :         {"dash", &gobject_check},
    1043          46 :         {"dash", &gobject_submit},
    1044          46 :         {"dash", &gobject_list},
    1045          46 :         {"dash", &gobject_diff},
    1046          46 :         {"dash", &gobject_get},
    1047          46 :         {"dash", &gobject_getcurrentvotes},
    1048          46 :         {"dash", &voteraw},
    1049             :     };
    1050        2314 :     for (const auto& command : commands) {
    1051        2136 :         t.appendCommand(command.name, &command);
    1052             :     }
    1053         178 : }

Generated by: LCOV version 1.16