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