text: // Copyright (c) 2012-2014 The Bitcoin developers
  // Copyright (c) 2014-2015 The Dash developers
  // Copyright (c) 2015-2018 The Seedcore developers
  // Distributed under the MIT software license, see the accompanying
  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
  
  #include "alert.h"
  
  #include "base58.h"
  #include "clientversion.h"
  #include "init.h"
  #include "net.h"
  #include "pubkey.h"
  #include "timedata.h"
  #include "ui_interface.h"
  #include "util.h"
  #include "version.h"
  
  #include <algorithm>
  #include <map>
  #include <stdint.h>
  
  #include <boost/algorithm/string/classification.hpp>
  #include <boost/algorithm/string/replace.hpp>
  #include <boost/thread.hpp>
  
  using namespace std;
  
  map<uint256, CAlert> mapAlerts;
  CCriticalSection cs_mapAlerts;
  
  void CUnsignedAlert::SetNull()
  {
      nVersion = 1;
      nRelayUntil = 0;
      nExpiration = 0;
      nID = 0;
      nCancel = 0;
      setCancel.clear();
      nMinVer = 0;
      nMaxVer = 0;
      setSubVer.clear();
      nPriority = 0;
  
      strComment.clear();
      strStatusBar.clear();
      strReserved.clear();
  }
  
  std::string CUnsignedAlert::ToString() const
  {
      std::string strSetCancel;
      for (auto& n: setCancel)
          strSetCancel += strprintf("%d ", n);
      std::string strSetSubVer;
      for (std::string str : setSubVer)
          strSetSubVer += "\"" + str + "\" ";
      return strprintf(
          "CAlert(\n"
          "    nVersion     = %d\n"
          "    nRelayUntil  = %d\n"
          "    nExpiration  = %d\n"
          "    nID          = %d\n"
          "    nCancel      = %d\n"
          "    setCancel    = %s\n"
          "    nMinVer      = %d\n"
          "    nMaxVer      = %d\n"
          "    setSubVer    = %s\n"
          "    nPriority    = %d\n"
          "    strComment   = \"%s\"\n"
          "    strStatusBar = \"%s\"\n"
          ")\n",
          nVersion,
          nRelayUntil,
          nExpiration,
          nID,
          nCancel,
          strSetCancel,
          nMinVer,
          nMaxVer,
          strSetSubVer,
          nPriority,
          strComment,
          strStatusBar);
  }
  
  void CAlert::SetNull()
  {
      CUnsignedAlert::SetNull();
      vchMsg.clear();
      vchSig.clear();
  }
  
  bool CAlert::IsNull() const
  {
      return (nExpiration == 0);
  }
  
  uint256 CAlert::GetHash() const
  {
      return Hash(this->vchMsg.begin(), this->vchMsg.end());
  }
  
  bool CAlert::IsInEffect() const
  {
      return (GetAdjustedTime() < nExpiration);
  }
  
  bool CAlert::Cancels(const CAlert& alert) const
  {
      if (!IsInEffect())
          return false; // this was a no-op before 31403
      return (alert.nID <= nCancel || setCancel.count(alert.nID));
  }
  
  bool CAlert::AppliesTo(int nVersion, std::string strSubVerIn) const
  {
      // TODO: rework for client-version-embedded-in-strSubVer ?
      return (IsInEffect() &&
              nMinVer <= nVersion && nVersion <= nMaxVer &&
              (setSubVer.empty() || setSubVer.count(strSubVerIn)));
  }
  
  bool CAlert::AppliesToMe() const
  {
      return AppliesTo(PROTOCOL_VERSION, FormatSubVersion(CLIENT_NAME, CLIENT_VERSION, std::vector<std::string>()));
  }
  
  bool CAlert::RelayTo(CNode* pnode) const
  {
      if (!IsInEffect())
          return false;
      // don't relay to nodes which haven't sent their version message
      if (pnode->nVersion == 0)
          return false;
      // returns true if wasn't already contained in the set
      if (pnode->setKnown.insert(GetHash()).second) {
          if (AppliesTo(pnode->nVersion, pnode->strSubVer) ||
              AppliesToMe() ||
              GetAdjustedTime() < nRelayUntil) {
              pnode->PushMessage("alert", *this);
              return true;
          }
      }
      return false;
  }
  
  bool CAlert::Sign()
  {
      CDataStream sMsg(SER_NETWORK, CLIENT_VERSION);
      sMsg << *(CUnsignedAlert*)this;
      vchMsg = std::vector<unsigned char>(sMsg.begin(), sMsg.end());
      CBitcoinSecret vchSecret;
      if (!vchSecret.SetString(GetArg("-alertkey", "")))
      {
          printf("CAlert::SignAlert() : vchSecret.SetString failed\n");
          return false;
      }
      CKey key = vchSecret.GetKey();
      if (!key.Sign(Hash(vchMsg.begin(), vchMsg.end()), vchSig))
      {
          printf("CAlert::SignAlert() : key.Sign failed\n");
          return false;
      }
  
      return true;
  }
  
  bool CAlert::CheckSignature(const std::vector<unsigned char>& alertKey) const
  {
      CPubKey key(alertKey);
      if (!key.Verify(Hash(vchMsg.begin(), vchMsg.end()), vchSig))
          return error("CAlert::CheckSignature() : verify signature failed");
  
      // Now unserialize the data
      CDataStream sMsg(vchMsg, SER_NETWORK, PROTOCOL_VERSION);
      sMsg >> *(CUnsignedAlert*)this;
      return true;
  }
  
  CAlert CAlert::getAlertByHash(const uint256& hash)
  {
      CAlert retval;
      {
          LOCK(cs_mapAlerts);
          map<uint256, CAlert>::iterator mi = mapAlerts.find(hash);
          if (mi != mapAlerts.end())
              retval = mi->second;
      }
      return retval;
  }
  
  bool CAlert::ProcessAlert(const std::vector<unsigned char>& alertKey, bool fThread)
  {
      if (!CheckSignature(alertKey))
          return false;
      if (!IsInEffect())
          return false;
  
      // alert.nID=max is reserved for if the alert key is
      // compromised. It must have a pre-defined message,
      // must never expire, must apply to all versions,
      // and must cancel all previous
      // alerts or it will be ignored (so an attacker can't
      // send an "everything is OK, don't panic" version that
      // cannot be overridden):
      int maxInt = std::numeric_limits<int>::max();
      if (nID == maxInt) {
          if (!(
                  nExpiration == maxInt &&
                  nCancel == (maxInt - 1) &&
                  nMinVer == 0 &&
                  nMaxVer == maxInt &&
                  setSubVer.empty() &&
                  nPriority == maxInt &&
                  strStatusBar == "URGENT: Alert key compromised, upgrade required"))
              return false;
      }
  
      {
          LOCK(cs_mapAlerts);
          // Cancel previous alerts
          for (map<uint256, CAlert>::iterator mi = mapAlerts.begin(); mi != mapAlerts.end();) {
              const CAlert& alert = (*mi).second;
              if (Cancels(alert)) {
                  LogPrint("alert", "cancelling alert %d\n", alert.nID);
                  uiInterface.NotifyAlertChanged((*mi).first, CT_DELETED);
                  mapAlerts.erase(mi++);
              } else if (!alert.IsInEffect()) {
                  LogPrint("alert", "expiring alert %d\n", alert.nID);
                  uiInterface.NotifyAlertChanged((*mi).first, CT_DELETED);
                  mapAlerts.erase(mi++);
              } else
                  mi++;
          }
  
          // Check if this alert has been cancelled
          for (PAIRTYPE(const uint256, CAlert) & item : mapAlerts) {
              const CAlert& alert = item.second;
              if (alert.Cancels(*this)) {
                  LogPrint("alert", "alert already cancelled by %d\n", alert.nID);
                  return false;
              }
          }
  
          // Add to mapAlerts
          mapAlerts.insert(make_pair(GetHash(), *this));
          // Notify UI and -alertnotify if it applies to me
          if (AppliesToMe()) {
              uiInterface.NotifyAlertChanged(GetHash(), CT_NEW);
              Notify(strStatusBar, fThread);
          }
      }
  
      LogPrint("alert", "accepted alert %d, AppliesToMe()=%d\n", nID, AppliesToMe());
      return true;
  }
  
  void CAlert::Notify(const std::string& strMessage, bool fThread)
  {
      std::string strCmd = GetArg("-alertnotify", "");
      if (strCmd.empty()) return;
  
      // Alert text should be plain ascii coming from a trusted source, but to
      // be safe we first strip anything not in safeChars, then add single quotes around
      // the whole string before passing it to the shell:
      std::string singleQuote("'");
      std::string safeStatus = SanitizeString(strMessage);
      safeStatus = singleQuote + safeStatus + singleQuote;
      boost::replace_all(strCmd, "%s", safeStatus);
  
      if (fThread)
          boost::thread t(runCommand, strCmd); // thread runs free
      else
          runCommand(strCmd);
  }
  
  void ThreadSendAlert()
  {
      if (!mapArgs.count("-sendalert") && !mapArgs.count("-printalert"))
          return;
  
      // Wait one minute so we get well connected. If we only need to print
      // but not to broadcast - do this right away.
      if (mapArgs.count("-sendalert"))
          MilliSleep(60*1000);
  
      //
      // Alerts are relayed around the network until nRelayUntil, flood
      // filling to every node.
      // After the relay time is past, new nodes are told about alerts
      // when they connect to peers, until either nExpiration or
      // the alert is cancelled by a newer alert.
      // Nodes never save alerts to disk, they are in-memory-only.
      //
      CAlert alert;
      alert.nRelayUntil   = GetTime() + 15 * 60;
      alert.nExpiration   = GetTime() + 30 * 60 * 60;
      alert.nID           = 1;  // keep track of alert IDs somewhere
      alert.nCancel       = 0;   // cancels previous messages up to this ID number
  
      // These versions are protocol versions
      alert.nMinVer       = 65000;
      alert.nMaxVer       = PROTOCOL_VERSION;
  
      //
      //  1000 for Misc warnings like out of disk space and clock is wrong
      //  2000 for longer invalid proof-of-work chain
      //  Higher numbers mean higher priority
      alert.nPriority     = 5000;
      alert.strComment    = "";
      alert.strStatusBar  = "UPGRADE REQUIRED: https://github.com/seed-core/seed";
  
      // Sign
      if(!alert.Sign()){
          LogPrintf("ThreadSendAlert() : could not sign alert\n");
          return;
      }
  
      // Test
      CDataStream sBuffer(SER_NETWORK, CLIENT_VERSION);
      sBuffer << alert;
      CAlert alert2;
      sBuffer >> alert2;
      if (!alert2.CheckSignature(Params().AlertKey())){
          printf("ThreadSendAlert() : CheckSignature failed\n");
          return;
      }
      assert(alert2.vchMsg == alert.vchMsg);
      assert(alert2.vchSig == alert.vchSig);
      alert.SetNull();
      printf("\nThreadSendAlert:\n");
      printf("hash=%s\n", alert2.GetHash().ToString().c_str());
      printf("%s", alert2.ToString().c_str());
      printf("vchMsg=%s\n", HexStr(alert2.vchMsg).c_str());
      printf("vchSig=%s\n", HexStr(alert2.vchSig).c_str());
  
      // Confirm
      if (!mapArgs.count("-sendalert"))
          return;
      while (vNodes.empty() && !ShutdownRequested())
          MilliSleep(500);
      if (ShutdownRequested())
          return;
  
      // Send
      printf("ThreadSendAlert() : Sending alert\n");
      int nSent = 0;{
          LOCK(cs_vNodes);
          for (CNode* pnode : vNodes){
              if (alert2.RelayTo(pnode)){
                  printf("ThreadSendAlert() : Sent alert to %s\n", pnode->addr.ToString().c_str());
                  nSent++;
              }
          }
      }
      printf("ThreadSendAlert() : Alert sent to %d nodes\n", nSent);
  }
  