1 // Copyright (c) 2011-2020 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 <qt/clientmodel.h>
6
7 #include <qt/bantablemodel.h>
8 #include <qt/guiconstants.h>
9 #include <qt/guiutil.h>
10 #include <qt/peertablemodel.h>
11
12 #include <clientversion.h>
13 #include <interfaces/handler.h>
14 #include <interfaces/node.h>
15 #include <net.h>
16 #include <netbase.h>
17 #include <util/system.h>
18 #include <util/threadnames.h>
19 #include <validation.h>
20
21 #include <stdint.h>
22
23 #include <QDebug>
24 #include <QThread>
25 #include <QTimer>
26
27 static int64_t nLastHeaderTipUpdateNotification = 0;
28 static int64_t nLastBlockTipUpdateNotification = 0;
29
ClientModel(interfaces::Node & node,OptionsModel * _optionsModel,QObject * parent)30 ClientModel::ClientModel(interfaces::Node& node, OptionsModel *_optionsModel, QObject *parent) :
31 QObject(parent),
32 m_node(node),
33 optionsModel(_optionsModel),
34 peerTableModel(nullptr),
35 banTableModel(nullptr),
36 m_thread(new QThread(this))
37 {
38 cachedBestHeaderHeight = -1;
39 cachedBestHeaderTime = -1;
40 peerTableModel = new PeerTableModel(m_node, this);
41 banTableModel = new BanTableModel(m_node, this);
42
43 QTimer* timer = new QTimer;
44 timer->setInterval(MODEL_UPDATE_DELAY);
45 connect(timer, &QTimer::timeout, [this] {
46 // no locking required at this point
47 // the following calls will acquire the required lock
48 Q_EMIT mempoolSizeChanged(m_node.getMempoolSize(), m_node.getMempoolDynamicUsage());
49 Q_EMIT bytesChanged(m_node.getTotalBytesRecv(), m_node.getTotalBytesSent());
50 });
51 connect(m_thread, &QThread::finished, timer, &QObject::deleteLater);
52 connect(m_thread, &QThread::started, [timer] { timer->start(); });
53 // move timer to thread so that polling doesn't disturb main event loop
54 timer->moveToThread(m_thread);
55 m_thread->start();
56 QTimer::singleShot(0, timer, []() {
57 util::ThreadRename("qt-clientmodl");
58 });
59
60 subscribeToCoreSignals();
61 }
62
~ClientModel()63 ClientModel::~ClientModel()
64 {
65 unsubscribeFromCoreSignals();
66
67 m_thread->quit();
68 m_thread->wait();
69 }
70
getNumConnections(unsigned int flags) const71 int ClientModel::getNumConnections(unsigned int flags) const
72 {
73 CConnman::NumConnections connections = CConnman::CONNECTIONS_NONE;
74
75 if(flags == CONNECTIONS_IN)
76 connections = CConnman::CONNECTIONS_IN;
77 else if (flags == CONNECTIONS_OUT)
78 connections = CConnman::CONNECTIONS_OUT;
79 else if (flags == CONNECTIONS_ALL)
80 connections = CConnman::CONNECTIONS_ALL;
81
82 return m_node.getNodeCount(connections);
83 }
84
getHeaderTipHeight() const85 int ClientModel::getHeaderTipHeight() const
86 {
87 if (cachedBestHeaderHeight == -1) {
88 // make sure we initially populate the cache via a cs_main lock
89 // otherwise we need to wait for a tip update
90 int height;
91 int64_t blockTime;
92 if (m_node.getHeaderTip(height, blockTime)) {
93 cachedBestHeaderHeight = height;
94 cachedBestHeaderTime = blockTime;
95 }
96 }
97 return cachedBestHeaderHeight;
98 }
99
getHeaderTipTime() const100 int64_t ClientModel::getHeaderTipTime() const
101 {
102 if (cachedBestHeaderTime == -1) {
103 int height;
104 int64_t blockTime;
105 if (m_node.getHeaderTip(height, blockTime)) {
106 cachedBestHeaderHeight = height;
107 cachedBestHeaderTime = blockTime;
108 }
109 }
110 return cachedBestHeaderTime;
111 }
112
getNumBlocks() const113 int ClientModel::getNumBlocks() const
114 {
115 if (m_cached_num_blocks == -1) {
116 m_cached_num_blocks = m_node.getNumBlocks();
117 }
118 return m_cached_num_blocks;
119 }
120
getBestBlockHash()121 uint256 ClientModel::getBestBlockHash()
122 {
123 uint256 tip{WITH_LOCK(m_cached_tip_mutex, return m_cached_tip_blocks)};
124
125 if (!tip.IsNull()) {
126 return tip;
127 }
128
129 // Lock order must be: first `cs_main`, then `m_cached_tip_mutex`.
130 // The following will lock `cs_main` (and release it), so we must not
131 // own `m_cached_tip_mutex` here.
132 tip = m_node.getBestBlockHash();
133
134 LOCK(m_cached_tip_mutex);
135 // We checked that `m_cached_tip_blocks` is not null above, but then we
136 // released the mutex `m_cached_tip_mutex`, so it could have changed in the
137 // meantime. Thus, check again.
138 if (m_cached_tip_blocks.IsNull()) {
139 m_cached_tip_blocks = tip;
140 }
141 return m_cached_tip_blocks;
142 }
143
updateNumConnections(int numConnections)144 void ClientModel::updateNumConnections(int numConnections)
145 {
146 Q_EMIT numConnectionsChanged(numConnections);
147 }
148
updateNetworkActive(bool networkActive)149 void ClientModel::updateNetworkActive(bool networkActive)
150 {
151 Q_EMIT networkActiveChanged(networkActive);
152 }
153
updateAlert()154 void ClientModel::updateAlert()
155 {
156 Q_EMIT alertsChanged(getStatusBarWarnings());
157 }
158
getBlockSource() const159 enum BlockSource ClientModel::getBlockSource() const
160 {
161 if (m_node.getReindex())
162 return BlockSource::REINDEX;
163 else if (m_node.getImporting())
164 return BlockSource::DISK;
165 else if (getNumConnections() > 0)
166 return BlockSource::NETWORK;
167
168 return BlockSource::NONE;
169 }
170
getStatusBarWarnings() const171 QString ClientModel::getStatusBarWarnings() const
172 {
173 return QString::fromStdString(m_node.getWarnings().translated);
174 }
175
getOptionsModel()176 OptionsModel *ClientModel::getOptionsModel()
177 {
178 return optionsModel;
179 }
180
getPeerTableModel()181 PeerTableModel *ClientModel::getPeerTableModel()
182 {
183 return peerTableModel;
184 }
185
getBanTableModel()186 BanTableModel *ClientModel::getBanTableModel()
187 {
188 return banTableModel;
189 }
190
formatFullVersion() const191 QString ClientModel::formatFullVersion() const
192 {
193 return QString::fromStdString(FormatFullVersion());
194 }
195
formatSubVersion() const196 QString ClientModel::formatSubVersion() const
197 {
198 return QString::fromStdString(strSubVersion);
199 }
200
isReleaseVersion() const201 bool ClientModel::isReleaseVersion() const
202 {
203 return CLIENT_VERSION_IS_RELEASE;
204 }
205
formatClientStartupTime() const206 QString ClientModel::formatClientStartupTime() const
207 {
208 return QDateTime::fromTime_t(GetStartupTime()).toString();
209 }
210
dataDir() const211 QString ClientModel::dataDir() const
212 {
213 return GUIUtil::boostPathToQString(GetDataDir());
214 }
215
blocksDir() const216 QString ClientModel::blocksDir() const
217 {
218 return GUIUtil::boostPathToQString(GetBlocksDir());
219 }
220
updateBanlist()221 void ClientModel::updateBanlist()
222 {
223 banTableModel->refresh();
224 }
225
226 // Handlers for core signals
ShowProgress(ClientModel * clientmodel,const std::string & title,int nProgress)227 static void ShowProgress(ClientModel *clientmodel, const std::string &title, int nProgress)
228 {
229 // emits signal "showProgress"
230 bool invoked = QMetaObject::invokeMethod(clientmodel, "showProgress", Qt::QueuedConnection,
231 Q_ARG(QString, QString::fromStdString(title)),
232 Q_ARG(int, nProgress));
233 assert(invoked);
234 }
235
NotifyNumConnectionsChanged(ClientModel * clientmodel,int newNumConnections)236 static void NotifyNumConnectionsChanged(ClientModel *clientmodel, int newNumConnections)
237 {
238 // Too noisy: qDebug() << "NotifyNumConnectionsChanged: " + QString::number(newNumConnections);
239 bool invoked = QMetaObject::invokeMethod(clientmodel, "updateNumConnections", Qt::QueuedConnection,
240 Q_ARG(int, newNumConnections));
241 assert(invoked);
242 }
243
NotifyNetworkActiveChanged(ClientModel * clientmodel,bool networkActive)244 static void NotifyNetworkActiveChanged(ClientModel *clientmodel, bool networkActive)
245 {
246 bool invoked = QMetaObject::invokeMethod(clientmodel, "updateNetworkActive", Qt::QueuedConnection,
247 Q_ARG(bool, networkActive));
248 assert(invoked);
249 }
250
NotifyAlertChanged(ClientModel * clientmodel)251 static void NotifyAlertChanged(ClientModel *clientmodel)
252 {
253 qDebug() << "NotifyAlertChanged";
254 bool invoked = QMetaObject::invokeMethod(clientmodel, "updateAlert", Qt::QueuedConnection);
255 assert(invoked);
256 }
257
BannedListChanged(ClientModel * clientmodel)258 static void BannedListChanged(ClientModel *clientmodel)
259 {
260 qDebug() << QString("%1: Requesting update for peer banlist").arg(__func__);
261 bool invoked = QMetaObject::invokeMethod(clientmodel, "updateBanlist", Qt::QueuedConnection);
262 assert(invoked);
263 }
264
BlockTipChanged(ClientModel * clientmodel,SynchronizationState sync_state,interfaces::BlockTip tip,double verificationProgress,bool fHeader)265 static void BlockTipChanged(ClientModel* clientmodel, SynchronizationState sync_state, interfaces::BlockTip tip, double verificationProgress, bool fHeader)
266 {
267 if (fHeader) {
268 // cache best headers time and height to reduce future cs_main locks
269 clientmodel->cachedBestHeaderHeight = tip.block_height;
270 clientmodel->cachedBestHeaderTime = tip.block_time;
271 } else {
272 clientmodel->m_cached_num_blocks = tip.block_height;
273 WITH_LOCK(clientmodel->m_cached_tip_mutex, clientmodel->m_cached_tip_blocks = tip.block_hash;);
274 }
275
276 // Throttle GUI notifications about (a) blocks during initial sync, and (b) both blocks and headers during reindex.
277 const bool throttle = (sync_state != SynchronizationState::POST_INIT && !fHeader) || sync_state == SynchronizationState::INIT_REINDEX;
278 const int64_t now = throttle ? GetTimeMillis() : 0;
279 int64_t& nLastUpdateNotification = fHeader ? nLastHeaderTipUpdateNotification : nLastBlockTipUpdateNotification;
280 if (throttle && now < nLastUpdateNotification + MODEL_UPDATE_DELAY) {
281 return;
282 }
283
284 bool invoked = QMetaObject::invokeMethod(clientmodel, "numBlocksChanged", Qt::QueuedConnection,
285 Q_ARG(int, tip.block_height),
286 Q_ARG(QDateTime, QDateTime::fromTime_t(tip.block_time)),
287 Q_ARG(double, verificationProgress),
288 Q_ARG(bool, fHeader),
289 Q_ARG(SynchronizationState, sync_state));
290 assert(invoked);
291 nLastUpdateNotification = now;
292 }
293
subscribeToCoreSignals()294 void ClientModel::subscribeToCoreSignals()
295 {
296 // Connect signals to client
297 m_handler_show_progress = m_node.handleShowProgress(std::bind(ShowProgress, this, std::placeholders::_1, std::placeholders::_2));
298 m_handler_notify_num_connections_changed = m_node.handleNotifyNumConnectionsChanged(std::bind(NotifyNumConnectionsChanged, this, std::placeholders::_1));
299 m_handler_notify_network_active_changed = m_node.handleNotifyNetworkActiveChanged(std::bind(NotifyNetworkActiveChanged, this, std::placeholders::_1));
300 m_handler_notify_alert_changed = m_node.handleNotifyAlertChanged(std::bind(NotifyAlertChanged, this));
301 m_handler_banned_list_changed = m_node.handleBannedListChanged(std::bind(BannedListChanged, this));
302 m_handler_notify_block_tip = m_node.handleNotifyBlockTip(std::bind(BlockTipChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, false));
303 m_handler_notify_header_tip = m_node.handleNotifyHeaderTip(std::bind(BlockTipChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, true));
304 }
305
unsubscribeFromCoreSignals()306 void ClientModel::unsubscribeFromCoreSignals()
307 {
308 // Disconnect signals from client
309 m_handler_show_progress->disconnect();
310 m_handler_notify_num_connections_changed->disconnect();
311 m_handler_notify_network_active_changed->disconnect();
312 m_handler_notify_alert_changed->disconnect();
313 m_handler_banned_list_changed->disconnect();
314 m_handler_notify_block_tip->disconnect();
315 m_handler_notify_header_tip->disconnect();
316 }
317
getProxyInfo(std::string & ip_port) const318 bool ClientModel::getProxyInfo(std::string& ip_port) const
319 {
320 proxyType ipv4, ipv6;
321 if (m_node.getProxy((Network) 1, ipv4) && m_node.getProxy((Network) 2, ipv6)) {
322 ip_port = ipv4.proxy.ToStringIPPort();
323 return true;
324 }
325 return false;
326 }
327