text: // Copyright (c) 2014-2017 The TradingCoin Core developers
  // Distributed under the MIT/X11 software license, see the accompanying
  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
  
  #include "activemasternode.h"
  #include "checkpoints.h"
  #include "governance.h"
  #include "validation.h"
  #include "masternode.h"
  #include "masternode-payments.h"
  #include "masternode-sync.h"
  #include "masternodeman.h"
  #include "netfulfilledman.h"
  #include "spork.h"
  #include "ui_interface.h"
  #include "util.h"
  
  class CMasternodeSync;
  CMasternodeSync masternodeSync;
  
  void CMasternodeSync::Fail()
  {
      nTimeLastFailure = GetTime();
      nRequestedMasternodeAssets = MASTERNODE_SYNC_FAILED;
  }
  
  void CMasternodeSync::Reset()
  {
      nRequestedMasternodeAssets = MASTERNODE_SYNC_INITIAL;
      nRequestedMasternodeAttempt = 0;
      nTimeAssetSyncStarted = GetTime();
      nTimeLastBumped = GetTime();
      nTimeLastFailure = 0;
  }
  
  void CMasternodeSync::BumpAssetLastTime(std::string strFuncName)
  {
      if(IsSynced() || IsFailed()) return;
      nTimeLastBumped = GetTime();
      LogPrint("mnsync", "CMasternodeSync::BumpAssetLastTime -- %s\n", strFuncName);
  }
  
  std::string CMasternodeSync::GetAssetName()
  {
      switch(nRequestedMasternodeAssets)
      {
          case(MASTERNODE_SYNC_INITIAL):      return "MASTERNODE_SYNC_INITIAL";
          case(MASTERNODE_SYNC_WAITING):      return "MASTERNODE_SYNC_WAITING";
          case(MASTERNODE_SYNC_LIST):         return "MASTERNODE_SYNC_LIST";
          case(MASTERNODE_SYNC_MNW):          return "MASTERNODE_SYNC_MNW";
          case(MASTERNODE_SYNC_GOVERNANCE):   return "MASTERNODE_SYNC_GOVERNANCE";
          case(MASTERNODE_SYNC_FAILED):       return "MASTERNODE_SYNC_FAILED";
          case MASTERNODE_SYNC_FINISHED:      return "MASTERNODE_SYNC_FINISHED";
          default:                            return "UNKNOWN";
      }
  }
  
  void CMasternodeSync::SwitchToNextAsset(CConnman& connman)
  {
      switch(nRequestedMasternodeAssets)
      {
          case(MASTERNODE_SYNC_FAILED):
              throw std::runtime_error("Can't switch to next asset from failed, should use Reset() first!");
              break;
          case(MASTERNODE_SYNC_INITIAL):
              ClearFulfilledRequests(connman);
              nRequestedMasternodeAssets = MASTERNODE_SYNC_WAITING;
              LogPrintf("CMasternodeSync::SwitchToNextAsset -- Starting %s\n", GetAssetName());
              break;
          case(MASTERNODE_SYNC_WAITING):
              ClearFulfilledRequests(connman);
              LogPrintf("CMasternodeSync::SwitchToNextAsset -- Completed %s in %llds\n", GetAssetName(), GetTime() - nTimeAssetSyncStarted);
              nRequestedMasternodeAssets = MASTERNODE_SYNC_LIST;
              LogPrintf("CMasternodeSync::SwitchToNextAsset -- Starting %s\n", GetAssetName());
              break;
          case(MASTERNODE_SYNC_LIST):
              LogPrintf("CMasternodeSync::SwitchToNextAsset -- Completed %s in %llds\n", GetAssetName(), GetTime() - nTimeAssetSyncStarted);
              nRequestedMasternodeAssets = MASTERNODE_SYNC_MNW;
              LogPrintf("CMasternodeSync::SwitchToNextAsset -- Starting %s\n", GetAssetName());
              break;
          case(MASTERNODE_SYNC_MNW):
              LogPrintf("CMasternodeSync::SwitchToNextAsset -- Completed %s in %llds\n", GetAssetName(), GetTime() - nTimeAssetSyncStarted);
              nRequestedMasternodeAssets = MASTERNODE_SYNC_GOVERNANCE;
              LogPrintf("CMasternodeSync::SwitchToNextAsset -- Starting %s\n", GetAssetName());
              break;
          case(MASTERNODE_SYNC_GOVERNANCE):
              LogPrintf("CMasternodeSync::SwitchToNextAsset -- Completed %s in %llds\n", GetAssetName(), GetTime() - nTimeAssetSyncStarted);
              nRequestedMasternodeAssets = MASTERNODE_SYNC_FINISHED;
              uiInterface.NotifyAdditionalDataSyncProgressChanged(1);
              //try to activate our masternode if possible
              activeMasternode.ManageState(connman);
  
              // TODO: Find out whether we can just use LOCK instead of:
              // TRY_LOCK(cs_vNodes, lockRecv);
              // if(lockRecv) { ... }
  
              connman.ForEachNode(CConnman::AllNodes, [](CNode* pnode) {
                  netfulfilledman.AddFulfilledRequest(pnode->addr, "full-sync");
              });
              LogPrintf("CMasternodeSync::SwitchToNextAsset -- Sync has finished\n");
  
              break;
      }
      nRequestedMasternodeAttempt = 0;
      nTimeAssetSyncStarted = GetTime();
      BumpAssetLastTime("CMasternodeSync::SwitchToNextAsset");
  }
  
  std::string CMasternodeSync::GetSyncStatus()
  {
      switch (masternodeSync.nRequestedMasternodeAssets) {
          case MASTERNODE_SYNC_INITIAL:       return _("Synchroning blockchain...");
          case MASTERNODE_SYNC_WAITING:       return _("Synchronization pending...");
          case MASTERNODE_SYNC_LIST:          return _("Synchronizing masternodes...");
          case MASTERNODE_SYNC_MNW:           return _("Synchronizing masternode payments...");
          case MASTERNODE_SYNC_GOVERNANCE:    return _("Synchronizing governance objects...");
          case MASTERNODE_SYNC_FAILED:        return _("Synchronization failed");
          case MASTERNODE_SYNC_FINISHED:      return _("Synchronization finished");
          default:                            return "";
      }
  }
  
  void CMasternodeSync::ProcessMessage(CNode* pfrom, std::string& strCommand, CDataStream& vRecv)
  {
      if (strCommand == NetMsgType::SYNCSTATUSCOUNT) { //Sync status count
  
          //do not care about stats if sync process finished or failed
          if(IsSynced() || IsFailed()) return;
  
          int nItemID;
          int nCount;
          vRecv >> nItemID >> nCount;
  
          LogPrintf("SYNCSTATUSCOUNT -- got inventory count: nItemID=%d  nCount=%d  peer=%d\n", nItemID, nCount, pfrom->id);
      }
  }
  
  void CMasternodeSync::ClearFulfilledRequests(CConnman& connman)
  {
      // TODO: Find out whether we can just use LOCK instead of:
      // TRY_LOCK(cs_vNodes, lockRecv);
      // if(!lockRecv) return;
  
      connman.ForEachNode(CConnman::AllNodes, [](CNode* pnode) {
          netfulfilledman.RemoveFulfilledRequest(pnode->addr, "spork-sync");
          netfulfilledman.RemoveFulfilledRequest(pnode->addr, "masternode-list-sync");
          netfulfilledman.RemoveFulfilledRequest(pnode->addr, "masternode-payment-sync");
          netfulfilledman.RemoveFulfilledRequest(pnode->addr, "governance-sync");
          netfulfilledman.RemoveFulfilledRequest(pnode->addr, "full-sync");
      });
  }
  
  void CMasternodeSync::ProcessTick(CConnman& connman)
  {
      static int nTick = 0;
      if(nTick++ % MASTERNODE_SYNC_TICK_SECONDS != 0) return;
  
      // reset the sync process if the last call to this function was more than 60 minutes ago (client was in sleep mode)
      static int64_t nTimeLastProcess = GetTime();
      if(GetTime() - nTimeLastProcess > 60*60) {
          LogPrintf("CMasternodeSync::HasSyncFailures -- WARNING: no actions for too long, restarting sync...\n");
          Reset();
          SwitchToNextAsset(connman);
          nTimeLastProcess = GetTime();
          return;
      }
      nTimeLastProcess = GetTime();
  
      // reset sync status in case of any other sync failure
      if(IsFailed()) {
          if(nTimeLastFailure + (1*60) < GetTime()) { // 1 minute cooldown after failed sync
              LogPrintf("CMasternodeSync::HasSyncFailures -- WARNING: failed to sync, trying again...\n");
              Reset();
              SwitchToNextAsset(connman);
          }
          return;
      }
  
      // gradually request the rest of the votes after sync finished
      if(IsSynced()) {
          std::vector<CNode*> vNodesCopy = connman.CopyNodeVector();
          governance.RequestGovernanceObjectVotes(vNodesCopy, connman);
          connman.ReleaseNodeVector(vNodesCopy);
          return;
      }
  
      // Calculate "progress" for LOG reporting / GUI notification
      double nSyncProgress = double(nRequestedMasternodeAttempt + (nRequestedMasternodeAssets - 1) * 8) / (8*4);
      LogPrintf("CMasternodeSync::ProcessTick -- nTick %d nRequestedMasternodeAssets %d nRequestedMasternodeAttempt %d nSyncProgress %f\n", nTick, nRequestedMasternodeAssets, nRequestedMasternodeAttempt, nSyncProgress);
      uiInterface.NotifyAdditionalDataSyncProgressChanged(nSyncProgress);
  
      std::vector<CNode*> vNodesCopy = connman.CopyNodeVector();
  
      BOOST_FOREACH(CNode* pnode, vNodesCopy)
      {
          // Don't try to sync any data from outbound "masternode" connections -
          // they are temporary and should be considered unreliable for a sync process.
          // Inbound connection this early is most likely a "masternode" connection
          // initiated from another node, so skip it too.
          if(pnode->fMasternode || (fMasterNode && pnode->fInbound)) continue;
  
          // QUICK MODE (REGTEST ONLY!)
          if(Params().NetworkIDString() == CBaseChainParams::REGTEST)
          {
              if(nRequestedMasternodeAttempt <= 2) {
                  connman.PushMessageWithVersion(pnode, INIT_PROTO_VERSION, NetMsgType::GETSPORKS); //get current network sporks
              } else if(nRequestedMasternodeAttempt < 4) {
                  mnodeman.DsegUpdate(pnode, connman);
              } else if(nRequestedMasternodeAttempt < 6) {
                  int nMnCount = mnodeman.CountMasternodes();
                  connman.PushMessage(pnode, NetMsgType::MASTERNODEPAYMENTSYNC, nMnCount); //sync payment votes
                  SendGovernanceSyncRequest(pnode, connman);
              } else {
                  nRequestedMasternodeAssets = MASTERNODE_SYNC_FINISHED;
              }
              nRequestedMasternodeAttempt++;
              connman.ReleaseNodeVector(vNodesCopy);
              return;
          }
  
          // NORMAL NETWORK MODE - TESTNET/MAINNET
          {
              if(netfulfilledman.HasFulfilledRequest(pnode->addr, "full-sync")) {
                  // We already fully synced from this node recently,
                  // disconnect to free this connection slot for another peer.
                  pnode->fDisconnect = true;
                  LogPrintf("CMasternodeSync::ProcessTick -- disconnecting from recently synced peer %d\n", pnode->id);
                  continue;
              }
  
              // SPORK : ALWAYS ASK FOR SPORKS AS WE SYNC
  
              if(!netfulfilledman.HasFulfilledRequest(pnode->addr, "spork-sync")) {
                  // always get sporks first, only request once from each peer
                  netfulfilledman.AddFulfilledRequest(pnode->addr, "spork-sync");
                  // get current network sporks
                  connman.PushMessageWithVersion(pnode, INIT_PROTO_VERSION, NetMsgType::GETSPORKS);
                  LogPrintf("CMasternodeSync::ProcessTick -- nTick %d nRequestedMasternodeAssets %d -- requesting sporks from peer %d\n", nTick, nRequestedMasternodeAssets, pnode->id);
              }
  
              // INITIAL TIMEOUT
  
              if(nRequestedMasternodeAssets == MASTERNODE_SYNC_WAITING) {
                  if(GetTime() - nTimeLastBumped > MASTERNODE_SYNC_TIMEOUT_SECONDS) {
                      // At this point we know that:
                      // a) there are peers (because we are looping on at least one of them);
                      // b) we waited for at least MASTERNODE_SYNC_TIMEOUT_SECONDS since we reached
                      //    the headers tip the last time (i.e. since we switched from
                      //     MASTERNODE_SYNC_INITIAL to MASTERNODE_SYNC_WAITING and bumped time);
                      // c) there were no blocks (UpdatedBlockTip, NotifyHeaderTip) or headers (AcceptedBlockHeader)
                      //    for at least MASTERNODE_SYNC_TIMEOUT_SECONDS.
                      // We must be at the tip already, let's move to the next asset.
                      SwitchToNextAsset(connman);
                  }
              }
  
              // MNLIST : SYNC MASTERNODE LIST FROM OTHER CONNECTED CLIENTS
  
              if(nRequestedMasternodeAssets == MASTERNODE_SYNC_LIST) {
                  LogPrint("masternode", "CMasternodeSync::ProcessTick -- nTick %d nRequestedMasternodeAssets %d nTimeLastBumped %lld GetTime() %lld diff %lld\n", nTick, nRequestedMasternodeAssets, nTimeLastBumped, GetTime(), GetTime() - nTimeLastBumped);
                  // check for timeout first
                  if(GetTime() - nTimeLastBumped > MASTERNODE_SYNC_TIMEOUT_SECONDS) {
                      LogPrintf("CMasternodeSync::ProcessTick -- nTick %d nRequestedMasternodeAssets %d -- timeout\n", nTick, nRequestedMasternodeAssets);
                      if (nRequestedMasternodeAttempt == 0) {
                          LogPrintf("CMasternodeSync::ProcessTick -- ERROR: failed to sync %s\n", GetAssetName());
                          // there is no way we can continue without masternode list, fail here and try later
                          Fail();
                          connman.ReleaseNodeVector(vNodesCopy);
                          return;
                      }
                      SwitchToNextAsset(connman);
                      connman.ReleaseNodeVector(vNodesCopy);
                      return;
                  }
  
                  // only request once from each peer
                  if(netfulfilledman.HasFulfilledRequest(pnode->addr, "masternode-list-sync")) continue;
                  netfulfilledman.AddFulfilledRequest(pnode->addr, "masternode-list-sync");
  
                  if (pnode->nVersion < mnpayments.GetMinMasternodePaymentsProto()) continue;
                  nRequestedMasternodeAttempt++;
  
                  mnodeman.DsegUpdate(pnode, connman);
  
                  connman.ReleaseNodeVector(vNodesCopy);
                  return; //this will cause each peer to get one request each six seconds for the various assets we need
              }
  
              // MNW : SYNC MASTERNODE PAYMENT VOTES FROM OTHER CONNECTED CLIENTS
  
              if(nRequestedMasternodeAssets == MASTERNODE_SYNC_MNW) {
                  LogPrint("mnpayments", "CMasternodeSync::ProcessTick -- nTick %d nRequestedMasternodeAssets %d nTimeLastBumped %lld GetTime() %lld diff %lld\n", nTick, nRequestedMasternodeAssets, nTimeLastBumped, GetTime(), GetTime() - nTimeLastBumped);
                  // check for timeout first
                  // This might take a lot longer than MASTERNODE_SYNC_TIMEOUT_SECONDS due to new blocks,
                  // but that should be OK and it should timeout eventually.
                  if(GetTime() - nTimeLastBumped > MASTERNODE_SYNC_TIMEOUT_SECONDS) {
                      LogPrintf("CMasternodeSync::ProcessTick -- nTick %d nRequestedMasternodeAssets %d -- timeout\n", nTick, nRequestedMasternodeAssets);
                      if (nRequestedMasternodeAttempt == 0) {
                          LogPrintf("CMasternodeSync::ProcessTick -- ERROR: failed to sync %s\n", GetAssetName());
                          // probably not a good idea to proceed without winner list
                          Fail();
                          connman.ReleaseNodeVector(vNodesCopy);
                          return;
                      }
                      SwitchToNextAsset(connman);
                      connman.ReleaseNodeVector(vNodesCopy);
                      return;
                  }
  
                  // check for data
                  // if mnpayments already has enough blocks and votes, switch to the next asset
                  // try to fetch data from at least two peers though
                  if(nRequestedMasternodeAttempt > 1 && mnpayments.IsEnoughData()) {
                      LogPrintf("CMasternodeSync::ProcessTick -- nTick %d nRequestedMasternodeAssets %d -- found enough data\n", nTick, nRequestedMasternodeAssets);
                      SwitchToNextAsset(connman);
                      connman.ReleaseNodeVector(vNodesCopy);
                      return;
                  }
  
                  // only request once from each peer
                  if(netfulfilledman.HasFulfilledRequest(pnode->addr, "masternode-payment-sync")) continue;
                  netfulfilledman.AddFulfilledRequest(pnode->addr, "masternode-payment-sync");
  
                  if(pnode->nVersion < mnpayments.GetMinMasternodePaymentsProto()) continue;
                  nRequestedMasternodeAttempt++;
  
                  // ask node for all payment votes it has (new nodes will only return votes for future payments)
                  connman.PushMessage(pnode, NetMsgType::MASTERNODEPAYMENTSYNC, mnpayments.GetStorageLimit());
                  // ask node for missing pieces only (old nodes will not be asked)
                  mnpayments.RequestLowDataPaymentBlocks(pnode, connman);
  
                  connman.ReleaseNodeVector(vNodesCopy);
                  return; //this will cause each peer to get one request each six seconds for the various assets we need
              }
  
              // GOVOBJ : SYNC GOVERNANCE ITEMS FROM OUR PEERS
  
              if(nRequestedMasternodeAssets == MASTERNODE_SYNC_GOVERNANCE) {
                  LogPrint("gobject", "CMasternodeSync::ProcessTick -- nTick %d nRequestedMasternodeAssets %d nTimeLastBumped %lld GetTime() %lld diff %lld\n", nTick, nRequestedMasternodeAssets, nTimeLastBumped, GetTime(), GetTime() - nTimeLastBumped);
  
                  // check for timeout first
                  if(GetTime() - nTimeLastBumped > MASTERNODE_SYNC_TIMEOUT_SECONDS) {
                      LogPrintf("CMasternodeSync::ProcessTick -- nTick %d nRequestedMasternodeAssets %d -- timeout\n", nTick, nRequestedMasternodeAssets);
                      if(nRequestedMasternodeAttempt == 0) {
                          LogPrintf("CMasternodeSync::ProcessTick -- WARNING: failed to sync %s\n", GetAssetName());
                          // it's kind of ok to skip this for now, hopefully we'll catch up later?
                      }
                      SwitchToNextAsset(connman);
                      connman.ReleaseNodeVector(vNodesCopy);
                      return;
                  }
  
                  // only request obj sync once from each peer, then request votes on per-obj basis
                  if(netfulfilledman.HasFulfilledRequest(pnode->addr, "governance-sync")) {
                      int nObjsLeftToAsk = governance.RequestGovernanceObjectVotes(pnode, connman);
                      static int64_t nTimeNoObjectsLeft = 0;
                      // check for data
                      if(nObjsLeftToAsk == 0) {
                          static int nLastTick = 0;
                          static int nLastVotes = 0;
                          if(nTimeNoObjectsLeft == 0) {
                              // asked all objects for votes for the first time
                              nTimeNoObjectsLeft = GetTime();
                          }
                          // make sure the condition below is checked only once per tick
                          if(nLastTick == nTick) continue;
                          if(GetTime() - nTimeNoObjectsLeft > MASTERNODE_SYNC_TIMEOUT_SECONDS &&
                              governance.GetVoteCount() - nLastVotes < std::max(int(0.0001 * nLastVotes), MASTERNODE_SYNC_TICK_SECONDS)
                          ) {
                              // We already asked for all objects, waited for MASTERNODE_SYNC_TIMEOUT_SECONDS
                              // after that and less then 0.01% or MASTERNODE_SYNC_TICK_SECONDS
                              // (i.e. 1 per second) votes were recieved during the last tick.
                              // We can be pretty sure that we are done syncing.
                              LogPrintf("CMasternodeSync::ProcessTick -- nTick %d nRequestedMasternodeAssets %d -- asked for all objects, nothing to do\n", nTick, nRequestedMasternodeAssets);
                              // reset nTimeNoObjectsLeft to be able to use the same condition on resync
                              nTimeNoObjectsLeft = 0;
                              SwitchToNextAsset(connman);
                              connman.ReleaseNodeVector(vNodesCopy);
                              return;
                          }
                          nLastTick = nTick;
                          nLastVotes = governance.GetVoteCount();
                      }
                      continue;
                  }
                  netfulfilledman.AddFulfilledRequest(pnode->addr, "governance-sync");
  
                  if (pnode->nVersion < MIN_GOVERNANCE_PEER_PROTO_VERSION) continue;
                  nRequestedMasternodeAttempt++;
  
                  SendGovernanceSyncRequest(pnode, connman);
  
                  connman.ReleaseNodeVector(vNodesCopy);
                  return; //this will cause each peer to get one request each six seconds for the various assets we need
              }
          }
      }
      // looped through all nodes, release them
      connman.ReleaseNodeVector(vNodesCopy);
  }
  
  void CMasternodeSync::SendGovernanceSyncRequest(CNode* pnode, CConnman& connman)
  {
      if(pnode->nVersion >= GOVERNANCE_FILTER_PROTO_VERSION) {
          CBloomFilter filter;
          filter.clear();
  
          connman.PushMessage(pnode, NetMsgType::MNGOVERNANCESYNC, uint256(), filter);
      }
      else {
          connman.PushMessage(pnode, NetMsgType::MNGOVERNANCESYNC, uint256());
      }
  }
  
  void CMasternodeSync::AcceptedBlockHeader(const CBlockIndex *pindexNew)
  {
      LogPrint("mnsync", "CMasternodeSync::AcceptedBlockHeader -- pindexNew->nHeight: %d\n", pindexNew->nHeight);
  
      if (!IsBlockchainSynced()) {
          // Postpone timeout each time new block header arrives while we are still syncing blockchain
          BumpAssetLastTime("CMasternodeSync::AcceptedBlockHeader");
      }
  }
  
  void CMasternodeSync::NotifyHeaderTip(const CBlockIndex *pindexNew, bool fInitialDownload, CConnman& connman)
  {
      LogPrint("mnsync", "CMasternodeSync::NotifyHeaderTip -- pindexNew->nHeight: %d fInitialDownload=%d\n", pindexNew->nHeight, fInitialDownload);
  
      if (IsFailed() || IsSynced() || !pindexBestHeader)
          return;
  
      if (!IsBlockchainSynced()) {
          // Postpone timeout each time new block arrives while we are still syncing blockchain
          BumpAssetLastTime("CMasternodeSync::NotifyHeaderTip");
      }
  }
  
  void CMasternodeSync::UpdatedBlockTip(const CBlockIndex *pindexNew, bool fInitialDownload, CConnman& connman)
  {
      LogPrint("mnsync", "CMasternodeSync::UpdatedBlockTip -- pindexNew->nHeight: %d fInitialDownload=%d\n", pindexNew->nHeight, fInitialDownload);
  
      if (IsFailed() || IsSynced() || !pindexBestHeader)
          return;
  
      if (!IsBlockchainSynced()) {
          // Postpone timeout each time new block arrives while we are still syncing blockchain
          BumpAssetLastTime("CMasternodeSync::UpdatedBlockTip");
      }
  
      if (fInitialDownload) {
          // switched too early
          if (IsBlockchainSynced()) {
              Reset();
          }
  
          // no need to check any further while still in IBD mode
          return;
      }
  
      // Note: since we sync headers first, it should be ok to use this
      static bool fReachedBestHeader = false;
      bool fReachedBestHeaderNew = pindexNew->GetBlockHash() == pindexBestHeader->GetBlockHash();
  
      if (fReachedBestHeader && !fReachedBestHeaderNew) {
          // Switching from true to false means that we previousely stuck syncing headers for some reason,
          // probably initial timeout was not enough,
          // because there is no way we can update tip not having best header
          Reset();
          fReachedBestHeader = false;
          return;
      }
  
      fReachedBestHeader = fReachedBestHeaderNew;
  
      LogPrint("mnsync", "CMasternodeSync::UpdatedBlockTip -- pindexNew->nHeight: %d pindexBestHeader->nHeight: %d fInitialDownload=%d fReachedBestHeader=%d\n",
                  pindexNew->nHeight, pindexBestHeader->nHeight, fInitialDownload, fReachedBestHeader);
  
      if (!IsBlockchainSynced() && fReachedBestHeader) {
          // Reached best header while being in initial mode.
          // We must be at the tip already, let's move to the next asset.
          SwitchToNextAsset(connman);
      }
  }
  