text: // Copyright (c) 2014-2019 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 "masternode/activemasternode.h"
  #include "consensus/validation.h"
  #include "governance/governance-classes.h"
  #include "init.h"
  #include "masternode/masternode-payments.h"
  #include "masternode/masternode-sync.h"
  #include "messagesigner.h"
  #include "netfulfilledman.h"
  #include "netmessagemaker.h"
  #include "spork.h"
  #include "util.h"
  #include "validation.h"
  
  #include "evo/deterministicmns.h"
  
  #include <string>
  
  CMasternodePayments mnpayments;
  
  /**
  * IsBlockValueValid
  *
  *   Determine if coinbase outgoing created money is the correct value
  *
  *   Why is this needed?
  *   - In PACGlobal some blocks are superblocks, which output much higher amounts of coins
  *   - Otherblocks are 10% lower in outgoing value, so in total, no extra coins are created
  *   - When non-superblocks are detected, the normal schedule should be maintained
  */
  
  bool IsBlockValueValid(const CBlock& block, int nBlockHeight, CAmount blockReward, std::string& strErrorRet)
  {
      int n = block.IsProofOfStake() ? 1 : 0;
      CAmount nValueIn = 0;
      if (block.IsProofOfStake()) {
          CCoinsViewCache view(pcoinsTip.get());
          nValueIn += view.GetValueIn(*block.vtx[1]);
      }
  
      CAmount blockValue = block.vtx[n]->GetValueOut() - nValueIn;
      bool isBlockRewardValueMet = (blockValue <= blockReward);
  
      strErrorRet = "";
  
      LogPrint(BCLog::MNPAYMENTS, "        - blockValue %lld <= blockReward %lld\n", blockValue, blockReward);
  
      CAmount nSuperblockMaxValue = blockReward + CSuperblock::GetPaymentsLimit(nBlockHeight);
      bool isSuperblockMaxValueMet = (blockValue <= nSuperblockMaxValue);
      LogPrint(BCLog::MNPAYMENTS, "block.vtx[0]->GetValueOut() %lld <= blockReward %lld\n", block.vtx[0]->GetValueOut(), blockReward);
  
      LogPrint(BCLog::MNPAYMENTS, "        - blockValue %lld <= nSuperblockMaxValue %lld\n", blockValue, nSuperblockMaxValue);
  
      bool isGenerationHeight = (nBlockHeight == Params().GetConsensus().nGenerationHeight || nBlockHeight == Params().GetConsensus().nGenerationHeight2);
      if (isGenerationHeight)
          return true;
  
      if (!CSuperblock::IsValidBlockHeight(nBlockHeight)) {
          // can't possibly be a superblock, so lets just check for block reward limits
          if (!isBlockRewardValueMet) {
              strErrorRet = strprintf("coinbase pays too much at height %d (actual=%d vs limit=%d), exceeded block reward, only regular blocks are allowed at this height", nBlockHeight, blockValue, blockReward);
          }
          return isBlockRewardValueMet;
      }
  
      // bail out in case superblock limits were exceeded
      if (!isSuperblockMaxValueMet) {
          strErrorRet = strprintf("coinbase pays too much at height %d (actual=%d vs limit=%d), exceeded superblock max value", nBlockHeight, blockValue, nSuperblockMaxValue);
          return false;
      }
  
      if(!masternodeSync.IsSynced() || fLiteMode) {
          LogPrint(BCLog::MNPAYMENTS, "%s -- WARNING: Not enough data, checked superblock max bounds only\n", __func__);
          // not enough data for full checks but at least we know that the superblock limits were honored.
          // We rely on the network to have followed the correct chain in this case
          return true;
      }
  
      // we are synced and possibly on a superblock now
      if (!sporkManager.IsSporkActive(SPORK_9_SUPERBLOCKS_ENABLED)) {
          // should NOT allow superblocks at all, when superblocks are disabled
          // revert to block reward limits in this case
          LogPrint(BCLog::GOBJECT, "%s -- Superblocks are disabled, no superblocks allowed\n", __func__);
          if(!isBlockRewardValueMet) {
              strErrorRet = strprintf("coinbase pays too much at height %d (actual=%d vs limit=%d), exceeded block reward, superblocks are disabled",
                                      nBlockHeight, block.vtx[0]->GetValueOut(), blockReward);
          }
          return isBlockRewardValueMet;
      }
  
      if (!CSuperblockManager::IsSuperblockTriggered(nBlockHeight)) {
          // we are on a valid superblock height but a superblock was not triggered
          // revert to block reward limits in this case
          if (!isBlockRewardValueMet) {
              strErrorRet = strprintf("coinbase pays too much at height %d (actual=%d vs limit=%d), exceeded block reward, no triggered superblock detected",
                                      nBlockHeight, blockValue, blockReward);
          }
          return isBlockRewardValueMet;
      }
  
      // this actually also checks for correct payees and not only amount
      if (!CSuperblockManager::IsValid(*block.vtx[n], nBlockHeight, blockReward)) {
          // triggered but invalid? that's weird
          LogPrintf("       - %s -- ERROR: Invalid superblock detected at height %d: %s", __func__, nBlockHeight, block.vtx[n]->ToString());
          // should NOT allow invalid superblocks, when superblocks are enabled
          strErrorRet = strprintf("invalid superblock detected at height %d", nBlockHeight);
          return false;
      }
  
      // we got a valid superblock
      return true;
  }
  
  bool IsBlockPayeeValid(const CTransaction& txNew, int nBlockHeight, CAmount blockReward)
  {
      // for nGenerationAmount - make sure this only ever goes to the spork key
      if (nBlockHeight == Params().GetConsensus().nGenerationHeight ||
          nBlockHeight == Params().GetConsensus().nGenerationHeight2)
      {
          CBitcoinAddress address = Params().SporkAddresses().front();
          CScript payeeAddr = GetScriptForDestination(address.Get());
          CAmount nAmountGenerated = (nBlockHeight == Params().GetConsensus().nGenerationHeight) ? Params().GetConsensus().nGenerationAmount : Params().GetConsensus().nGenerationAmount2;
          for (const auto& tx : txNew.vout) {
             if (tx.nValue == nAmountGenerated) {
                if (tx.scriptPubKey == payeeAddr) {
                    LogPrintf("Found correct recipient at height %d\n", nBlockHeight);
                    return true;
                }
             }
          }
          LogPrintf("Didn't find correct recipient at height %d\n", nBlockHeight);
          return false;
      }
  
      if(fLiteMode) {
          //there is no budget data to use to check anything, let's just accept the longest chain
          LogPrint(BCLog::MNPAYMENTS, "%s -- WARNING: Not enough data, skipping block payee checks\n", __func__);
          return true;
      }
  
      // we are still using budgets, but we have no data about them anymore,
      // we can only check masternode payments
  
      const Consensus::Params& consensusParams = Params().GetConsensus();
  
      if(nBlockHeight < consensusParams.nSuperblockStartBlock) {
          // NOTE: old budget system is disabled since 12.1 and we should never enter this branch
          // anymore when sync is finished (on mainnet). We have no old budget data but these blocks
          // have tons of confirmations and can be safely accepted without payee verification
          LogPrint(BCLog::GOBJECT, "%s -- WARNING: Client synced but old budget system is disabled, accepting any payee\n", __func__);
          return true;
      }
  
      // superblocks started
      // SEE IF THIS IS A VALID SUPERBLOCK
  
      if(sporkManager.IsSporkActive(SPORK_9_SUPERBLOCKS_ENABLED)) {
          if(CSuperblockManager::IsSuperblockTriggered(nBlockHeight)) {
              if(CSuperblockManager::IsValid(txNew, nBlockHeight, blockReward)) {
                  LogPrint(BCLog::GOBJECT, "%s -- Valid superblock at height %d: %s", __func__, nBlockHeight, txNew.ToString());
                  // continue validation, should also pay MN
              } else {
                  LogPrintf("%s -- ERROR: Invalid superblock detected at height %d: %s", __func__, nBlockHeight, txNew.ToString());
                  // should NOT allow such superblocks, when superblocks are enabled
                  return false;
              }
          } else {
              LogPrint(BCLog::GOBJECT, "%s -- No triggered superblock detected at height %d\n", __func__, nBlockHeight);
          }
      } else {
          // should NOT allow superblocks at all, when superblocks are disabled
          LogPrint(BCLog::GOBJECT, "%s -- Superblocks are disabled, no superblocks allowed\n", __func__);
      }
  
      // Check for correct masternode payment
      if(mnpayments.IsTransactionValid(txNew, nBlockHeight, blockReward)) {
          LogPrint(BCLog::MNPAYMENTS, "%s -- Valid masternode payment at height %d: %s", __func__, nBlockHeight, txNew.ToString());
          return true;
      }
  
      LogPrintf("%s -- ERROR: Invalid masternode payment detected at height %d: %s", __func__, nBlockHeight, txNew.ToString());
      return false;
  }
  
  void FillBlockPayments(CMutableTransaction& txNew, int nBlockHeight, CAmount blockReward, std::vector<CTxOut>& voutMasternodePaymentsRet, std::vector<CTxOut>& voutSuperblockPaymentsRet)
  {
      // only create superblocks if spork is enabled AND if superblock is actually triggered
      // (height should be validated inside)
      if(sporkManager.IsSporkActive(SPORK_9_SUPERBLOCKS_ENABLED) &&
          CSuperblockManager::IsSuperblockTriggered(nBlockHeight)) {
              LogPrint(BCLog::GOBJECT, "%s -- triggered superblock creation at height %d\n", __func__, nBlockHeight);
              CSuperblockManager::GetSuperblockPayments(nBlockHeight, voutSuperblockPaymentsRet);
      }
  
      if (!mnpayments.GetMasternodeTxOuts(nBlockHeight, blockReward, voutMasternodePaymentsRet)) {
          LogPrint(BCLog::MNPAYMENTS, "%s -- no masternode to pay (MN list probably empty)\n", __func__);
      }
  
      ///////////////////////////////////////////////////////////////////////////////////////
      if (nBlockHeight == Params().GetConsensus().nGenerationHeight ||
          nBlockHeight == Params().GetConsensus().nGenerationHeight2)
      {
          CAmount nAmountGenerated = (nBlockHeight == Params().GetConsensus().nGenerationHeight) ? Params().GetConsensus().nGenerationAmount : Params().GetConsensus().nGenerationAmount2;
          CBitcoinAddress address = Params().SporkAddresses().front();
          CScript payeeAddr = GetScriptForDestination(address.Get());
          CTxOut managementTx = CTxOut(nAmountGenerated, payeeAddr);
          txNew.vout.push_back(managementTx);
      }
      ///////////////////////////////////////////////////////////////////////////////////////
  
      txNew.vout.insert(txNew.vout.end(), voutMasternodePaymentsRet.begin(), voutMasternodePaymentsRet.end());
      txNew.vout.insert(txNew.vout.end(), voutSuperblockPaymentsRet.begin(), voutSuperblockPaymentsRet.end());
  
      // done this way to be capable of pow/mn & pos/mn if desired
      std::string voutMasternodeStr;
      bool IsProofOfStake = nBlockHeight > Params().GetConsensus().nLastPoWBlock ? true : false;
      for (const auto& txout : voutMasternodePaymentsRet) {
          // subtract MN payment from miner reward
          txNew.vout[IsProofOfStake].nValue -= txout.nValue;
          if (!voutMasternodeStr.empty())
              voutMasternodeStr += ",";
          voutMasternodeStr += txout.ToString();
      }
  
      LogPrint(BCLog::MNPAYMENTS, "%s -- nBlockHeight %d blockReward %lld voutMasternodePaymentsRet \"%s\" txNew %s", __func__,
                              nBlockHeight, blockReward, voutMasternodeStr, txNew.ToString());
  }
  
  std::string GetRequiredPaymentsString(int nBlockHeight, const CDeterministicMNCPtr &payee)
  {
      std::string strPayee = "Unknown";
      if (payee) {
          CTxDestination dest;
          if (!ExtractDestination(payee->pdmnState->scriptPayout, dest))
              assert(false);
          strPayee = CBitcoinAddress(dest).ToString();
      }
      if (CSuperblockManager::IsSuperblockTriggered(nBlockHeight)) {
          strPayee += ", " + CSuperblockManager::GetRequiredPaymentsString(nBlockHeight);
      }
      return strPayee;
  }
  
  std::map<int, std::string> GetRequiredPaymentsStrings(int nStartHeight, int nEndHeight)
  {
      std::map<int, std::string> mapPayments;
  
      if (nStartHeight < 1) {
          nStartHeight = 1;
      }
  
      LOCK(cs_main);
      int nChainTipHeight = chainActive.Height();
  
      bool doProjection = false;
      for(int h = nStartHeight; h < nEndHeight; h++) {
          if (h <= nChainTipHeight) {
              auto payee = deterministicMNManager->GetListForBlock(chainActive[h - 1]).GetMNPayee();
              mapPayments.emplace(h, GetRequiredPaymentsString(h, payee));
          } else {
              doProjection = true;
              break;
          }
      }
      if (doProjection) {
          auto projection = deterministicMNManager->GetListAtChainTip().GetProjectedMNPayees(nEndHeight - nChainTipHeight);
          for (size_t i = 0; i < projection.size(); i++) {
              auto payee = projection[i];
              int h = nChainTipHeight + 1 + i;
              mapPayments.emplace(h, GetRequiredPaymentsString(h, payee));
          }
      }
  
      return mapPayments;
  }
  
  /**
  *   GetMasternodeTxOuts
  *
  *   Get masternode payment tx outputs
  */
  
  bool CMasternodePayments::GetMasternodeTxOuts(int nBlockHeight, CAmount blockReward, std::vector<CTxOut>& voutMasternodePaymentsRet) const
  {
      // make sure it's not filled yet
      voutMasternodePaymentsRet.clear();
  
      if(!GetBlockTxOuts(nBlockHeight, blockReward, voutMasternodePaymentsRet)) {
          LogPrintf("CMasternodePayments::%s -- no payee (deterministic masternode list empty)\n", __func__);
          return false;
      }
  
      for (const auto& txout : voutMasternodePaymentsRet) {
          CTxDestination address1;
          ExtractDestination(txout.scriptPubKey, address1);
          CBitcoinAddress address2(address1);
  
          LogPrintf("CMasternodePayments::%s -- Masternode payment %lld to %s\n", __func__, txout.nValue, address2.ToString());
      }
  
      return true;
  }
  
  bool CMasternodePayments::GetBlockTxOuts(int nBlockHeight, CAmount blockReward, std::vector<CTxOut>& voutMasternodePaymentsRet) const
  {
      voutMasternodePaymentsRet.clear();
  
      CAmount masternodeReward = GetMasternodePayment(nBlockHeight, blockReward);
  
      const CBlockIndex* pindex;
      {
          LOCK(cs_main);
          pindex = chainActive[nBlockHeight - 1];
      }
      uint256 proTxHash;
      auto dmnPayee = deterministicMNManager->GetListForBlock(pindex).GetMNPayee();
      if (!dmnPayee) {
          return false;
      }
  
      CAmount operatorReward = 0;
      if (dmnPayee->nOperatorReward != 0 && dmnPayee->pdmnState->scriptOperatorPayout != CScript()) {
          // This calculation might eventually turn out to result in 0 even if an operator reward percentage is given.
          // This will however only happen in a few years when the block rewards drops very low.
          operatorReward = (masternodeReward * dmnPayee->nOperatorReward) / 10000;
          masternodeReward -= operatorReward;
      }
  
      if (masternodeReward > 0) {
          voutMasternodePaymentsRet.emplace_back(masternodeReward, dmnPayee->pdmnState->scriptPayout);
      }
      if (operatorReward > 0) {
          voutMasternodePaymentsRet.emplace_back(operatorReward, dmnPayee->pdmnState->scriptOperatorPayout);
      }
  
      return true;
  }
  
  // Is this masternode scheduled to get paid soon?
  // -- Only look ahead up to 8 blocks to allow for propagation of the latest 2 blocks of votes
  bool CMasternodePayments::IsScheduled(const CDeterministicMNCPtr& dmnIn, int nNotBlockHeight) const
  {
      // can't verify historical blocks here
      if (!FullDIP0003Mode()) return true;
  
      auto projectedPayees = deterministicMNManager->GetListAtChainTip().GetProjectedMNPayees(8);
      for (const auto &dmn : projectedPayees) {
          if (dmn->proTxHash == dmnIn->proTxHash) {
              return true;
          }
      }
      return false;
  }
  
  bool CMasternodePayments::IsTransactionValid(const CTransaction& txNew, int nBlockHeight, CAmount blockReward) const
  {
      std::vector<CTxOut> voutMasternodePayments;
      if (!GetBlockTxOuts(nBlockHeight, blockReward, voutMasternodePayments)) {
          LogPrintf("CMasternodePayments::%s -- ERROR failed to get payees for block at height %s\n", __func__, nBlockHeight);
          return true;
      }
  
      for (const auto& txout : voutMasternodePayments) {
          bool found = false;
          for (const auto& txout2 : txNew.vout) {
              if (txout == txout2) {
                  found = true;
                  break;
              }
          }
          if (!found) {
              CTxDestination dest;
              if (!ExtractDestination(txout.scriptPubKey, dest))
                  assert(false);
              LogPrintf("found -> %s\n", txNew.ToString().c_str());
              LogPrintf("CMasternodePayments::%s -- ERROR failed to find expected payee %s in block at height %s\n", __func__, CBitcoinAddress(dest).ToString(), nBlockHeight);
              return false;
          }
      }
      return true;
  }
  