dash/src/rpc/governance.cpp
2022-05-18 20:50:50 +05:30

1216 lines
50 KiB
C++

// Copyright (c) 2014-2021 The Dash Core developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <chainparams.h>
#include <core_io.h>
#include <evo/deterministicmns.h>
#include <governance/classes.h>
#include <governance/governance.h>
#include <governance/validators.h>
#include <governance/vote.h>
#include <index/txindex.h>
#include <masternode/node.h>
#include <masternode/sync.h>
#include <messagesigner.h>
#include <node/context.h>
#include <net.h>
#include <rpc/blockchain.h>
#include <rpc/server.h>
#include <rpc/util.h>
#include <util/system.h>
#include <validation.h>
#include <wallet/rpcwallet.h>
#ifdef ENABLE_WALLET
#include <wallet/wallet.h>
#endif // ENABLE_WALLET
static void gobject_count_help(const JSONRPCRequest& request)
{
RPCHelpMan{"gobject count",
"Count governance objects and votes\n",
{
{"mode", RPCArg::Type::STR, /* default */ "json", "Output format: json (\"json\") or string in free form (\"all\")"},
},
RPCResults{},
RPCExamples{""}
}.Check(request);
}
static UniValue gobject_count(const JSONRPCRequest& request)
{
gobject_count_help(request);
std::string strMode{"json"};
if (!request.params[0].isNull()) {
strMode = request.params[0].get_str();
}
if (strMode != "json" && strMode != "all")
gobject_count_help(request);
return strMode == "json" ? governance.ToJson() : governance.ToString();
}
static void gobject_deserialize_help(const JSONRPCRequest& request)
{
RPCHelpMan{"gobject deserialize",
"Deserialize governance object from hex string to JSON\n",
{
{"hex_data", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "data in hex string form"},
},
RPCResults{},
RPCExamples{""}
}.Check(request);
}
static UniValue gobject_deserialize(const JSONRPCRequest& request)
{
gobject_deserialize_help(request);
std::string strHex = request.params[0].get_str();
std::vector<unsigned char> v = ParseHex(strHex);
std::string s(v.begin(), v.end());
UniValue u(UniValue::VOBJ);
u.read(s);
return u.write().c_str();
}
static void gobject_check_help(const JSONRPCRequest& request)
{
RPCHelpMan{"gobject check",
"Validate governance object data (proposal only)\n",
{
{"hex_data", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "data in hex string format"},
},
RPCResults{},
RPCExamples{""}
}.Check(request);
}
static UniValue gobject_check(const JSONRPCRequest& request)
{
gobject_check_help(request);
// ASSEMBLE NEW GOVERNANCE OBJECT FROM USER PARAMETERS
uint256 hashParent;
int nRevision = 1;
int64_t nTime = GetAdjustedTime();
std::string strDataHex = request.params[0].get_str();
CGovernanceObject govobj(hashParent, nRevision, nTime, uint256(), strDataHex);
if (govobj.GetObjectType() == GOVERNANCE_OBJECT_PROPOSAL) {
LOCK(cs_main);
bool fAllowScript = (VersionBitsTipState(Params().GetConsensus(), Consensus::DEPLOYMENT_DIP0024) == ThresholdState::ACTIVE);
// Note: we do not allow legacy format in RPC already, no need to reuse DEPLOYMENT_DIP0024
CProposalValidator validator(strDataHex, false, fAllowScript);
if (!validator.Validate()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid proposal data, error messages:" + validator.GetErrorMessages());
}
} else {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid object type, only proposals can be validated");
}
UniValue objResult(UniValue::VOBJ);
objResult.pushKV("Object status", "OK");
return objResult;
}
#ifdef ENABLE_WALLET
static void gobject_prepare_help(const JSONRPCRequest& request)
{
RPCHelpMan{"gobject prepare",
"Prepare governance object by signing and creating tx\n"
+ HelpRequiringPassphrase() + "\n",
{
{"parent-hash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "hash of the parent object, \"0\" is root"},
{"revision", RPCArg::Type::NUM, RPCArg::Optional::NO, "object revision in the system"},
{"time", RPCArg::Type::NUM, RPCArg::Optional::NO, "time this object was created"},
{"data-hex", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "data in hex string form"},
{"use-IS", RPCArg::Type::BOOL, /* default */ "false", "Deprecated and ignored"},
{"outputHash", RPCArg::Type::STR_HEX, /* default */ "", "the single output to submit the proposal fee from"},
{"outputIndex", RPCArg::Type::NUM, /* default */ "", "The output index."},
},
RPCResults{},
RPCExamples{""}
}.Check(request);
}
static UniValue gobject_prepare(const JSONRPCRequest& request)
{
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) return NullUniValue;
CWallet* const pwallet = wallet.get();
gobject_prepare_help(request);
EnsureWalletIsUnlocked(pwallet);
// ASSEMBLE NEW GOVERNANCE OBJECT FROM USER PARAMETERS
uint256 hashParent;
// -- attach to root node (root node doesn't really exist, but has a hash of zero)
if (request.params[0].get_str() == "0") {
hashParent = uint256();
} else {
hashParent = ParseHashV(request.params[0], "parent-hash");
}
int nRevision = ParseInt32V(request.params[1], "revision");
int64_t nTime = ParseInt64V(request.params[2], "time");
std::string strDataHex = request.params[3].get_str();
// CREATE A NEW COLLATERAL TRANSACTION FOR THIS SPECIFIC OBJECT
CGovernanceObject govobj(hashParent, nRevision, nTime, uint256(), strDataHex);
// This command is dangerous because it consumes 5 DASH irreversibly.
// If params are lost, it's very hard to bruteforce them and yet
// users ignore all instructions on dashcentral etc. and do not save them...
// Let's log them here and hope users do not mess with debug.log
LogPrintf("gobject_prepare -- params: %s %s %s %s, data: %s, hash: %s\n",
request.params[0].getValStr(), request.params[1].getValStr(),
request.params[2].getValStr(), request.params[3].getValStr(),
govobj.GetDataAsPlainString(), govobj.GetHash().ToString());
if (govobj.GetObjectType() == GOVERNANCE_OBJECT_PROPOSAL) {
LOCK(cs_main);
bool fAllowScript = (VersionBitsTipState(Params().GetConsensus(), Consensus::DEPLOYMENT_DIP0024) == ThresholdState::ACTIVE);
// Note: we do not allow legacy format in RPC already, no need to reuse DEPLOYMENT_DIP0024
CProposalValidator validator(strDataHex, false, fAllowScript);
if (!validator.Validate()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid proposal data, error messages:" + validator.GetErrorMessages());
}
}
if (govobj.GetObjectType() == GOVERNANCE_OBJECT_TRIGGER) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Trigger objects need not be prepared (however only masternodes can create them)");
}
if (g_txindex) {
g_txindex->BlockUntilSyncedToCurrentChain();
}
auto locked_chain = wallet->chain().lock();
LOCK(pwallet->cs_wallet);
{
LOCK(cs_main);
std::string strError = "";
if (!govobj.IsValidLocally(strError, false))
throw JSONRPCError(RPC_INTERNAL_ERROR, "Governance object is not valid - " + govobj.GetHash().ToString() + " - " + strError);
}
// If specified, spend this outpoint as the proposal fee
COutPoint outpoint;
outpoint.SetNull();
if (!request.params[5].isNull() && !request.params[6].isNull()) {
uint256 collateralHash = ParseHashV(request.params[5], "outputHash");
int32_t collateralIndex = ParseInt32V(request.params[6], "outputIndex");
if (collateralHash.IsNull() || collateralIndex < 0) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("invalid hash or index: %s-%d", collateralHash.ToString(), collateralIndex));
}
outpoint = COutPoint(collateralHash, (uint32_t)collateralIndex);
}
CTransactionRef tx;
bool fork_active = VersionBitsTipState(Params().GetConsensus(), Consensus::DEPLOYMENT_DIP0024) == ThresholdState::ACTIVE;
if (!pwallet->GetBudgetSystemCollateralTX(*locked_chain, tx, govobj.GetHash(), govobj.GetMinCollateralFee(fork_active), outpoint)) {
std::string err = "Error making collateral transaction for governance object. Please check your wallet balance and make sure your wallet is unlocked.";
if (!request.params[5].isNull() && !request.params[6].isNull()) {
err += "Please verify your specified output is valid and is enough for the combined proposal fee and transaction fee.";
}
throw JSONRPCError(RPC_INTERNAL_ERROR, err);
}
if (!pwallet->WriteGovernanceObject({hashParent, nRevision, nTime, tx->GetHash(), strDataHex})) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "WriteGovernanceObject failed");
}
// -- send the tx to the network
pwallet->CommitTransaction(tx, {}, {});
LogPrint(BCLog::GOBJECT, "gobject_prepare -- GetDataAsPlainString = %s, hash = %s, txid = %s\n",
govobj.GetDataAsPlainString(), govobj.GetHash().ToString(), tx->GetHash().ToString());
return tx->GetHash().ToString();
}
static void gobject_list_prepared_help(const JSONRPCRequest& request)
{
RPCHelpMan{"gobject list-prepared",
"Returns a list of governance objects prepared by this wallet with \"gobject prepare\" sorted by their creation time.\n"
+ HelpRequiringPassphrase() + "\n",
{
{"count", RPCArg::Type::NUM, /* default */ "10", "Maximum number of objects to return."},
},
RPCResults{},
RPCExamples{""}
}.Check(request);
}
static UniValue gobject_list_prepared(const JSONRPCRequest& request)
{
gobject_list_prepared_help(request);
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) return NullUniValue;
CWallet* const pwallet = wallet.get();
EnsureWalletIsUnlocked(pwallet);
int64_t nCount = request.params.empty() ? 10 : ParseInt64V(request.params[0], "count");
if (nCount < 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative count");
}
// Get a list of all prepared governance objects stored in the wallet
LOCK(pwallet->cs_wallet);
std::vector<const CGovernanceObject*> vecObjects = pwallet->GetGovernanceObjects();
// Sort the vector by the object creation time/hex data
std::sort(vecObjects.begin(), vecObjects.end(), [](const CGovernanceObject* a, const CGovernanceObject* b) {
bool fGreater = a->GetCreationTime() > b->GetCreationTime();
bool fEqual = a->GetCreationTime() == b->GetCreationTime();
bool fHexGreater = a->GetDataAsHexString() > b->GetDataAsHexString();
return fGreater || (fEqual && fHexGreater);
});
UniValue jsonArray(UniValue::VARR);
auto it = vecObjects.rbegin() + std::max<int>(0, vecObjects.size() - nCount);
while (it != vecObjects.rend()) {
jsonArray.push_back((*it++)->ToJson());
}
return jsonArray;
}
#endif // ENABLE_WALLET
static void gobject_submit_help(const JSONRPCRequest& request)
{
RPCHelpMan{"gobject submit",
"Submit governance object to network\n",
{
{"parent-hash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "hash of the parent object, \"0\" is root"},
{"revision", RPCArg::Type::NUM, RPCArg::Optional::NO, "object revision in the system"},
{"time", RPCArg::Type::NUM, RPCArg::Optional::NO, "time this object was created"},
{"data-hex", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "data in hex string form"},
{"fee-txid", RPCArg::Type::STR_HEX, /* default */ "", "fee-tx id, required for all objects except triggers"},
},
RPCResults{},
RPCExamples{""}
}.Check(request);
}
static UniValue gobject_submit(const JSONRPCRequest& request)
{
gobject_submit_help(request);
if(!masternodeSync.IsBlockchainSynced()) {
throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "Must wait for client to sync with masternode network. Try again in a minute or so.");
}
auto mnList = deterministicMNManager->GetListAtChainTip();
bool fMnFound = WITH_LOCK(activeMasternodeInfoCs, return mnList.HasValidMNByCollateral(activeMasternodeInfo.outpoint));
LogPrint(BCLog::GOBJECT, "gobject_submit -- pubKeyOperator = %s, outpoint = %s, params.size() = %lld, fMnFound = %d\n",
(WITH_LOCK(activeMasternodeInfoCs, return activeMasternodeInfo.blsPubKeyOperator ? activeMasternodeInfo.blsPubKeyOperator->ToString() : "N/A")),
WITH_LOCK(activeMasternodeInfoCs, return activeMasternodeInfo.outpoint.ToStringShort()), request.params.size(), fMnFound);
// ASSEMBLE NEW GOVERNANCE OBJECT FROM USER PARAMETERS
uint256 txidFee;
if (!request.params[4].isNull()) {
txidFee = ParseHashV(request.params[4], "fee-txid");
}
uint256 hashParent;
if (request.params[0].get_str() == "0") { // attach to root node (root node doesn't really exist, but has a hash of zero)
hashParent = uint256();
} else {
hashParent = ParseHashV(request.params[0], "parent-hash");
}
// GET THE PARAMETERS FROM USER
int nRevision = ParseInt32V(request.params[1], "revision");
int64_t nTime = ParseInt64V(request.params[2], "time");
std::string strDataHex = request.params[3].get_str();
CGovernanceObject govobj(hashParent, nRevision, nTime, txidFee, strDataHex);
LogPrint(BCLog::GOBJECT, "gobject_submit -- GetDataAsPlainString = %s, hash = %s, txid = %s\n",
govobj.GetDataAsPlainString(), govobj.GetHash().ToString(), txidFee.ToString());
if (govobj.GetObjectType() == GOVERNANCE_OBJECT_PROPOSAL) {
LOCK(cs_main);
bool fAllowScript = (VersionBitsTipState(Params().GetConsensus(), Consensus::DEPLOYMENT_DIP0024) == ThresholdState::ACTIVE);
// Note: we do not allow legacy format in RPC already, no need to reuse DEPLOYMENT_DIP0024
CProposalValidator validator(strDataHex, false, fAllowScript);
if (!validator.Validate()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid proposal data, error messages:" + validator.GetErrorMessages());
}
}
// Attempt to sign triggers if we are a MN
if (govobj.GetObjectType() == GOVERNANCE_OBJECT_TRIGGER) {
if (fMnFound) {
LOCK(activeMasternodeInfoCs);
govobj.SetMasternodeOutpoint(activeMasternodeInfo.outpoint);
govobj.Sign(*activeMasternodeInfo.blsKeyOperator);
} else {
LogPrintf("gobject(submit) -- Object submission rejected because node is not a masternode\n");
throw JSONRPCError(RPC_INVALID_PARAMETER, "Only valid masternodes can submit this type of object");
}
} else if (request.params.size() != 5) {
LogPrintf("gobject(submit) -- Object submission rejected because fee tx not provided\n");
throw JSONRPCError(RPC_INVALID_PARAMETER, "The fee-txid parameter must be included to submit this type of object");
}
std::string strHash = govobj.GetHash().ToString();
std::string strError = "";
bool fMissingConfirmations;
{
if (g_txindex) {
g_txindex->BlockUntilSyncedToCurrentChain();
}
LOCK2(cs_main, ::mempool.cs);
if (!govobj.IsValidLocally(strError, fMissingConfirmations, true) && !fMissingConfirmations) {
LogPrintf("gobject(submit) -- Object submission rejected because object is not valid - hash = %s, strError = %s\n", strHash, strError);
throw JSONRPCError(RPC_INTERNAL_ERROR, "Governance object is not valid - " + strHash + " - " + strError);
}
}
// RELAY THIS OBJECT
// Reject if rate check fails but don't update buffer
if (!governance.MasternodeRateCheck(govobj)) {
LogPrintf("gobject(submit) -- Object submission rejected because of rate check failure - hash = %s\n", strHash);
throw JSONRPCError(RPC_INVALID_PARAMETER, "Object creation rate limit exceeded");
}
LogPrintf("gobject(submit) -- Adding locally created governance object - %s\n", strHash);
const NodeContext& node = EnsureNodeContext(request.context);
if (fMissingConfirmations) {
governance.AddPostponedObject(govobj);
govobj.Relay(*node.connman);
} else {
governance.AddGovernanceObject(govobj, *node.connman);
}
return govobj.GetHash().ToString();
}
static void gobject_vote_conf_help(const JSONRPCRequest& request)
{
RPCHelpMan{"gobject vote-conf",
"Vote on a governance object by masternode configured in dash.conf\n",
{
{"governance-hash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "hash of the governance object"},
{"vote", RPCArg::Type::STR, RPCArg::Optional::NO, "vote, possible values: [funding|valid|delete|endorsed]"},
{"vote-outcome", RPCArg::Type::STR, RPCArg::Optional::NO, "vote outcome, possible values: [yes|no|abstain]"},
},
RPCResults{},
RPCExamples{""}
}.Check(request);
}
static UniValue gobject_vote_conf(const JSONRPCRequest& request)
{
gobject_vote_conf_help(request);
uint256 hash;
hash = ParseHashV(request.params[0], "Object hash");
std::string strVoteSignal = request.params[1].get_str();
std::string strVoteOutcome = request.params[2].get_str();
vote_signal_enum_t eVoteSignal = CGovernanceVoting::ConvertVoteSignal(strVoteSignal);
if (eVoteSignal == VOTE_SIGNAL_NONE) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Invalid vote signal. Please using one of the following: "
"(funding|valid|delete|endorsed)");
}
vote_outcome_enum_t eVoteOutcome = CGovernanceVoting::ConvertVoteOutcome(strVoteOutcome);
if (eVoteOutcome == VOTE_OUTCOME_NONE) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid vote outcome. Please use one of the following: 'yes', 'no' or 'abstain'");
}
int govObjType;
{
LOCK(governance.cs);
CGovernanceObject *pGovObj = governance.FindGovernanceObject(hash);
if (!pGovObj) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Governance object not found");
}
govObjType = pGovObj->GetObjectType();
}
int nSuccessful = 0;
int nFailed = 0;
UniValue resultsObj(UniValue::VOBJ);
UniValue statusObj(UniValue::VOBJ);
UniValue returnObj(UniValue::VOBJ);
auto dmn = WITH_LOCK(activeMasternodeInfoCs, return deterministicMNManager->GetListAtChainTip().GetValidMNByCollateral(activeMasternodeInfo.outpoint));
if (!dmn) {
nFailed++;
statusObj.pushKV("result", "failed");
statusObj.pushKV("errorMessage", "Can't find masternode by collateral output");
resultsObj.pushKV("dash.conf", statusObj);
returnObj.pushKV("overall", strprintf("Voted successfully %d time(s) and failed %d time(s).", nSuccessful, nFailed));
returnObj.pushKV("detail", resultsObj);
return returnObj;
}
CGovernanceVote vote(dmn->collateralOutpoint, hash, eVoteSignal, eVoteOutcome);
bool signSuccess = false;
if (govObjType == GOVERNANCE_OBJECT_PROPOSAL && eVoteSignal == VOTE_SIGNAL_FUNDING) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Can't use vote-conf for proposals");
}
{
LOCK(activeMasternodeInfoCs);
if (activeMasternodeInfo.blsKeyOperator) {
signSuccess = vote.Sign(*activeMasternodeInfo.blsKeyOperator);
}
}
if (!signSuccess) {
nFailed++;
statusObj.pushKV("result", "failed");
statusObj.pushKV("errorMessage", "Failure to sign.");
resultsObj.pushKV("dash.conf", statusObj);
returnObj.pushKV("overall", strprintf("Voted successfully %d time(s) and failed %d time(s).", nSuccessful, nFailed));
returnObj.pushKV("detail", resultsObj);
return returnObj;
}
CGovernanceException exception;
const NodeContext& node = EnsureNodeContext(request.context);
if (governance.ProcessVoteAndRelay(vote, exception, *node.connman)) {
nSuccessful++;
statusObj.pushKV("result", "success");
} else {
nFailed++;
statusObj.pushKV("result", "failed");
statusObj.pushKV("errorMessage", exception.GetMessage());
}
resultsObj.pushKV("dash.conf", statusObj);
returnObj.pushKV("overall", strprintf("Voted successfully %d time(s) and failed %d time(s).", nSuccessful, nFailed));
returnObj.pushKV("detail", resultsObj);
return returnObj;
}
static UniValue VoteWithMasternodes(const JSONRPCRequest& request, const std::map<uint256, CKey>& keys,
const uint256& hash, vote_signal_enum_t eVoteSignal,
vote_outcome_enum_t eVoteOutcome)
{
const NodeContext& node = EnsureNodeContext(request.context);
{
LOCK(governance.cs);
CGovernanceObject *pGovObj = governance.FindGovernanceObject(hash);
if (!pGovObj) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Governance object not found");
}
}
int nSuccessful = 0;
int nFailed = 0;
auto mnList = deterministicMNManager->GetListAtChainTip();
UniValue resultsObj(UniValue::VOBJ);
for (const auto& p : keys) {
const auto& proTxHash = p.first;
const auto& key = p.second;
UniValue statusObj(UniValue::VOBJ);
auto dmn = mnList.GetValidMN(proTxHash);
if (!dmn) {
nFailed++;
statusObj.pushKV("result", "failed");
statusObj.pushKV("errorMessage", "Can't find masternode by proTxHash");
resultsObj.pushKV(proTxHash.ToString(), statusObj);
continue;
}
CGovernanceVote vote(dmn->collateralOutpoint, hash, eVoteSignal, eVoteOutcome);
if (!vote.Sign(key, key.GetPubKey().GetID())) {
nFailed++;
statusObj.pushKV("result", "failed");
statusObj.pushKV("errorMessage", "Failure to sign.");
resultsObj.pushKV(proTxHash.ToString(), statusObj);
continue;
}
CGovernanceException exception;
if (governance.ProcessVoteAndRelay(vote, exception, *node.connman)) {
nSuccessful++;
statusObj.pushKV("result", "success");
} else {
nFailed++;
statusObj.pushKV("result", "failed");
statusObj.pushKV("errorMessage", exception.GetMessage());
}
resultsObj.pushKV(proTxHash.ToString(), statusObj);
}
UniValue returnObj(UniValue::VOBJ);
returnObj.pushKV("overall", strprintf("Voted successfully %d time(s) and failed %d time(s).", nSuccessful, nFailed));
returnObj.pushKV("detail", resultsObj);
return returnObj;
}
#ifdef ENABLE_WALLET
static void gobject_vote_many_help(const JSONRPCRequest& request)
{
RPCHelpMan{"gobject vote-many",
"Vote on a governance object by all masternodes for which the voting key is present in the local wallet\n"
+ HelpRequiringPassphrase() + "\n",
{
{"governance-hash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "hash of the governance object"},
{"vote", RPCArg::Type::STR, RPCArg::Optional::NO, "vote, possible values: [funding|valid|delete|endorsed]"},
{"vote-outcome", RPCArg::Type::STR, RPCArg::Optional::NO, "vote outcome, possible values: [yes|no|abstain]"},
},
RPCResults{},
RPCExamples{""}
}.Check(request);
}
static UniValue gobject_vote_many(const JSONRPCRequest& request)
{
gobject_vote_many_help(request);
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) return NullUniValue;
CWallet* const pwallet = wallet.get();
uint256 hash = ParseHashV(request.params[0], "Object hash");
std::string strVoteSignal = request.params[1].get_str();
std::string strVoteOutcome = request.params[2].get_str();
vote_signal_enum_t eVoteSignal = CGovernanceVoting::ConvertVoteSignal(strVoteSignal);
if (eVoteSignal == VOTE_SIGNAL_NONE) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Invalid vote signal. Please using one of the following: "
"(funding|valid|delete|endorsed)");
}
vote_outcome_enum_t eVoteOutcome = CGovernanceVoting::ConvertVoteOutcome(strVoteOutcome);
if (eVoteOutcome == VOTE_OUTCOME_NONE) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid vote outcome. Please use one of the following: 'yes', 'no' or 'abstain'");
}
EnsureWalletIsUnlocked(pwallet);
std::map<uint256, CKey> votingKeys;
auto mnList = deterministicMNManager->GetListAtChainTip();
mnList.ForEachMN(true, [&](auto& dmn) {
CKey votingKey;
if (pwallet->GetKey(dmn.pdmnState->keyIDVoting, votingKey)) {
votingKeys.emplace(dmn.proTxHash, votingKey);
}
});
return VoteWithMasternodes(request, votingKeys, hash, eVoteSignal, eVoteOutcome);
}
static void gobject_vote_alias_help(const JSONRPCRequest& request)
{
RPCHelpMan{"gobject vote-alias",
"Vote on a governance object by masternode's voting key (if present in local wallet)\n"
+ HelpRequiringPassphrase() + "\n",
{
{"governance-hash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "hash of the governance object"},
{"vote", RPCArg::Type::STR, RPCArg::Optional::NO, "vote, possible values: [funding|valid|delete|endorsed]"},
{"vote-outcome", RPCArg::Type::STR, RPCArg::Optional::NO, "vote outcome, possible values: [yes|no|abstain]"},
{"protx-hash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "masternode's proTxHash"},
},
RPCResults{},
RPCExamples{""}
}.Check(request);
}
static UniValue gobject_vote_alias(const JSONRPCRequest& request)
{
gobject_vote_alias_help(request);
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) return NullUniValue;
CWallet* const pwallet = wallet.get();
uint256 hash = ParseHashV(request.params[0], "Object hash");
std::string strVoteSignal = request.params[1].get_str();
std::string strVoteOutcome = request.params[2].get_str();
vote_signal_enum_t eVoteSignal = CGovernanceVoting::ConvertVoteSignal(strVoteSignal);
if (eVoteSignal == VOTE_SIGNAL_NONE) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Invalid vote signal. Please using one of the following: "
"(funding|valid|delete|endorsed)");
}
vote_outcome_enum_t eVoteOutcome = CGovernanceVoting::ConvertVoteOutcome(strVoteOutcome);
if (eVoteOutcome == VOTE_OUTCOME_NONE) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid vote outcome. Please use one of the following: 'yes', 'no' or 'abstain'");
}
EnsureWalletIsUnlocked(pwallet);
uint256 proTxHash = ParseHashV(request.params[3], "protx-hash");
auto dmn = deterministicMNManager->GetListAtChainTip().GetValidMN(proTxHash);
if (!dmn) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid or unknown proTxHash");
}
CKey votingKey;
if (!pwallet->GetKey(dmn->pdmnState->keyIDVoting, votingKey)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Private key for voting address %s not known by wallet", EncodeDestination(dmn->pdmnState->keyIDVoting)));
}
std::map<uint256, CKey> votingKeys;
votingKeys.emplace(proTxHash, votingKey);
return VoteWithMasternodes(request, votingKeys, hash, eVoteSignal, eVoteOutcome);
}
#endif
static UniValue ListObjects(const std::string& strCachedSignal, const std::string& strType, int nStartTime)
{
UniValue objResult(UniValue::VOBJ);
// GET MATCHING GOVERNANCE OBJECTS
if (g_txindex) {
g_txindex->BlockUntilSyncedToCurrentChain();
}
LOCK2(cs_main, governance.cs);
std::vector<CGovernanceObject> objs = governance.GetAllNewerThan(nStartTime);
governance.UpdateLastDiffTime(GetTime());
// CREATE RESULTS FOR USER
for (const auto& govObj : objs) {
if (strCachedSignal == "valid" && !govObj.IsSetCachedValid()) continue;
if (strCachedSignal == "funding" && !govObj.IsSetCachedFunding()) continue;
if (strCachedSignal == "delete" && !govObj.IsSetCachedDelete()) continue;
if (strCachedSignal == "endorsed" && !govObj.IsSetCachedEndorsed()) continue;
if (strType == "proposals" && govObj.GetObjectType() != GOVERNANCE_OBJECT_PROPOSAL) continue;
if (strType == "triggers" && govObj.GetObjectType() != GOVERNANCE_OBJECT_TRIGGER) continue;
UniValue bObj(UniValue::VOBJ);
bObj.pushKV("DataHex", govObj.GetDataAsHexString());
bObj.pushKV("DataString", govObj.GetDataAsPlainString());
bObj.pushKV("Hash", govObj.GetHash().ToString());
bObj.pushKV("CollateralHash", govObj.GetCollateralHash().ToString());
bObj.pushKV("ObjectType", govObj.GetObjectType());
bObj.pushKV("CreationTime", govObj.GetCreationTime());
const COutPoint& masternodeOutpoint = govObj.GetMasternodeOutpoint();
if (masternodeOutpoint != COutPoint()) {
bObj.pushKV("SigningMasternode", masternodeOutpoint.ToStringShort());
}
// REPORT STATUS FOR FUNDING VOTES SPECIFICALLY
bObj.pushKV("AbsoluteYesCount", govObj.GetAbsoluteYesCount(VOTE_SIGNAL_FUNDING));
bObj.pushKV("YesCount", govObj.GetYesCount(VOTE_SIGNAL_FUNDING));
bObj.pushKV("NoCount", govObj.GetNoCount(VOTE_SIGNAL_FUNDING));
bObj.pushKV("AbstainCount", govObj.GetAbstainCount(VOTE_SIGNAL_FUNDING));
// REPORT VALIDITY AND CACHING FLAGS FOR VARIOUS SETTINGS
std::string strError = "";
bObj.pushKV("fBlockchainValidity", govObj.IsValidLocally(strError, false));
bObj.pushKV("IsValidReason", strError.c_str());
bObj.pushKV("fCachedValid", govObj.IsSetCachedValid());
bObj.pushKV("fCachedFunding", govObj.IsSetCachedFunding());
bObj.pushKV("fCachedDelete", govObj.IsSetCachedDelete());
bObj.pushKV("fCachedEndorsed", govObj.IsSetCachedEndorsed());
objResult.pushKV(govObj.GetHash().ToString(), bObj);
}
return objResult;
}
static void gobject_list_help(const JSONRPCRequest& request)
{
RPCHelpMan{"gobject list",
"List governance objects (can be filtered by signal and/or object type)\n",
{
{"signal", RPCArg::Type::STR, /* default */ "valid", "cached signal, possible values: [valid|funding|delete|endorsed|all]"},
{"type", RPCArg::Type::STR, /* default */ "all", "object type, possible values: [proposals|triggers|all]"},
},
RPCResults{},
RPCExamples{""}
}.Check(request);
}
static UniValue gobject_list(const JSONRPCRequest& request)
{
gobject_list_help(request);
std::string strCachedSignal = "valid";
if (!request.params[0].isNull()) {
strCachedSignal = request.params[0].get_str();
}
if (strCachedSignal != "valid" && strCachedSignal != "funding" && strCachedSignal != "delete" && strCachedSignal != "endorsed" && strCachedSignal != "all")
return "Invalid signal, should be 'valid', 'funding', 'delete', 'endorsed' or 'all'";
std::string strType = "all";
if (!request.params[1].isNull()) {
strType = request.params[1].get_str();
}
if (strType != "proposals" && strType != "triggers" && strType != "all")
return "Invalid type, should be 'proposals', 'triggers' or 'all'";
return ListObjects(strCachedSignal, strType, 0);
}
static void gobject_diff_help(const JSONRPCRequest& request)
{
RPCHelpMan{"gobject diff",
"List differences since last diff or list\n",
{
{"signal", RPCArg::Type::STR, /* default */ "valid", "cached signal, possible values: [valid|funding|delete|endorsed|all]"},
{"type", RPCArg::Type::STR, /* default */ "all", "object type, possible values: [proposals|triggers|all]"},
},
RPCResults{},
RPCExamples{""}
}.Check(request);
}
static UniValue gobject_diff(const JSONRPCRequest& request)
{
gobject_diff_help(request);
std::string strCachedSignal = "valid";
if (!request.params[0].isNull()) {
strCachedSignal = request.params[0].get_str();
}
if (strCachedSignal != "valid" && strCachedSignal != "funding" && strCachedSignal != "delete" && strCachedSignal != "endorsed" && strCachedSignal != "all")
return "Invalid signal, should be 'valid', 'funding', 'delete', 'endorsed' or 'all'";
std::string strType = "all";
if (!request.params[1].isNull()) {
strType = request.params[1].get_str();
}
if (strType != "proposals" && strType != "triggers" && strType != "all")
return "Invalid type, should be 'proposals', 'triggers' or 'all'";
return ListObjects(strCachedSignal, strType, governance.GetLastDiffTime());
}
static void gobject_get_help(const JSONRPCRequest& request)
{
RPCHelpMan{"gobject get",
"Get governance object by hash\n",
{
{"governance-hash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "object id"},
},
RPCResults{},
RPCExamples{""}
}.Check(request);
}
static UniValue gobject_get(const JSONRPCRequest& request)
{
gobject_get_help(request);
// COLLECT VARIABLES FROM OUR USER
uint256 hash = ParseHashV(request.params[0], "GovObj hash");
if (g_txindex) {
g_txindex->BlockUntilSyncedToCurrentChain();
}
// FIND THE GOVERNANCE OBJECT THE USER IS LOOKING FOR
LOCK2(cs_main, governance.cs);
CGovernanceObject* pGovObj = governance.FindGovernanceObject(hash);
if (pGovObj == nullptr) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Unknown governance object");
}
// REPORT BASIC OBJECT STATS
UniValue objResult(UniValue::VOBJ);
objResult.pushKV("DataHex", pGovObj->GetDataAsHexString());
objResult.pushKV("DataString", pGovObj->GetDataAsPlainString());
objResult.pushKV("Hash", pGovObj->GetHash().ToString());
objResult.pushKV("CollateralHash", pGovObj->GetCollateralHash().ToString());
objResult.pushKV("ObjectType", pGovObj->GetObjectType());
objResult.pushKV("CreationTime", pGovObj->GetCreationTime());
const COutPoint& masternodeOutpoint = pGovObj->GetMasternodeOutpoint();
if (masternodeOutpoint != COutPoint()) {
objResult.pushKV("SigningMasternode", masternodeOutpoint.ToStringShort());
}
// SHOW (MUCH MORE) INFORMATION ABOUT VOTES FOR GOVERNANCE OBJECT (THAN LIST/DIFF ABOVE)
// -- FUNDING VOTING RESULTS
UniValue objFundingResult(UniValue::VOBJ);
objFundingResult.pushKV("AbsoluteYesCount", pGovObj->GetAbsoluteYesCount(VOTE_SIGNAL_FUNDING));
objFundingResult.pushKV("YesCount", pGovObj->GetYesCount(VOTE_SIGNAL_FUNDING));
objFundingResult.pushKV("NoCount", pGovObj->GetNoCount(VOTE_SIGNAL_FUNDING));
objFundingResult.pushKV("AbstainCount", pGovObj->GetAbstainCount(VOTE_SIGNAL_FUNDING));
objResult.pushKV("FundingResult", objFundingResult);
// -- VALIDITY VOTING RESULTS
UniValue objValid(UniValue::VOBJ);
objValid.pushKV("AbsoluteYesCount", pGovObj->GetAbsoluteYesCount(VOTE_SIGNAL_VALID));
objValid.pushKV("YesCount", pGovObj->GetYesCount(VOTE_SIGNAL_VALID));
objValid.pushKV("NoCount", pGovObj->GetNoCount(VOTE_SIGNAL_VALID));
objValid.pushKV("AbstainCount", pGovObj->GetAbstainCount(VOTE_SIGNAL_VALID));
objResult.pushKV("ValidResult", objValid);
// -- DELETION CRITERION VOTING RESULTS
UniValue objDelete(UniValue::VOBJ);
objDelete.pushKV("AbsoluteYesCount", pGovObj->GetAbsoluteYesCount(VOTE_SIGNAL_DELETE));
objDelete.pushKV("YesCount", pGovObj->GetYesCount(VOTE_SIGNAL_DELETE));
objDelete.pushKV("NoCount", pGovObj->GetNoCount(VOTE_SIGNAL_DELETE));
objDelete.pushKV("AbstainCount", pGovObj->GetAbstainCount(VOTE_SIGNAL_DELETE));
objResult.pushKV("DeleteResult", objDelete);
// -- ENDORSED VIA MASTERNODE-ELECTED BOARD
UniValue objEndorsed(UniValue::VOBJ);
objEndorsed.pushKV("AbsoluteYesCount", pGovObj->GetAbsoluteYesCount(VOTE_SIGNAL_ENDORSED));
objEndorsed.pushKV("YesCount", pGovObj->GetYesCount(VOTE_SIGNAL_ENDORSED));
objEndorsed.pushKV("NoCount", pGovObj->GetNoCount(VOTE_SIGNAL_ENDORSED));
objEndorsed.pushKV("AbstainCount", pGovObj->GetAbstainCount(VOTE_SIGNAL_ENDORSED));
objResult.pushKV("EndorsedResult", objEndorsed);
// --
std::string strError = "";
objResult.pushKV("fLocalValidity", pGovObj->IsValidLocally(strError, false));
objResult.pushKV("IsValidReason", strError.c_str());
objResult.pushKV("fCachedValid", pGovObj->IsSetCachedValid());
objResult.pushKV("fCachedFunding", pGovObj->IsSetCachedFunding());
objResult.pushKV("fCachedDelete", pGovObj->IsSetCachedDelete());
objResult.pushKV("fCachedEndorsed", pGovObj->IsSetCachedEndorsed());
return objResult;
}
static void gobject_getcurrentvotes_help(const JSONRPCRequest& request)
{
RPCHelpMan{"gobject getcurrentvotes",
"Get only current (tallying) votes for a governance object hash (does not include old votes)\n",
{
{"governance-hash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "object id"},
{"txid", RPCArg::Type::STR_HEX, /* default */ "", "masternode collateral txid"},
{"vout", RPCArg::Type::STR, /* default */ "", "masternode collateral output index, required if <txid> presents"},
},
RPCResults{},
RPCExamples{""}
}.Check(request);
}
static UniValue gobject_getcurrentvotes(const JSONRPCRequest& request)
{
gobject_getcurrentvotes_help(request);
// COLLECT PARAMETERS FROM USER
uint256 hash = ParseHashV(request.params[0], "Governance hash");
COutPoint mnCollateralOutpoint;
if (!request.params[1].isNull() && !request.params[2].isNull()) {
uint256 txid = ParseHashV(request.params[1], "Masternode Collateral hash");
std::string strVout = request.params[2].get_str();
mnCollateralOutpoint = COutPoint(txid, (uint32_t)atoi(strVout));
}
// FIND OBJECT USER IS LOOKING FOR
LOCK(governance.cs);
CGovernanceObject* pGovObj = governance.FindGovernanceObject(hash);
if (pGovObj == nullptr) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Unknown governance-hash");
}
// REPORT RESULTS TO USER
UniValue bResult(UniValue::VOBJ);
// GET MATCHING VOTES BY HASH, THEN SHOW USERS VOTE INFORMATION
std::vector<CGovernanceVote> vecVotes = governance.GetCurrentVotes(hash, mnCollateralOutpoint);
for (const auto& vote : vecVotes) {
bResult.pushKV(vote.GetHash().ToString(), vote.ToString());
}
return bResult;
}
[[ noreturn ]] static void gobject_help()
{
RPCHelpMan{"gobject",
"Set of commands to manage governance objects.\n"
"\nAvailable commands:\n"
" check - Validate governance object data (proposal only)\n"
#ifdef ENABLE_WALLET
" prepare - Prepare governance object by signing and creating tx\n"
" list-prepared - Returns a list of governance objects prepared by this wallet with \"gobject prepare\"\n"
#endif // ENABLE_WALLET
" submit - Submit governance object to network\n"
" deserialize - Deserialize governance object from hex string to JSON\n"
" count - Count governance objects and votes (additional param: 'json' or 'all', default: 'json')\n"
" get - Get governance object by hash\n"
" getcurrentvotes - Get only current (tallying) votes for a governance object hash (does not include old votes)\n"
" list - List governance objects (can be filtered by signal and/or object type)\n"
" diff - List differences since last diff\n"
#ifdef ENABLE_WALLET
" vote-alias - Vote on a governance object by masternode proTxHash\n"
#endif // ENABLE_WALLET
" vote-conf - Vote on a governance object by masternode configured in dash.conf\n"
#ifdef ENABLE_WALLET
" vote-many - Vote on a governance object by all masternodes for which the voting key is in the wallet\n"
#endif // ENABLE_WALLET
,
{},
RPCResults{},
RPCExamples{""}
}.Throw();
}
static UniValue gobject(const JSONRPCRequest& request)
{
const JSONRPCRequest new_request{request.strMethod == "gobject" ? request.squashed() : request};
const std::string command{new_request.strMethod};
if (command == "gobjectcount") {
return gobject_count(new_request);
} else if (command == "gobjectdeserialize") {
// DEBUG : TEST DESERIALIZATION OF GOVERNANCE META DATA
return gobject_deserialize(new_request);
} else if (command == "gobjectcheck") {
// VALIDATE A GOVERNANCE OBJECT PRIOR TO SUBMISSION
return gobject_check(new_request);
#ifdef ENABLE_WALLET
} else if (command == "gobjectprepare") {
// PREPARE THE GOVERNANCE OBJECT BY CREATING A COLLATERAL TRANSACTION
return gobject_prepare(new_request);
} else if (command == "gobjectlist-prepared") {
return gobject_list_prepared(new_request);
#endif // ENABLE_WALLET
} else if (command == "gobjectsubmit") {
// AFTER COLLATERAL TRANSACTION HAS MATURED USER CAN SUBMIT GOVERNANCE OBJECT TO PROPAGATE NETWORK
/*
------ Example Governance Item ------
gobject submit 6e622bb41bad1fb18e7f23ae96770aeb33129e18bd9efe790522488e580a0a03 0 1 1464292854 "beer-reimbursement" 5b5b22636f6e7472616374222c207b2270726f6a6563745f6e616d65223a20225c22626565722d7265696d62757273656d656e745c22222c20227061796d656e745f61646472657373223a20225c225879324c4b4a4a64655178657948726e34744744514238626a6876464564615576375c22222c2022656e645f64617465223a202231343936333030343030222c20226465736372697074696f6e5f75726c223a20225c227777772e646173687768616c652e6f72672f702f626565722d7265696d62757273656d656e745c22222c2022636f6e74726163745f75726c223a20225c22626565722d7265696d62757273656d656e742e636f6d2f3030312e7064665c22222c20227061796d656e745f616d6f756e74223a20223233342e323334323232222c2022676f7665726e616e63655f6f626a6563745f6964223a2037342c202273746172745f64617465223a202231343833323534303030227d5d5d1
*/
return gobject_submit(new_request);
} else if (command == "gobjectvote-conf") {
return gobject_vote_conf(new_request);
#ifdef ENABLE_WALLET
} else if (command == "gobjectvote-many") {
return gobject_vote_many(new_request);
} else if (command == "gobjectvote-alias") {
return gobject_vote_alias(new_request);
#endif
} else if (command == "gobjectlist") {
// USERS CAN QUERY THE SYSTEM FOR A LIST OF VARIOUS GOVERNANCE ITEMS
return gobject_list(new_request);
} else if (command == "gobjectdiff") {
return gobject_diff(new_request);
} else if (command == "gobjectget") {
// GET SPECIFIC GOVERNANCE ENTRY
return gobject_get(new_request);
} else if (command == "gobjectgetcurrentvotes") {
// GET VOTES FOR SPECIFIC GOVERNANCE OBJECT
return gobject_getcurrentvotes(new_request);
} else {
gobject_help();
}
}
static UniValue voteraw(const JSONRPCRequest& request)
{
RPCHelpMan{"voteraw",
"Compile and relay a governance vote with provided external signature instead of signing vote internally\n",
{
{"mn-collateral-tx-hash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, ""},
{"mn-collateral-tx-index", RPCArg::Type::NUM, RPCArg::Optional::NO, ""},
{"governance-hash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, ""},
{"vote-signal", RPCArg::Type::STR, RPCArg::Optional::NO, ""},
{"vote-outcome", RPCArg::Type::STR, RPCArg::Optional::NO, "yes|no|abstain"},
{"time", RPCArg::Type::NUM, RPCArg::Optional::NO, ""},
{"vote-sig", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, ""},
},
RPCResults{},
RPCExamples{""}
}.Check(request);
uint256 hashMnCollateralTx = ParseHashV(request.params[0], "mn collateral tx hash");
int nMnCollateralTxIndex = request.params[1].get_int();
COutPoint outpoint = COutPoint(hashMnCollateralTx, nMnCollateralTxIndex);
uint256 hashGovObj = ParseHashV(request.params[2], "Governance hash");
std::string strVoteSignal = request.params[3].get_str();
std::string strVoteOutcome = request.params[4].get_str();
vote_signal_enum_t eVoteSignal = CGovernanceVoting::ConvertVoteSignal(strVoteSignal);
if (eVoteSignal == VOTE_SIGNAL_NONE) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Invalid vote signal. Please using one of the following: "
"(funding|valid|delete|endorsed)");
}
vote_outcome_enum_t eVoteOutcome = CGovernanceVoting::ConvertVoteOutcome(strVoteOutcome);
if (eVoteOutcome == VOTE_OUTCOME_NONE) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid vote outcome. Please use one of the following: 'yes', 'no' or 'abstain'");
}
int govObjType;
{
LOCK(governance.cs);
CGovernanceObject *pGovObj = governance.FindGovernanceObject(hashGovObj);
if (!pGovObj) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Governance object not found");
}
govObjType = pGovObj->GetObjectType();
}
int64_t nTime = request.params[5].get_int64();
std::string strSig = request.params[6].get_str();
bool fInvalid = false;
std::vector<unsigned char> vchSig = DecodeBase64(strSig.c_str(), &fInvalid);
if (fInvalid) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Malformed base64 encoding");
}
auto dmn = deterministicMNManager->GetListAtChainTip().GetValidMNByCollateral(outpoint);
if (!dmn) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "Failure to find masternode in list : " + outpoint.ToStringShort());
}
CGovernanceVote vote(outpoint, hashGovObj, eVoteSignal, eVoteOutcome);
vote.SetTime(nTime);
vote.SetSignature(vchSig);
bool onlyVotingKeyAllowed = govObjType == GOVERNANCE_OBJECT_PROPOSAL && vote.GetSignal() == VOTE_SIGNAL_FUNDING;
if (!vote.IsValid(onlyVotingKeyAllowed)) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "Failure to verify vote.");
}
CGovernanceException exception;
const NodeContext& node = EnsureNodeContext(request.context);
if (governance.ProcessVoteAndRelay(vote, exception, *node.connman)) {
return "Voted successfully";
} else {
throw JSONRPCError(RPC_INTERNAL_ERROR, "Error voting : " + exception.GetMessage());
}
}
static UniValue getgovernanceinfo(const JSONRPCRequest& request)
{
RPCHelpMan{"getgovernanceinfo",
"Returns an object containing governance parameters.\n",
{},
RPCResult{
RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::NUM, "governanceminquorum", "the absolute minimum number of votes needed to trigger a governance action"},
{RPCResult::Type::NUM, "proposalfee", "the collateral transaction fee which must be paid to create a proposal in " + CURRENCY_UNIT + ""},
{RPCResult::Type::NUM, "superblockcycle", "the number of blocks between superblocks"},
{RPCResult::Type::NUM, "lastsuperblock", "the block number of the last superblock"},
{RPCResult::Type::NUM, "nextsuperblock", "the block number of the next superblock"},
}},
RPCExamples{
HelpExampleCli("getgovernanceinfo", "")
+ HelpExampleRpc("getgovernanceinfo", "")
},
}.Check(request);
int nLastSuperblock = 0, nNextSuperblock = 0;
const auto* pindex = WITH_LOCK(cs_main, return ::ChainActive().Tip());
int nBlockHeight = pindex->nHeight;
CSuperblock::GetNearestSuperblocksHeights(nBlockHeight, nLastSuperblock, nNextSuperblock);
UniValue obj(UniValue::VOBJ);
obj.pushKV("governanceminquorum", Params().GetConsensus().nGovernanceMinQuorum);
bool fork_active = VersionBitsState(pindex, Params().GetConsensus(), Consensus::DEPLOYMENT_DIP0024, versionbitscache) == ThresholdState::ACTIVE;
obj.pushKV("proposalfee", ValueFromAmount(fork_active ? GOVERNANCE_PROPOSAL_FEE_TX : GOVERNANCE_PROPOSAL_FEE_TX_OLD));
obj.pushKV("superblockcycle", Params().GetConsensus().nSuperblockCycle);
obj.pushKV("lastsuperblock", nLastSuperblock);
obj.pushKV("nextsuperblock", nNextSuperblock);
return obj;
}
static UniValue getsuperblockbudget(const JSONRPCRequest& request)
{
RPCHelpMan{"getsuperblockbudget",
"\nReturns the absolute maximum sum of superblock payments allowed.\n",
{
{"index", RPCArg::Type::NUM, RPCArg::Optional::NO, "The block index"},
},
RPCResult{
RPCResult::Type::NUM, "n", "The absolute maximum sum of superblock payments allowed, in " + CURRENCY_UNIT
},
RPCExamples{
HelpExampleCli("getsuperblockbudget", "1000")
+ HelpExampleRpc("getsuperblockbudget", "1000")
},
}.Check(request);
int nBlockHeight = request.params[0].get_int();
if (nBlockHeight < 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Block height out of range");
}
return ValueFromAmount(CSuperblock::GetPaymentsLimit(nBlockHeight));
}
// clang-format off
static const CRPCCommand commands[] =
{ // category name actor (function) argNames
// --------------------- ------------------------ ----------------------- ----------
/* Dash features */
{ "dash", "getgovernanceinfo", &getgovernanceinfo, {} },
{ "dash", "getsuperblockbudget", &getsuperblockbudget, {"index"} },
{ "dash", "gobject", &gobject, {} },
{ "dash", "voteraw", &voteraw, {"tx_hash","tx_index","gov_hash","signal","outcome","time","sig"} },
};
// clang-format on
void RegisterGovernanceRPCCommands(CRPCTable &t)
{
for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++)
t.appendCommand(commands[vcidx].name, &commands[vcidx]);
}