text: // Copyright (c) 2014-2017 The FLCoin Core developers
  // Distributed under the MIT/X11 software license, see the accompanying
  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
  
  #include "core_io.h"
  #include "governance.h"
  #include "governance-classes.h"
  #include "governance-object.h"
  #include "governance-vote.h"
  #include "instantx.h"
  #include "masternodeman.h"
  #include "messagesigner.h"
  #include "util.h"
  
  #include <univalue.h>
  
  CGovernanceObject::CGovernanceObject()
  : cs(),
    nObjectType(GOVERNANCE_OBJECT_UNKNOWN),
    nHashParent(),
    nRevision(0),
    nTime(0),
    nDeletionTime(0),
    nCollateralHash(),
    strData(),
    vinMasternode(),
    vchSig(),
    fCachedLocalValidity(false),
    strLocalValidityError(),
    fCachedFunding(false),
    fCachedValid(true),
    fCachedDelete(false),
    fCachedEndorsed(false),
    fDirtyCache(true),
    fExpired(false),
    fUnparsable(false),
    mapCurrentMNVotes(),
    mapOrphanVotes(),
    fileVotes()
  {
      // PARSE JSON DATA STORAGE (STRDATA)
      LoadData();
  }
  
  CGovernanceObject::CGovernanceObject(uint256 nHashParentIn, int nRevisionIn, int64_t nTimeIn, uint256 nCollateralHashIn, std::string strDataIn)
  : cs(),
    nObjectType(GOVERNANCE_OBJECT_UNKNOWN),
    nHashParent(nHashParentIn),
    nRevision(nRevisionIn),
    nTime(nTimeIn),
    nDeletionTime(0),
    nCollateralHash(nCollateralHashIn),
    strData(strDataIn),
    vinMasternode(),
    vchSig(),
    fCachedLocalValidity(false),
    strLocalValidityError(),
    fCachedFunding(false),
    fCachedValid(true),
    fCachedDelete(false),
    fCachedEndorsed(false),
    fDirtyCache(true),
    fExpired(false),
    fUnparsable(false),
    mapCurrentMNVotes(),
    mapOrphanVotes(),
    fileVotes()
  {
      // PARSE JSON DATA STORAGE (STRDATA)
      LoadData();
  }
  
  CGovernanceObject::CGovernanceObject(const CGovernanceObject& other)
  : cs(),
    nObjectType(other.nObjectType),
    nHashParent(other.nHashParent),
    nRevision(other.nRevision),
    nTime(other.nTime),
    nDeletionTime(other.nDeletionTime),
    nCollateralHash(other.nCollateralHash),
    strData(other.strData),
    vinMasternode(other.vinMasternode),
    vchSig(other.vchSig),
    fCachedLocalValidity(other.fCachedLocalValidity),
    strLocalValidityError(other.strLocalValidityError),
    fCachedFunding(other.fCachedFunding),
    fCachedValid(other.fCachedValid),
    fCachedDelete(other.fCachedDelete),
    fCachedEndorsed(other.fCachedEndorsed),
    fDirtyCache(other.fDirtyCache),
    fExpired(other.fExpired),
    fUnparsable(other.fUnparsable),
    mapCurrentMNVotes(other.mapCurrentMNVotes),
    mapOrphanVotes(other.mapOrphanVotes),
    fileVotes(other.fileVotes)
  {}
  
  bool CGovernanceObject::ProcessVote(CNode* pfrom,
                                      const CGovernanceVote& vote,
                                      CGovernanceException& exception,
                                      CConnman& connman)
  {
      if(!mnodeman.Has(vote.GetMasternodeOutpoint())) {
          std::ostringstream ostr;
          ostr << "CGovernanceObject::ProcessVote -- Masternode index not found";
          exception = CGovernanceException(ostr.str(), GOVERNANCE_EXCEPTION_WARNING);
          if(mapOrphanVotes.Insert(vote.GetMasternodeOutpoint(), vote_time_pair_t(vote, GetAdjustedTime() + GOVERNANCE_ORPHAN_EXPIRATION_TIME))) {
              if(pfrom) {
                  mnodeman.AskForMN(pfrom, vote.GetMasternodeOutpoint(), connman);
              }
              LogPrintf("%s\n", ostr.str());
          }
          else {
              LogPrint("gobject", "%s\n", ostr.str());
          }
          return false;
      }
  
      vote_m_it it = mapCurrentMNVotes.find(vote.GetMasternodeOutpoint());
      if(it == mapCurrentMNVotes.end()) {
          it = mapCurrentMNVotes.insert(vote_m_t::value_type(vote.GetMasternodeOutpoint(), vote_rec_t())).first;
      }
      vote_rec_t& recVote = it->second;
      vote_signal_enum_t eSignal = vote.GetSignal();
      if(eSignal == VOTE_SIGNAL_NONE) {
          std::ostringstream ostr;
          ostr << "CGovernanceObject::ProcessVote -- Vote signal: none";
          LogPrint("gobject", "%s\n", ostr.str());
          exception = CGovernanceException(ostr.str(), GOVERNANCE_EXCEPTION_WARNING);
          return false;
      }
      if(eSignal > MAX_SUPPORTED_VOTE_SIGNAL) {
          std::ostringstream ostr;
          ostr << "CGovernanceObject::ProcessVote -- Unsupported vote signal: " << CGovernanceVoting::ConvertSignalToString(vote.GetSignal());
          LogPrintf("%s\n", ostr.str());
          exception = CGovernanceException(ostr.str(), GOVERNANCE_EXCEPTION_PERMANENT_ERROR, 20);
          return false;
      }
      vote_instance_m_it it2 = recVote.mapInstances.find(int(eSignal));
      if(it2 == recVote.mapInstances.end()) {
          it2 = recVote.mapInstances.insert(vote_instance_m_t::value_type(int(eSignal), vote_instance_t())).first;
      }
      vote_instance_t& voteInstance = it2->second;
  
      // Reject obsolete votes
      if(vote.GetTimestamp() < voteInstance.nCreationTime) {
          std::ostringstream ostr;
          ostr << "CGovernanceObject::ProcessVote -- Obsolete vote";
          LogPrint("gobject", "%s\n", ostr.str());
          exception = CGovernanceException(ostr.str(), GOVERNANCE_EXCEPTION_NONE);
          return false;
      }
  
      int64_t nNow = GetAdjustedTime();
      int64_t nVoteTimeUpdate = voteInstance.nTime;
      if(governance.AreRateChecksEnabled()) {
          int64_t nTimeDelta = nNow - voteInstance.nTime;
          if(nTimeDelta < GOVERNANCE_UPDATE_MIN) {
              std::ostringstream ostr;
              ostr << "CGovernanceObject::ProcessVote -- Masternode voting too often"
                   << ", MN outpoint = " << vote.GetMasternodeOutpoint().ToStringShort()
                   << ", governance object hash = " << GetHash().ToString()
                   << ", time delta = " << nTimeDelta;
              LogPrint("gobject", "%s\n", ostr.str());
              exception = CGovernanceException(ostr.str(), GOVERNANCE_EXCEPTION_TEMPORARY_ERROR);
              nVoteTimeUpdate = nNow;
              return false;
          }
      }
      // Finally check that the vote is actually valid (done last because of cost of signature verification)
      if(!vote.IsValid(true)) {
          std::ostringstream ostr;
          ostr << "CGovernanceObject::ProcessVote -- Invalid vote"
                  << ", MN outpoint = " << vote.GetMasternodeOutpoint().ToStringShort()
                  << ", governance object hash = " << GetHash().ToString()
                  << ", vote hash = " << vote.GetHash().ToString();
          LogPrintf("%s\n", ostr.str());
          exception = CGovernanceException(ostr.str(), GOVERNANCE_EXCEPTION_PERMANENT_ERROR, 20);
          governance.AddInvalidVote(vote);
          return false;
      }
      if(!mnodeman.AddGovernanceVote(vote.GetMasternodeOutpoint(), vote.GetParentHash())) {
          std::ostringstream ostr;
          ostr << "CGovernanceObject::ProcessVote -- Unable to add governance vote"
               << ", MN outpoint = " << vote.GetMasternodeOutpoint().ToStringShort()
               << ", governance object hash = " << GetHash().ToString();
          LogPrint("gobject", "%s\n", ostr.str());
          exception = CGovernanceException(ostr.str(), GOVERNANCE_EXCEPTION_PERMANENT_ERROR);
          return false;
      }
      voteInstance = vote_instance_t(vote.GetOutcome(), nVoteTimeUpdate, vote.GetTimestamp());
      if(!fileVotes.HasVote(vote.GetHash())) {
          fileVotes.AddVote(vote);
      }
      fDirtyCache = true;
      return true;
  }
  
  void CGovernanceObject::ClearMasternodeVotes()
  {
      vote_m_it it = mapCurrentMNVotes.begin();
      while(it != mapCurrentMNVotes.end()) {
          if(!mnodeman.Has(it->first)) {
              fileVotes.RemoveVotesFromMasternode(it->first);
              mapCurrentMNVotes.erase(it++);
          }
          else {
              ++it;
          }
      }
  }
  
  std::string CGovernanceObject::GetSignatureMessage() const
  {
      LOCK(cs);
      std::string strMessage = nHashParent.ToString() + "|" +
          boost::lexical_cast<std::string>(nRevision) + "|" +
          boost::lexical_cast<std::string>(nTime) + "|" +
          strData + "|" +
          vinMasternode.prevout.ToStringShort() + "|" +
          nCollateralHash.ToString();
  
      return strMessage;
  }
  
  void CGovernanceObject::SetMasternodeVin(const COutPoint& outpoint)
  {
      vinMasternode = CTxIn(outpoint);
  }
  
  bool CGovernanceObject::Sign(CKey& keyMasternode, CPubKey& pubKeyMasternode)
  {
      std::string strError;
      std::string strMessage = GetSignatureMessage();
  
      LOCK(cs);
  
      if(!CMessageSigner::SignMessage(strMessage, vchSig, keyMasternode)) {
          LogPrintf("CGovernanceObject::Sign -- SignMessage() failed\n");
          return false;
      }
  
      if(!CMessageSigner::VerifyMessage(pubKeyMasternode, vchSig, strMessage, strError)) {
          LogPrintf("CGovernanceObject::Sign -- VerifyMessage() failed, error: %s\n", strError);
          return false;
      }
  
      LogPrint("gobject", "CGovernanceObject::Sign -- pubkey id = %s, vin = %s\n",
               pubKeyMasternode.GetID().ToString(), vinMasternode.prevout.ToStringShort());
  
  
      return true;
  }
  
  bool CGovernanceObject::CheckSignature(CPubKey& pubKeyMasternode)
  {
      std::string strError;
  
      std::string strMessage = GetSignatureMessage();
  
      LOCK(cs);
      if(!CMessageSigner::VerifyMessage(pubKeyMasternode, vchSig, strMessage, strError)) {
          LogPrintf("CGovernance::CheckSignature -- VerifyMessage() failed, error: %s\n", strError);
          return false;
      }
  
      return true;
  }
  
  int CGovernanceObject::GetObjectSubtype()
  {
      // todo - 12.1
      //   - detect subtype from strData json, obj["subtype"]
  
      if(nObjectType == GOVERNANCE_OBJECT_TRIGGER) return TRIGGER_SUPERBLOCK;
      return -1;
  }
  
  uint256 CGovernanceObject::GetHash() const
  {
      // CREATE HASH OF ALL IMPORTANT PIECES OF DATA
  
      CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION);
      ss << nHashParent;
      ss << nRevision;
      ss << nTime;
      ss << strData;
      ss << vinMasternode;
      ss << vchSig;
      // fee_tx is left out on purpose
      uint256 h1 = ss.GetHash();
  
      DBG( printf("CGovernanceObject::GetHash %i %li %s\n", nRevision, nTime, strData.c_str()); );
  
      return h1;
  }
  
  /**
     Return the actual object from the strData JSON structure.
  
     Returns an empty object on error.
   */
  UniValue CGovernanceObject::GetJSONObject()
  {
      UniValue obj(UniValue::VOBJ);
      if(strData.empty()) {
          return obj;
      }
  
      UniValue objResult(UniValue::VOBJ);
      GetData(objResult);
  
      std::vector<UniValue> arr1 = objResult.getValues();
      std::vector<UniValue> arr2 = arr1.at( 0 ).getValues();
      obj = arr2.at( 1 );
  
      return obj;
  }
  
  /**
  *   LoadData
  *   --------------------------------------------------------
  *
  *   Attempt to load data from strData
  *
  */
  
  void CGovernanceObject::LoadData()
  {
      // todo : 12.1 - resolved
      //return;
  
      if(strData.empty()) {
          return;
      }
  
      try  {
          // ATTEMPT TO LOAD JSON STRING FROM STRDATA
          UniValue objResult(UniValue::VOBJ);
          GetData(objResult);
  
          DBG( cout << "CGovernanceObject::LoadData strData = "
               << GetDataAsString()
               << endl; );
  
          UniValue obj = GetJSONObject();
          nObjectType = obj["type"].get_int();
      }
      catch(std::exception& e) {
          fUnparsable = true;
          std::ostringstream ostr;
          ostr << "CGovernanceObject::LoadData Error parsing JSON"
               << ", e.what() = " << e.what();
          DBG( cout << ostr.str() << endl; );
          LogPrintf("%s\n", ostr.str());
          return;
      }
      catch(...) {
          fUnparsable = true;
          std::ostringstream ostr;
          ostr << "CGovernanceObject::LoadData Unknown Error parsing JSON";
          DBG( cout << ostr.str() << endl; );
          LogPrintf("%s\n", ostr.str());
          return;
      }
  }
  
  /**
  *   GetData - Example usage:
  *   --------------------------------------------------------
  *
  *   Decode governance object data into UniValue(VOBJ)
  *
  */
  
  void CGovernanceObject::GetData(UniValue& objResult)
  {
      UniValue o(UniValue::VOBJ);
      std::string s = GetDataAsString();
      o.read(s);
      objResult = o;
  }
  
  /**
  *   GetData - As
  *   --------------------------------------------------------
  *
  */
  
  std::string CGovernanceObject::GetDataAsHex()
  {
      return strData;
  }
  
  std::string CGovernanceObject::GetDataAsString()
  {
      std::vector<unsigned char> v = ParseHex(strData);
      std::string s(v.begin(), v.end());
  
      return s;
  }
  
  void CGovernanceObject::UpdateLocalValidity()
  {
      LOCK(cs_main);
      // THIS DOES NOT CHECK COLLATERAL, THIS IS CHECKED UPON ORIGINAL ARRIVAL
      fCachedLocalValidity = IsValidLocally(strLocalValidityError, false);
  };
  
  
  bool CGovernanceObject::IsValidLocally(std::string& strError, bool fCheckCollateral)
  {
      bool fMissingMasternode = false;
      bool fMissingConfirmations = false;
  
      return IsValidLocally(strError, fMissingMasternode, fMissingConfirmations, fCheckCollateral);
  }
  
  bool CGovernanceObject::IsValidLocally(std::string& strError, bool& fMissingMasternode, bool& fMissingConfirmations, bool fCheckCollateral)
  {
      fMissingMasternode = false;
      fMissingConfirmations = false;
  
      if(fUnparsable) {
          strError = "Object data unparseable";
          return false;
      }
  
      switch(nObjectType) {
          case GOVERNANCE_OBJECT_PROPOSAL:
          case GOVERNANCE_OBJECT_TRIGGER:
          case GOVERNANCE_OBJECT_WATCHDOG:
              break;
          default:
              strError = strprintf("Invalid object type %d", nObjectType);
              return false;
      }
  
      // IF ABSOLUTE NO COUNT (NO-YES VALID VOTES) IS MORE THAN 10% OF THE NETWORK MASTERNODES, OBJ IS INVALID
  
      // CHECK COLLATERAL IF REQUIRED (HIGH CPU USAGE)
  
      if(fCheckCollateral) { 
          if((nObjectType == GOVERNANCE_OBJECT_TRIGGER) || (nObjectType == GOVERNANCE_OBJECT_WATCHDOG)) {
              std::string strOutpoint = vinMasternode.prevout.ToStringShort();
              masternode_info_t infoMn;
              if(!mnodeman.GetMasternodeInfo(vinMasternode.prevout, infoMn)) {
  
                  CMasternode::CollateralStatus err = CMasternode::CheckCollateral(vinMasternode.prevout);
                  if (err == CMasternode::COLLATERAL_OK) {
                      fMissingMasternode = true;
                      strError = "Masternode not found: " + strOutpoint;
                  } else if (err == CMasternode::COLLATERAL_UTXO_NOT_FOUND) {
                      strError = "Failed to find Masternode UTXO, missing masternode=" + strOutpoint + "\n";
                  } else if (err == CMasternode::COLLATERAL_INVALID_AMOUNT) {
                      strError = "Masternode UTXO should have 1000 FLCOIN, missing masternode=" + strOutpoint + "\n";
                  }
  
                  return false;
              }
  
              // Check that we have a valid MN signature
              if(!CheckSignature(infoMn.pubKeyMasternode)) {
                  strError = "Invalid masternode signature for: " + strOutpoint + ", pubkey id = " + infoMn.pubKeyMasternode.GetID().ToString();
                  return false;
              }
  
              return true;
          }
  
          if (!IsCollateralValid(strError, fMissingConfirmations))
              return false;
      }
  
      /*
          TODO
  
          - There might be an issue with multisig in the coinbase on mainnet, we will add support for it in a future release.
          - Post 12.2+ (test multisig coinbase transaction)
      */
  
      // 12.1 - todo - compile error
      // if(address.IsPayToScriptHash()) {
      //     strError = "Governance system - multisig is not currently supported";
      //     return false;
      // }
  
      return true;
  }
  
  CAmount CGovernanceObject::GetMinCollateralFee()
  {
      // Only 1 type has a fee for the moment but switch statement allows for future object types
      switch(nObjectType) {
          case GOVERNANCE_OBJECT_PROPOSAL:    return GOVERNANCE_PROPOSAL_FEE_TX;
          case GOVERNANCE_OBJECT_TRIGGER:     return 0;
          case GOVERNANCE_OBJECT_WATCHDOG:    return 0;
          default:                            return MAX_MONEY;
      }
  }
  
  bool CGovernanceObject::IsCollateralValid(std::string& strError, bool& fMissingConfirmations)
  {
      strError = "";
      fMissingConfirmations = false;
      CAmount nMinFee = GetMinCollateralFee();
      uint256 nExpectedHash = GetHash();
  
      CTransaction txCollateral;
      uint256 nBlockHash;
  
      // RETRIEVE TRANSACTION IN QUESTION
  
      if(!GetTransaction(nCollateralHash, txCollateral, Params().GetConsensus(), nBlockHash, true)){
          strError = strprintf("Can't find collateral tx %s", txCollateral.ToString());
          LogPrintf("CGovernanceObject::IsCollateralValid -- %s\n", strError);
          return false;
      }
  
      if(txCollateral.vout.size() < 1) {
          strError = strprintf("tx vout size less than 1 | %d", txCollateral.vout.size());
          LogPrintf("CGovernanceObject::IsCollateralValid -- %s\n", strError);
          return false;
      }
  
      // LOOK FOR SPECIALIZED GOVERNANCE SCRIPT (PROOF OF BURN)
  
      CScript findScript;
      findScript << OP_RETURN << ToByteVector(nExpectedHash);
  
      DBG( cout << "IsCollateralValid: txCollateral.vout.size() = " << txCollateral.vout.size() << endl; );
  
      DBG( cout << "IsCollateralValid: findScript = " << ScriptToAsmStr( findScript, false ) << endl; );
  
      DBG( cout << "IsCollateralValid: nMinFee = " << nMinFee << endl; );
  
  
      bool foundOpReturn = false;
      BOOST_FOREACH(const CTxOut o, txCollateral.vout) {
          DBG( cout << "IsCollateralValid txout : " << o.ToString()
               << ", o.nValue = " << o.nValue
               << ", o.scriptPubKey = " << ScriptToAsmStr( o.scriptPubKey, false )
               << endl; );
          if(!o.scriptPubKey.IsNormalPaymentScript() && !o.scriptPubKey.IsUnspendable()){
              strError = strprintf("Invalid Script %s", txCollateral.ToString());
              LogPrintf ("CGovernanceObject::IsCollateralValid -- %s\n", strError);
              return false;
          }
          if(o.scriptPubKey == findScript && o.nValue >= nMinFee) {
              DBG( cout << "IsCollateralValid foundOpReturn = true" << endl; );
              foundOpReturn = true;
          }
          else  {
              DBG( cout << "IsCollateralValid No match, continuing" << endl; );
          }
  
      }
  
      if(!foundOpReturn){
          strError = strprintf("Couldn't find opReturn %s in %s", nExpectedHash.ToString(), txCollateral.ToString());
          LogPrintf ("CGovernanceObject::IsCollateralValid -- %s\n", strError);
          return false;
      }
  
      // GET CONFIRMATIONS FOR TRANSACTION
  
      AssertLockHeld(cs_main);
      int nConfirmationsIn = instantsend.GetConfirmations(nCollateralHash);
      if (nBlockHash != uint256()) {
          BlockMap::iterator mi = mapBlockIndex.find(nBlockHash);
          if (mi != mapBlockIndex.end() && (*mi).second) {
              CBlockIndex* pindex = (*mi).second;
              if (chainActive.Contains(pindex)) {
                  nConfirmationsIn += chainActive.Height() - pindex->nHeight + 1;
              }
          }
      }
  
      if(nConfirmationsIn < GOVERNANCE_FEE_CONFIRMATIONS) {
          strError = strprintf("Collateral requires at least %d confirmations to be relayed throughout the network (it has only %d)", GOVERNANCE_FEE_CONFIRMATIONS, nConfirmationsIn);
          if (nConfirmationsIn >= GOVERNANCE_MIN_RELAY_FEE_CONFIRMATIONS) {
              fMissingConfirmations = true;
              strError += ", pre-accepted -- waiting for required confirmations";
          } else {
              strError += ", rejected -- try again later";
          }
          LogPrintf ("CGovernanceObject::IsCollateralValid -- %s\n", strError);
  
          return false;
      }
  
      strError = "valid";
      return true;
  }
  
  int CGovernanceObject::CountMatchingVotes(vote_signal_enum_t eVoteSignalIn, vote_outcome_enum_t eVoteOutcomeIn) const
  {
      int nCount = 0;
      for(vote_m_cit it = mapCurrentMNVotes.begin(); it != mapCurrentMNVotes.end(); ++it) {
          const vote_rec_t& recVote = it->second;
          vote_instance_m_cit it2 = recVote.mapInstances.find(eVoteSignalIn);
          if(it2 == recVote.mapInstances.end()) {
              continue;
          }
          const vote_instance_t& voteInstance = it2->second;
          if(voteInstance.eOutcome == eVoteOutcomeIn) {
              ++nCount;
          }
      }
      return nCount;
  }
  
  /**
  *   Get specific vote counts for each outcome (funding, validity, etc)
  */
  
  int CGovernanceObject::GetAbsoluteYesCount(vote_signal_enum_t eVoteSignalIn) const
  {
      return GetYesCount(eVoteSignalIn) - GetNoCount(eVoteSignalIn);
  }
  
  int CGovernanceObject::GetAbsoluteNoCount(vote_signal_enum_t eVoteSignalIn) const
  {
      return GetNoCount(eVoteSignalIn) - GetYesCount(eVoteSignalIn);
  }
  
  int CGovernanceObject::GetYesCount(vote_signal_enum_t eVoteSignalIn) const
  {
      return CountMatchingVotes(eVoteSignalIn, VOTE_OUTCOME_YES);
  }
  
  int CGovernanceObject::GetNoCount(vote_signal_enum_t eVoteSignalIn) const
  {
      return CountMatchingVotes(eVoteSignalIn, VOTE_OUTCOME_NO);
  }
  
  int CGovernanceObject::GetAbstainCount(vote_signal_enum_t eVoteSignalIn) const
  {
      return CountMatchingVotes(eVoteSignalIn, VOTE_OUTCOME_ABSTAIN);
  }
  
  bool CGovernanceObject::GetCurrentMNVotes(const COutPoint& mnCollateralOutpoint, vote_rec_t& voteRecord)
  {
      vote_m_it it = mapCurrentMNVotes.find(mnCollateralOutpoint);
      if (it == mapCurrentMNVotes.end()) {
          return false;
      }
      voteRecord = it->second;
      return  true;
  }
  
  void CGovernanceObject::Relay(CConnman& connman)
  {
      CInv inv(MSG_GOVERNANCE_OBJECT, GetHash());
      connman.RelayInv(inv, MIN_GOVERNANCE_PEER_PROTO_VERSION);
  }
  
  void CGovernanceObject::UpdateSentinelVariables()
  {
      // CALCULATE MINIMUM SUPPORT LEVELS REQUIRED
  
      int nMnCount = mnodeman.CountEnabled();
      if(nMnCount == 0) return;
  
      // CALCULATE THE MINUMUM VOTE COUNT REQUIRED FOR FULL SIGNAL
  
      // todo - 12.1 - should be set to `10` after governance vote compression is implemented
      int nAbsVoteReq = std::max(Params().GetConsensus().nGovernanceMinQuorum, nMnCount / 10);
      int nAbsDeleteReq = std::max(Params().GetConsensus().nGovernanceMinQuorum, (2 * nMnCount) / 3);
      // todo - 12.1 - Temporarily set to 1 for testing - reverted
      //nAbsVoteReq = 1;
  
      // SET SENTINEL FLAGS TO FALSE
  
      fCachedFunding = false;
      fCachedValid = true; //default to valid
      fCachedEndorsed = false;
      fDirtyCache = false;
  
      // SET SENTINEL FLAGS TO TRUE IF MIMIMUM SUPPORT LEVELS ARE REACHED
      // ARE ANY OF THESE FLAGS CURRENTLY ACTIVATED?
  
      if(GetAbsoluteYesCount(VOTE_SIGNAL_FUNDING) >= nAbsVoteReq) fCachedFunding = true;
      if((GetAbsoluteYesCount(VOTE_SIGNAL_DELETE) >= nAbsDeleteReq) && !fCachedDelete) {
          fCachedDelete = true;
          if(nDeletionTime == 0) {
              nDeletionTime = GetAdjustedTime();
          }
      }
      if(GetAbsoluteYesCount(VOTE_SIGNAL_ENDORSED) >= nAbsVoteReq) fCachedEndorsed = true;
  
      if(GetAbsoluteNoCount(VOTE_SIGNAL_VALID) >= nAbsVoteReq) fCachedValid = false;
  }
  
  void CGovernanceObject::swap(CGovernanceObject& first, CGovernanceObject& second) // nothrow
  {
      // enable ADL (not necessary in our case, but good practice)
      using std::swap;
  
      // by swapping the members of two classes,
      // the two classes are effectively swapped
      swap(first.nHashParent, second.nHashParent);
      swap(first.nRevision, second.nRevision);
      swap(first.nTime, second.nTime);
      swap(first.nDeletionTime, second.nDeletionTime);
      swap(first.nCollateralHash, second.nCollateralHash);
      swap(first.strData, second.strData);
      swap(first.nObjectType, second.nObjectType);
  
      // swap all cached valid flags
      swap(first.fCachedFunding, second.fCachedFunding);
      swap(first.fCachedValid, second.fCachedValid);
      swap(first.fCachedDelete, second.fCachedDelete);
      swap(first.fCachedEndorsed, second.fCachedEndorsed);
      swap(first.fDirtyCache, second.fDirtyCache);
      swap(first.fExpired, second.fExpired);
  }
  
  void CGovernanceObject::CheckOrphanVotes(CConnman& connman)
  {
      int64_t nNow = GetAdjustedTime();
      const vote_mcache_t::list_t& listVotes = mapOrphanVotes.GetItemList();
      vote_mcache_t::list_cit it = listVotes.begin();
      while(it != listVotes.end()) {
          bool fRemove = false;
          const COutPoint& key = it->key;
          const vote_time_pair_t& pairVote = it->value;
          const CGovernanceVote& vote = pairVote.first;
          if(pairVote.second < nNow) {
              fRemove = true;
          }
          else if(!mnodeman.Has(vote.GetMasternodeOutpoint())) {
              ++it;
              continue;
          }
          CGovernanceException exception;
          if(!ProcessVote(NULL, vote, exception, connman)) {
              LogPrintf("CGovernanceObject::CheckOrphanVotes -- Failed to add orphan vote: %s\n", exception.what());
          }
          else {
              vote.Relay(connman);
              fRemove = true;
          }
          ++it;
          if(fRemove) {
              mapOrphanVotes.Erase(key, pairVote);
          }
      }
  }
  