text: // Copyright (c) 2014-2017 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 "activemasternode.h"
  #include "base58.h"
  #include "clientversion.h"
  #include "init.h"
  #include "netbase.h"
  #include "validation.h"
  #include "masternode-payments.h"
  #include "masternode-sync.h"
  #include "masternodeconfig.h"
  #include "masternodeman.h"
  #ifdef ENABLE_WALLET
  #include "privatesend-client.h"
  #endif // ENABLE_WALLET
  #include "privatesend-server.h"
  #include "rpc/server.h"
  #include "util.h"
  #include "utilmoneystr.h"
  #include "txmempool.h"
  
  #include "evo/specialtx.h"
  #include "evo/deterministicmns.h"
  
  #include "evo/deterministicmns.h"
  
  #include <fstream>
  #include <iomanip>
  #include <univalue.h>
  
  UniValue masternodelist(const JSONRPCRequest& request);
  
  bool EnsureWalletIsAvailable(bool avoidException);
  
  #ifdef ENABLE_WALLET
  void EnsureWalletIsUnlocked();
  
  UniValue privatesend(const JSONRPCRequest& request)
  {
      if (!EnsureWalletIsAvailable(request.fHelp))
          return NullUniValue;
  
      if (request.fHelp || request.params.size() != 1)
          throw std::runtime_error(
              "privatesend \"command\"\n"
              "\nArguments:\n"
              "1. \"command\"        (string or set of strings, required) The command to execute\n"
              "\nAvailable commands:\n"
              "  start       - Start mixing\n"
              "  stop        - Stop mixing\n"
              "  reset       - Reset mixing\n"
              );
  
      if (fMasternodeMode)
          throw JSONRPCError(RPC_INTERNAL_ERROR, "Client-side mixing is not supported on masternodes");
  
      if (request.params[0].get_str() == "start") {
          {
              LOCK(pwalletMain->cs_wallet);
              if (pwalletMain->IsLocked(true))
                  throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Error: Please unlock wallet for mixing with walletpassphrase first.");
          }
  
          privateSendClient.fEnablePrivateSend = true;
          bool result = privateSendClient.DoAutomaticDenominating(*g_connman);
          return "Mixing " + (result ? "started successfully" : ("start failed: " + privateSendClient.GetStatuses() + ", will retry"));
      }
  
      if (request.params[0].get_str() == "stop") {
          privateSendClient.fEnablePrivateSend = false;
          return "Mixing was stopped";
      }
  
      if (request.params[0].get_str() == "reset") {
          privateSendClient.ResetPool();
          return "Mixing was reset";
      }
  
      return "Unknown command, please see \"help privatesend\"";
  }
  #endif // ENABLE_WALLET
  
  UniValue getpoolinfo(const JSONRPCRequest& request)
  {
      if (request.fHelp || request.params.size() != 0)
          throw std::runtime_error(
              "getpoolinfo\n"
              "Returns an object containing mixing pool related information.\n");
  
  #ifdef ENABLE_WALLET
      CPrivateSendBaseManager* pprivateSendBaseManager = fMasternodeMode ? (CPrivateSendBaseManager*)&privateSendServer : (CPrivateSendBaseManager*)&privateSendClient;
  
      UniValue obj(UniValue::VOBJ);
      // TODO:
      // obj.push_back(Pair("state",             pprivateSendBase->GetStateString()));
      obj.push_back(Pair("queue",             pprivateSendBaseManager->GetQueueSize()));
      // obj.push_back(Pair("entries",           pprivateSendBase->GetEntriesCount()));
      obj.push_back(Pair("status",            privateSendClient.GetStatuses()));
  
      std::vector<masternode_info_t> vecMnInfo;
      if (privateSendClient.GetMixingMasternodesInfo(vecMnInfo)) {
          UniValue pools(UniValue::VARR);
          for (const auto& mnInfo : vecMnInfo) {
              UniValue pool(UniValue::VOBJ);
              pool.push_back(Pair("outpoint",      mnInfo.outpoint.ToStringShort()));
              pool.push_back(Pair("addr",          mnInfo.addr.ToString()));
              pools.push_back(pool);
          }
          obj.push_back(Pair("pools", pools));
      }
  
      if (pwalletMain) {
          obj.push_back(Pair("keys_left",     pwalletMain->nKeysLeftSinceAutoBackup));
          obj.push_back(Pair("warnings",      pwalletMain->nKeysLeftSinceAutoBackup < PRIVATESEND_KEYS_THRESHOLD_WARNING
                                                  ? "WARNING: keypool is almost depleted!" : ""));
      }
  #else // ENABLE_WALLET
      UniValue obj(UniValue::VOBJ);
      obj.push_back(Pair("state",             privateSendServer.GetStateString()));
      obj.push_back(Pair("queue",             privateSendServer.GetQueueSize()));
      obj.push_back(Pair("entries",           privateSendServer.GetEntriesCount()));
  #endif // ENABLE_WALLET
  
      return obj;
  }
  
  void masternode_list_help()
  {
      throw std::runtime_error(
              "masternode list ( \"mode\" \"filter\" )\n"
              "Get a list of masternodes in different modes. This call is identical to masternodelist 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"
              "  activeseconds  - Print number of seconds masternode recognized by the network as enabled\n"
              "                   (since latest issued \"masternode start/start-many/start-alias\")\n"
              "  addr           - Print ip address associated with a masternode (can be additionally filtered, partial match)\n"
              "  daemon         - Print daemon version of a masternode (can be additionally filtered, exact match)\n"
              "  full           - Print info in format 'status protocol payee lastseen activeseconds lastpaidtime lastpaidblock IP'\n"
              "                   (can be additionally filtered, partial match)\n"
              "  info           - Print info in format 'status protocol payee lastseen activeseconds sentinelversion sentinelstate 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"
              "  lastseen       - Print timestamp of when a masternode was last seen on the network\n"
              "  owneraddress   - Print the masternode owner Ukkey address\n"
              "  payee          - Print the masternode payout Ukkey address (can be additionally filtered,\n"
              "                   partial match)\n"
              "  protocol       - Print protocol of a masternode (can be additionally filtered, exact match)\n"
              "  keyid          - Print the masternode (not collateral) key id\n"
              "  rank           - Print rank of a masternode based on current block\n"
              "  sentinel       - Print sentinel version of a masternode (can be additionally filtered, exact match)\n"
              "  status         - Print masternode status: PRE_ENABLED / ENABLED / EXPIRED / SENTINEL_PING_EXPIRED / NEW_START_REQUIRED /\n"
              "                   UPDATE_REQUIRED / POSE_BAN / OUTPOINT_SPENT (can be additionally filtered, partial match)\n"
              "  votingaddress  - Print the masternode voting Ukkey address\n"
          );
  }
  
  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);
  }
  
  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"
          );
  }
  
  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";
  }
  
  void masternode_count_help()
  {
      throw std::runtime_error(
              "masternode count (\"mode\")\n"
              "  Get information about number of masternodes. Mode\n"
              "  usage is depricated, call without mode params returns\n"
              "  all values in JSON format.\n"
              "\nArguments:\n"
              "1. \"mode\"      (string, optional, DEPRICATED) Option to get number of masternodes in different states\n"
              "\nAvailable modes:\n"
              "  total         - total number of masternodes"
              "  ps            - number of PrivateSend compatible masternodes"
              "  enabled       - number of enabled masternodes"
              "  qualify       - number of qualified masternodes"
              "  all           - all above in one string"
          );
  }
  
  UniValue masternode_count(const JSONRPCRequest& request)
  {
      if (request.fHelp || request.params.size() > 2)
          masternode_count_help();
  
      int nCount;
      int total = mnodeman.CountMasternodes(0);
      if (deterministicMNManager->IsDeterministicMNsSporkActive()) {
          nCount = mnodeman.CountEnabled();
      } else {
          masternode_info_t mnInfo;
          mnodeman.GetNextMasternodeInQueueForPayment(true, nCount, mnInfo);
      }
  
      int ps = mnodeman.CountEnabled(MIN_PRIVATESEND_PEER_PROTO_VERSION);
      int enabled = mnodeman.CountEnabled();
  
      if (request.params.size() == 1) {
          UniValue obj(UniValue::VOBJ);
  
          obj.push_back(Pair("total", total));
          obj.push_back(Pair("ps_compatible", ps));
          obj.push_back(Pair("enabled", enabled));
          obj.push_back(Pair("qualify", nCount));
  
          return obj;
      }
  
      std::string strMode = request.params[1].get_str();
  
      if (strMode == "total")
          return total;
  
      if (strMode == "ps")
          return ps;
  
      if (strMode == "enabled")
          return enabled;
  
      if (strMode == "qualify")
          return nCount;
  
      if (strMode == "all")
          return strprintf("Total: %d (PS Compatible: %d / Enabled: %d / Qualify: %d)",
              total, ps, enabled, nCount);
  
      throw JSONRPCError(RPC_INVALID_PARAMETER, "Unknown mode value");
  }
  
  UniValue GetNextMasternodeForPayment(int heightShift)
  {
      int nCount;
      int nHeight;
      masternode_info_t mnInfo;
      CBlockIndex* pindex = NULL;
      {
          LOCK(cs_main);
          pindex = chainActive.Tip();
      }
  
      nHeight = pindex->nHeight + heightShift;
      mnodeman.UpdateLastPaid(pindex);
  
      CScript payeeScript;
      if (deterministicMNManager->IsDeterministicMNsSporkActive()) {
          auto payee = deterministicMNManager->GetListAtChainTip().GetMNPayee();
          if (!payee || !mnodeman.GetMasternodeInfo(payee->proTxHash, mnInfo))
              return "unknown";
          payeeScript = payee->pdmnState->scriptPayout;
      } else {
          if (!mnodeman.GetNextMasternodeInQueueForPayment(nHeight, true, nCount, mnInfo))
              return "unknown";
          payeeScript = GetScriptForDestination(mnInfo.keyIDCollateralAddress);
      }
  
      CTxDestination payeeDest;
      CBitcoinAddress payeeAddr;
      if (ExtractDestination(payeeScript, payeeDest)) {
          payeeAddr = CBitcoinAddress(payeeDest);
      }
  
      UniValue obj(UniValue::VOBJ);
  
      obj.push_back(Pair("height",        nHeight));
      obj.push_back(Pair("IP:port",       mnInfo.addr.ToString()));
      obj.push_back(Pair("protocol",      mnInfo.nProtocolVersion));
      obj.push_back(Pair("outpoint",      mnInfo.outpoint.ToStringShort()));
      obj.push_back(Pair("payee",         payeeAddr.IsValid() ? payeeAddr.ToString() : "UNKNOWN"));
      obj.push_back(Pair("lastseen",      mnInfo.nTimeLastPing));
      obj.push_back(Pair("activeseconds", mnInfo.nTimeLastPing - mnInfo.sigTime));
      return obj;
  }
  
  void masternode_winner_help()
  {
      throw std::runtime_error(
              "masternode winner\n"
              "Print info on next masternode winner to vote for\n"
          );
  }
  
  UniValue masternode_winner(const JSONRPCRequest& request)
  {
      if (request.fHelp)
          masternode_winner_help();
  
      return GetNextMasternodeForPayment(10);
  }
  
  void masternode_current_help()
  {
      throw std::runtime_error(
              "masternode current\n"
              "Print info on current masternode winner to be paid the next block (calculated locally)\n"
          );
  }
  
  UniValue masternode_current(const JSONRPCRequest& request)
  {
      if (request.fHelp)
          masternode_current_help();
  
      return GetNextMasternodeForPayment(1);
  }
  
  #ifdef ENABLE_WALLET
  void masternode_start_alias_help()
  {
      throw std::runtime_error(
              "masternode start-alias \"alias\"\n"
              "Start single remote masternode by assigned alias\n"
              "\nArguments:\n"
              "1. \"alias\"      (string, required) The alias of the remote masternode configured in masternode.conf\n"
          );
  }
  
  UniValue masternode_start_alias(const JSONRPCRequest& request)
  {
      if (request.fHelp || request.params.size() < 2)
          masternode_start_alias_help();
      if (deterministicMNManager->IsDeterministicMNsSporkActive())
          throw JSONRPCError(RPC_MISC_ERROR, "start-alias is not supported when deterministic masternode list is active (DIP3)");
  
      if (!EnsureWalletIsAvailable(request.fHelp))
          return NullUniValue;
  
      {
          LOCK(pwalletMain->cs_wallet);
          EnsureWalletIsUnlocked();
      }
  
      std::string strAlias = request.params[1].get_str();
  
      bool fFound = false;
  
      UniValue statusObj(UniValue::VOBJ);
      statusObj.push_back(Pair("alias", strAlias));
  
      for (const auto& mne : masternodeConfig.getEntries()) {
          if (mne.getAlias() == strAlias) {
              fFound = true;
              std::string strError;
              CMasternodeBroadcast mnb;
  
              bool fResult = CMasternodeBroadcast::Create(mne.getIp(), mne.getPrivKey(), mne.getTxHash(), mne.getOutputIndex(), strError, mnb);
  
              int nDoS;
              if (fResult && !mnodeman.CheckMnbAndUpdateMasternodeList(NULL, mnb, nDoS, *g_connman)) {
                  strError = "Failed to verify MNB";
                  fResult = false;
              }
  
              statusObj.push_back(Pair("result", fResult ? "successful" : "failed"));
              if (!fResult) {
                  statusObj.push_back(Pair("errorMessage", strError));
              }
              mnodeman.NotifyMasternodeUpdates(*g_connman);
              break;
          }
      }
  
      if (!fFound) {
          statusObj.push_back(Pair("result", "failed"));
          statusObj.push_back(Pair("errorMessage", "Could not find alias in config. Verify with list-conf."));
      }
  
      return statusObj;
  }
  
  void masternode_start_all_help()
  {
      throw std::runtime_error(
              "masternode start-all\n"
              "Start remote masternodes configured in masternode.conf\n"
          );
  }
  
  UniValue StartMasternodeList(const std::vector<CMasternodeConfig::CMasternodeEntry>& entries)
  {
      int nSuccessful = 0;
      int nFailed = 0;
  
      UniValue resultsObj(UniValue::VOBJ);
  
      for (const auto& mne : entries) {
          std::string strError;
          CMasternodeBroadcast mnb;
  
          bool fResult = CMasternodeBroadcast::Create(mne.getIp(), mne.getPrivKey(), mne.getTxHash(), mne.getOutputIndex(), strError, mnb);
  
          int nDoS;
          if (fResult && !mnodeman.CheckMnbAndUpdateMasternodeList(NULL, mnb, nDoS, *g_connman)) {
              strError = "Failed to verify MNB";
              fResult = false;
          }
  
          UniValue statusObj(UniValue::VOBJ);
          statusObj.push_back(Pair("alias", mne.getAlias()));
          statusObj.push_back(Pair("result", fResult ? "successful" : "failed"));
  
          if (fResult) {
              nSuccessful++;
          } else {
              nFailed++;
              statusObj.push_back(Pair("errorMessage", strError));
          }
  
          resultsObj.push_back(Pair("status", statusObj));
      }
      mnodeman.NotifyMasternodeUpdates(*g_connman);
  
      UniValue returnObj(UniValue::VOBJ);
      returnObj.push_back(Pair("overall", strprintf("Successfully started %d masternodes, failed to start %d, total %d", nSuccessful, nFailed, nSuccessful + nFailed)));
      returnObj.push_back(Pair("detail", resultsObj));
  
      return returnObj;
  }
  
  UniValue masternode_start_all(const JSONRPCRequest& request)
  {
      if (request.fHelp)
          masternode_start_all_help();
      if (deterministicMNManager->IsDeterministicMNsSporkActive())
          throw JSONRPCError(RPC_MISC_ERROR, strprintf("start-all is not supported when deterministic masternode list is active (DIP3)"));
  
      if (!EnsureWalletIsAvailable(request.fHelp))
          return NullUniValue;
  
      {
          LOCK(pwalletMain->cs_wallet);
          EnsureWalletIsUnlocked();
      }
  
      return StartMasternodeList(masternodeConfig.getEntries());
  }
  
  void masternode_start_missing_help()
  {
      throw std::runtime_error(
              "masternode start-missing\n"
              "Start not started remote masternodes configured in masternode.conf\n"
          );
  }
  
  UniValue masternode_start_missing(const JSONRPCRequest& request)
  {
      if (request.fHelp)
          masternode_start_missing_help();
  
      if (!EnsureWalletIsAvailable(request.fHelp))
          return NullUniValue;
  
      {
          LOCK(pwalletMain->cs_wallet);
          EnsureWalletIsUnlocked();
      }
  
      if (!masternodeSync.IsMasternodeListSynced()) {
          throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "You can't use this command until masternode list is synced");
      }
  
      std::vector<CMasternodeConfig::CMasternodeEntry> entries;
      for (const auto& mne : masternodeConfig.getEntries()) {
          COutPoint outpoint = COutPoint(uint256S(mne.getTxHash()), (uint32_t)atoi(mne.getOutputIndex()));
          CMasternode mn;
          bool fFound = mnodeman.Get(outpoint, mn);
          if (fFound)
              entries.push_back(mne);
      }
      return StartMasternodeList(entries);
  }
  
  void masternode_start_disabled_help()
  {
      throw std::runtime_error(
              "masternode start-disabled\n"
              "Start not started and disabled remote masternodes configured in masternode.conf\n"
          );
  }
  
  UniValue masternode_start_disabled(const JSONRPCRequest& request)
  {
      if (request.fHelp)
          masternode_start_disabled_help();
  
      if (!EnsureWalletIsAvailable(request.fHelp))
          return NullUniValue;
  
      {
          LOCK(pwalletMain->cs_wallet);
          EnsureWalletIsUnlocked();
      }
  
      if (!masternodeSync.IsMasternodeListSynced()) {
          throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "You can't use this command until masternode list is synced");
      }
  
      std::vector<CMasternodeConfig::CMasternodeEntry> entries;
      for (const auto& mne : masternodeConfig.getEntries()) {
          COutPoint outpoint = COutPoint(uint256S(mne.getTxHash()), (uint32_t)atoi(mne.getOutputIndex()));
          CMasternode mn;
          bool fFound = mnodeman.Get(outpoint, mn);
  
          if (fFound && mn.IsEnabled()) continue;
  
          entries.push_back(mne);
      }
      return StartMasternodeList(entries);
  }
  
  void masternode_outputs_help()
  {
      throw std::runtime_error(
              "masternode outputs\n"
              "Print masternode compatible outputs\n"
          );
  }
  
  UniValue masternode_outputs(const JSONRPCRequest& request)
  {
      if (request.fHelp)
          masternode_outputs_help();
  
      if (!EnsureWalletIsAvailable(request.fHelp))
          return NullUniValue;
  
      // Find possible candidates
      std::vector<COutput> vPossibleCoins;
      pwalletMain->AvailableCoins(vPossibleCoins, true, NULL, false, ONLY_5000);
  
      UniValue obj(UniValue::VOBJ);
      for (const auto& out : vPossibleCoins) {
          obj.push_back(Pair(out.tx->GetHash().ToString(), strprintf("%d", out.i)));
      }
  
      return obj;
  }
  
  #endif // ENABLE_WALLET
  
  void masternode_genkey_help()
  {
      throw std::runtime_error(
              "masternode genkey (compressed)\n"
              "Generate new masternodeprivkey\n"
              "\nArguments:\n"
              "1. compressed        (boolean, optional, default=false) generate compressed privkey\n"
          );
  }
  
  UniValue masternode_genkey(const JSONRPCRequest& request)
  {
      if (request.fHelp)
          masternode_genkey_help();
  
      bool fCompressed = false;
      if (request.params.size() > 1) {
          fCompressed = ParseBoolV(request.params[1], "compressed");
      }
  
      CKey secret;
      secret.MakeNewKey(fCompressed);
  
      return CBitcoinSecret(secret).ToString();
  }
  
  void masternode_list_conf_help()
  {
      throw std::runtime_error(
              "masternode list-conf\n"
              "Print masternode.conf in JSON format\n"
          );
  }
  
  UniValue masternode_list_conf(const JSONRPCRequest& request)
  {
      if (request.fHelp)
          masternode_list_conf_help();
  
      UniValue resultObj(UniValue::VOBJ);
  
      for (const auto& mne : masternodeConfig.getEntries()) {
          COutPoint outpoint = COutPoint(uint256S(mne.getTxHash()), (uint32_t)atoi(mne.getOutputIndex()));
          CMasternode mn;
          bool fFound = mnodeman.Get(outpoint, mn);
  
          std::string strStatus = fFound ? mn.GetStatus() : "MISSING";
  
          UniValue mnObj(UniValue::VOBJ);
          mnObj.push_back(Pair("alias", mne.getAlias()));
          mnObj.push_back(Pair("address", mne.getIp()));
          mnObj.push_back(Pair("privateKey", mne.getPrivKey()));
          mnObj.push_back(Pair("txHash", mne.getTxHash()));
          mnObj.push_back(Pair("outputIndex", mne.getOutputIndex()));
          mnObj.push_back(Pair("status", strStatus));
          resultObj.push_back(Pair("masternode", mnObj));
      }
  
      return resultObj;
  }
  
  void masternode_status_help()
  {
      throw std::runtime_error(
              "masternode status\n"
              "Print masternode status information\n"
          );
  }
  
  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);
  
      // keep compatibility with legacy status for now (might get deprecated/removed later)
      mnObj.push_back(Pair("outpoint", activeMasternodeInfo.outpoint.ToStringShort()));
      mnObj.push_back(Pair("service", activeMasternodeInfo.service.ToString()));
  
      if (deterministicMNManager->IsDeterministicMNsSporkActive()) {
          auto dmn = activeMasternodeManager->GetDMN();
          if (dmn) {
              mnObj.push_back(Pair("proTxHash", dmn->proTxHash.ToString()));
              mnObj.push_back(Pair("collateralHash", dmn->collateralOutpoint.hash.ToString()));
              mnObj.push_back(Pair("collateralIndex", (int)dmn->collateralOutpoint.n));
              UniValue stateObj;
              dmn->pdmnState->ToJson(stateObj);
              mnObj.push_back(Pair("dmnState", stateObj));
          }
          mnObj.push_back(Pair("state", activeMasternodeManager->GetStateString()));
          mnObj.push_back(Pair("status", activeMasternodeManager->GetStatus()));
      } else {
          CMasternode mn;
          if (mnodeman.Get(activeMasternodeInfo.outpoint, mn)) {
              mnObj.push_back(Pair("payee", CBitcoinAddress(mn.keyIDCollateralAddress).ToString()));
          }
  
          mnObj.push_back(Pair("status", legacyActiveMasternodeManager.GetStatus()));
      }
      return mnObj;
  }
  
  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"
          );
  }
  
  UniValue masternode_winners(const JSONRPCRequest& request)
  {
      if (request.fHelp)
          masternode_winners_help();
  
      int nHeight;
      {
          LOCK(cs_main);
          CBlockIndex* pindex = chainActive.Tip();
          if (!pindex) return NullUniValue;
  
          nHeight = pindex->nHeight;
      }
  
      int nLast = 10;
      std::string strFilter = "";
  
      if (request.params.size() >= 2) {
          nLast = atoi(request.params[1].get_str());
      }
  
      if (request.params.size() == 3) {
          strFilter = request.params[2].get_str();
      }
  
      if (request.params.size() > 3)
          throw JSONRPCError(RPC_INVALID_PARAMETER, "Correct usage is 'masternode winners ( \"count\" \"filter\" )'");
  
      UniValue obj(UniValue::VOBJ);
      auto mapPayments = GetRequiredPaymentsStrings(nHeight - nLast, nHeight + 20);
      for (const auto &p : mapPayments) {
          obj.push_back(Pair(strprintf("%d", p.first), p.second));
      }
  
      return obj;
  }
  
  void masternode_check_help()
  {
      throw std::runtime_error(
              "masternode check\n"
              "Force check all masternodes and remove invalid ones\n"
          );
  }
  
  UniValue masternode_check(const JSONRPCRequest& request)
  {
      if (request.fHelp)
          masternode_check_help();
  
      int countBeforeCheck = mnodeman.CountMasternodes();
      int countEnabledBeforeCheck = mnodeman.CountEnabled();
  
      mnodeman.CheckAndRemove(*g_connman);
  
      int countAfterCheck = mnodeman.CountMasternodes();
      int countEnabledAfterCheck = mnodeman.CountEnabled();
      int removedCount = std::max(0, countBeforeCheck - countAfterCheck);
      int removedEnabledCount = std::max(0, countEnabledBeforeCheck - countEnabledAfterCheck);
  
      UniValue obj(UniValue::VOBJ);
      obj.push_back(Pair("removedTotalCount", removedCount));
      obj.push_back(Pair("removedEnabledCount", removedEnabledCount));
      obj.push_back(Pair("totalCount", countAfterCheck));
      obj.push_back(Pair("enabledCount", countEnabledAfterCheck));
  
      return obj;
  }
  
  [[ noreturn ]] 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"
          "  check        - Force check all masternodes and remove invalid ones\n"
          "  count        - Get information about number of masternodes (DEPRECATED options: 'total', 'ps', 'enabled', 'qualify', 'all')\n"
          "  current      - Print info on current masternode winner to be paid the next block (calculated locally)\n"
          "  genkey       - Generate new masternodeprivkey, optional param: 'compressed' (boolean, optional, default=false) generate compressed privkey\n"
  #ifdef ENABLE_WALLET
          "  outputs      - Print masternode compatible outputs\n"
          "  start-alias  - Start single remote masternode by assigned alias configured in masternode.conf\n"
          "  start-<mode> - Start remote masternodes configured in masternode.conf (<mode>: 'all', 'missing', 'disabled')\n"
  #endif // ENABLE_WALLET
          "  status       - Print masternode status information\n"
          "  list         - Print list of all known masternodes (see masternodelist for more info)\n"
          "  list-conf    - Print masternode.conf in JSON format\n"
          "  winner       - Print info on next masternode winner to vote for\n"
          "  winners      - Print list of masternode winners\n"
          );
  }
  
  UniValue masternode(const JSONRPCRequest& request)
  {
      std::string strCommand;
      if (request.params.size() >= 1) {
          strCommand = request.params[0].get_str();
      }
  
  #ifdef ENABLE_WALLET
      if (strCommand == "start-many")
          throw JSONRPCError(RPC_INVALID_PARAMETER, "DEPRECATED, please use start-all instead");
  #endif // ENABLE_WALLET
  
      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 == "start-alias") {
          return masternode_start_alias(request);
      } else if (strCommand == "start-all") {
          return masternode_start_all(request);
      } else if (strCommand == "start-missing") {
          return masternode_start_missing(request);
      } else if (strCommand == "start-disabled") {
          return masternode_start_disabled(request);
  #endif // ENABLE_WALLET
      } else if (strCommand == "genkey") {
          return masternode_genkey(request);
      } else if (strCommand == "list-conf") {
          return masternode_list_conf(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 == "winners") {
          return masternode_winners(request);
      } else if (strCommand == "check") {
          return masternode_check(request);
      } else {
          masternode_help();
      }
  }
  
  UniValue masternodelist(const JSONRPCRequest& request)
  {
      std::string strMode = "json";
      std::string strFilter = "";
  
      if (request.params.size() >= 1) strMode = request.params[0].get_str();
      if (request.params.size() == 2) strFilter = request.params[1].get_str();
  
      if (request.fHelp || (
                  strMode != "activeseconds" && strMode != "addr" && strMode != "daemon" && strMode != "full" && strMode != "info" && strMode != "json" &&
                  strMode != "owneraddress" && strMode != "votingaddress" && strMode != "keyid" &&
                  strMode != "lastseen" && strMode != "lastpaidtime" && strMode != "lastpaidblock" &&
                  strMode != "protocol" && strMode != "payee" && strMode != "pubkey" &&
                  strMode != "rank" && strMode != "sentinel" && strMode != "status"))
      {
          masternode_list_help();
      }
  
      if (strMode == "full" || strMode == "json" || strMode == "lastpaidtime" || strMode == "lastpaidblock") {
          CBlockIndex* pindex = NULL;
          {
              LOCK(cs_main);
              pindex = chainActive.Tip();
          }
          mnodeman.UpdateLastPaid(pindex);
      }
  
      UniValue obj(UniValue::VOBJ);
      if (strMode == "rank") {
          CMasternodeMan::rank_pair_vec_t vMasternodeRanks;
          mnodeman.GetMasternodeRanks(vMasternodeRanks);
          for (const auto& rankpair : vMasternodeRanks) {
              std::string strOutpoint = rankpair.second.outpoint.ToStringShort();
              if (strFilter !="" && strOutpoint.find(strFilter) == std::string::npos) continue;
              obj.push_back(Pair(strOutpoint, rankpair.first));
          }
      } else {
          std::map<COutPoint, CMasternode> mapMasternodes = mnodeman.GetFullMasternodeMap();
          for (const auto& mnpair : mapMasternodes) {
              CMasternode mn = mnpair.second;
              std::string strOutpoint = mnpair.first.ToStringShort();
  
              CScript payeeScript;
              std::string collateralAddressStr = "UNKNOWN";
              if (deterministicMNManager->IsDeterministicMNsSporkActive()) {
                  auto dmn = deterministicMNManager->GetListAtChainTip().GetMNByCollateral(mn.outpoint);
                  if (dmn) {
                      payeeScript = dmn->pdmnState->scriptPayout;
                      Coin coin;
                      if (GetUTXOCoin(dmn->collateralOutpoint, coin)) {
                          CTxDestination collateralDest;
                          if (ExtractDestination(coin.out.scriptPubKey, collateralDest)) {
                              collateralAddressStr = CBitcoinAddress(collateralDest).ToString();
                          }
                      }
                  }
              } else {
                  payeeScript = GetScriptForDestination(mn.keyIDCollateralAddress);
                  collateralAddressStr = CBitcoinAddress(mn.keyIDCollateralAddress).ToString();
              }
  
              CTxDestination payeeDest;
              std::string payeeStr = "UNKNOWN";
              if (ExtractDestination(payeeScript, payeeDest)) {
                  payeeStr = CBitcoinAddress(payeeDest).ToString();
              }
  
              if (strMode == "activeseconds") {
                  if (strFilter !="" && strOutpoint.find(strFilter) == std::string::npos) continue;
                  obj.push_back(Pair(strOutpoint, (int64_t)(mn.lastPing.sigTime - mn.sigTime)));
              } else if (strMode == "addr") {
                  std::string strAddress = mn.addr.ToString();
                  if (strFilter !="" && strAddress.find(strFilter) == std::string::npos &&
                      strOutpoint.find(strFilter) == std::string::npos) continue;
                  obj.push_back(Pair(strOutpoint, strAddress));
              } else if (strMode == "daemon") {
                  std::string strDaemon = mn.lastPing.GetDaemonString();
                  if (strFilter !="" && strDaemon.find(strFilter) == std::string::npos &&
                      strOutpoint.find(strFilter) == std::string::npos) continue;
                  obj.push_back(Pair(strOutpoint, strDaemon));
              } else if (strMode == "sentinel") {
                  std::string strSentinel = mn.lastPing.GetSentinelString();
                  if (strFilter !="" && strSentinel.find(strFilter) == std::string::npos &&
                      strOutpoint.find(strFilter) == std::string::npos) continue;
                  obj.push_back(Pair(strOutpoint, strSentinel));
              } else if (strMode == "full") {
                  std::ostringstream streamFull;
                  streamFull << std::setw(18) <<
                                 mn.GetStatus() << " " <<
                                 mn.nProtocolVersion << " " <<
                                 payeeStr << " " <<
                                 (int64_t)mn.lastPing.sigTime << " " << std::setw(8) <<
                                 (int64_t)(mn.lastPing.sigTime - mn.sigTime) << " " << std::setw(10) <<
                                 mn.GetLastPaidTime() << " "  << std::setw(6) <<
                                 mn.GetLastPaidBlock() << " " <<
                                 mn.addr.ToString();
                  std::string strFull = streamFull.str();
                  if (strFilter !="" && strFull.find(strFilter) == std::string::npos &&
                      strOutpoint.find(strFilter) == std::string::npos) continue;
                  obj.push_back(Pair(strOutpoint, strFull));
              } else if (strMode == "info") {
                  std::ostringstream streamInfo;
                  streamInfo << std::setw(18) <<
                                 mn.GetStatus() << " " <<
                                 mn.nProtocolVersion << " " <<
                                 payeeStr << " " <<
                                 (int64_t)mn.lastPing.sigTime << " " << std::setw(8) <<
                                 (int64_t)(mn.lastPing.sigTime - mn.sigTime) << " " <<
                                 mn.lastPing.GetSentinelString() << " "  <<
                                 (mn.lastPing.fSentinelIsCurrent ? "current" : "expired") << " " <<
                                 mn.addr.ToString();
                  std::string strInfo = streamInfo.str();
                  if (strFilter !="" && strInfo.find(strFilter) == std::string::npos &&
                      strOutpoint.find(strFilter) == std::string::npos) continue;
                  obj.push_back(Pair(strOutpoint, strInfo));
              } else if (strMode == "json") {
                  std::ostringstream streamInfo;
                  streamInfo <<  mn.addr.ToString() << " " <<
                                 payeeStr << " " <<
                                 mn.GetStatus() << " " <<
                                 mn.nProtocolVersion << " " <<
                                 mn.lastPing.nDaemonVersion << " " <<
                                 mn.lastPing.GetSentinelString() << " " <<
                                 (mn.lastPing.fSentinelIsCurrent ? "current" : "expired") << " " <<
                                 (int64_t)mn.lastPing.sigTime << " " <<
                                 (int64_t)(mn.lastPing.sigTime - mn.sigTime) << " " <<
                                 mn.GetLastPaidTime() << " " <<
                                 mn.GetLastPaidBlock() << " " <<
                                 collateralAddressStr;
                  std::string strInfo = streamInfo.str();
                  if (strFilter !="" && strInfo.find(strFilter) == std::string::npos &&
                      strOutpoint.find(strFilter) == std::string::npos) continue;
                  UniValue objMN(UniValue::VOBJ);
                  objMN.push_back(Pair("address", mn.addr.ToString()));
                  objMN.push_back(Pair("payee", payeeStr));
                  objMN.push_back(Pair("status", mn.GetStatus()));
                  objMN.push_back(Pair("protocol", mn.nProtocolVersion));
                  objMN.push_back(Pair("daemonversion", mn.lastPing.GetDaemonString()));
                  objMN.push_back(Pair("sentinelversion", mn.lastPing.GetSentinelString()));
                  objMN.push_back(Pair("sentinelstate", (mn.lastPing.fSentinelIsCurrent ? "current" : "expired")));
                  objMN.push_back(Pair("lastseen", (int64_t)mn.lastPing.sigTime));
                  objMN.push_back(Pair("activeseconds", (int64_t)(mn.lastPing.sigTime - mn.sigTime)));
                  objMN.push_back(Pair("lastpaidtime", mn.GetLastPaidTime()));
                  objMN.push_back(Pair("lastpaidblock", mn.GetLastPaidBlock()));
                  objMN.push_back(Pair("owneraddress", CBitcoinAddress(mn.keyIDOwner).ToString()));
                  objMN.push_back(Pair("votingaddress", CBitcoinAddress(mn.keyIDVoting).ToString()));
                  objMN.push_back(Pair("collateraladdress", collateralAddressStr));
                  obj.push_back(Pair(strOutpoint, objMN));
              } else if (strMode == "keyid") {
                  if (strFilter !="" && strOutpoint.find(strFilter) == std::string::npos) continue;
                  obj.push_back(Pair(strOutpoint, HexStr(mn.legacyKeyIDOperator)));
              } else if (strMode == "lastpaidblock") {
                  if (strFilter !="" && strOutpoint.find(strFilter) == std::string::npos) continue;
                  obj.push_back(Pair(strOutpoint, mn.GetLastPaidBlock()));
              } else if (strMode == "lastpaidtime") {
                  if (strFilter !="" && strOutpoint.find(strFilter) == std::string::npos) continue;
                  obj.push_back(Pair(strOutpoint, mn.GetLastPaidTime()));
              } else if (strMode == "lastseen") {
                  if (strFilter !="" && strOutpoint.find(strFilter) == std::string::npos) continue;
                  obj.push_back(Pair(strOutpoint, (int64_t)mn.lastPing.sigTime));
              } else if (strMode == "owneraddress") {
                  if (strFilter !="" && strOutpoint.find(strFilter) == std::string::npos) continue;
                  obj.push_back(Pair(strOutpoint, CBitcoinAddress(mn.keyIDOwner).ToString()));
              } else if (strMode == "payee") {
                  if (strFilter !="" && payeeStr.find(strFilter) == std::string::npos &&
                      strOutpoint.find(strFilter) == std::string::npos) continue;
                  obj.push_back(Pair(strOutpoint, payeeStr));
              } else if (strMode == "protocol") {
                  if (strFilter !="" && strFilter != strprintf("%d", mn.nProtocolVersion) &&
                      strOutpoint.find(strFilter) == std::string::npos) continue;
                  obj.push_back(Pair(strOutpoint, mn.nProtocolVersion));
              } else if (strMode == "status") {
                  std::string strStatus = mn.GetStatus();
                  if (strFilter !="" && strStatus.find(strFilter) == std::string::npos &&
                      strOutpoint.find(strFilter) == std::string::npos) continue;
                  obj.push_back(Pair(strOutpoint, strStatus));
              } else if (strMode == "votingaddress") {
                  if (strFilter !="" && strOutpoint.find(strFilter) == std::string::npos) continue;
                  obj.push_back(Pair(strOutpoint, CBitcoinAddress(mn.keyIDVoting).ToString()));
              }
          }
      }
      return obj;
  }
  
  bool DecodeHexVecMnb(std::vector<CMasternodeBroadcast>& vecMnb, std::string strHexMnb) {
  
      if (!IsHex(strHexMnb))
          return false;
  
      std::vector<unsigned char> mnbData(ParseHex(strHexMnb));
      CDataStream ssData(mnbData, SER_NETWORK, PROTOCOL_VERSION);
      try {
          ssData >> vecMnb;
      } catch (const std::exception&) {
          return false;
      }
  
      return true;
  }
  
  UniValue masternodebroadcast(const JSONRPCRequest& request)
  {
      std::string strCommand;
      if (request.params.size() >= 1)
          strCommand = request.params[0].get_str();
  
      if (request.fHelp  ||
          (
  #ifdef ENABLE_WALLET
              strCommand != "create-alias" && strCommand != "create-all" &&
  #endif // ENABLE_WALLET
              strCommand != "decode" && strCommand != "relay"))
          throw std::runtime_error(
                  "masternodebroadcast \"command\"...\n"
                  "Set of commands to create and relay masternode broadcast messages\n"
                  "\nArguments:\n"
                  "1. \"command\"        (string or set of strings, required) The command to execute\n"
                  "\nAvailable commands:\n"
  #ifdef ENABLE_WALLET
                  "  create-alias  - Create single remote masternode broadcast message by assigned alias configured in masternode.conf\n"
                  "  create-all    - Create remote masternode broadcast messages for all masternodes configured in masternode.conf\n"
  #endif // ENABLE_WALLET
                  "  decode        - Decode masternode broadcast message\n"
                  "  relay         - Relay masternode broadcast message to the network\n"
                  );
  
  #ifdef ENABLE_WALLET
      if (strCommand == "create-alias") {
          if (!EnsureWalletIsAvailable(request.fHelp))
              return NullUniValue;
  
          // wait for reindex and/or import to finish
          if (fImporting || fReindex)
              throw JSONRPCError(RPC_INTERNAL_ERROR, "Wait for reindex and/or import to finish");
  
          if (request.params.size() < 2)
              throw JSONRPCError(RPC_INVALID_PARAMETER, "Please specify an alias");
  
          {
              LOCK(pwalletMain->cs_wallet);
              EnsureWalletIsUnlocked();
          }
  
          bool fFound = false;
          std::string strAlias = request.params[1].get_str();
  
          UniValue statusObj(UniValue::VOBJ);
          std::vector<CMasternodeBroadcast> vecMnb;
  
          statusObj.push_back(Pair("alias", strAlias));
  
          for (const auto& mne : masternodeConfig.getEntries()) {
              if (mne.getAlias() == strAlias) {
                  fFound = true;
                  std::string strError;
                  CMasternodeBroadcast mnb;
  
                  bool fResult = CMasternodeBroadcast::Create(mne.getIp(), mne.getPrivKey(), mne.getTxHash(), mne.getOutputIndex(), strError, mnb, true);
  
                  statusObj.push_back(Pair("result", fResult ? "successful" : "failed"));
                  if (fResult) {
                      vecMnb.push_back(mnb);
                      CDataStream ssVecMnb(SER_NETWORK, PROTOCOL_VERSION);
                      ssVecMnb << vecMnb;
                      statusObj.push_back(Pair("hex", HexStr(ssVecMnb)));
                  } else {
                      statusObj.push_back(Pair("errorMessage", strError));
                  }
                  break;
              }
          }
  
          if (!fFound) {
              statusObj.push_back(Pair("result", "not found"));
              statusObj.push_back(Pair("errorMessage", "Could not find alias in config. Verify with list-conf."));
          }
  
          return statusObj;
  
      }
  
      if (strCommand == "create-all") {
          if (!EnsureWalletIsAvailable(request.fHelp))
              return NullUniValue;
  
          // wait for reindex and/or import to finish
          if (fImporting || fReindex)
              throw JSONRPCError(RPC_INTERNAL_ERROR, "Wait for reindex and/or import to finish");
  
          {
              LOCK(pwalletMain->cs_wallet);
              EnsureWalletIsUnlocked();
          }
  
          int nSuccessful = 0;
          int nFailed = 0;
  
          UniValue resultsObj(UniValue::VOBJ);
          std::vector<CMasternodeBroadcast> vecMnb;
  
          for (const auto& mne : masternodeConfig.getEntries()) {
              std::string strError;
              CMasternodeBroadcast mnb;
  
              bool fResult = CMasternodeBroadcast::Create(mne.getIp(), mne.getPrivKey(), mne.getTxHash(), mne.getOutputIndex(), strError, mnb, true);
  
              UniValue statusObj(UniValue::VOBJ);
              statusObj.push_back(Pair("alias", mne.getAlias()));
              statusObj.push_back(Pair("result", fResult ? "successful" : "failed"));
  
              if (fResult) {
                  nSuccessful++;
                  vecMnb.push_back(mnb);
              } else {
                  nFailed++;
                  statusObj.push_back(Pair("errorMessage", strError));
              }
  
              resultsObj.push_back(Pair("status", statusObj));
          }
  
          CDataStream ssVecMnb(SER_NETWORK, PROTOCOL_VERSION);
          ssVecMnb << vecMnb;
          UniValue returnObj(UniValue::VOBJ);
          returnObj.push_back(Pair("overall", strprintf("Successfully created broadcast messages for %d masternodes, failed to create %d, total %d", nSuccessful, nFailed, nSuccessful + nFailed)));
          returnObj.push_back(Pair("detail", resultsObj));
          returnObj.push_back(Pair("hex", HexStr(ssVecMnb.begin(), ssVecMnb.end())));
  
          return returnObj;
      }
  #endif // ENABLE_WALLET
  
      if (strCommand == "decode") {
          if (request.params.size() != 2)
              throw JSONRPCError(RPC_INVALID_PARAMETER, "Correct usage is 'masternodebroadcast decode \"hexstring\"'");
  
          std::vector<CMasternodeBroadcast> vecMnb;
  
          if (!DecodeHexVecMnb(vecMnb, request.params[1].get_str()))
              throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Masternode broadcast message decode failed");
  
          int nSuccessful = 0;
          int nFailed = 0;
          int nDos = 0;
          UniValue returnObj(UniValue::VOBJ);
  
          for (const auto& mnb : vecMnb) {
              UniValue resultObj(UniValue::VOBJ);
  
              if (mnb.CheckSignature(nDos)) {
                  nSuccessful++;
                  resultObj.push_back(Pair("outpoint", mnb.outpoint.ToStringShort()));
                  resultObj.push_back(Pair("addr", mnb.addr.ToString()));
                  resultObj.push_back(Pair("keyIDCollateralAddress", CBitcoinAddress(mnb.keyIDCollateralAddress).ToString()));
                  resultObj.push_back(Pair("keyIDMasternode", CBitcoinAddress(mnb.legacyKeyIDOperator).ToString()));
                  resultObj.push_back(Pair("vchSig", EncodeBase64(&mnb.vchSig[0], mnb.vchSig.size())));
                  resultObj.push_back(Pair("sigTime", mnb.sigTime));
                  resultObj.push_back(Pair("protocolVersion", mnb.nProtocolVersion));
                  resultObj.push_back(Pair("nLastDsq", mnb.nLastDsq));
  
                  UniValue lastPingObj(UniValue::VOBJ);
                  lastPingObj.push_back(Pair("outpoint", mnb.lastPing.masternodeOutpoint.ToStringShort()));
                  lastPingObj.push_back(Pair("blockHash", mnb.lastPing.blockHash.ToString()));
                  lastPingObj.push_back(Pair("sigTime", mnb.lastPing.sigTime));
                  lastPingObj.push_back(Pair("vchSig", EncodeBase64(&mnb.lastPing.vchSig[0], mnb.lastPing.vchSig.size())));
  
                  resultObj.push_back(Pair("lastPing", lastPingObj));
              } else {
                  nFailed++;
                  resultObj.push_back(Pair("errorMessage", "Masternode broadcast signature verification failed"));
              }
  
              returnObj.push_back(Pair(mnb.GetHash().ToString(), resultObj));
          }
  
          returnObj.push_back(Pair("overall", strprintf("Successfully decoded broadcast messages for %d masternodes, failed to decode %d, total %d", nSuccessful, nFailed, nSuccessful + nFailed)));
  
          return returnObj;
      }
  
      if (strCommand == "relay") {
          if (request.params.size() < 2 || request.params.size() > 3)
              throw JSONRPCError(RPC_INVALID_PARAMETER,   "masternodebroadcast relay \"hexstring\"\n"
                                                          "\nArguments:\n"
                                                          "1. \"hex\"      (string, required) Broadcast messages hex string\n");
  
          std::vector<CMasternodeBroadcast> vecMnb;
  
          if (!DecodeHexVecMnb(vecMnb, request.params[1].get_str()))
              throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Masternode broadcast message decode failed");
  
          int nSuccessful = 0;
          int nFailed = 0;
          UniValue returnObj(UniValue::VOBJ);
  
          // verify all signatures first, bailout if any of them broken
          for (const auto& mnb : vecMnb) {
              UniValue resultObj(UniValue::VOBJ);
  
              resultObj.push_back(Pair("outpoint", mnb.outpoint.ToStringShort()));
              resultObj.push_back(Pair("addr", mnb.addr.ToString()));
  
              int nDos = 0;
              bool fResult;
              if (mnb.CheckSignature(nDos)) {
                  fResult = mnodeman.CheckMnbAndUpdateMasternodeList(NULL, mnb, nDos, *g_connman);
                  mnodeman.NotifyMasternodeUpdates(*g_connman);
              } else fResult = false;
  
              if (fResult) {
                  nSuccessful++;
                  resultObj.push_back(Pair(mnb.GetHash().ToString(), "successful"));
              } else {
                  nFailed++;
                  resultObj.push_back(Pair("errorMessage", "Masternode broadcast signature verification failed"));
              }
  
              returnObj.push_back(Pair(mnb.GetHash().ToString(), resultObj));
          }
  
          returnObj.push_back(Pair("overall", strprintf("Successfully relayed broadcast messages for %d masternodes, failed to relay %d, total %d", nSuccessful, nFailed, nSuccessful + nFailed)));
  
          return returnObj;
      }
  
      return NullUniValue;
  }
  
  UniValue sentinelping(const JSONRPCRequest& request)
  {
      if (request.fHelp || request.params.size() != 1) {
          throw std::runtime_error(
              "sentinelping version\n"
              "\nSentinel ping.\n"
              "\nArguments:\n"
              "1. version           (string, required) Sentinel version in the form \"x.x.x\"\n"
              "\nResult:\n"
              "state                (boolean) Ping result\n"
              "\nExamples:\n"
              + HelpExampleCli("sentinelping", "1.0.2")
              + HelpExampleRpc("sentinelping", "1.0.2")
          );
      }
  
      legacyActiveMasternodeManager.UpdateSentinelPing(StringVersionToInt(request.params[0].get_str()));
      return true;
  }
  
  static const CRPCCommand commands[] =
  { //  category              name                      actor (function)         okSafe argNames
    //  --------------------- ------------------------  -----------------------  ------ ----------
      { "ukkey",               "masternode",             &masternode,             true,  {} },
      { "ukkey",               "masternodelist",         &masternodelist,         true,  {} },
      { "ukkey",               "masternodebroadcast",    &masternodebroadcast,    true,  {} },
      { "ukkey",               "getpoolinfo",            &getpoolinfo,            true,  {} },
      { "ukkey",               "sentinelping",           &sentinelping,           true,  {} },
  #ifdef ENABLE_WALLET
      { "ukkey",               "privatesend",            &privatesend,            false, {} },
  #endif // ENABLE_WALLET
  };
  
  void RegisterMasternodeRPCCommands(CRPCTable &t)
  {
      for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++)
          t.appendCommand(commands[vcidx].name, &commands[vcidx]);
  }
  