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