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 }