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