// Copyright (c) 2014-2021 The Dash Core developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include #include #include #include #include #include #include #include #ifdef ENABLE_WALLET #include #endif // ENABLE_WALLET #include #include #include static UniValue masternodelist(const JSONRPCRequest& request); static void masternode_list_help() { throw std::runtime_error( "masternodelist ( \"mode\" \"filter\" )\n" "Get a list of masternodes in different modes. This call is identical to 'masternode list' call.\n" "\nArguments:\n" "1. \"mode\" (string, optional/required to use filter, defaults = json) The mode to run list in\n" "2. \"filter\" (string, optional) Filter results. Partial match by outpoint by default in all modes,\n" " additional matches in some modes are also available\n" "\nAvailable modes:\n" " addr - Print ip address associated with a masternode (can be additionally filtered, partial match)\n" " full - Print info in format 'status payee lastpaidtime lastpaidblock IP'\n" " (can be additionally filtered, partial match)\n" " info - Print info in format 'status payee IP'\n" " (can be additionally filtered, partial match)\n" " json - Print info in JSON format (can be additionally filtered, partial match)\n" " lastpaidblock - Print the last block height a node was paid on the network\n" " lastpaidtime - Print the last time a node was paid on the network\n" " owneraddress - Print the masternode owner Dash address\n" " payee - Print the masternode payout Dash address (can be additionally filtered,\n" " partial match)\n" " pubKeyOperator - Print the masternode operator public key\n" " status - Print masternode status: ENABLED / POSE_BANNED\n" " (can be additionally filtered, partial match)\n" " votingaddress - Print the masternode voting Dash address\n" ); } static UniValue masternode_list(const JSONRPCRequest& request) { if (request.fHelp) masternode_list_help(); JSONRPCRequest newRequest = request; newRequest.params.setArray(); // forward params but skip "list" for (unsigned int i = 1; i < request.params.size(); i++) { newRequest.params.push_back(request.params[i]); } return masternodelist(newRequest); } static void masternode_connect_help() { throw std::runtime_error( "masternode connect \"address\"\n" "Connect to given masternode\n" "\nArguments:\n" "1. \"address\" (string, required) The address of the masternode to connect\n" ); } static UniValue masternode_connect(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() < 2) masternode_connect_help(); std::string strAddress = request.params[1].get_str(); CService addr; if (!Lookup(strAddress.c_str(), addr, 0, false)) throw JSONRPCError(RPC_INTERNAL_ERROR, strprintf("Incorrect masternode address %s", strAddress)); // TODO: Pass CConnman instance somehow and don't use global variable. g_connman->OpenMasternodeConnection(CAddress(addr, NODE_NETWORK)); if (!g_connman->IsConnected(CAddress(addr, NODE_NETWORK), CConnman::AllNodes)) throw JSONRPCError(RPC_INTERNAL_ERROR, strprintf("Couldn't connect to masternode %s", strAddress)); return "successfully connected"; } static void masternode_count_help() { throw std::runtime_error( "masternode count\n" "Get information about number of masternodes.\n" ); } static UniValue masternode_count(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() > 1) masternode_count_help(); auto mnList = deterministicMNManager->GetListAtChainTip(); int total = mnList.GetAllMNsCount(); int enabled = mnList.GetValidMNsCount(); UniValue obj(UniValue::VOBJ); obj.pushKV("total", total); obj.pushKV("enabled", enabled); return obj; } static UniValue GetNextMasternodeForPayment(int heightShift) { auto mnList = deterministicMNManager->GetListAtChainTip(); auto payees = mnList.GetProjectedMNPayees(heightShift); if (payees.empty()) return "unknown"; auto payee = payees.back(); CScript payeeScript = payee->pdmnState->scriptPayout; CTxDestination payeeDest; ExtractDestination(payeeScript, payeeDest); UniValue obj(UniValue::VOBJ); obj.pushKV("height", mnList.GetHeight() + heightShift); obj.pushKV("IP:port", payee->pdmnState->addr.ToString()); obj.pushKV("proTxHash", payee->proTxHash.ToString()); obj.pushKV("outpoint", payee->collateralOutpoint.ToStringShort()); obj.pushKV("payee", IsValidDestination(payeeDest) ? EncodeDestination(payeeDest) : "UNKNOWN"); return obj; } static void masternode_winner_help() { if (!IsDeprecatedRPCEnabled("masternode_winner")) { throw std::runtime_error("DEPRECATED: set -deprecatedrpc=masternode_winner to enable it"); } throw std::runtime_error( "masternode winner\n" "Print info on next masternode winner to vote for\n" ); } static UniValue masternode_winner(const JSONRPCRequest& request) { if (request.fHelp || !IsDeprecatedRPCEnabled("masternode_winner")) masternode_winner_help(); return GetNextMasternodeForPayment(10); } static void masternode_current_help() { if (!IsDeprecatedRPCEnabled("masternode_current")) { throw std::runtime_error("DEPRECATED: set -deprecatedrpc=masternode_current to enable it"); } throw std::runtime_error( "masternode current\n" "Print info on current masternode winner to be paid the next block (calculated locally)\n" ); } static UniValue masternode_current(const JSONRPCRequest& request) { if (request.fHelp || !IsDeprecatedRPCEnabled("masternode_current")) masternode_current_help(); return GetNextMasternodeForPayment(1); } #ifdef ENABLE_WALLET static void masternode_outputs_help() { throw std::runtime_error( "masternode outputs\n" "Print masternode compatible outputs\n" ); } static UniValue masternode_outputs(const JSONRPCRequest& request) { std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request); CWallet* const pwallet = wallet.get(); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) return NullUniValue; if (request.fHelp) masternode_outputs_help(); LOCK2(cs_main, pwallet->cs_wallet); // Find possible candidates std::vector vPossibleCoins; CCoinControl coin_control; coin_control.nCoinType = CoinType::ONLY_MASTERNODE_COLLATERAL; pwallet->AvailableCoins(vPossibleCoins, true, &coin_control); UniValue obj(UniValue::VOBJ); for (const auto& out : vPossibleCoins) { obj.pushKV(out.tx->GetHash().ToString(), strprintf("%d", out.i)); } return obj; } #endif // ENABLE_WALLET static void masternode_status_help() { throw std::runtime_error( "masternode status\n" "Print masternode status information\n" ); } static UniValue masternode_status(const JSONRPCRequest& request) { if (request.fHelp) masternode_status_help(); if (!fMasternodeMode) throw JSONRPCError(RPC_INTERNAL_ERROR, "This is not a masternode"); UniValue mnObj(UniValue::VOBJ); CDeterministicMNCPtr dmn; { LOCK(activeMasternodeInfoCs); // keep compatibility with legacy status for now (might get deprecated/removed later) mnObj.pushKV("outpoint", activeMasternodeInfo.outpoint.ToStringShort()); mnObj.pushKV("service", activeMasternodeInfo.service.ToString()); dmn = deterministicMNManager->GetListAtChainTip().GetMN(activeMasternodeInfo.proTxHash); } if (dmn) { mnObj.pushKV("proTxHash", dmn->proTxHash.ToString()); mnObj.pushKV("collateralHash", dmn->collateralOutpoint.hash.ToString()); mnObj.pushKV("collateralIndex", (int)dmn->collateralOutpoint.n); UniValue stateObj; dmn->pdmnState->ToJson(stateObj); mnObj.pushKV("dmnState", stateObj); } mnObj.pushKV("state", activeMasternodeManager->GetStateString()); mnObj.pushKV("status", activeMasternodeManager->GetStatus()); return mnObj; } static std::string GetRequiredPaymentsString(int nBlockHeight, const CDeterministicMNCPtr &payee) { std::string strPayments = "Unknown"; if (payee) { CTxDestination dest; if (!ExtractDestination(payee->pdmnState->scriptPayout, dest)) { assert(false); } strPayments = EncodeDestination(dest); if (payee->nOperatorReward != 0 && payee->pdmnState->scriptOperatorPayout != CScript()) { if (!ExtractDestination(payee->pdmnState->scriptOperatorPayout, dest)) { assert(false); } strPayments += ", " + EncodeDestination(dest); } } if (CSuperblockManager::IsSuperblockTriggered(nBlockHeight)) { std::vector voutSuperblock; if (!CSuperblockManager::GetSuperblockPayments(nBlockHeight, voutSuperblock)) { return strPayments + ", error"; } std::string strSBPayees = "Unknown"; for (const auto& txout : voutSuperblock) { CTxDestination dest; ExtractDestination(txout.scriptPubKey, dest); if (strSBPayees != "Unknown") { strSBPayees += ", " + EncodeDestination(dest); } else { strSBPayees = EncodeDestination(dest); } } strPayments += ", " + strSBPayees; } return strPayments; } static void masternode_winners_help() { throw std::runtime_error( "masternode winners ( count \"filter\" )\n" "Print list of masternode winners\n" "\nArguments:\n" "1. count (numeric, optional) number of last winners to return\n" "2. filter (string, optional) filter for returned winners\n" ); } static UniValue masternode_winners(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() > 3) masternode_winners_help(); const CBlockIndex* pindexTip{nullptr}; { LOCK(cs_main); pindexTip = chainActive.Tip(); if (!pindexTip) return NullUniValue; } int nCount = 10; std::string strFilter = ""; if (!request.params[1].isNull()) { nCount = atoi(request.params[1].get_str()); } if (!request.params[2].isNull()) { strFilter = request.params[2].get_str(); } UniValue obj(UniValue::VOBJ); int nChainTipHeight = pindexTip->nHeight; int nStartHeight = std::max(nChainTipHeight - nCount, 1); for (int h = nStartHeight; h <= nChainTipHeight; h++) { auto payee = deterministicMNManager->GetListForBlock(pindexTip->GetAncestor(h - 1)).GetMNPayee(); std::string strPayments = GetRequiredPaymentsString(h, payee); if (strFilter != "" && strPayments.find(strFilter) == std::string::npos) continue; obj.pushKV(strprintf("%d", h), strPayments); } auto projection = deterministicMNManager->GetListForBlock(pindexTip).GetProjectedMNPayees(20); for (size_t i = 0; i < projection.size(); i++) { int h = nChainTipHeight + 1 + i; std::string strPayments = GetRequiredPaymentsString(h, projection[i]); if (strFilter != "" && strPayments.find(strFilter) == std::string::npos) continue; obj.pushKV(strprintf("%d", h), strPayments); } return obj; } static void masternode_payments_help() { throw std::runtime_error( "masternode payments ( \"blockhash\" count )\n" "\nReturns an array of deterministic masternodes and their payments for the specified block\n" "\nArguments:\n" "1. \"blockhash\" (string, optional, default=tip) The hash of the starting block\n" "2. count (numeric, optional, default=1) The number of blocks to return.\n" " Will return previous blocks if is negative.\n" " Both 1 and -1 correspond to the chain tip.\n" "\nResult:\n" " [ (array) Blocks\n" " {\n" " \"height\" : n, (numeric) The height of the block\n" " \"blockhash\" : \"hash\", (string) The hash of the block\n" " \"amount\": n (numeric) Amount received in this block by all masternodes\n" " \"masternodes\": [ (array) Masternodes that received payments in this block\n" " {\n" " \"proTxHash\": \"xxxx\", (string) The hash of the corresponding ProRegTx\n" " \"amount\": n (numeric) Amount received by this masternode\n" " \"payees\": [ (array) Payees who received a share of this payment\n" " {\n" " \"address\" : \"xxx\", (string) Payee address\n" " \"script\" : \"xxx\", (string) Payee scriptPubKey\n" " \"amount\": n (numeric) Amount received by this payee\n" " },...\n" " ]\n" " },...\n" " ]\n" " },...\n" " ]\n" ); } static UniValue masternode_payments(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() > 3) { masternode_payments_help(); } CBlockIndex* pindex{nullptr}; if (g_txindex) { g_txindex->BlockUntilSyncedToCurrentChain(); } if (request.params[1].isNull()) { LOCK(cs_main); pindex = chainActive.Tip(); } else { LOCK(cs_main); uint256 blockHash = ParseHashV(request.params[1], "blockhash"); pindex = LookupBlockIndex(blockHash); if (pindex == nullptr) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } } int64_t nCount = request.params.size() > 2 ? ParseInt64V(request.params[2], "count") : 1; // A temporary vector which is used to sort results properly (there is no "reverse" in/for UniValue) std::vector vecPayments; while (vecPayments.size() < std::abs(nCount) != 0 && pindex != nullptr) { CBlock block; if (!ReadBlockFromDisk(block, pindex, Params().GetConsensus())) { throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk"); } // Note: we have to actually calculate block reward from scratch instead of simply querying coinbase vout // because miners might collect less coins than they potentially could and this would break our calculations. CAmount nBlockFees{0}; for (const auto& tx : block.vtx) { if (tx->IsCoinBase()) { continue; } CAmount nValueIn{0}; for (const auto txin : tx->vin) { CTransactionRef txPrev; uint256 blockHashTmp; GetTransaction(txin.prevout.hash, txPrev, Params().GetConsensus(), blockHashTmp); nValueIn += txPrev->vout[txin.prevout.n].nValue; } nBlockFees += nValueIn - tx->GetValueOut(); } std::vector voutMasternodePayments, voutDummy; CMutableTransaction dummyTx; CAmount blockReward = nBlockFees + GetBlockSubsidy(pindex->pprev->nBits, pindex->pprev->nHeight, Params().GetConsensus()); FillBlockPayments(dummyTx, pindex->nHeight, blockReward, voutMasternodePayments, voutDummy); UniValue blockObj(UniValue::VOBJ); CAmount payedPerBlock{0}; UniValue masternodeArr(UniValue::VARR); UniValue protxObj(UniValue::VOBJ); UniValue payeesArr(UniValue::VARR); CAmount payedPerMasternode{0}; for (const auto& txout : voutMasternodePayments) { UniValue obj(UniValue::VOBJ); CTxDestination dest; ExtractDestination(txout.scriptPubKey, dest); obj.pushKV("address", EncodeDestination(dest)); obj.pushKV("script", HexStr(txout.scriptPubKey)); obj.pushKV("amount", txout.nValue); payedPerMasternode += txout.nValue; payeesArr.push_back(obj); } const auto dmnPayee = deterministicMNManager->GetListForBlock(pindex).GetMNPayee(); protxObj.pushKV("proTxHash", dmnPayee == nullptr ? "" : dmnPayee->proTxHash.ToString()); protxObj.pushKV("amount", payedPerMasternode); protxObj.pushKV("payees", payeesArr); payedPerBlock += payedPerMasternode; masternodeArr.push_back(protxObj); blockObj.pushKV("height", pindex->nHeight); blockObj.pushKV("blockhash", pindex->GetBlockHash().ToString()); blockObj.pushKV("amount", payedPerBlock); blockObj.pushKV("masternodes", masternodeArr); vecPayments.push_back(blockObj); if (nCount > 0) { LOCK(cs_main); pindex = chainActive.Next(pindex); } else { pindex = pindex->pprev; } } if (nCount < 0) { std::reverse(vecPayments.begin(), vecPayments.end()); } UniValue paymentsArr(UniValue::VARR); for (const auto& payment : vecPayments) { paymentsArr.push_back(payment); } return paymentsArr; } [[ noreturn ]] static void masternode_help() { throw std::runtime_error( "masternode \"command\" ...\n" "Set of commands to execute masternode related actions\n" "\nArguments:\n" "1. \"command\" (string or set of strings, required) The command to execute\n" "\nAvailable commands:\n" " count - Get information about number of masternodes\n" " current - DEPRECATED Print info on current masternode winner to be paid the next block (calculated locally)\n" #ifdef ENABLE_WALLET " outputs - Print masternode compatible outputs\n" #endif // ENABLE_WALLET " status - Print masternode status information\n" " list - Print list of all known masternodes (see masternodelist for more info)\n" " payments - Return information about masternode payments in a mined block\n" " winner - DEPRECATED Print info on next masternode winner to vote for\n" " winners - Print list of masternode winners\n" ); } static UniValue masternode(const JSONRPCRequest& request) { std::string strCommand; if (!request.params[0].isNull()) { strCommand = request.params[0].get_str(); } if (request.fHelp && strCommand.empty()) { masternode_help(); } if (strCommand == "list") { return masternode_list(request); } else if (strCommand == "connect") { return masternode_connect(request); } else if (strCommand == "count") { return masternode_count(request); } else if (strCommand == "current") { return masternode_current(request); } else if (strCommand == "winner") { return masternode_winner(request); #ifdef ENABLE_WALLET } else if (strCommand == "outputs") { return masternode_outputs(request); #endif // ENABLE_WALLET } else if (strCommand == "status") { return masternode_status(request); } else if (strCommand == "payments") { return masternode_payments(request); } else if (strCommand == "winners") { return masternode_winners(request); } else { masternode_help(); } } static UniValue masternodelist(const JSONRPCRequest& request) { std::string strMode = "json"; std::string strFilter = ""; if (!request.params[0].isNull()) strMode = request.params[0].get_str(); if (!request.params[1].isNull()) strFilter = request.params[1].get_str(); std::transform(strMode.begin(), strMode.end(), strMode.begin(), ToLower); if (request.fHelp || ( strMode != "addr" && strMode != "full" && strMode != "info" && strMode != "json" && strMode != "owneraddress" && strMode != "votingaddress" && strMode != "lastpaidtime" && strMode != "lastpaidblock" && strMode != "payee" && strMode != "pubkeyoperator" && strMode != "status")) { masternode_list_help(); } UniValue obj(UniValue::VOBJ); auto mnList = deterministicMNManager->GetListAtChainTip(); auto dmnToStatus = [&](const CDeterministicMNCPtr& dmn) { if (mnList.IsMNValid(dmn)) { return "ENABLED"; } if (mnList.IsMNPoSeBanned(dmn)) { return "POSE_BANNED"; } return "UNKNOWN"; }; auto dmnToLastPaidTime = [&](const CDeterministicMNCPtr& dmn) { if (dmn->pdmnState->nLastPaidHeight == 0) { return (int)0; } LOCK(cs_main); const CBlockIndex* pindex = chainActive[dmn->pdmnState->nLastPaidHeight]; return (int)pindex->nTime; }; mnList.ForEachMN(false, [&](const CDeterministicMNCPtr& dmn) { std::string strOutpoint = dmn->collateralOutpoint.ToStringShort(); Coin coin; std::string collateralAddressStr = "UNKNOWN"; if (GetUTXOCoin(dmn->collateralOutpoint, coin)) { CTxDestination collateralDest; if (ExtractDestination(coin.out.scriptPubKey, collateralDest)) { collateralAddressStr = EncodeDestination(collateralDest); } } CScript payeeScript = dmn->pdmnState->scriptPayout; CTxDestination payeeDest; std::string payeeStr = "UNKNOWN"; if (ExtractDestination(payeeScript, payeeDest)) { payeeStr = EncodeDestination(payeeDest); } if (strMode == "addr") { std::string strAddress = dmn->pdmnState->addr.ToString(false); if (strFilter !="" && strAddress.find(strFilter) == std::string::npos && strOutpoint.find(strFilter) == std::string::npos) return; obj.pushKV(strOutpoint, strAddress); } else if (strMode == "full") { std::ostringstream streamFull; streamFull << std::setw(18) << dmnToStatus(dmn) << " " << payeeStr << " " << std::setw(10) << dmnToLastPaidTime(dmn) << " " << std::setw(6) << dmn->pdmnState->nLastPaidHeight << " " << dmn->pdmnState->addr.ToString(); std::string strFull = streamFull.str(); if (strFilter !="" && strFull.find(strFilter) == std::string::npos && strOutpoint.find(strFilter) == std::string::npos) return; obj.pushKV(strOutpoint, strFull); } else if (strMode == "info") { std::ostringstream streamInfo; streamInfo << std::setw(18) << dmnToStatus(dmn) << " " << payeeStr << " " << dmn->pdmnState->addr.ToString(); std::string strInfo = streamInfo.str(); if (strFilter !="" && strInfo.find(strFilter) == std::string::npos && strOutpoint.find(strFilter) == std::string::npos) return; obj.pushKV(strOutpoint, strInfo); } else if (strMode == "json") { std::ostringstream streamInfo; streamInfo << dmn->proTxHash.ToString() << " " << dmn->pdmnState->addr.ToString() << " " << payeeStr << " " << dmnToStatus(dmn) << " " << dmnToLastPaidTime(dmn) << " " << dmn->pdmnState->nLastPaidHeight << " " << EncodeDestination(dmn->pdmnState->keyIDOwner) << " " << EncodeDestination(dmn->pdmnState->keyIDVoting) << " " << collateralAddressStr << " " << dmn->pdmnState->pubKeyOperator.Get().ToString(); std::string strInfo = streamInfo.str(); if (strFilter !="" && strInfo.find(strFilter) == std::string::npos && strOutpoint.find(strFilter) == std::string::npos) return; UniValue objMN(UniValue::VOBJ); objMN.pushKV("proTxHash", dmn->proTxHash.ToString()); objMN.pushKV("address", dmn->pdmnState->addr.ToString()); objMN.pushKV("payee", payeeStr); objMN.pushKV("status", dmnToStatus(dmn)); objMN.pushKV("lastpaidtime", dmnToLastPaidTime(dmn)); objMN.pushKV("lastpaidblock", dmn->pdmnState->nLastPaidHeight); objMN.pushKV("owneraddress", EncodeDestination(dmn->pdmnState->keyIDOwner)); objMN.pushKV("votingaddress", EncodeDestination(dmn->pdmnState->keyIDVoting)); objMN.pushKV("collateraladdress", collateralAddressStr); objMN.pushKV("pubkeyoperator", dmn->pdmnState->pubKeyOperator.Get().ToString()); obj.pushKV(strOutpoint, objMN); } else if (strMode == "lastpaidblock") { if (strFilter !="" && strOutpoint.find(strFilter) == std::string::npos) return; obj.pushKV(strOutpoint, dmn->pdmnState->nLastPaidHeight); } else if (strMode == "lastpaidtime") { if (strFilter !="" && strOutpoint.find(strFilter) == std::string::npos) return; obj.pushKV(strOutpoint, dmnToLastPaidTime(dmn)); } else if (strMode == "payee") { if (strFilter !="" && payeeStr.find(strFilter) == std::string::npos && strOutpoint.find(strFilter) == std::string::npos) return; obj.pushKV(strOutpoint, payeeStr); } else if (strMode == "owneraddress") { if (strFilter !="" && strOutpoint.find(strFilter) == std::string::npos) return; obj.pushKV(strOutpoint, EncodeDestination(dmn->pdmnState->keyIDOwner)); } else if (strMode == "pubkeyoperator") { if (strFilter !="" && strOutpoint.find(strFilter) == std::string::npos) return; obj.pushKV(strOutpoint, dmn->pdmnState->pubKeyOperator.Get().ToString()); } else if (strMode == "status") { std::string strStatus = dmnToStatus(dmn); if (strFilter !="" && strStatus.find(strFilter) == std::string::npos && strOutpoint.find(strFilter) == std::string::npos) return; obj.pushKV(strOutpoint, strStatus); } else if (strMode == "votingaddress") { if (strFilter !="" && strOutpoint.find(strFilter) == std::string::npos) return; obj.pushKV(strOutpoint, EncodeDestination(dmn->pdmnState->keyIDVoting)); } }); return obj; } static const CRPCCommand commands[] = { // category name actor (function) argNames // --------------------- ------------------------ ----------------------- ---------- { "dash", "masternode", &masternode, {} }, { "dash", "masternodelist", &masternodelist, {} }, }; void RegisterMasternodeRPCCommands(CRPCTable &t) { for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++) t.appendCommand(commands[vcidx].name, &commands[vcidx]); }