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