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 : }
|