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 92 : static RPCHelpMan masternode_connect()
48 : {
49 184 : return RPCHelpMan{"masternode connect",
50 92 : "Connect to given masternode\n",
51 276 : {
52 92 : {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The address of the masternode to connect"},
53 92 : {"v2transport", RPCArg::Type::BOOL, RPCArg::DefaultHint{"set by -v2transport"}, "Attempt to connect using BIP324 v2 transport protocol"},
54 : },
55 92 : RPCResult{
56 92 : RPCResult::Type::STR, "status", "Returns 'successfully connected' if successful"
57 : },
58 92 : RPCExamples{""},
59 92 : [&](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 92 : static RPCHelpMan masternode_count()
89 : {
90 184 : return RPCHelpMan{"masternode count",
91 92 : "Get information about number of masternodes.\n",
92 92 : {},
93 92 : RPCResult{
94 92 : RPCResult::Type::OBJ, "", "",
95 368 : {
96 92 : {RPCResult::Type::NUM, "total", "Total number of Masternodes"},
97 92 : {RPCResult::Type::NUM, "enabled", "Number of enabled Masternodes"},
98 184 : {RPCResult::Type::OBJ, "details", "Breakdown of masternodes by type",
99 184 : {{RPCResult::Type::OBJ, "", "",
100 276 : {
101 184 : {RPCResult::Type::OBJ, "regular", "Details for regular masternodes",
102 276 : {
103 92 : {RPCResult::Type::NUM, "total", "Total number of regular Masternodes"},
104 92 : {RPCResult::Type::NUM, "enabled", "Number of enabled regular Masternodes"}
105 : }},
106 184 : {RPCResult::Type::OBJ, "evo", "Details for Evo nodes",
107 276 : {
108 92 : {RPCResult::Type::NUM, "total", "Total number of Evo nodes"},
109 92 : {RPCResult::Type::NUM, "enabled", "Number of enabled Evo nodes"}
110 : }},
111 : }},
112 : }}
113 : }
114 : },
115 92 : RPCExamples{""},
116 92 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
117 : {
118 0 : const NodeContext& node = EnsureAnyNodeContext(request.context);
119 :
120 0 : const auto counts{node.dmnman->GetListAtChainTip().GetCounts()};
121 :
122 0 : UniValue obj(UniValue::VOBJ);
123 0 : obj.pushKV("total", counts.total());
124 0 : obj.pushKV("enabled", counts.enabled());
125 :
126 0 : UniValue evoObj(UniValue::VOBJ);
127 0 : evoObj.pushKV("total", counts.m_total_evo);
128 0 : evoObj.pushKV("enabled", counts.m_valid_evo);
129 :
130 0 : UniValue regularObj(UniValue::VOBJ);
131 0 : regularObj.pushKV("total", counts.m_total_mn);
132 0 : regularObj.pushKV("enabled", counts.m_valid_mn);
133 :
134 0 : UniValue detailedObj(UniValue::VOBJ);
135 0 : detailedObj.pushKV("regular", regularObj);
136 0 : detailedObj.pushKV("evo", evoObj);
137 0 : obj.pushKV("detailed", detailedObj);
138 :
139 0 : return obj;
140 0 : },
141 : };
142 0 : }
143 :
144 : #ifdef ENABLE_WALLET
145 0 : static RPCHelpMan masternode_outputs()
146 : {
147 0 : return RPCHelpMan{"masternode outputs",
148 0 : "Print masternode compatible outputs\n",
149 0 : {},
150 0 : RPCResult {
151 0 : RPCResult::Type::ARR, "", "A list of outpoints that can be/are used as masternode collaterals",
152 0 : {
153 0 : {RPCResult::Type::STR, "", "A (potential) masternode collateral"},
154 : }},
155 0 : RPCExamples{""},
156 0 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
157 : {
158 0 : const std::shared_ptr<const CWallet> wallet = GetWalletForJSONRPCRequest(request);
159 0 : if (!wallet) return UniValue::VNULL;
160 :
161 : // Find possible candidates
162 0 : CCoinControl coin_control(CoinType::ONLY_MASTERNODE_COLLATERAL);
163 :
164 0 : UniValue outputsArr(UniValue::VARR);
165 0 : for (const auto& out : WITH_LOCK(wallet->cs_wallet, return AvailableCoinsListUnspent(*wallet, &coin_control).all())) {
166 0 : outputsArr.push_back(out.outpoint.ToStringShort());
167 : }
168 :
169 0 : return outputsArr;
170 0 : },
171 : };
172 0 : }
173 :
174 : #endif // ENABLE_WALLET
175 :
176 92 : static RPCHelpMan masternode_status()
177 : {
178 184 : return RPCHelpMan{"masternode status",
179 92 : "Print masternode status information\n",
180 92 : {},
181 92 : RPCResult{
182 92 : RPCResult::Type::OBJ, "", "",
183 920 : {
184 92 : GetRpcResult("outpoint"),
185 92 : GetRpcResult("service", /*optional=*/true),
186 92 : GetRpcResult("proTxHash", /*optional=*/true),
187 92 : GetRpcResult("type_str", /*optional=*/true, /*override_name=*/"type"),
188 92 : GetRpcResult("collateralHash", /*optional=*/true),
189 92 : GetRpcResult("collateralIndex", /*optional=*/true),
190 92 : CDeterministicMNState::GetJsonHelp(/*key=*/"dmnState", /*optional=*/true),
191 92 : {RPCResult::Type::STR, "state", "Masternode state (human-readable string)"},
192 92 : {RPCResult::Type::STR, "status", "Masternode status (human-readable string, based on current state)"},
193 : }
194 : },
195 92 : RPCExamples{""},
196 92 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
197 : {
198 0 : const NodeContext& node = EnsureAnyNodeContext(request.context);
199 :
200 0 : if (!node.active_ctx) {
201 0 : throw JSONRPCError(RPC_INTERNAL_ERROR, "This node does not run an active masternode.");
202 : }
203 0 : const auto& mn_activeman{*node.active_ctx->nodeman};
204 :
205 0 : UniValue mnObj(UniValue::VOBJ);
206 : // keep compatibility with legacy status for now (TODO: get deprecated/removed later)
207 0 : mnObj.pushKV("outpoint", mn_activeman.GetOutPoint().ToStringShort());
208 0 : mnObj.pushKV("service", mn_activeman.GetService().ToStringAddrPort());
209 0 : auto dmn = CHECK_NONFATAL(node.dmnman)->GetListAtChainTip().GetMN(mn_activeman.GetProTxHash());
210 0 : if (dmn) {
211 0 : mnObj.pushKV("proTxHash", dmn->proTxHash.ToString());
212 0 : mnObj.pushKV("type", std::string(GetMnType(dmn->nType).description));
213 0 : mnObj.pushKV("collateralHash", dmn->collateralOutpoint.hash.ToString());
214 0 : mnObj.pushKV("collateralIndex", dmn->collateralOutpoint.n);
215 0 : mnObj.pushKV("dmnState", dmn->pdmnState->ToJson(dmn->nType));
216 0 : }
217 0 : mnObj.pushKV("state", mn_activeman.GetStateString());
218 0 : mnObj.pushKV("status", mn_activeman.GetStatus());
219 :
220 0 : return mnObj;
221 0 : },
222 : };
223 0 : }
224 :
225 0 : static std::string GetRequiredPaymentsString(governance::SuperblockManager& superblocks,
226 : const CDeterministicMNList& tip_mn_list, int nBlockHeight,
227 : const CDeterministicMNCPtr& payee)
228 : {
229 0 : std::string strPayments = "Unknown";
230 0 : if (payee) {
231 0 : CTxDestination dest;
232 0 : if (!ExtractDestination(payee->pdmnState->scriptPayout, dest)) {
233 0 : NONFATAL_UNREACHABLE();
234 : }
235 0 : strPayments = EncodeDestination(dest);
236 0 : if (payee->nOperatorReward != 0 && payee->pdmnState->scriptOperatorPayout != CScript()) {
237 0 : if (!ExtractDestination(payee->pdmnState->scriptOperatorPayout, dest)) {
238 0 : NONFATAL_UNREACHABLE();
239 : }
240 0 : strPayments += ", " + EncodeDestination(dest);
241 0 : }
242 0 : }
243 0 : 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 0 : return strPayments;
261 0 : }
262 :
263 92 : static RPCHelpMan masternode_winners()
264 : {
265 184 : return RPCHelpMan{"masternode winners",
266 92 : "Print list of masternode winners\n",
267 276 : {
268 92 : {"count", RPCArg::Type::NUM, RPCArg::Default{10}, "number of last winners to return"},
269 92 : {"filter", RPCArg::Type::STR, RPCArg::Default{""}, "filter for returned winners"},
270 : },
271 92 : RPCResult{
272 92 : RPCResult::Type::OBJ_DYN, "", "Keys are block heights (as strings); values describe the payees for that height",
273 184 : {
274 92 : {RPCResult::Type::STR, "payee", "Payee for the height"}
275 : }
276 : },
277 92 : RPCExamples{""},
278 92 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
279 : {
280 0 : const NodeContext& node = EnsureAnyNodeContext(request.context);
281 0 : const ChainstateManager& chainman = EnsureChainman(node);
282 0 : const CBlockIndex* pindexTip{nullptr};
283 : {
284 0 : LOCK(::cs_main);
285 0 : pindexTip = chainman.ActiveChain().Tip();
286 0 : if (!pindexTip) return UniValue::VNULL;
287 0 : }
288 :
289 0 : int nCount = 10;
290 0 : std::string strFilter;
291 :
292 0 : if (!request.params[0].isNull()) {
293 0 : nCount = request.params[0].getInt<int>();
294 0 : }
295 :
296 0 : if (!request.params[1].isNull()) {
297 0 : strFilter = request.params[1].get_str();
298 0 : }
299 :
300 0 : UniValue obj(UniValue::VOBJ);
301 :
302 0 : int nChainTipHeight = pindexTip->nHeight;
303 0 : int nStartHeight = std::max(nChainTipHeight - nCount, 1);
304 :
305 0 : const auto tip_mn_list = CHECK_NONFATAL(node.dmnman)->GetListAtChainTip();
306 0 : for (int h = nStartHeight; h <= nChainTipHeight; h++) {
307 0 : const CBlockIndex* pIndex = pindexTip->GetAncestor(h - 1);
308 0 : auto payee = node.dmnman->GetListForBlock(pIndex).GetMNPayee(pIndex);
309 0 : if (payee) {
310 0 : std::string strPayments = GetRequiredPaymentsString(*CHECK_NONFATAL(node.chain_helper)->superblocks,
311 0 : tip_mn_list, h, payee);
312 0 : if (!strFilter.empty() && strPayments.find(strFilter) == std::string::npos) continue;
313 0 : obj.pushKV(strprintf("%d", h), strPayments);
314 0 : }
315 0 : }
316 :
317 0 : auto projection = node.dmnman->GetListForBlock(pindexTip).GetProjectedMNPayees(pindexTip, /*nCount=*/20);
318 0 : for (size_t i = 0; i < projection.size(); i++) {
319 0 : int h = nChainTipHeight + 1 + i;
320 0 : std::string strPayments = GetRequiredPaymentsString(*node.chain_helper->superblocks, tip_mn_list, h, projection[i]);
321 0 : if (!strFilter.empty() && strPayments.find(strFilter) == std::string::npos) continue;
322 0 : obj.pushKV(strprintf("%d", h), strPayments);
323 0 : }
324 :
325 0 : return obj;
326 0 : },
327 : };
328 0 : }
329 :
330 92 : static RPCHelpMan masternode_payments()
331 : {
332 184 : return RPCHelpMan{"masternode payments",
333 92 : "\nReturns an array of deterministic masternodes and their payments for the specified block\n",
334 276 : {
335 92 : {"blockhash", RPCArg::Type::STR_HEX, RPCArg::DefaultHint{"tip"}, "The hash of the starting block"},
336 92 : {"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 92 : RPCResult {
339 92 : RPCResult::Type::ARR, "", "Blocks",
340 184 : {
341 184 : {RPCResult::Type::OBJ, "", "",
342 460 : {
343 92 : {RPCResult::Type::NUM, "height", "The height of the block"},
344 92 : {RPCResult::Type::STR_HEX, "blockhash", "The hash of the block"},
345 92 : {RPCResult::Type::NUM, "amount", "Amount received in this block by all masternodes"},
346 184 : {RPCResult::Type::ARR, "masternodes", "Masternodes that received payments in this block",
347 368 : {
348 92 : {RPCResult::Type::STR_HEX, "proTxHash", "The hash of the corresponding ProRegTx"},
349 92 : {RPCResult::Type::NUM, "amount", "Amount received by this masternode"},
350 184 : {RPCResult::Type::ARR, "payees", "Payees who received a share of this payment",
351 368 : {
352 92 : {RPCResult::Type::STR, "address", "Payee address"},
353 92 : {RPCResult::Type::STR_HEX, "script", "Payee scriptPubKey"},
354 92 : {RPCResult::Type::NUM, "amount", "Amount received by this payee"},
355 : }},
356 : }},
357 : }},
358 : },
359 : },
360 92 : RPCExamples{""},
361 92 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
362 : {
363 0 : const NodeContext& node = EnsureAnyNodeContext(request.context);
364 0 : const ChainstateManager& chainman = EnsureChainman(node);
365 :
366 0 : const CBlockIndex* pindex{nullptr};
367 :
368 0 : if (g_txindex) {
369 0 : g_txindex->BlockUntilSyncedToCurrentChain();
370 0 : }
371 :
372 0 : if (request.params[0].isNull()) {
373 0 : LOCK(::cs_main);
374 0 : pindex = chainman.ActiveChain().Tip();
375 0 : } else {
376 0 : LOCK(::cs_main);
377 0 : uint256 blockHash(ParseHashV(request.params[0], "blockhash"));
378 0 : pindex = chainman.m_blockman.LookupBlockIndex(blockHash);
379 0 : if (pindex == nullptr) {
380 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
381 : }
382 0 : }
383 :
384 0 : 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 0 : std::vector<UniValue> vecPayments;
388 :
389 0 : CHECK_NONFATAL(node.chain_helper);
390 0 : CHECK_NONFATAL(node.dmnman);
391 0 : while (vecPayments.size() < uint64_t(std::abs(nCount)) && pindex != nullptr) {
392 0 : CBlock block;
393 0 : 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 0 : CAmount nBlockFees{0};
400 0 : const CTxMemPool& mempool = EnsureAnyMemPool(request.context);
401 0 : for (const auto& tx : block.vtx) {
402 0 : if (tx->IsCoinBase()) {
403 0 : continue;
404 : }
405 0 : 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 0 : CAmount nValueIn{0};
413 0 : 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 0 : nBlockFees += nValueIn - tx->GetValueOut();
419 : }
420 :
421 0 : std::vector<CTxOut> voutMasternodePayments, voutDummy;
422 0 : CMutableTransaction dummyTx;
423 0 : CAmount blockSubsidy = GetBlockSubsidy(pindex, Params().GetConsensus());
424 0 : node.chain_helper->mn_payments->FillBlockPayments(dummyTx, pindex->pprev, blockSubsidy, nBlockFees, voutMasternodePayments, voutDummy);
425 :
426 0 : UniValue blockObj(UniValue::VOBJ);
427 0 : CAmount payedPerBlock{0};
428 :
429 0 : UniValue masternodeArr(UniValue::VARR);
430 0 : UniValue protxObj(UniValue::VOBJ);
431 0 : UniValue payeesArr(UniValue::VARR);
432 0 : CAmount payedPerMasternode{0};
433 :
434 0 : for (const auto& txout : voutMasternodePayments) {
435 0 : UniValue obj(UniValue::VOBJ);
436 0 : CTxDestination dest;
437 0 : ExtractDestination(txout.scriptPubKey, dest);
438 0 : obj.pushKV("address", EncodeDestination(dest));
439 0 : obj.pushKV("script", HexStr(txout.scriptPubKey));
440 0 : obj.pushKV("amount", txout.nValue);
441 0 : payedPerMasternode += txout.nValue;
442 0 : payeesArr.push_back(obj);
443 0 : }
444 :
445 : // NOTE: we use _previous_ block to find a payee for the current one
446 0 : const auto dmnPayee = node.dmnman->GetListForBlock(pindex->pprev).GetMNPayee(pindex->pprev);
447 0 : protxObj.pushKV("proTxHash", dmnPayee == nullptr ? "" : dmnPayee->proTxHash.ToString());
448 0 : protxObj.pushKV("amount", payedPerMasternode);
449 0 : protxObj.pushKV("payees", payeesArr);
450 0 : payedPerBlock += payedPerMasternode;
451 0 : masternodeArr.push_back(protxObj);
452 :
453 0 : blockObj.pushKV("height", pindex->nHeight);
454 0 : blockObj.pushKV("blockhash", pindex->GetBlockHash().ToString());
455 0 : blockObj.pushKV("amount", payedPerBlock);
456 0 : blockObj.pushKV("masternodes", masternodeArr);
457 0 : vecPayments.push_back(blockObj);
458 :
459 0 : if (nCount > 0) {
460 0 : LOCK(::cs_main);
461 0 : pindex = chainman.ActiveChain().Next(pindex);
462 0 : } else {
463 0 : pindex = pindex->pprev;
464 : }
465 0 : }
466 :
467 0 : if (nCount < 0) {
468 0 : std::reverse(vecPayments.begin(), vecPayments.end());
469 0 : }
470 :
471 0 : UniValue paymentsArr(UniValue::VARR);
472 0 : for (const auto& payment : vecPayments) {
473 0 : paymentsArr.push_back(payment);
474 : }
475 :
476 0 : return paymentsArr;
477 0 : },
478 : };
479 0 : }
480 :
481 92 : static RPCHelpMan masternode_help()
482 : {
483 184 : return RPCHelpMan{"masternode",
484 92 : "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 184 : {
497 92 : {"command", RPCArg::Type::STR, RPCArg::Optional::NO, "The command to execute"},
498 : },
499 92 : RPCResult{RPCResult::Type::NONE, "", ""},
500 92 : RPCExamples{""},
501 92 : [&](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 184 : 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 368 : return RPCHelpMan{is_composite ? "masternode list" : "masternodelist",
513 184 : "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 552 : {
533 184 : {"mode", RPCArg::Type::STR, RPCArg::DefaultHint{"json"}, "The mode to run list in"},
534 184 : {"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 184 : RPCResult{
537 2208 : RPCResult::Type::OBJ, "<outpoint>", "", {
538 184 : RPCResult{"for mode = addr", RPCResult::Type::STR, "<address>", "Flattened list of all addresses registered to masternode"},
539 184 : 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 184 : RPCResult{"for mode = info", RPCResult::Type::STR, "<info>", "Flattened list of a masternode's status, payee address and service addresses"},
541 3312 : RPCResult{"for mode = evo, json or recent", RPCResult::Type::OBJ, "", "", {
542 184 : GetRpcResult("proTxHash"),
543 184 : GetRpcResult("service", /*optional=*/true, /*override_name=*/"address"),
544 184 : GetRpcResult("addresses"),
545 184 : GetRpcResult("payoutAddress", /*optional=*/false, /*override_name=*/"payee"),
546 184 : {RPCResult::Type::STR, "status", "Masternode status (human-readable string)"},
547 184 : GetRpcResult("type_str", /*optional=*/false, /*override_name=*/"type"),
548 184 : GetRpcResult("platformNodeID", /*optional=*/true),
549 184 : GetRpcResult("platformP2PPort", /*optional=*/true),
550 184 : GetRpcResult("platformHTTPPort", /*optional=*/true),
551 184 : GetRpcResult("PoSePenalty", /*optional=*/false, /*override_name=*/"pospenaltyscore"),
552 184 : GetRpcResult("consecutivePayments"),
553 184 : {RPCResult::Type::NUM, "lastpaidtime", "Timestamp of block the masternode was last paid"},
554 184 : GetRpcResult("lastPaidHeight", /*optional=*/false, /*override_name=*/"lastpaidblock"),
555 184 : GetRpcResult("ownerAddress", /*optional=*/false, /*override_name=*/"owneraddress"),
556 184 : GetRpcResult("votingAddress", /*optional=*/false, /*override_name=*/"votingaddress"),
557 184 : GetRpcResult("collateralAddress", /*optional=*/false, /*override_name=*/"collateraladdress"),
558 184 : GetRpcResult("pubKeyOperator", /*optional=*/false, /*override_name=*/"pubkeyoperator"),
559 : }},
560 184 : RPCResult{"for mode = lastpaidblock", RPCResult::Type::NUM, "<height>", "Height masternode was last paid"},
561 184 : RPCResult{"for mode = lastpaidtime", RPCResult::Type::NUM, "<time>", "Timestamp of block the masternode was last paid"},
562 184 : RPCResult{"for mode = payee", RPCResult::Type::STR, "<addr>", "Dash address used for masternode reward payments"},
563 184 : RPCResult{"for mode = owneraddress", RPCResult::Type::STR, "<addr>", "Dash address used for payee updates and proposal voting"},
564 184 : RPCResult{"for mode = pubkeyoperator", RPCResult::Type::STR, "<addr>", "BLS public key used for operator signing"},
565 184 : RPCResult{"for mode = status", RPCResult::Type::STR, "<status>", "Masternode status (human-readable string)"},
566 184 : RPCResult{"for mode = votingaddress", RPCResult::Type::STR, "<addr>", "Dash address used for voting"},
567 : }
568 : },
569 184 : RPCExamples{""},
570 184 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
571 : {
572 0 : std::string strMode = "json";
573 0 : std::string strFilter;
574 :
575 0 : if (!request.params[0].isNull()) strMode = request.params[0].get_str();
576 0 : if (!request.params[1].isNull()) strFilter = request.params[1].get_str();
577 :
578 0 : strMode = ToLower(strMode);
579 :
580 : if (
581 0 : strMode != "addr" && strMode != "full" && strMode != "info" && strMode != "json" &&
582 0 : strMode != "owneraddress" && strMode != "votingaddress" &&
583 0 : strMode != "lastpaidtime" && strMode != "lastpaidblock" &&
584 0 : strMode != "payee" && strMode != "pubkeyoperator" &&
585 0 : strMode != "status" && strMode != "recent" && strMode != "evo")
586 : {
587 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("strMode %s not found", strMode));
588 : }
589 :
590 0 : const NodeContext& node = EnsureAnyNodeContext(request.context);
591 0 : const ChainstateManager& chainman = EnsureChainman(node);
592 :
593 0 : UniValue obj(UniValue::VOBJ);
594 :
595 0 : const auto mnList = CHECK_NONFATAL(node.dmnman)->GetListAtChainTip();
596 0 : const auto dmnToStatus = [&](const auto& dmn) {
597 0 : if (dmn.pdmnState->IsBanned()) {
598 0 : return "POSE_BANNED";
599 : }
600 0 : return "ENABLED";
601 0 : };
602 0 : const auto dmnToLastPaidTime = [&](const auto& dmn) {
603 0 : if (dmn.pdmnState->nLastPaidHeight == 0) {
604 0 : return (int)0;
605 : }
606 :
607 0 : LOCK(::cs_main);
608 0 : const CBlockIndex* pindex = chainman.ActiveChain()[dmn.pdmnState->nLastPaidHeight];
609 0 : return (int)pindex->nTime;
610 0 : };
611 :
612 0 : const bool showRecentMnsOnly = strMode == "recent";
613 0 : const bool showEvoOnly = strMode == "evo";
614 0 : const int tipHeight = WITH_LOCK(::cs_main, return chainman.ActiveChain().Tip()->nHeight);
615 0 : mnList.ForEachMN(/*onlyValid=*/false, [&](const auto& dmn) {
616 0 : if (showRecentMnsOnly && dmn.pdmnState->IsBanned()) {
617 0 : if (tipHeight - dmn.pdmnState->GetBannedHeight() > Params().GetConsensus().nSuperblockCycle) {
618 0 : return;
619 : }
620 0 : }
621 0 : if (showEvoOnly && dmn.nType != MnType::Evo) {
622 0 : return;
623 : }
624 :
625 0 : std::string strOutpoint = dmn.collateralOutpoint.ToStringShort();
626 0 : Coin coin;
627 0 : std::string collateralAddressStr = "UNKNOWN";
628 0 : if (GetUTXOCoin(chainman.ActiveChainstate(), dmn.collateralOutpoint, coin)) {
629 0 : CTxDestination collateralDest;
630 0 : if (ExtractDestination(coin.out.scriptPubKey, collateralDest)) {
631 0 : collateralAddressStr = EncodeDestination(collateralDest);
632 0 : }
633 0 : }
634 :
635 0 : CScript payeeScript = dmn.pdmnState->scriptPayout;
636 0 : CTxDestination payeeDest;
637 0 : std::string payeeStr = "UNKNOWN";
638 0 : if (ExtractDestination(payeeScript, payeeDest)) {
639 0 : payeeStr = EncodeDestination(payeeDest);
640 0 : }
641 :
642 0 : std::string strAddress{};
643 0 : if (strMode == "addr" || strMode == "full" || strMode == "info" || strMode == "json" || strMode == "recent" ||
644 0 : strMode == "evo") {
645 0 : for (const auto& entry : dmn.pdmnState->netInfo->GetEntries()) {
646 0 : strAddress += entry.ToStringAddrPort() + " ";
647 : }
648 0 : if (!strAddress.empty()) strAddress.pop_back(); // Remove trailing space
649 0 : }
650 :
651 0 : 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 0 : } 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 0 : } 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 0 : } else if (strMode == "json" || strMode == "recent" || strMode == "evo") {
679 0 : std::string strInfo = strprintf("%s %s %s %s %d %d %d %s %s %s %s",
680 0 : dmn.proTxHash.ToString(),
681 : strAddress,
682 : payeeStr,
683 0 : dmnToStatus(dmn),
684 0 : dmn.pdmnState->nPoSePenalty,
685 0 : dmnToLastPaidTime(dmn),
686 0 : dmn.pdmnState->nLastPaidHeight,
687 0 : EncodeDestination(PKHash(dmn.pdmnState->keyIDOwner)),
688 0 : EncodeDestination(PKHash(dmn.pdmnState->keyIDVoting)),
689 : collateralAddressStr,
690 0 : dmn.pdmnState->pubKeyOperator.ToString());
691 0 : if (!strFilter.empty() && strInfo.find(strFilter) == std::string::npos &&
692 0 : strOutpoint.find(strFilter) == std::string::npos)
693 0 : return;
694 0 : UniValue objMN(UniValue::VOBJ);
695 0 : objMN.pushKV("proTxHash", dmn.proTxHash.ToString());
696 0 : if (IsDeprecatedRPCEnabled("service")) {
697 0 : objMN.pushKV("address", dmn.pdmnState->netInfo->GetPrimary().ToStringAddrPort());
698 0 : }
699 0 : objMN.pushKV("addresses", GetNetInfoWithLegacyFields(*dmn.pdmnState, dmn.nType));
700 0 : objMN.pushKV("payee", payeeStr);
701 0 : objMN.pushKV("status", dmnToStatus(dmn));
702 0 : objMN.pushKV("type", std::string(GetMnType(dmn.nType).description));
703 0 : if (dmn.nType == MnType::Evo) {
704 0 : objMN.pushKV("platformNodeID", dmn.pdmnState->platformNodeID.ToString());
705 0 : if (IsDeprecatedRPCEnabled("service")) {
706 0 : objMN.pushKV("platformP2PPort", GetPlatformPort</*is_p2p=*/true>(*dmn.pdmnState));
707 0 : objMN.pushKV("platformHTTPPort", GetPlatformPort</*is_p2p=*/false>(*dmn.pdmnState));
708 0 : }
709 0 : }
710 0 : objMN.pushKV("pospenaltyscore", dmn.pdmnState->nPoSePenalty);
711 0 : objMN.pushKV("consecutivePayments", dmn.pdmnState->nConsecutivePayments);
712 0 : objMN.pushKV("lastpaidtime", dmnToLastPaidTime(dmn));
713 0 : objMN.pushKV("lastpaidblock", dmn.pdmnState->nLastPaidHeight);
714 0 : objMN.pushKV("owneraddress", EncodeDestination(PKHash(dmn.pdmnState->keyIDOwner)));
715 0 : objMN.pushKV("votingaddress", EncodeDestination(PKHash(dmn.pdmnState->keyIDVoting)));
716 0 : objMN.pushKV("collateraladdress", collateralAddressStr);
717 0 : objMN.pushKV("pubkeyoperator", dmn.pdmnState->pubKeyOperator.ToString());
718 0 : obj.pushKV(strOutpoint, objMN);
719 0 : } 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 0 : } else if (strMode == "lastpaidtime") {
723 0 : if (!strFilter.empty() && strOutpoint.find(strFilter) == std::string::npos) return;
724 0 : obj.pushKV(strOutpoint, dmnToLastPaidTime(dmn));
725 0 : } 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 0 : } 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 0 : } 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 0 : } else if (strMode == "status") {
737 0 : std::string strStatus = dmnToStatus(dmn);
738 0 : if (!strFilter.empty() && strStatus.find(strFilter) == std::string::npos &&
739 0 : strOutpoint.find(strFilter) == std::string::npos)
740 0 : return;
741 0 : obj.pushKV(strOutpoint, strStatus);
742 0 : } 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 0 : });
747 :
748 0 : return obj;
749 0 : },
750 : };
751 0 : }
752 :
753 92 : static RPCHelpMan masternodelist()
754 : {
755 92 : return masternodelist_helper(false);
756 : }
757 :
758 92 : static RPCHelpMan masternodelist_composite()
759 : {
760 92 : return masternodelist_helper(true);
761 : }
762 :
763 : #ifdef ENABLE_WALLET
764 0 : Span<const CRPCCommand> GetWalletMasternodeRPCCommands()
765 : {
766 0 : static const CRPCCommand commands[]{
767 0 : {"dash", &masternode_outputs},
768 : };
769 0 : return commands;
770 0 : }
771 : #endif // ENABLE_WALLET
772 :
773 178 : void RegisterMasternodeRPCCommands(CRPCTable &t)
774 : {
775 546 : static const CRPCCommand commands[]{
776 46 : {"dash", &masternode_help},
777 46 : {"dash", &masternodelist_composite},
778 46 : {"dash", &masternodelist},
779 46 : {"dash", &masternode_connect},
780 46 : {"dash", &masternode_count},
781 46 : {"dash", &masternode_status},
782 46 : {"dash", &masternode_payments},
783 46 : {"dash", &masternode_winners},
784 : };
785 1602 : for (const auto& command : commands) {
786 1424 : t.appendCommand(command.name, &command);
787 : }
788 178 : }
|