text: // Copyright (c) 2014-2015 The Dash developers
  // Copyright (c) 2017-2018 The PIVX developers
  // Copyright (c) 2018 The LRMcoin developers
  // Distributed under the MIT/X11 software license, see the accompanying
  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
  
  #include "masternode-payments.h"
  #include "addrman.h"
  #include "masternode-budget.h"
  #include "masternode-sync.h"
  #include "masternodeman.h"
  #include "obfuscation.h"
  #include "spork.h"
  #include "sync.h"
  #include "util.h"
  #include "utilmoneystr.h"
  #include <boost/filesystem.hpp>
  #include <boost/lexical_cast.hpp>
  #define REV_BLOCK 260000
  #define PHZ_BLOCK 10000
  
  /** Object for who's going to get paid on which blocks */
  CMasternodePayments masternodePayments;
  
  CCriticalSection cs_vecPayments;
  CCriticalSection cs_mapMasternodeBlocks;
  CCriticalSection cs_mapMasternodePayeeVotes;
  
  //
  // CMasternodePaymentDB
  //
  
  CMasternodePaymentDB::CMasternodePaymentDB()
  {
      pathDB = GetDataDir() / "mnpayments.dat";
      strMagicMessage = "MasternodePayments";
  }
  
  bool CMasternodePaymentDB::Write(const CMasternodePayments& objToSave)
  {
      int64_t nStart = GetTimeMillis();
  
      // serialize, checksum data up to that point, then append checksum
      CDataStream ssObj(SER_DISK, CLIENT_VERSION);
      ssObj << strMagicMessage;                   // masternode cache file specific magic message
      ssObj << FLATDATA(Params().MessageStart()); // network specific magic number
      ssObj << objToSave;
      uint256 hash = Hash(ssObj.begin(), ssObj.end());
      ssObj << hash;
  
      // open output file, and associate with CAutoFile
      FILE* file = fopen(pathDB.string().c_str(), "wb");
      CAutoFile fileout(file, SER_DISK, CLIENT_VERSION);
      if (fileout.IsNull())
          return error("%s : Failed to open file %s", __func__, pathDB.string());
  
      // Write and commit header, data
      try {
          fileout << ssObj;
      } catch (std::exception& e) {
          return error("%s : Serialize or I/O error - %s", __func__, e.what());
      }
      fileout.fclose();
  
      LogPrint("masternode","Written info to mnpayments.dat  %dms\n", GetTimeMillis() - nStart);
  
      return true;
  }
  
  CMasternodePaymentDB::ReadResult CMasternodePaymentDB::Read(CMasternodePayments& objToLoad, bool fDryRun)
  {
      int64_t nStart = GetTimeMillis();
      // open input file, and associate with CAutoFile
      FILE* file = fopen(pathDB.string().c_str(), "rb");
      CAutoFile filein(file, SER_DISK, CLIENT_VERSION);
      if (filein.IsNull()) {
          error("%s : Failed to open file %s", __func__, pathDB.string());
          return FileError;
      }
  
      // use file size to size memory buffer
      int fileSize = boost::filesystem::file_size(pathDB);
      int dataSize = fileSize - sizeof(uint256);
      // Don't try to resize to a negative number if file is small
      if (dataSize < 0)
          dataSize = 0;
      vector<unsigned char> vchData;
      vchData.resize(dataSize);
      uint256 hashIn;
  
      // read data and checksum from file
      try {
          filein.read((char*)&vchData[0], dataSize);
          filein >> hashIn;
      } catch (std::exception& e) {
          error("%s : Deserialize or I/O error - %s", __func__, e.what());
          return HashReadError;
      }
      filein.fclose();
  
      CDataStream ssObj(vchData, SER_DISK, CLIENT_VERSION);
  
      // verify stored checksum matches input data
      uint256 hashTmp = Hash(ssObj.begin(), ssObj.end());
      if (hashIn != hashTmp) {
          error("%s : Checksum mismatch, data corrupted", __func__);
          return IncorrectHash;
      }
  
      unsigned char pchMsgTmp[4];
      std::string strMagicMessageTmp;
      try {
          // de-serialize file header (masternode cache file specific magic message) and ..
          ssObj >> strMagicMessageTmp;
  
          // ... verify the message matches predefined one
          if (strMagicMessage != strMagicMessageTmp) {
              error("%s : Invalid masternode payement cache magic message", __func__);
              return IncorrectMagicMessage;
          }
  
  
          // de-serialize file header (network specific magic number) and ..
          ssObj >> FLATDATA(pchMsgTmp);
  
          // ... verify the network matches ours
          if (memcmp(pchMsgTmp, Params().MessageStart(), sizeof(pchMsgTmp))) {
              error("%s : Invalid network magic number", __func__);
              return IncorrectMagicNumber;
          }
  
          // de-serialize data into CMasternodePayments object
          ssObj >> objToLoad;
      } catch (std::exception& e) {
          objToLoad.Clear();
          error("%s : Deserialize or I/O error - %s", __func__, e.what());
          return IncorrectFormat;
      }
  
      LogPrint("masternode","Loaded info from mnpayments.dat  %dms\n", GetTimeMillis() - nStart);
      LogPrint("masternode","  %s\n", objToLoad.ToString());
      if (!fDryRun) {
          LogPrint("masternode","Masternode payments manager - cleaning....\n");
          objToLoad.CleanPaymentList();
          LogPrint("masternode","Masternode payments manager - result:\n");
          LogPrint("masternode","  %s\n", objToLoad.ToString());
      }
  
      return Ok;
  }
  
  void DumpMasternodePayments()
  {
      int64_t nStart = GetTimeMillis();
  
      CMasternodePaymentDB paymentdb;
      CMasternodePayments tempPayments;
  
      LogPrint("masternode","Verifying mnpayments.dat format...\n");
      CMasternodePaymentDB::ReadResult readResult = paymentdb.Read(tempPayments, true);
      // there was an error and it was not an error on file opening => do not proceed
      if (readResult == CMasternodePaymentDB::FileError)
          LogPrint("masternode","Missing budgets file - mnpayments.dat, will try to recreate\n");
      else if (readResult != CMasternodePaymentDB::Ok) {
          LogPrint("masternode","Error reading mnpayments.dat: ");
          if (readResult == CMasternodePaymentDB::IncorrectFormat)
              LogPrint("masternode","magic is ok but data has invalid format, will try to recreate\n");
          else {
              LogPrint("masternode","file format is unknown or invalid, please fix it manually\n");
              return;
          }
      }
      LogPrint("masternode","Writting info to mnpayments.dat...\n");
      paymentdb.Write(masternodePayments);
  
      LogPrint("masternode","Budget dump finished  %dms\n", GetTimeMillis() - nStart);
  }
  
  bool IsBlockValueValid(const CBlock& block, CAmount nExpectedValue, CAmount nMinted)
  {
      CBlockIndex* pindexPrev = chainActive.Tip();
      if (pindexPrev == NULL) return true;
  
      int nHeight = 0;
      if (pindexPrev->GetBlockHash() == block.hashPrevBlock) {
          nHeight = pindexPrev->nHeight + 1;
      } else { //out of order
          BlockMap::iterator mi = mapBlockIndex.find(block.hashPrevBlock);
          if (mi != mapBlockIndex.end() && (*mi).second)
              nHeight = (*mi).second->nHeight + 1;
      }
  
      if (nHeight == 0) {
          LogPrint("masternode","IsBlockValueValid() : WARNING: Couldn't find previous block\n");
      }
  
      //LogPrintf("XX69----------> IsBlockValueValid(): nMinted: %d, nExpectedValue: %d\n", FormatMoney(nMinted), FormatMoney(nExpectedValue));
  //    CAmount nMaxMintValue = GetBlockValue(nHeight);
      if (!masternodeSync.IsSynced()) { //there is no budget data to use to check anything
          //super blocks will always be on these blocks, max 100 per budgeting
          if (nHeight % GetBudgetPaymentCycleBlocks() < 100) {
              return true;
          } else {
             if (nHeight < 5000)
                  return true;
              if (nHeight <= REV_BLOCK && nHeight % PHZ_BLOCK != 0 && nMinted > nExpectedValue) {
                  if (fDebug)
                      LogPrint("masternode","Reverse Block #1-1: Reward is not pays actual=%s > limit=%s Blocks=%s\n",
                              FormatMoney(nMinted), FormatMoney(nExpectedValue), nHeight);
                  return false;
              }
              else if (nHeight <= REV_BLOCK && nHeight % PHZ_BLOCK == 0 && nMinted < nExpectedValue) {
                  if (fDebug)
                      LogPrint("masternode","Reverse Block #1-2: Reward is not pays actual=%s < limit=%s Blocks=%s\n",
                              FormatMoney(nMinted), FormatMoney(nExpectedValue), nHeight);
                  return false;
              }
              else if (nHeight > REV_BLOCK && nMinted > nExpectedValue) {
                  if (fDebug)
                      LogPrint("masternode","Reverse Block #1-3: Reward is not pays actual=%s > limit=%s Blocks=%s\n",
                              FormatMoney(nMinted), FormatMoney(nExpectedValue), nHeight);
                  return false;
              }
              else {
                  if (fDebug)
                      LogPrint("masternode","Reverse Block #1-4: Reward pays actual=%s limit=%s Blocks=%s\n",
                              FormatMoney(nMinted), FormatMoney(nExpectedValue), nHeight);
              }
          }
      } else { // we're synced and have data so check the budget schedule
  
      //are these blocks even enabled
          if (!IsSporkActive(SPORK_13_ENABLE_SUPERBLOCKS)) {
              if (nHeight <= REV_BLOCK && nHeight % PHZ_BLOCK != 0 && nMinted > nExpectedValue) {
                  if (fDebug)
                      LogPrint("masternode","Reverse Block #2-1: Reward is not pays actual=%s > limit=%s Blocks=%s\n",
                              FormatMoney(nMinted), FormatMoney(nExpectedValue), nHeight);
                  return false;
              }
              else if (nHeight <= REV_BLOCK && nHeight % PHZ_BLOCK == 0 && nMinted < nExpectedValue) {
                  if (fDebug)
                      LogPrint("masternode","Reverse Block #2-2: Reward is not pays actual=%s < limit=%s Blocks=%s\n",
                              FormatMoney(nMinted), FormatMoney(nExpectedValue), nHeight);
                  return false;
              }
              else if (nHeight > REV_BLOCK && nMinted > nExpectedValue) {
                  if (fDebug)
                      LogPrint("masternode","Reverse Block #2-3: Reward is not pays actual=%s > limit=%s Blocks=%s\n",
                              FormatMoney(nMinted), FormatMoney(nExpectedValue), nHeight);
                  return false;
              }
              else {
                  if (fDebug)
                      LogPrint("masternode","Reverse Block #2-4: Reward pays actual=%s limit=%s Blocks=%s\n",
                              FormatMoney(nMinted), FormatMoney(nExpectedValue), nHeight);
              }
              return true;
          }
  
          if (budget.IsBudgetPaymentBlock(nHeight)) {
              //the value of the block is evaluated in CheckBlock
              return true;
          } else {
             if (nHeight < 5000)
                  return true;
              if (nHeight <= REV_BLOCK && nHeight % PHZ_BLOCK != 0 && nMinted > nExpectedValue) {
                  if (fDebug)
                      LogPrint("masternode","Reverse Block #3-1: Reward is not pays actual=%s > limit=%s Blocks=%s\n",
                              FormatMoney(nMinted), FormatMoney(nExpectedValue), nHeight);
                  return false;
              }
              else if (nHeight <= REV_BLOCK && nHeight % PHZ_BLOCK == 0 && nMinted < nExpectedValue) {
                  if (fDebug)
                      LogPrint("masternode","Reverse Block #3-2: Reward is not pays actual=%s < limit=%s Blocks=%s\n",
                              FormatMoney(nMinted), FormatMoney(nExpectedValue), nHeight);
                  return false;
              }
              else if (nHeight > REV_BLOCK && nMinted > nExpectedValue) {
                  if (fDebug)
                      LogPrint("masternode","Reverse Block #3-3: Reward is not pays actual=%s > limit=%s Blocks=%s\n",
                              FormatMoney(nMinted), FormatMoney(nExpectedValue), nHeight);
                  return false;
              }
              else {
                  if (fDebug)
                      LogPrint("masternode","Reverse Block #3-4: Reward pays actual=%s limit=%s Blocks=%s\n",
                              FormatMoney(nMinted), FormatMoney(nExpectedValue), nHeight);
              }
          }
      }
  
      return true;
  }
  
  bool IsBlockPayeeValid(const CBlock& block, int nBlockHeight)
  {
      TrxValidationStatus transactionStatus = TrxValidationStatus::InValid;
  
      if (!masternodeSync.IsSynced()) { //there is no budget data to use to check anything -- find the longest chain
          LogPrint("mnpayments", "Client not synced, skipping block payee checks\n");
          return true;
      }
  
      const CTransaction& txNew = (nBlockHeight > Params().LAST_POW_BLOCK() ? block.vtx[1] : block.vtx[0]);
  
      //check if it's a budget block
      if (IsSporkActive(SPORK_13_ENABLE_SUPERBLOCKS)) {
          if (budget.IsBudgetPaymentBlock(nBlockHeight)) {
              transactionStatus = budget.IsTransactionValid(txNew, nBlockHeight);
              if (transactionStatus == TrxValidationStatus::Valid) {
                  return true;
              }
  
              if (transactionStatus == TrxValidationStatus::InValid) {
                  LogPrint("masternode","Invalid budget payment detected %s\n", txNew.ToString().c_str());
                  if (IsSporkActive(SPORK_9_MASTERNODE_BUDGET_ENFORCEMENT))
                      return false;
  
                  LogPrint("masternode","Budget enforcement is disabled, accepting block\n");
              }
          }
      }
  
      // If we end here the transaction was either TrxValidationStatus::InValid and Budget enforcement is disabled, or
      // a double budget payment (status = TrxValidationStatus::DoublePayment) was detected, or no/not enough masternode
      // votes (status = TrxValidationStatus::VoteThreshold) for a finalized budget were found
      // In all cases a masternode will get the payment for this block
  
      //check for masternode payee
      if (masternodePayments.IsTransactionValid(txNew, nBlockHeight))
          return true;
      LogPrint("masternode","Invalid mn payment detected %s\n", txNew.ToString().c_str());
  
      if (IsSporkActive(SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT))
          return false;
      LogPrint("masternode","Masternode payment enforcement is disabled, accepting block\n");
  
      return true;
  }
  
  
  void FillBlockPayee(CMutableTransaction& txNew, CAmount nFees, bool fProofOfStake, bool fZLRMStake)
  {
      CBlockIndex* pindexPrev = chainActive.Tip();
      if (!pindexPrev) return;
  
      if (IsSporkActive(SPORK_13_ENABLE_SUPERBLOCKS) && budget.IsBudgetPaymentBlock(pindexPrev->nHeight + 1)) {
          budget.FillBlockPayee(txNew, nFees, fProofOfStake);
      } else {
          masternodePayments.FillBlockPayee(txNew, nFees, fProofOfStake, fZLRMStake);
      }
  }
  
  std::string GetRequiredPaymentsString(int nBlockHeight)
  {
      if (IsSporkActive(SPORK_13_ENABLE_SUPERBLOCKS) && budget.IsBudgetPaymentBlock(nBlockHeight)) {
          return budget.GetRequiredPaymentsString(nBlockHeight);
      } else {
          return masternodePayments.GetRequiredPaymentsString(nBlockHeight);
      }
  }
  
  void CMasternodePayments::FillBlockPayee(CMutableTransaction& txNew, int64_t nFees, bool fProofOfStake, bool fZLRMStake)
  {
      CBlockIndex* pindexPrev = chainActive.Tip();
      if (!pindexPrev) return;
  
      bool hasPayment = true;
      CScript payee;
  
      //spork
      if (!masternodePayments.GetBlockPayee(pindexPrev->nHeight + 1, payee)) {
          //no masternode detected
          CMasternode* winningNode = mnodeman.GetCurrentMasterNode(1);
          if (winningNode) {
              payee = GetScriptForDestination(winningNode->pubKeyCollateralAddress.GetID());
          } else {
              LogPrint("masternode","CreateNewBlock: Failed to detect masternode to pay\n");
              hasPayment = false;
          }
      }
  
      CAmount blockValue = GetBlockValue(pindexPrev->nHeight);
      CAmount masternodePayment = GetMasternodePayment(pindexPrev->nHeight, blockValue, 0, fZLRMStake);
  
      if (hasPayment) {
          if (fProofOfStake) {
              /**For Proof Of Stake vout[0] must be null
               * Stake reward can be split into many different outputs, so we must
               * use vout.size() to align with several different cases.
               * An additional output is appended as the masternode payment
               */
              unsigned int i = txNew.vout.size();
              txNew.vout.resize(i + 1);
              txNew.vout[i].scriptPubKey = payee;
              txNew.vout[i].nValue = masternodePayment;
  
              //subtract mn payment from the stake reward
              if (!txNew.vout[1].IsZerocoinMint())
                  txNew.vout[i - 1].nValue -= masternodePayment;
          } else {
              txNew.vout.resize(2);
              txNew.vout[1].scriptPubKey = payee;
              txNew.vout[1].nValue = masternodePayment;
              txNew.vout[0].nValue = blockValue - masternodePayment;
          }
  
          CTxDestination address1;
          ExtractDestination(payee, address1);
          CBitcoinAddress address2(address1);
  
          LogPrint("masternode","Masternode payment of %s to %s\n", FormatMoney(masternodePayment).c_str(), address2.ToString().c_str());
      }
  }
  
  int CMasternodePayments::GetMinMasternodePaymentsProto()
  {
      if (IsSporkActive(SPORK_10_MASTERNODE_PAY_UPDATED_NODES))
          return ActiveProtocol();                          // Allow only updated peers
      else
          return MIN_PEER_PROTO_VERSION_BEFORE_ENFORCEMENT; // Also allow old peers as long as they are allowed to run
  }
  
  void CMasternodePayments::ProcessMessageMasternodePayments(CNode* pfrom, std::string& strCommand, CDataStream& vRecv)
  {
      if (!masternodeSync.IsBlockchainSynced()) return;
  
      if (fLiteMode) return; //disable all Obfuscation/Masternode related functionality
  
  
      if (strCommand == "mnget") { //Masternode Payments Request Sync
          if (fLiteMode) return;   //disable all Obfuscation/Masternode related functionality
  
          int nCountNeeded;
          vRecv >> nCountNeeded;
  
          if (Params().NetworkID() == CBaseChainParams::MAIN) {
              if (pfrom->HasFulfilledRequest("mnget")) {
                  LogPrintf("CMasternodePayments::ProcessMessageMasternodePayments() : mnget - peer already asked me for the list\n");
                  Misbehaving(pfrom->GetId(), 20);
                  return;
              }
          }
  
          pfrom->FulfilledRequest("mnget");
          masternodePayments.Sync(pfrom, nCountNeeded);
          LogPrint("mnpayments", "mnget - Sent Masternode winners to peer %i\n", pfrom->GetId());
      } else if (strCommand == "mnw") { //Masternode Payments Declare Winner
          //this is required in litemodef
          CMasternodePaymentWinner winner;
          vRecv >> winner;
  
          if (pfrom->nVersion < ActiveProtocol()) return;
  
          int nHeight;
          {
              TRY_LOCK(cs_main, locked);
              if (!locked || chainActive.Tip() == NULL) return;
              nHeight = chainActive.Tip()->nHeight;
          }
  
          if (masternodePayments.mapMasternodePayeeVotes.count(winner.GetHash())) {
              LogPrint("mnpayments", "mnw - Already seen - %s bestHeight %d\n", winner.GetHash().ToString().c_str(), nHeight);
              masternodeSync.AddedMasternodeWinner(winner.GetHash());
              return;
          }
  
          int nFirstBlock = nHeight - (mnodeman.CountEnabled() * 1.25);
          if (winner.nBlockHeight < nFirstBlock || winner.nBlockHeight > nHeight + 20) {
              LogPrint("mnpayments", "mnw - winner out of range - FirstBlock %d Height %d bestHeight %d\n", nFirstBlock, winner.nBlockHeight, nHeight);
              return;
          }
  
          std::string strError = "";
          if (!winner.IsValid(pfrom, strError)) {
              // if(strError != "") LogPrint("masternode","mnw - invalid message - %s\n", strError);
              return;
          }
  
          if (!masternodePayments.CanVote(winner.vinMasternode.prevout, winner.nBlockHeight)) {
              //  LogPrint("masternode","mnw - masternode already voted - %s\n", winner.vinMasternode.prevout.ToStringShort());
              return;
          }
  
          if (!winner.SignatureValid()) {
              if (masternodeSync.IsSynced()) {
                  LogPrintf("CMasternodePayments::ProcessMessageMasternodePayments() : mnw - invalid signature\n");
                  Misbehaving(pfrom->GetId(), 20);
              }
              // it could just be a non-synced masternode
              mnodeman.AskForMN(pfrom, winner.vinMasternode);
              return;
          }
  
          CTxDestination address1;
          ExtractDestination(winner.payee, address1);
          CBitcoinAddress address2(address1);
  
          //   LogPrint("mnpayments", "mnw - winning vote - Addr %s Height %d bestHeight %d - %s\n", address2.ToString().c_str(), winner.nBlockHeight, nHeight, winner.vinMasternode.prevout.ToStringShort());
  
          if (masternodePayments.AddWinningMasternode(winner)) {
              winner.Relay();
              masternodeSync.AddedMasternodeWinner(winner.GetHash());
          }
      }
  }
  
  bool CMasternodePaymentWinner::Sign(CKey& keyMasternode, CPubKey& pubKeyMasternode)
  {
      std::string errorMessage;
      std::string strMasterNodeSignMessage;
  
      std::string strMessage = vinMasternode.prevout.ToStringShort() +
                               boost::lexical_cast<std::string>(nBlockHeight) +
                               payee.ToString();
  
      if (!obfuScationSigner.SignMessage(strMessage, errorMessage, vchSig, keyMasternode)) {
          LogPrint("masternode","CMasternodePing::Sign() - Error: %s\n", errorMessage.c_str());
          return false;
      }
  
      if (!obfuScationSigner.VerifyMessage(pubKeyMasternode, vchSig, strMessage, errorMessage)) {
          LogPrint("masternode","CMasternodePing::Sign() - Error: %s\n", errorMessage.c_str());
          return false;
      }
  
      return true;
  }
  
  bool CMasternodePayments::GetBlockPayee(int nBlockHeight, CScript& payee)
  {
      if (mapMasternodeBlocks.count(nBlockHeight)) {
          return mapMasternodeBlocks[nBlockHeight].GetPayee(payee);
      }
  
      return false;
  }
  
  // Is this masternode scheduled to get paid soon?
  // -- Only look ahead up to 8 blocks to allow for propagation of the latest 2 winners
  bool CMasternodePayments::IsScheduled(CMasternode& mn, int nNotBlockHeight)
  {
      LOCK(cs_mapMasternodeBlocks);
  
      int nHeight;
      {
          TRY_LOCK(cs_main, locked);
          if (!locked || chainActive.Tip() == NULL) return false;
          nHeight = chainActive.Tip()->nHeight;
      }
  
      CScript mnpayee;
      mnpayee = GetScriptForDestination(mn.pubKeyCollateralAddress.GetID());
  
      CScript payee;
      for (int64_t h = nHeight; h <= nHeight + 8; h++) {
          if (h == nNotBlockHeight) continue;
          if (mapMasternodeBlocks.count(h)) {
              if (mapMasternodeBlocks[h].GetPayee(payee)) {
                  if (mnpayee == payee) {
                      return true;
                  }
              }
          }
      }
  
      return false;
  }
  
  bool CMasternodePayments::AddWinningMasternode(CMasternodePaymentWinner& winnerIn)
  {
      uint256 blockHash = 0;
      if (!GetBlockHash(blockHash, winnerIn.nBlockHeight - 100)) {
          return false;
      }
  
      {
          LOCK2(cs_mapMasternodePayeeVotes, cs_mapMasternodeBlocks);
  
          if (mapMasternodePayeeVotes.count(winnerIn.GetHash())) {
              return false;
          }
  
          mapMasternodePayeeVotes[winnerIn.GetHash()] = winnerIn;
  
          if (!mapMasternodeBlocks.count(winnerIn.nBlockHeight)) {
              CMasternodeBlockPayees blockPayees(winnerIn.nBlockHeight);
              mapMasternodeBlocks[winnerIn.nBlockHeight] = blockPayees;
          }
      }
  
      mapMasternodeBlocks[winnerIn.nBlockHeight].AddPayee(winnerIn.payee, 1);
  
      return true;
  }
  
  bool CMasternodeBlockPayees::IsTransactionValid(const CTransaction& txNew)
  {
      LOCK(cs_vecPayments);
  
      int nMaxSignatures = 0;
      int nMasternode_Drift_Count = 0;
  
      std::string strPayeesPossible = "";
  
      CAmount nReward = GetBlockValue(nBlockHeight);
  
      if (IsSporkActive(SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT)) {
          // Get a stable number of masternodes by ignoring newly activated (< 8000 sec old) masternodes
          nMasternode_Drift_Count = mnodeman.stable_size() + Params().MasternodeCountDrift();
      }
      else {
          //account for the fact that all peers do not see the same masternode count. A allowance of being off our masternode count is given
          //we only need to look at an increased masternode count because as count increases, the reward decreases. This code only checks
          //for mnPayment >= required, so it only makes sense to check the max node count allowed.
          nMasternode_Drift_Count = mnodeman.size() + Params().MasternodeCountDrift();
      }
  
      CAmount requiredMasternodePayment = GetMasternodePayment(nBlockHeight, nReward, nMasternode_Drift_Count, txNew.IsZerocoinSpend());
  
      //require at least 6 signatures
      BOOST_FOREACH (CMasternodePayee& payee, vecPayments)
          if (payee.nVotes >= nMaxSignatures && payee.nVotes >= MNPAYMENTS_SIGNATURES_REQUIRED)
              nMaxSignatures = payee.nVotes;
  
      // if we don't have at least 6 signatures on a payee, approve whichever is the longest chain
      if (nMaxSignatures < MNPAYMENTS_SIGNATURES_REQUIRED) return true;
  
      BOOST_FOREACH (CMasternodePayee& payee, vecPayments) {
          bool found = false;
          BOOST_FOREACH (CTxOut out, txNew.vout) {
              if (payee.scriptPubKey == out.scriptPubKey) {
                  if(nBlockHeight <= REV_BLOCK && out.nValue <= requiredMasternodePayment) {
                      found = true;
                      if (fDebug)
                          LogPrint("masternode","Masternode payment was made successfully. Paid=%s Max=%s Blocks=%s Reward=%s\n",
                                    FormatMoney(out.nValue).c_str(), FormatMoney(requiredMasternodePayment).c_str(), nBlockHeight, FormatMoney(nReward).c_str());
                  }
                  else if(nBlockHeight > REV_BLOCK && out.nValue >= requiredMasternodePayment) {
                      found = true;
                      if (fDebug)
                          LogPrint("masternode","Masternode payment was made successfully. Paid=%s Min=%s Blocks=%s Reward=%s\n",
                                    FormatMoney(out.nValue).c_str(), FormatMoney(requiredMasternodePayment).c_str(), nBlockHeight, FormatMoney(nReward).c_str());
                  }
                  else
                      LogPrint("masternode","Masternode payment is out of drift range. Paid=%s Min=%s Blocks=%s Reward=%s\n",
                                FormatMoney(out.nValue).c_str(), FormatMoney(requiredMasternodePayment).c_str(), nBlockHeight, FormatMoney(nReward).c_str());
              }
          }
  
          if (payee.nVotes >= MNPAYMENTS_SIGNATURES_REQUIRED) {
              if (found) return true;
  
              CTxDestination address1;
              ExtractDestination(payee.scriptPubKey, address1);
              CBitcoinAddress address2(address1);
  
              if (strPayeesPossible == "") {
                  strPayeesPossible += address2.ToString();
              } else {
                  strPayeesPossible += "," + address2.ToString();
              }
          }
      }
  
      LogPrint("masternode","CMasternodePayments::IsTransactionValid - Missing required payment of %s to %s\n", FormatMoney(requiredMasternodePayment).c_str(), strPayeesPossible.c_str());
      return false;
  }
  
  std::string CMasternodeBlockPayees::GetRequiredPaymentsString()
  {
      LOCK(cs_vecPayments);
  
      std::string ret = "Unknown";
  
      BOOST_FOREACH (CMasternodePayee& payee, vecPayments) {
          CTxDestination address1;
          ExtractDestination(payee.scriptPubKey, address1);
          CBitcoinAddress address2(address1);
  
          if (ret != "Unknown") {
              ret += ", " + address2.ToString() + ":" + boost::lexical_cast<std::string>(payee.nVotes);
          } else {
              ret = address2.ToString() + ":" + boost::lexical_cast<std::string>(payee.nVotes);
          }
      }
  
      return ret;
  }
  
  std::string CMasternodePayments::GetRequiredPaymentsString(int nBlockHeight)
  {
      LOCK(cs_mapMasternodeBlocks);
  
      if (mapMasternodeBlocks.count(nBlockHeight)) {
          return mapMasternodeBlocks[nBlockHeight].GetRequiredPaymentsString();
      }
  
      return "Unknown";
  }
  
  bool CMasternodePayments::IsTransactionValid(const CTransaction& txNew, int nBlockHeight)
  {
      LOCK(cs_mapMasternodeBlocks);
  
      if (mapMasternodeBlocks.count(nBlockHeight)) {
          return mapMasternodeBlocks[nBlockHeight].IsTransactionValid(txNew);
      }
  
      return true;
  }
  
  void CMasternodePayments::CleanPaymentList()
  {
      LOCK2(cs_mapMasternodePayeeVotes, cs_mapMasternodeBlocks);
  
      int nHeight;
      {
          TRY_LOCK(cs_main, locked);
          if (!locked || chainActive.Tip() == NULL) return;
          nHeight = chainActive.Tip()->nHeight;
      }
  
      //keep up to five cycles for historical sake
      int nLimit = std::max(int(mnodeman.size() * 1.25), 1000);
  
      std::map<uint256, CMasternodePaymentWinner>::iterator it = mapMasternodePayeeVotes.begin();
      while (it != mapMasternodePayeeVotes.end()) {
          CMasternodePaymentWinner winner = (*it).second;
  
          if (nHeight - winner.nBlockHeight > nLimit) {
              LogPrint("mnpayments", "CMasternodePayments::CleanPaymentList - Removing old Masternode payment - block %d\n", winner.nBlockHeight);
              masternodeSync.mapSeenSyncMNW.erase((*it).first);
              mapMasternodePayeeVotes.erase(it++);
              mapMasternodeBlocks.erase(winner.nBlockHeight);
          } else {
              ++it;
          }
      }
  }
  
  bool CMasternodePaymentWinner::IsValid(CNode* pnode, std::string& strError)
  {
      CMasternode* pmn = mnodeman.Find(vinMasternode);
  
      if (!pmn) {
          strError = strprintf("Unknown Masternode %s", vinMasternode.prevout.hash.ToString());
          LogPrint("masternode","CMasternodePaymentWinner::IsValid - %s\n", strError);
          mnodeman.AskForMN(pnode, vinMasternode);
          return false;
      }
  
      if (pmn->protocolVersion < ActiveProtocol()) {
          strError = strprintf("Masternode protocol too old %d - req %d", pmn->protocolVersion, ActiveProtocol());
          LogPrint("masternode","CMasternodePaymentWinner::IsValid - %s\n", strError);
          return false;
      }
  
      int n = mnodeman.GetMasternodeRank(vinMasternode, nBlockHeight - 100, ActiveProtocol());
  
      if (n > MNPAYMENTS_SIGNATURES_TOTAL) {
          //It's common to have masternodes mistakenly think they are in the top 10
          // We don't want to print all of these messages, or punish them unless they're way off
          if (n > MNPAYMENTS_SIGNATURES_TOTAL * 2) {
              strError = strprintf("Masternode not in the top %d (%d)", MNPAYMENTS_SIGNATURES_TOTAL * 2, n);
              LogPrint("masternode","CMasternodePaymentWinner::IsValid - %s\n", strError);
              //if (masternodeSync.IsSynced()) Misbehaving(pnode->GetId(), 20);
          }
          return false;
      }
  
      return true;
  }
  
  bool CMasternodePayments::ProcessBlock(int nBlockHeight)
  {
      if (!fMasterNode) return false;
  
      //reference node - hybrid mode
  
      int n = mnodeman.GetMasternodeRank(activeMasternode.vin, nBlockHeight - 100, ActiveProtocol());
  
      if (n == -1) {
          LogPrint("mnpayments", "CMasternodePayments::ProcessBlock - Unknown Masternode\n");
          return false;
      }
  
      if (n > MNPAYMENTS_SIGNATURES_TOTAL) {
          LogPrint("mnpayments", "CMasternodePayments::ProcessBlock - Masternode not in the top %d (%d)\n", MNPAYMENTS_SIGNATURES_TOTAL, n);
          return false;
      }
  
      if (nBlockHeight <= nLastBlockHeight) return false;
  
      CMasternodePaymentWinner newWinner(activeMasternode.vin);
  
      if (budget.IsBudgetPaymentBlock(nBlockHeight)) {
          //is budget payment block -- handled by the budgeting software
      } else {
          LogPrint("masternode","CMasternodePayments::ProcessBlock() Start nHeight %d - vin %s. \n", nBlockHeight, activeMasternode.vin.prevout.hash.ToString());
  
          // pay to the oldest MN that still had no payment but its input is old enough and it was active long enough
          int nCount = 0;
          CMasternode* pmn = mnodeman.GetNextMasternodeInQueueForPayment(nBlockHeight, true, nCount);
  
          if (pmn != NULL) {
              LogPrint("masternode","CMasternodePayments::ProcessBlock() Found by FindOldestNotInVec \n");
  
              newWinner.nBlockHeight = nBlockHeight;
  
              CScript payee = GetScriptForDestination(pmn->pubKeyCollateralAddress.GetID());
              newWinner.AddPayee(payee);
  
              CTxDestination address1;
              ExtractDestination(payee, address1);
              CBitcoinAddress address2(address1);
  
              LogPrint("masternode","CMasternodePayments::ProcessBlock() Winner payee %s nHeight %d. \n", address2.ToString().c_str(), newWinner.nBlockHeight);
          } else {
              LogPrint("masternode","CMasternodePayments::ProcessBlock() Failed to find masternode to pay\n");
          }
      }
  
      std::string errorMessage;
      CPubKey pubKeyMasternode;
      CKey keyMasternode;
  
      if (!obfuScationSigner.SetKey(strMasterNodePrivKey, errorMessage, keyMasternode, pubKeyMasternode)) {
          LogPrint("masternode","CMasternodePayments::ProcessBlock() - Error upon calling SetKey: %s\n", errorMessage.c_str());
          return false;
      }
  
      LogPrint("masternode","CMasternodePayments::ProcessBlock() - Signing Winner\n");
      if (newWinner.Sign(keyMasternode, pubKeyMasternode)) {
          LogPrint("masternode","CMasternodePayments::ProcessBlock() - AddWinningMasternode\n");
  
          if (AddWinningMasternode(newWinner)) {
              newWinner.Relay();
              nLastBlockHeight = nBlockHeight;
              return true;
          }
      }
  
      return false;
  }
  
  void CMasternodePaymentWinner::Relay()
  {
      CInv inv(MSG_MASTERNODE_WINNER, GetHash());
      RelayInv(inv);
  }
  
  bool CMasternodePaymentWinner::SignatureValid()
  {
      CMasternode* pmn = mnodeman.Find(vinMasternode);
  
      if (pmn != NULL) {
          std::string strMessage = vinMasternode.prevout.ToStringShort() +
                                   boost::lexical_cast<std::string>(nBlockHeight) +
                                   payee.ToString();
  
          std::string errorMessage = "";
          if (!obfuScationSigner.VerifyMessage(pmn->pubKeyMasternode, vchSig, strMessage, errorMessage)) {
              return error("CMasternodePaymentWinner::SignatureValid() - Got bad Masternode address signature %s\n", vinMasternode.prevout.hash.ToString());
          }
  
          return true;
      }
  
      return false;
  }
  
  void CMasternodePayments::Sync(CNode* node, int nCountNeeded)
  {
      LOCK(cs_mapMasternodePayeeVotes);
  
      int nHeight;
      {
          TRY_LOCK(cs_main, locked);
          if (!locked || chainActive.Tip() == NULL) return;
          nHeight = chainActive.Tip()->nHeight;
      }
  
      int nCount = (mnodeman.CountEnabled() * 1.25);
      if (nCountNeeded > nCount) nCountNeeded = nCount;
  
      int nInvCount = 0;
      std::map<uint256, CMasternodePaymentWinner>::iterator it = mapMasternodePayeeVotes.begin();
      while (it != mapMasternodePayeeVotes.end()) {
          CMasternodePaymentWinner winner = (*it).second;
          if (winner.nBlockHeight >= nHeight - nCountNeeded && winner.nBlockHeight <= nHeight + 20) {
              node->PushInventory(CInv(MSG_MASTERNODE_WINNER, winner.GetHash()));
              nInvCount++;
          }
          ++it;
      }
      node->PushMessage("ssc", MASTERNODE_SYNC_MNW, nInvCount);
  }
  
  std::string CMasternodePayments::ToString() const
  {
      std::ostringstream info;
  
      info << "Votes: " << (int)mapMasternodePayeeVotes.size() << ", Blocks: " << (int)mapMasternodeBlocks.size();
  
      return info.str();
  }
  
  
  int CMasternodePayments::GetOldestBlock()
  {
      LOCK(cs_mapMasternodeBlocks);
  
      int nOldestBlock = std::numeric_limits<int>::max();
  
      std::map<int, CMasternodeBlockPayees>::iterator it = mapMasternodeBlocks.begin();
      while (it != mapMasternodeBlocks.end()) {
          if ((*it).first < nOldestBlock) {
              nOldestBlock = (*it).first;
          }
          it++;
      }
  
      return nOldestBlock;
  }
  
  
  int CMasternodePayments::GetNewestBlock()
  {
      LOCK(cs_mapMasternodeBlocks);
  
      int nNewestBlock = 0;
  
      std::map<int, CMasternodeBlockPayees>::iterator it = mapMasternodeBlocks.begin();
      while (it != mapMasternodeBlocks.end()) {
          if ((*it).first > nNewestBlock) {
              nNewestBlock = (*it).first;
          }
          it++;
      }
  
      return nNewestBlock;
  }
  