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 }