clientmodel.cpp raw

   1  // Copyright (c) 2011-present The Bitcoin Core developers
   2  // Distributed under the MIT software license, see the accompanying
   3  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
   4  
   5  #include <bitcoin-build-config.h> // IWYU pragma: keep
   6  
   7  #include <qt/clientmodel.h>
   8  
   9  #include <qt/bantablemodel.h>
  10  #include <qt/guiconstants.h>
  11  #include <qt/guiutil.h>
  12  #include <qt/peertablemodel.h>
  13  #include <qt/peertablesortproxy.h>
  14  
  15  #include <clientversion.h>
  16  #include <common/args.h>
  17  #include <common/system.h>
  18  #include <interfaces/handler.h>
  19  #include <interfaces/node.h>
  20  #include <net.h>
  21  #include <netbase.h>
  22  #include <util/threadnames.h>
  23  #include <util/time.h>
  24  #include <validation.h>
  25  
  26  #include <cstdint>
  27  
  28  #include <QDebug>
  29  #include <QMetaObject>
  30  #include <QThread>
  31  #include <QTimer>
  32  
  33  static SteadyClock::time_point g_last_header_tip_update_notification{};
  34  static SteadyClock::time_point g_last_block_tip_update_notification{};
  35  
  36  ClientModel::ClientModel(interfaces::Node& node, OptionsModel *_optionsModel, QObject *parent) :
  37      QObject(parent),
  38      m_node(node),
  39      optionsModel(_optionsModel),
  40      m_thread(new QThread(this))
  41  {
  42      cachedBestHeaderHeight = -1;
  43      cachedBestHeaderTime = -1;
  44  
  45      peerTableModel = new PeerTableModel(m_node, this);
  46      m_peer_table_sort_proxy = new PeerTableSortProxy(this);
  47      m_peer_table_sort_proxy->setSourceModel(peerTableModel);
  48  
  49      banTableModel = new BanTableModel(m_node, this);
  50  
  51      QTimer* timer = new QTimer;
  52      timer->setInterval(MODEL_UPDATE_DELAY);
  53      connect(timer, &QTimer::timeout, [this] {
  54          // no locking required at this point
  55          // the following calls will acquire the required lock
  56          Q_EMIT mempoolSizeChanged(m_node.getMempoolSize(), m_node.getMempoolDynamicUsage(), m_node.getMempoolMaxUsage());
  57          Q_EMIT bytesChanged(m_node.getTotalBytesRecv(), m_node.getTotalBytesSent());
  58      });
  59      connect(m_thread, &QThread::finished, timer, &QObject::deleteLater);
  60      connect(m_thread, &QThread::started, [timer] { timer->start(); });
  61      // move timer to thread so that polling doesn't disturb main event loop
  62      timer->moveToThread(m_thread);
  63      m_thread->start();
  64      QTimer::singleShot(0, timer, []() {
  65          util::ThreadRename("qt-clientmodl");
  66      });
  67  
  68      subscribeToCoreSignals();
  69  }
  70  
  71  void ClientModel::stop()
  72  {
  73      unsubscribeFromCoreSignals();
  74  
  75      m_thread->quit();
  76      m_thread->wait();
  77  }
  78  
  79  ClientModel::~ClientModel()
  80  {
  81      stop();
  82  }
  83  
  84  int ClientModel::getNumConnections(unsigned int flags) const
  85  {
  86      ConnectionDirection connections = ConnectionDirection::None;
  87  
  88      if(flags == CONNECTIONS_IN)
  89          connections = ConnectionDirection::In;
  90      else if (flags == CONNECTIONS_OUT)
  91          connections = ConnectionDirection::Out;
  92      else if (flags == CONNECTIONS_ALL)
  93          connections = ConnectionDirection::Both;
  94  
  95      return m_node.getNodeCount(connections);
  96  }
  97  
  98  int ClientModel::getHeaderTipHeight() const
  99  {
 100      if (cachedBestHeaderHeight == -1) {
 101          // make sure we initially populate the cache via a cs_main lock
 102          // otherwise we need to wait for a tip update
 103          int height;
 104          int64_t blockTime;
 105          if (m_node.getHeaderTip(height, blockTime)) {
 106              cachedBestHeaderHeight = height;
 107              cachedBestHeaderTime = blockTime;
 108          }
 109      }
 110      return cachedBestHeaderHeight;
 111  }
 112  
 113  int64_t ClientModel::getHeaderTipTime() const
 114  {
 115      if (cachedBestHeaderTime == -1) {
 116          int height;
 117          int64_t blockTime;
 118          if (m_node.getHeaderTip(height, blockTime)) {
 119              cachedBestHeaderHeight = height;
 120              cachedBestHeaderTime = blockTime;
 121          }
 122      }
 123      return cachedBestHeaderTime;
 124  }
 125  
 126  
 127  std::map<CNetAddr, LocalServiceInfo> ClientModel::getNetLocalAddresses() const
 128  {
 129      return m_node.getNetLocalAddresses();
 130  }
 131  
 132  
 133  int ClientModel::getNumBlocks() const
 134  {
 135      if (m_cached_num_blocks == -1) {
 136          m_cached_num_blocks = m_node.getNumBlocks();
 137      }
 138      return m_cached_num_blocks;
 139  }
 140  
 141  uint256 ClientModel::getBestBlockHash()
 142  {
 143      uint256 tip{WITH_LOCK(m_cached_tip_mutex, return m_cached_tip_blocks)};
 144  
 145      if (!tip.IsNull()) {
 146          return tip;
 147      }
 148  
 149      // Lock order must be: first `cs_main`, then `m_cached_tip_mutex`.
 150      // The following will lock `cs_main` (and release it), so we must not
 151      // own `m_cached_tip_mutex` here.
 152      tip = m_node.getBestBlockHash();
 153  
 154      LOCK(m_cached_tip_mutex);
 155      // We checked that `m_cached_tip_blocks` is not null above, but then we
 156      // released the mutex `m_cached_tip_mutex`, so it could have changed in the
 157      // meantime. Thus, check again.
 158      if (m_cached_tip_blocks.IsNull()) {
 159          m_cached_tip_blocks = tip;
 160      }
 161      return m_cached_tip_blocks;
 162  }
 163  
 164  BlockSource ClientModel::getBlockSource() const
 165  {
 166      if (m_node.isLoadingBlocks()) return BlockSource::DISK;
 167      if (getNumConnections() > 0) return BlockSource::NETWORK;
 168      return BlockSource::NONE;
 169  }
 170  
 171  QString ClientModel::getStatusBarWarnings() const
 172  {
 173      return QString::fromStdString(m_node.getWarnings().translated);
 174  }
 175  
 176  OptionsModel *ClientModel::getOptionsModel()
 177  {
 178      return optionsModel;
 179  }
 180  
 181  PeerTableModel *ClientModel::getPeerTableModel()
 182  {
 183      return peerTableModel;
 184  }
 185  
 186  PeerTableSortProxy* ClientModel::peerTableSortProxy()
 187  {
 188      return m_peer_table_sort_proxy;
 189  }
 190  
 191  BanTableModel *ClientModel::getBanTableModel()
 192  {
 193      return banTableModel;
 194  }
 195  
 196  QString ClientModel::formatFullVersion() const
 197  {
 198      return QString::fromStdString(FormatFullVersion());
 199  }
 200  
 201  QString ClientModel::formatSubVersion() const
 202  {
 203      return QString::fromStdString(strSubVersion);
 204  }
 205  
 206  bool ClientModel::isReleaseVersion() const
 207  {
 208      return CLIENT_VERSION_IS_RELEASE;
 209  }
 210  
 211  QString ClientModel::formatClientStartupTime() const
 212  {
 213      return QDateTime::currentDateTime().addSecs(-TicksSeconds(GetUptime())).toString();
 214  }
 215  
 216  QString ClientModel::dataDir() const
 217  {
 218      return GUIUtil::PathToQString(gArgs.GetDataDirNet());
 219  }
 220  
 221  QString ClientModel::blocksDir() const
 222  {
 223      return GUIUtil::PathToQString(gArgs.GetBlocksDirPath());
 224  }
 225  
 226  void ClientModel::TipChanged(SynchronizationState sync_state, interfaces::BlockTip tip, double verification_progress, SyncType synctype)
 227  {
 228      if (synctype == SyncType::HEADER_SYNC) {
 229          // cache best headers time and height to reduce future cs_main locks
 230          cachedBestHeaderHeight = tip.block_height;
 231          cachedBestHeaderTime = tip.block_time;
 232      } else if (synctype == SyncType::BLOCK_SYNC) {
 233          m_cached_num_blocks = tip.block_height;
 234          WITH_LOCK(m_cached_tip_mutex, m_cached_tip_blocks = tip.block_hash;);
 235      }
 236  
 237      // Throttle GUI notifications about (a) blocks during initial sync, and (b) both blocks and headers during reindex.
 238      const bool throttle = (sync_state != SynchronizationState::POST_INIT && synctype == SyncType::BLOCK_SYNC) || sync_state == SynchronizationState::INIT_REINDEX;
 239      const auto now{throttle ? SteadyClock::now() : SteadyClock::time_point{}};
 240      auto& nLastUpdateNotification = synctype != SyncType::BLOCK_SYNC ? g_last_header_tip_update_notification : g_last_block_tip_update_notification;
 241      if (throttle && now < nLastUpdateNotification + MODEL_UPDATE_DELAY) {
 242          return;
 243      }
 244  
 245      Q_EMIT numBlocksChanged(tip.block_height, QDateTime::fromSecsSinceEpoch(tip.block_time), verification_progress, synctype, sync_state);
 246      nLastUpdateNotification = now;
 247  }
 248  
 249  void ClientModel::subscribeToCoreSignals()
 250  {
 251      m_event_handlers.emplace_back(m_node.handleShowProgress(
 252          [this](const std::string& title, int progress, [[maybe_unused]] bool resume_possible) {
 253              Q_EMIT showProgress(QString::fromStdString(title), progress);
 254          }));
 255      m_event_handlers.emplace_back(m_node.handleNotifyNumConnectionsChanged(
 256          [this](int new_num_connections) {
 257              Q_EMIT numConnectionsChanged(new_num_connections);
 258          }));
 259      m_event_handlers.emplace_back(m_node.handleNotifyNetworkActiveChanged(
 260          [this](bool network_active) {
 261              Q_EMIT networkActiveChanged(network_active);
 262          }));
 263      m_event_handlers.emplace_back(m_node.handleNotifyAlertChanged(
 264          [this]() {
 265              qDebug() << "ClientModel: NotifyAlertChanged";
 266              Q_EMIT alertsChanged(getStatusBarWarnings());
 267          }));
 268      m_event_handlers.emplace_back(m_node.handleBannedListChanged(
 269          [this]() {
 270              qDebug() << "ClienModel: Requesting update for peer banlist";
 271              QMetaObject::invokeMethod(banTableModel, [this] { banTableModel->refresh(); });
 272          }));
 273      m_event_handlers.emplace_back(m_node.handleNotifyBlockTip(
 274          [this](SynchronizationState sync_state, interfaces::BlockTip tip, double verification_progress) {
 275              TipChanged(sync_state, tip, verification_progress, SyncType::BLOCK_SYNC);
 276          }));
 277      m_event_handlers.emplace_back(m_node.handleNotifyHeaderTip(
 278          [this](SynchronizationState sync_state, interfaces::BlockTip tip, bool presync) {
 279              TipChanged(sync_state, tip, /*verification_progress=*/0.0, presync ? SyncType::HEADER_PRESYNC : SyncType::HEADER_SYNC);
 280          }));
 281  }
 282  
 283  void ClientModel::unsubscribeFromCoreSignals()
 284  {
 285      m_event_handlers.clear();
 286  }
 287  
 288  bool ClientModel::getProxyInfo(std::string& ip_port) const
 289  {
 290      const auto ipv4 = m_node.getProxy(NET_IPV4);
 291      const auto ipv6 = m_node.getProxy(NET_IPV6);
 292      if (ipv4 && ipv6) {
 293          ip_port = ipv4->proxy.ToStringAddrPort();
 294          return true;
 295      }
 296      return false;
 297  }
 298