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/assetlocktx.h>
8 : #include <evo/chainhelper.h>
9 : #include <evo/deterministicmns.h>
10 : #include <governance/superblock.h>
11 : #include <masternode/payments.h>
12 : #include <rpc/evo_util.h>
13 :
14 : #include <chainparams.h>
15 : #include <core_io.h>
16 : #include <index/txindex.h>
17 : #include <net.h>
18 : #include <netbase.h>
19 : #include <node/blockstorage.h>
20 : #include <node/context.h>
21 : #include <rpc/server.h>
22 : #include <rpc/server_util.h>
23 : #include <rpc/util.h>
24 : #include <util/check.h>
25 : #include <util/strencodings.h>
26 : #include <validation.h>
27 : #include <wallet/coincontrol.h>
28 : #include <wallet/rpc/util.h>
29 :
30 : #ifdef ENABLE_WALLET
31 : #include <wallet/spend.h>
32 : #include <wallet/wallet.h>
33 : #endif // ENABLE_WALLET
34 :
35 : #include <univalue.h>
36 :
37 : using node::GetTransaction;
38 : using node::NodeContext;
39 : using node::ReadBlockFromDisk;
40 : #ifdef ENABLE_WALLET
41 : using wallet::CCoinControl;
42 : using wallet::CoinType;
43 : using wallet::CWallet;
44 : using wallet::GetWalletForJSONRPCRequest;
45 : #endif // ENABLE_WALLET
46 :
47 6138 : static RPCHelpMan masternode_connect()
48 : {
49 12276 : return RPCHelpMan{"masternode connect",
50 6138 : "Connect to given masternode\n",
51 18414 : {
52 6138 : {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The address of the masternode to connect"},
53 6138 : {"v2transport", RPCArg::Type::BOOL, RPCArg::DefaultHint{"set by -v2transport"}, "Attempt to connect using BIP324 v2 transport protocol"},
54 : },
55 6138 : RPCResult{
56 6138 : RPCResult::Type::STR, "status", "Returns 'successfully connected' if successful"
57 : },
58 6138 : RPCExamples{""},
59 6138 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
60 : {
61 0 : std::string strAddress = request.params[0].get_str();
62 :
63 0 : std::optional<CService> addr{Lookup(strAddress, 0, false)};
64 0 : if (!addr.has_value()) {
65 0 : throw JSONRPCError(RPC_INTERNAL_ERROR, strprintf("Incorrect masternode address %s", strAddress));
66 : }
67 :
68 0 : const NodeContext& node = EnsureAnyNodeContext(request.context);
69 0 : CConnman& connman = EnsureConnman(node);
70 :
71 0 : bool node_v2transport = connman.GetLocalServices() & NODE_P2P_V2;
72 0 : bool use_v2transport = request.params[1].isNull() ? node_v2transport : ParseBoolV(request.params[1], "v2transport");
73 :
74 0 : if (use_v2transport && !node_v2transport) {
75 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Error: Adding v2transport connections requires -v2transport init flag to be set.");
76 : }
77 :
78 0 : connman.OpenMasternodeConnection(CAddress(addr.value(), NODE_NETWORK), use_v2transport);
79 0 : if (!connman.IsConnected(CAddress(addr.value(), NODE_NETWORK), CConnman::AllNodes)) {
80 0 : throw JSONRPCError(RPC_INTERNAL_ERROR, strprintf("Couldn't connect to masternode %s", strAddress));
81 : }
82 :
83 0 : return "successfully connected";
84 0 : },
85 : };
86 0 : }
87 :
88 6148 : static RPCHelpMan masternode_count()
89 : {
90 12296 : return RPCHelpMan{"masternode count",
91 6148 : "Get information about number of masternodes.\n",
92 6148 : {},
93 6148 : RPCResult{
94 6148 : RPCResult::Type::OBJ, "", "",
95 24592 : {
96 6148 : {RPCResult::Type::NUM, "total", "Total number of Masternodes"},
97 6148 : {RPCResult::Type::NUM, "enabled", "Number of enabled Masternodes"},
98 12296 : {RPCResult::Type::OBJ, "details", "Breakdown of masternodes by type",
99 12296 : {{RPCResult::Type::OBJ, "", "",
100 18444 : {
101 12296 : {RPCResult::Type::OBJ, "regular", "Details for regular masternodes",
102 18444 : {
103 6148 : {RPCResult::Type::NUM, "total", "Total number of regular Masternodes"},
104 6148 : {RPCResult::Type::NUM, "enabled", "Number of enabled regular Masternodes"}
105 : }},
106 12296 : {RPCResult::Type::OBJ, "evo", "Details for Evo nodes",
107 18444 : {
108 6148 : {RPCResult::Type::NUM, "total", "Total number of Evo nodes"},
109 6148 : {RPCResult::Type::NUM, "enabled", "Number of enabled Evo nodes"}
110 : }},
111 : }},
112 : }}
113 : }
114 : },
115 6148 : RPCExamples{""},
116 6158 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
117 : {
118 10 : const NodeContext& node = EnsureAnyNodeContext(request.context);
119 :
120 10 : const auto counts{node.dmnman->GetListAtChainTip().GetCounts()};
121 :
122 10 : UniValue obj(UniValue::VOBJ);
123 10 : obj.pushKV("total", counts.total());
124 10 : obj.pushKV("enabled", counts.enabled());
125 :
126 10 : UniValue evoObj(UniValue::VOBJ);
127 10 : evoObj.pushKV("total", counts.m_total_evo);
128 10 : evoObj.pushKV("enabled", counts.m_valid_evo);
129 :
130 10 : UniValue regularObj(UniValue::VOBJ);
131 10 : regularObj.pushKV("total", counts.m_total_mn);
132 10 : regularObj.pushKV("enabled", counts.m_valid_mn);
133 :
134 10 : UniValue detailedObj(UniValue::VOBJ);
135 10 : detailedObj.pushKV("regular", regularObj);
136 10 : detailedObj.pushKV("evo", evoObj);
137 10 : obj.pushKV("detailed", detailedObj);
138 :
139 10 : return obj;
140 10 : },
141 : };
142 0 : }
143 :
144 : #ifdef ENABLE_WALLET
145 2874 : static RPCHelpMan masternode_outputs()
146 : {
147 5748 : return RPCHelpMan{"masternode outputs",
148 2874 : "Print masternode compatible outputs\n",
149 2874 : {},
150 2874 : RPCResult {
151 2874 : RPCResult::Type::ARR, "", "A list of outpoints that can be/are used as masternode collaterals",
152 5748 : {
153 2874 : {RPCResult::Type::STR, "", "A (potential) masternode collateral"},
154 : }},
155 2874 : RPCExamples{""},
156 2876 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
157 : {
158 2 : const std::shared_ptr<const CWallet> wallet = GetWalletForJSONRPCRequest(request);
159 2 : if (!wallet) return UniValue::VNULL;
160 :
161 : // Find possible candidates
162 2 : CCoinControl coin_control(CoinType::ONLY_MASTERNODE_COLLATERAL);
163 :
164 2 : UniValue outputsArr(UniValue::VARR);
165 14 : for (const auto& out : WITH_LOCK(wallet->cs_wallet, return AvailableCoinsListUnspent(*wallet, &coin_control).all())) {
166 10 : outputsArr.push_back(out.outpoint.ToStringShort());
167 : }
168 :
169 2 : return outputsArr;
170 2 : },
171 : };
172 0 : }
173 :
174 : #endif // ENABLE_WALLET
175 :
176 6150 : static RPCHelpMan masternode_status()
177 : {
178 12300 : return RPCHelpMan{"masternode status",
179 6150 : "Print masternode status information\n",
180 6150 : {},
181 6150 : RPCResult{
182 6150 : RPCResult::Type::OBJ, "", "",
183 61500 : {
184 6150 : GetRpcResult("outpoint"),
185 6150 : GetRpcResult("service", /*optional=*/true),
186 6150 : GetRpcResult("proTxHash", /*optional=*/true),
187 6150 : GetRpcResult("type_str", /*optional=*/true, /*override_name=*/"type"),
188 6150 : GetRpcResult("collateralHash", /*optional=*/true),
189 6150 : GetRpcResult("collateralIndex", /*optional=*/true),
190 6150 : CDeterministicMNState::GetJsonHelp(/*key=*/"dmnState", /*optional=*/true),
191 6150 : {RPCResult::Type::STR, "state", "Masternode state (human-readable string)"},
192 6150 : {RPCResult::Type::STR, "status", "Masternode status (human-readable string, based on current state)"},
193 : }
194 : },
195 6150 : RPCExamples{""},
196 6162 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
197 : {
198 12 : const NodeContext& node = EnsureAnyNodeContext(request.context);
199 :
200 12 : if (!node.active_ctx) {
201 0 : throw JSONRPCError(RPC_INTERNAL_ERROR, "This node does not run an active masternode.");
202 : }
203 12 : const auto& mn_activeman{*node.active_ctx->nodeman};
204 :
205 12 : UniValue mnObj(UniValue::VOBJ);
206 : // keep compatibility with legacy status for now (TODO: get deprecated/removed later)
207 12 : mnObj.pushKV("outpoint", mn_activeman.GetOutPoint().ToStringShort());
208 12 : mnObj.pushKV("service", mn_activeman.GetService().ToStringAddrPort());
209 12 : auto dmn = CHECK_NONFATAL(node.dmnman)->GetListAtChainTip().GetMN(mn_activeman.GetProTxHash());
210 12 : if (dmn) {
211 12 : mnObj.pushKV("proTxHash", dmn->proTxHash.ToString());
212 12 : mnObj.pushKV("type", std::string(GetMnType(dmn->nType).description));
213 12 : mnObj.pushKV("collateralHash", dmn->collateralOutpoint.hash.ToString());
214 12 : mnObj.pushKV("collateralIndex", dmn->collateralOutpoint.n);
215 12 : mnObj.pushKV("dmnState", dmn->pdmnState->ToJson(dmn->nType));
216 12 : }
217 12 : mnObj.pushKV("state", mn_activeman.GetStateString());
218 12 : mnObj.pushKV("status", mn_activeman.GetStatus());
219 :
220 12 : return mnObj;
221 12 : },
222 : };
223 0 : }
224 :
225 122 : static std::string GetRequiredPaymentsString(governance::SuperblockManager& superblocks,
226 : const CDeterministicMNList& tip_mn_list, int nBlockHeight,
227 : const CDeterministicMNCPtr& payee)
228 : {
229 122 : std::string strPayments = "Unknown";
230 122 : if (payee) {
231 122 : CTxDestination dest;
232 122 : if (!ExtractDestination(payee->pdmnState->scriptPayout, dest)) {
233 0 : NONFATAL_UNREACHABLE();
234 : }
235 122 : strPayments = EncodeDestination(dest);
236 214 : if (payee->nOperatorReward != 0 && payee->pdmnState->scriptOperatorPayout != CScript()) {
237 90 : if (!ExtractDestination(payee->pdmnState->scriptOperatorPayout, dest)) {
238 0 : NONFATAL_UNREACHABLE();
239 : }
240 90 : strPayments += ", " + EncodeDestination(dest);
241 90 : }
242 122 : }
243 122 : if (superblocks.IsSuperblockTriggered(tip_mn_list, nBlockHeight)) {
244 0 : std::vector<CTxOut> voutSuperblock;
245 0 : if (!superblocks.GetSuperblockPayments(tip_mn_list, nBlockHeight, voutSuperblock)) {
246 0 : return strPayments + ", error";
247 : }
248 0 : std::string strSBPayees = "Unknown";
249 0 : for (const auto& txout : voutSuperblock) {
250 0 : CTxDestination dest;
251 0 : ExtractDestination(txout.scriptPubKey, dest);
252 0 : if (strSBPayees != "Unknown") {
253 0 : strSBPayees += ", " + EncodeDestination(dest);
254 0 : } else {
255 0 : strSBPayees = EncodeDestination(dest);
256 : }
257 : }
258 0 : strPayments += ", " + strSBPayees;
259 0 : }
260 122 : return strPayments;
261 122 : }
262 :
263 6147 : static RPCHelpMan masternode_winners()
264 : {
265 12294 : return RPCHelpMan{"masternode winners",
266 6147 : "Print list of masternode winners\n",
267 18441 : {
268 6147 : {"count", RPCArg::Type::NUM, RPCArg::Default{10}, "number of last winners to return"},
269 6147 : {"filter", RPCArg::Type::STR, RPCArg::Default{""}, "filter for returned winners"},
270 : },
271 6147 : RPCResult{
272 6147 : RPCResult::Type::OBJ_DYN, "", "Keys are block heights (as strings); values describe the payees for that height",
273 12294 : {
274 6147 : {RPCResult::Type::STR, "payee", "Payee for the height"}
275 : }
276 : },
277 6147 : RPCExamples{""},
278 6156 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
279 : {
280 9 : const NodeContext& node = EnsureAnyNodeContext(request.context);
281 9 : const ChainstateManager& chainman = EnsureChainman(node);
282 9 : const CBlockIndex* pindexTip{nullptr};
283 : {
284 9 : LOCK(::cs_main);
285 9 : pindexTip = chainman.ActiveChain().Tip();
286 9 : if (!pindexTip) return UniValue::VNULL;
287 9 : }
288 :
289 9 : int nCount = 10;
290 9 : std::string strFilter;
291 :
292 9 : if (!request.params[0].isNull()) {
293 4 : nCount = request.params[0].getInt<int>();
294 4 : }
295 :
296 9 : if (!request.params[1].isNull()) {
297 0 : strFilter = request.params[1].get_str();
298 0 : }
299 :
300 9 : UniValue obj(UniValue::VOBJ);
301 :
302 9 : int nChainTipHeight = pindexTip->nHeight;
303 9 : int nStartHeight = std::max(nChainTipHeight - nCount, 1);
304 :
305 9 : const auto tip_mn_list = CHECK_NONFATAL(node.dmnman)->GetListAtChainTip();
306 68 : for (int h = nStartHeight; h <= nChainTipHeight; h++) {
307 59 : const CBlockIndex* pIndex = pindexTip->GetAncestor(h - 1);
308 59 : auto payee = node.dmnman->GetListForBlock(pIndex).GetMNPayee(pIndex);
309 59 : if (payee) {
310 59 : std::string strPayments = GetRequiredPaymentsString(*CHECK_NONFATAL(node.chain_helper)->superblocks,
311 59 : tip_mn_list, h, payee);
312 59 : if (!strFilter.empty() && strPayments.find(strFilter) == std::string::npos) continue;
313 59 : obj.pushKV(strprintf("%d", h), strPayments);
314 59 : }
315 59 : }
316 :
317 9 : auto projection = node.dmnman->GetListForBlock(pindexTip).GetProjectedMNPayees(pindexTip, /*nCount=*/20);
318 72 : for (size_t i = 0; i < projection.size(); i++) {
319 63 : int h = nChainTipHeight + 1 + i;
320 63 : std::string strPayments = GetRequiredPaymentsString(*node.chain_helper->superblocks, tip_mn_list, h, projection[i]);
321 63 : if (!strFilter.empty() && strPayments.find(strFilter) == std::string::npos) continue;
322 63 : obj.pushKV(strprintf("%d", h), strPayments);
323 63 : }
324 :
325 9 : return obj;
326 9 : },
327 : };
328 0 : }
329 :
330 6349 : static RPCHelpMan masternode_payments()
331 : {
332 12698 : return RPCHelpMan{"masternode payments",
333 6349 : "\nReturns an array of deterministic masternodes and their payments for the specified block\n",
334 19047 : {
335 6349 : {"blockhash", RPCArg::Type::STR_HEX, RPCArg::DefaultHint{"tip"}, "The hash of the starting block"},
336 6349 : {"count", RPCArg::Type::NUM, RPCArg::Default{1}, "The number of blocks to return. Will return <count> previous blocks if <count> is negative. Both 1 and -1 correspond to the chain tip."},
337 : },
338 6349 : RPCResult {
339 6349 : RPCResult::Type::ARR, "", "Blocks",
340 12698 : {
341 12698 : {RPCResult::Type::OBJ, "", "",
342 31745 : {
343 6349 : {RPCResult::Type::NUM, "height", "The height of the block"},
344 6349 : {RPCResult::Type::STR_HEX, "blockhash", "The hash of the block"},
345 6349 : {RPCResult::Type::NUM, "amount", "Amount received in this block by all masternodes"},
346 12698 : {RPCResult::Type::ARR, "masternodes", "Masternodes that received payments in this block",
347 25396 : {
348 6349 : {RPCResult::Type::STR_HEX, "proTxHash", "The hash of the corresponding ProRegTx"},
349 6349 : {RPCResult::Type::NUM, "amount", "Amount received by this masternode"},
350 12698 : {RPCResult::Type::ARR, "payees", "Payees who received a share of this payment",
351 25396 : {
352 6349 : {RPCResult::Type::STR, "address", "Payee address"},
353 6349 : {RPCResult::Type::STR_HEX, "script", "Payee scriptPubKey"},
354 6349 : {RPCResult::Type::NUM, "amount", "Amount received by this payee"},
355 : }},
356 : }},
357 : }},
358 : },
359 : },
360 6349 : RPCExamples{""},
361 6560 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
362 : {
363 211 : const NodeContext& node = EnsureAnyNodeContext(request.context);
364 211 : const ChainstateManager& chainman = EnsureChainman(node);
365 :
366 211 : const CBlockIndex* pindex{nullptr};
367 :
368 211 : if (g_txindex) {
369 211 : g_txindex->BlockUntilSyncedToCurrentChain();
370 211 : }
371 :
372 211 : if (request.params[0].isNull()) {
373 8 : LOCK(::cs_main);
374 8 : pindex = chainman.ActiveChain().Tip();
375 8 : } else {
376 203 : LOCK(::cs_main);
377 203 : uint256 blockHash(ParseHashV(request.params[0], "blockhash"));
378 203 : pindex = chainman.m_blockman.LookupBlockIndex(blockHash);
379 203 : if (pindex == nullptr) {
380 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
381 : }
382 203 : }
383 :
384 211 : int64_t nCount = request.params.size() > 1 ? request.params[1].getInt<int64_t>() : 1;
385 :
386 : // A temporary vector which is used to sort results properly (there is no "reverse" in/for UniValue)
387 211 : std::vector<UniValue> vecPayments;
388 :
389 211 : CHECK_NONFATAL(node.chain_helper);
390 211 : CHECK_NONFATAL(node.dmnman);
391 424 : while (vecPayments.size() < uint64_t(std::abs(nCount)) && pindex != nullptr) {
392 213 : CBlock block;
393 213 : if (!ReadBlockFromDisk(block, pindex, Params().GetConsensus())) {
394 0 : throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk");
395 : }
396 :
397 : // Note: we have to actually calculate block reward from scratch instead of simply querying coinbase vout
398 : // because miners might collect less coins than they potentially could and this would break our calculations.
399 213 : CAmount nBlockFees{0};
400 213 : const CTxMemPool& mempool = EnsureAnyMemPool(request.context);
401 712 : for (const auto& tx : block.vtx) {
402 499 : if (tx->IsCoinBase()) {
403 213 : continue;
404 : }
405 286 : if (tx->IsPlatformTransfer()) {
406 0 : auto payload = GetTxPayload<CAssetUnlockPayload>(*tx);
407 0 : CHECK_NONFATAL(payload);
408 0 : nBlockFees += payload->getFee();
409 : continue;
410 0 : }
411 :
412 286 : CAmount nValueIn{0};
413 286 : for (const auto& txin : tx->vin) {
414 0 : uint256 blockHashTmp;
415 0 : CTransactionRef txPrev = GetTransaction(/* block_index */ nullptr, &mempool, txin.prevout.hash, Params().GetConsensus(), blockHashTmp);
416 0 : nValueIn += txPrev->vout[txin.prevout.n].nValue;
417 0 : }
418 286 : nBlockFees += nValueIn - tx->GetValueOut();
419 : }
420 :
421 213 : std::vector<CTxOut> voutMasternodePayments, voutDummy;
422 213 : CMutableTransaction dummyTx;
423 213 : CAmount blockSubsidy = GetBlockSubsidy(pindex, Params().GetConsensus());
424 213 : node.chain_helper->mn_payments->FillBlockPayments(dummyTx, pindex->pprev, blockSubsidy, nBlockFees, voutMasternodePayments, voutDummy);
425 :
426 213 : UniValue blockObj(UniValue::VOBJ);
427 213 : CAmount payedPerBlock{0};
428 :
429 213 : UniValue masternodeArr(UniValue::VARR);
430 213 : UniValue protxObj(UniValue::VOBJ);
431 213 : UniValue payeesArr(UniValue::VARR);
432 213 : CAmount payedPerMasternode{0};
433 :
434 724 : for (const auto& txout : voutMasternodePayments) {
435 511 : UniValue obj(UniValue::VOBJ);
436 511 : CTxDestination dest;
437 511 : ExtractDestination(txout.scriptPubKey, dest);
438 511 : obj.pushKV("address", EncodeDestination(dest));
439 511 : obj.pushKV("script", HexStr(txout.scriptPubKey));
440 511 : obj.pushKV("amount", txout.nValue);
441 511 : payedPerMasternode += txout.nValue;
442 511 : payeesArr.push_back(obj);
443 511 : }
444 :
445 : // NOTE: we use _previous_ block to find a payee for the current one
446 213 : const auto dmnPayee = node.dmnman->GetListForBlock(pindex->pprev).GetMNPayee(pindex->pprev);
447 213 : protxObj.pushKV("proTxHash", dmnPayee == nullptr ? "" : dmnPayee->proTxHash.ToString());
448 213 : protxObj.pushKV("amount", payedPerMasternode);
449 213 : protxObj.pushKV("payees", payeesArr);
450 213 : payedPerBlock += payedPerMasternode;
451 213 : masternodeArr.push_back(protxObj);
452 :
453 213 : blockObj.pushKV("height", pindex->nHeight);
454 213 : blockObj.pushKV("blockhash", pindex->GetBlockHash().ToString());
455 213 : blockObj.pushKV("amount", payedPerBlock);
456 213 : blockObj.pushKV("masternodes", masternodeArr);
457 213 : vecPayments.push_back(blockObj);
458 :
459 213 : if (nCount > 0) {
460 207 : LOCK(::cs_main);
461 207 : pindex = chainman.ActiveChain().Next(pindex);
462 207 : } else {
463 6 : pindex = pindex->pprev;
464 : }
465 213 : }
466 :
467 211 : if (nCount < 0) {
468 4 : std::reverse(vecPayments.begin(), vecPayments.end());
469 4 : }
470 :
471 211 : UniValue paymentsArr(UniValue::VARR);
472 424 : for (const auto& payment : vecPayments) {
473 213 : paymentsArr.push_back(payment);
474 : }
475 :
476 211 : return paymentsArr;
477 211 : },
478 : };
479 0 : }
480 :
481 6156 : static RPCHelpMan masternode_help()
482 : {
483 12312 : return RPCHelpMan{"masternode",
484 6156 : "Set of commands to execute masternode related actions\n"
485 : "\nAvailable commands:\n"
486 : " count - Get information about number of masternodes\n"
487 : " current - DEPRECATED Print info on current masternode winner to be paid the next block (calculated locally)\n"
488 : #ifdef ENABLE_WALLET
489 : " outputs - Print masternode compatible outputs\n"
490 : #endif // ENABLE_WALLET
491 : " status - Print masternode status information\n"
492 : " list - Print list of all known masternodes (see masternodelist for more info)\n"
493 : " payments - Return information about masternode payments in a mined block\n"
494 : " winner - DEPRECATED Print info on next masternode winner to vote for\n"
495 : " winners - Print list of masternode winners\n",
496 12312 : {
497 6156 : {"command", RPCArg::Type::STR, RPCArg::Optional::NO, "The command to execute"},
498 : },
499 6156 : RPCResult{RPCResult::Type::NONE, "", ""},
500 6156 : RPCExamples{""},
501 6156 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
502 : {
503 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Must be a valid command");
504 0 : },
505 : };
506 0 : }
507 :
508 13895 : static RPCHelpMan masternodelist_helper(bool is_composite)
509 : {
510 : // We need both composite and non-composite options because we support
511 : // both options 'masternodelist' and 'masternode list'
512 27790 : return RPCHelpMan{is_composite ? "masternode list" : "masternodelist",
513 13895 : "Get a list of masternodes in different modes. This call is identical to 'masternode list' call.\n"
514 : "Available modes:\n"
515 : " addr - Print ip address associated with a masternode (can be additionally filtered, partial match)\n"
516 : " recent - Print info in JSON format for active and recently banned masternodes (can be additionally filtered, partial match)\n"
517 : " evo - Print info in JSON format for EvoNodes only\n"
518 : " full - Print info in format 'status payee lastpaidtime lastpaidblock IP'\n"
519 : " (can be additionally filtered, partial match)\n"
520 : " info - Print info in format 'status payee IP'\n"
521 : " (can be additionally filtered, partial match)\n"
522 : " json - Print info in JSON format (can be additionally filtered, partial match)\n"
523 : " lastpaidblock - Print the last block height a node was paid on the network\n"
524 : " lastpaidtime - Print the last time a node was paid on the network\n"
525 : " owneraddress - Print the masternode owner Dash address\n"
526 : " payee - Print the masternode payout Dash address (can be additionally filtered,\n"
527 : " partial match)\n"
528 : " pubKeyOperator - Print the masternode operator public key\n"
529 : " status - Print masternode status: ENABLED / POSE_BANNED\n"
530 : " (can be additionally filtered, partial match)\n"
531 : " votingaddress - Print the masternode voting Dash address\n",
532 41685 : {
533 13895 : {"mode", RPCArg::Type::STR, RPCArg::DefaultHint{"json"}, "The mode to run list in"},
534 13895 : {"filter", RPCArg::Type::STR, RPCArg::Default{""}, "Filter results. Partial match by outpoint by default in all modes, additional matches in some modes are also available"},
535 : },
536 13895 : RPCResult{
537 166740 : RPCResult::Type::OBJ, "<outpoint>", "", {
538 13895 : RPCResult{"for mode = addr", RPCResult::Type::STR, "<address>", "Flattened list of all addresses registered to masternode"},
539 13895 : RPCResult{"for mode = full", RPCResult::Type::STR, "<info>", "Flattened list of a masternode's status, payee address, last paid block's timestamp, height and service addresses"},
540 13895 : RPCResult{"for mode = info", RPCResult::Type::STR, "<info>", "Flattened list of a masternode's status, payee address and service addresses"},
541 250110 : RPCResult{"for mode = evo, json or recent", RPCResult::Type::OBJ, "", "", {
542 13895 : GetRpcResult("proTxHash"),
543 13895 : GetRpcResult("service", /*optional=*/true, /*override_name=*/"address"),
544 13895 : GetRpcResult("addresses"),
545 13895 : GetRpcResult("payoutAddress", /*optional=*/false, /*override_name=*/"payee"),
546 13895 : {RPCResult::Type::STR, "status", "Masternode status (human-readable string)"},
547 13895 : GetRpcResult("type_str", /*optional=*/false, /*override_name=*/"type"),
548 13895 : GetRpcResult("platformNodeID", /*optional=*/true),
549 13895 : GetRpcResult("platformP2PPort", /*optional=*/true),
550 13895 : GetRpcResult("platformHTTPPort", /*optional=*/true),
551 13895 : GetRpcResult("PoSePenalty", /*optional=*/false, /*override_name=*/"pospenaltyscore"),
552 13895 : GetRpcResult("consecutivePayments"),
553 13895 : {RPCResult::Type::NUM, "lastpaidtime", "Timestamp of block the masternode was last paid"},
554 13895 : GetRpcResult("lastPaidHeight", /*optional=*/false, /*override_name=*/"lastpaidblock"),
555 13895 : GetRpcResult("ownerAddress", /*optional=*/false, /*override_name=*/"owneraddress"),
556 13895 : GetRpcResult("votingAddress", /*optional=*/false, /*override_name=*/"votingaddress"),
557 13895 : GetRpcResult("collateralAddress", /*optional=*/false, /*override_name=*/"collateraladdress"),
558 13895 : GetRpcResult("pubKeyOperator", /*optional=*/false, /*override_name=*/"pubkeyoperator"),
559 : }},
560 13895 : RPCResult{"for mode = lastpaidblock", RPCResult::Type::NUM, "<height>", "Height masternode was last paid"},
561 13895 : RPCResult{"for mode = lastpaidtime", RPCResult::Type::NUM, "<time>", "Timestamp of block the masternode was last paid"},
562 13895 : RPCResult{"for mode = payee", RPCResult::Type::STR, "<addr>", "Dash address used for masternode reward payments"},
563 13895 : RPCResult{"for mode = owneraddress", RPCResult::Type::STR, "<addr>", "Dash address used for payee updates and proposal voting"},
564 13895 : RPCResult{"for mode = pubkeyoperator", RPCResult::Type::STR, "<addr>", "BLS public key used for operator signing"},
565 13895 : RPCResult{"for mode = status", RPCResult::Type::STR, "<status>", "Masternode status (human-readable string)"},
566 13895 : RPCResult{"for mode = votingaddress", RPCResult::Type::STR, "<addr>", "Dash address used for voting"},
567 : }
568 : },
569 13895 : RPCExamples{""},
570 15496 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
571 : {
572 1601 : std::string strMode = "json";
573 1601 : std::string strFilter;
574 :
575 1601 : if (!request.params[0].isNull()) strMode = request.params[0].get_str();
576 1601 : if (!request.params[1].isNull()) strFilter = request.params[1].get_str();
577 :
578 1601 : strMode = ToLower(strMode);
579 :
580 : if (
581 1603 : strMode != "addr" && strMode != "full" && strMode != "info" && strMode != "json" &&
582 908 : strMode != "owneraddress" && strMode != "votingaddress" &&
583 908 : strMode != "lastpaidtime" && strMode != "lastpaidblock" &&
584 908 : strMode != "payee" && strMode != "pubkeyoperator" &&
585 908 : strMode != "status" && strMode != "recent" && strMode != "evo")
586 : {
587 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("strMode %s not found", strMode));
588 : }
589 :
590 1601 : const NodeContext& node = EnsureAnyNodeContext(request.context);
591 1601 : const ChainstateManager& chainman = EnsureChainman(node);
592 :
593 1601 : UniValue obj(UniValue::VOBJ);
594 :
595 1601 : const auto mnList = CHECK_NONFATAL(node.dmnman)->GetListAtChainTip();
596 24246 : const auto dmnToStatus = [&](const auto& dmn) {
597 24246 : if (dmn.pdmnState->IsBanned()) {
598 114 : return "POSE_BANNED";
599 : }
600 24132 : return "ENABLED";
601 24246 : };
602 17217 : const auto dmnToLastPaidTime = [&](const auto& dmn) {
603 15616 : if (dmn.pdmnState->nLastPaidHeight == 0) {
604 136 : return (int)0;
605 : }
606 :
607 15480 : LOCK(::cs_main);
608 15480 : const CBlockIndex* pindex = chainman.ActiveChain()[dmn.pdmnState->nLastPaidHeight];
609 15480 : return (int)pindex->nTime;
610 15616 : };
611 :
612 1601 : const bool showRecentMnsOnly = strMode == "recent";
613 1601 : const bool showEvoOnly = strMode == "evo";
614 3202 : const int tipHeight = WITH_LOCK(::cs_main, return chainman.ActiveChain().Tip()->nHeight);
615 18043 : mnList.ForEachMN(/*onlyValid=*/false, [&](const auto& dmn) {
616 16442 : if (showRecentMnsOnly && dmn.pdmnState->IsBanned()) {
617 0 : if (tipHeight - dmn.pdmnState->GetBannedHeight() > Params().GetConsensus().nSuperblockCycle) {
618 0 : return;
619 : }
620 0 : }
621 16442 : if (showEvoOnly && dmn.nType != MnType::Evo) {
622 4 : return;
623 : }
624 :
625 16438 : std::string strOutpoint = dmn.collateralOutpoint.ToStringShort();
626 16438 : Coin coin;
627 16438 : std::string collateralAddressStr = "UNKNOWN";
628 16438 : if (GetUTXOCoin(chainman.ActiveChainstate(), dmn.collateralOutpoint, coin)) {
629 16438 : CTxDestination collateralDest;
630 16438 : if (ExtractDestination(coin.out.scriptPubKey, collateralDest)) {
631 16438 : collateralAddressStr = EncodeDestination(collateralDest);
632 16438 : }
633 16438 : }
634 :
635 16438 : CScript payeeScript = dmn.pdmnState->scriptPayout;
636 16438 : CTxDestination payeeDest;
637 16438 : std::string payeeStr = "UNKNOWN";
638 16438 : if (ExtractDestination(payeeScript, payeeDest)) {
639 16438 : payeeStr = EncodeDestination(payeeDest);
640 16438 : }
641 :
642 16438 : std::string strAddress{};
643 16438 : if (strMode == "addr" || strMode == "full" || strMode == "info" || strMode == "json" || strMode == "recent" ||
644 8638 : strMode == "evo") {
645 15607 : for (const auto& entry : dmn.pdmnState->netInfo->GetEntries()) {
646 7799 : strAddress += entry.ToStringAddrPort() + " ";
647 : }
648 7808 : if (!strAddress.empty()) strAddress.pop_back(); // Remove trailing space
649 7808 : }
650 :
651 16438 : if (strMode == "addr") {
652 0 : if (!strFilter.empty() && strAddress.find(strFilter) == std::string::npos &&
653 0 : strOutpoint.find(strFilter) == std::string::npos)
654 0 : return;
655 0 : obj.pushKV(strOutpoint, strAddress);
656 16438 : } else if (strMode == "full") {
657 0 : std::string strFull = strprintf("%s %d %s %s %s %s",
658 0 : PadString(dmnToStatus(dmn), 18),
659 0 : dmn.pdmnState->nPoSePenalty,
660 : payeeStr,
661 0 : PadString(ToString(dmnToLastPaidTime(dmn)), 10),
662 0 : PadString(ToString(dmn.pdmnState->nLastPaidHeight), 6),
663 : strAddress);
664 0 : if (!strFilter.empty() && strFull.find(strFilter) == std::string::npos &&
665 0 : strOutpoint.find(strFilter) == std::string::npos)
666 0 : return;
667 0 : obj.pushKV(strOutpoint, strFull);
668 16438 : } else if (strMode == "info") {
669 0 : std::string strInfo = strprintf("%s %d %s %s",
670 0 : PadString(dmnToStatus(dmn), 18),
671 0 : dmn.pdmnState->nPoSePenalty,
672 : payeeStr,
673 : strAddress);
674 0 : if (!strFilter.empty() && strInfo.find(strFilter) == std::string::npos &&
675 0 : strOutpoint.find(strFilter) == std::string::npos)
676 0 : return;
677 0 : obj.pushKV(strOutpoint, strInfo);
678 16438 : } else if (strMode == "json" || strMode == "recent" || strMode == "evo") {
679 7808 : std::string strInfo = strprintf("%s %s %s %s %d %d %d %s %s %s %s",
680 7808 : dmn.proTxHash.ToString(),
681 : strAddress,
682 : payeeStr,
683 7808 : dmnToStatus(dmn),
684 7808 : dmn.pdmnState->nPoSePenalty,
685 7808 : dmnToLastPaidTime(dmn),
686 7808 : dmn.pdmnState->nLastPaidHeight,
687 7808 : EncodeDestination(PKHash(dmn.pdmnState->keyIDOwner)),
688 7808 : EncodeDestination(PKHash(dmn.pdmnState->keyIDVoting)),
689 : collateralAddressStr,
690 7808 : dmn.pdmnState->pubKeyOperator.ToString());
691 7808 : if (!strFilter.empty() && strInfo.find(strFilter) == std::string::npos &&
692 0 : strOutpoint.find(strFilter) == std::string::npos)
693 0 : return;
694 7808 : UniValue objMN(UniValue::VOBJ);
695 7808 : objMN.pushKV("proTxHash", dmn.proTxHash.ToString());
696 7808 : if (IsDeprecatedRPCEnabled("service")) {
697 8 : objMN.pushKV("address", dmn.pdmnState->netInfo->GetPrimary().ToStringAddrPort());
698 8 : }
699 7808 : objMN.pushKV("addresses", GetNetInfoWithLegacyFields(*dmn.pdmnState, dmn.nType));
700 7808 : objMN.pushKV("payee", payeeStr);
701 7808 : objMN.pushKV("status", dmnToStatus(dmn));
702 7808 : objMN.pushKV("type", std::string(GetMnType(dmn.nType).description));
703 7808 : if (dmn.nType == MnType::Evo) {
704 118 : objMN.pushKV("platformNodeID", dmn.pdmnState->platformNodeID.ToString());
705 118 : if (IsDeprecatedRPCEnabled("service")) {
706 8 : objMN.pushKV("platformP2PPort", GetPlatformPort</*is_p2p=*/true>(*dmn.pdmnState));
707 8 : objMN.pushKV("platformHTTPPort", GetPlatformPort</*is_p2p=*/false>(*dmn.pdmnState));
708 8 : }
709 118 : }
710 7808 : objMN.pushKV("pospenaltyscore", dmn.pdmnState->nPoSePenalty);
711 7808 : objMN.pushKV("consecutivePayments", dmn.pdmnState->nConsecutivePayments);
712 7808 : objMN.pushKV("lastpaidtime", dmnToLastPaidTime(dmn));
713 7808 : objMN.pushKV("lastpaidblock", dmn.pdmnState->nLastPaidHeight);
714 7808 : objMN.pushKV("owneraddress", EncodeDestination(PKHash(dmn.pdmnState->keyIDOwner)));
715 7808 : objMN.pushKV("votingaddress", EncodeDestination(PKHash(dmn.pdmnState->keyIDVoting)));
716 7808 : objMN.pushKV("collateraladdress", collateralAddressStr);
717 7808 : objMN.pushKV("pubkeyoperator", dmn.pdmnState->pubKeyOperator.ToString());
718 7808 : obj.pushKV(strOutpoint, objMN);
719 16438 : } else if (strMode == "lastpaidblock") {
720 0 : if (!strFilter.empty() && strOutpoint.find(strFilter) == std::string::npos) return;
721 0 : obj.pushKV(strOutpoint, dmn.pdmnState->nLastPaidHeight);
722 8630 : } else if (strMode == "lastpaidtime") {
723 0 : if (!strFilter.empty() && strOutpoint.find(strFilter) == std::string::npos) return;
724 0 : obj.pushKV(strOutpoint, dmnToLastPaidTime(dmn));
725 8630 : } else if (strMode == "payee") {
726 0 : if (!strFilter.empty() && payeeStr.find(strFilter) == std::string::npos &&
727 0 : strOutpoint.find(strFilter) == std::string::npos)
728 0 : return;
729 0 : obj.pushKV(strOutpoint, payeeStr);
730 8630 : } else if (strMode == "owneraddress") {
731 0 : if (!strFilter.empty() && strOutpoint.find(strFilter) == std::string::npos) return;
732 0 : obj.pushKV(strOutpoint, EncodeDestination(PKHash(dmn.pdmnState->keyIDOwner)));
733 8630 : } else if (strMode == "pubkeyoperator") {
734 0 : if (!strFilter.empty() && strOutpoint.find(strFilter) == std::string::npos) return;
735 0 : obj.pushKV(strOutpoint, dmn.pdmnState->pubKeyOperator.ToString());
736 8630 : } else if (strMode == "status") {
737 8630 : std::string strStatus = dmnToStatus(dmn);
738 8630 : if (!strFilter.empty() && strStatus.find(strFilter) == std::string::npos &&
739 0 : strOutpoint.find(strFilter) == std::string::npos)
740 0 : return;
741 8630 : obj.pushKV(strOutpoint, strStatus);
742 8630 : } else if (strMode == "votingaddress") {
743 0 : if (!strFilter.empty() && strOutpoint.find(strFilter) == std::string::npos) return;
744 0 : obj.pushKV(strOutpoint, EncodeDestination(PKHash(dmn.pdmnState->keyIDVoting)));
745 0 : }
746 16442 : });
747 :
748 1601 : return obj;
749 1601 : },
750 : };
751 0 : }
752 :
753 6274 : static RPCHelpMan masternodelist()
754 : {
755 6274 : return masternodelist_helper(false);
756 : }
757 :
758 7621 : static RPCHelpMan masternodelist_composite()
759 : {
760 7621 : return masternodelist_helper(true);
761 : }
762 :
763 : #ifdef ENABLE_WALLET
764 1436 : Span<const CRPCCommand> GetWalletMasternodeRPCCommands()
765 : {
766 2872 : static const CRPCCommand commands[]{
767 1436 : {"dash", &masternode_outputs},
768 : };
769 1436 : return commands;
770 0 : }
771 : #endif // ENABLE_WALLET
772 :
773 3201 : void RegisterMasternodeRPCCommands(CRPCTable &t)
774 : {
775 27753 : static const CRPCCommand commands[]{
776 3069 : {"dash", &masternode_help},
777 3069 : {"dash", &masternodelist_composite},
778 3069 : {"dash", &masternodelist},
779 3069 : {"dash", &masternode_connect},
780 3069 : {"dash", &masternode_count},
781 3069 : {"dash", &masternode_status},
782 3069 : {"dash", &masternode_payments},
783 3069 : {"dash", &masternode_winners},
784 : };
785 28809 : for (const auto& command : commands) {
786 25608 : t.appendCommand(command.name, &command);
787 : }
788 3201 : }
|