/ src / qt / clientmodel.cpp
clientmodel.cpp
  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  }