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