Bitcoin Core 31.99.0
P2P Digital Currency
rpcconsole.cpp
Go to the documentation of this file.
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/rpcconsole.h>
8#include <qt/forms/ui_debugwindow.h>
9
10#include <chainparams.h>
11#include <common/system.h>
12#include <interfaces/node.h>
14#include <qt/bantablemodel.h>
15#include <qt/clientmodel.h>
16#include <qt/guiutil.h>
18#include <qt/platformstyle.h>
19#ifdef ENABLE_WALLET
20#include <qt/walletmodel.h>
21#endif // ENABLE_WALLET
22#include <rpc/client.h>
23#include <rpc/server.h>
24#include <util/byte_units.h>
25#include <util/strencodings.h>
26#include <util/string.h>
27#include <util/time.h>
28#include <util/threadnames.h>
29
30#include <univalue.h>
31
32#include <QAbstractButton>
33#include <QAbstractItemModel>
34#include <QDateTime>
35#include <QFont>
36#include <QKeyEvent>
37#include <QKeySequence>
38#include <QLatin1String>
39#include <QLocale>
40#include <QMenu>
41#include <QMessageBox>
42#include <QScreen>
43#include <QScrollBar>
44#include <QSettings>
45#include <QString>
46#include <QStringList>
47#include <QStyledItemDelegate>
48#include <QTime>
49#include <QTimer>
50#include <QVariant>
51
52#include <chrono>
53
54using util::Join;
55
56const int CONSOLE_HISTORY = 50;
58const QSize FONT_RANGE(4, 40);
59const char fontSizeSettingsKey[] = "consoleFontSize";
60
61const struct {
62 const char *url;
63 const char *source;
64} ICON_MAPPING[] = {
65 {"cmd-request", ":/icons/tx_input"},
66 {"cmd-reply", ":/icons/tx_output"},
67 {"cmd-error", ":/icons/tx_output"},
68 {"misc", ":/icons/tx_inout"},
69 {nullptr, nullptr}
70};
71
72namespace {
73
74// don't add private key handling cmd's to the history
75const QStringList historyFilter = QStringList()
76 << "createwallet"
77 << "createwalletdescriptor"
78 << "migratewallet"
79 << "signmessagewithprivkey"
80 << "signrawtransactionwithkey"
81 << "walletpassphrase"
82 << "walletpassphrasechange"
83 << "encryptwallet";
84
85}
86
87/* Object for executing console RPC commands in a separate thread.
88*/
89class RPCExecutor : public QObject
90{
91 Q_OBJECT
92public:
94
95public Q_SLOTS:
96 void request(const QString &command, const QString& wallet_name);
97
98Q_SIGNALS:
99 void reply(int category, const QString &command);
100
101private:
103};
104
105class PeerIdViewDelegate : public QStyledItemDelegate
106{
107 Q_OBJECT
108public:
109 explicit PeerIdViewDelegate(QObject* parent = nullptr)
110 : QStyledItemDelegate(parent) {}
111
112 QString displayText(const QVariant& value, const QLocale& locale) const override
113 {
114 // Additional spaces should visually separate right-aligned content
115 // from the next column to the right.
116 return value.toString() + QLatin1String(" ");
117 }
118};
119
120#include <qt/rpcconsole.moc>
121
142bool RPCConsole::RPCParseCommandLine(interfaces::Node* node, std::string &strResult, const std::string &strCommand, const bool fExecute, std::string * const pstrFilteredOut, const QString& wallet_name)
143{
144 std::vector< std::vector<std::string> > stack;
145 stack.emplace_back();
146
147 enum CmdParseState
148 {
149 STATE_EATING_SPACES,
150 STATE_EATING_SPACES_IN_ARG,
151 STATE_EATING_SPACES_IN_BRACKETS,
152 STATE_ARGUMENT,
153 STATE_SINGLEQUOTED,
154 STATE_DOUBLEQUOTED,
155 STATE_ESCAPE_OUTER,
156 STATE_ESCAPE_DOUBLEQUOTED,
157 STATE_COMMAND_EXECUTED,
158 STATE_COMMAND_EXECUTED_INNER
159 } state = STATE_EATING_SPACES;
160 std::string curarg;
161 UniValue lastResult;
162 bool command_parsed = false;
163 unsigned nDepthInsideSensitive = 0;
164 size_t filter_begin_pos = 0, chpos;
165 std::vector<std::pair<size_t, size_t>> filter_ranges;
166
167 auto add_to_current_stack = [&](const std::string& strArg) {
168 if (stack.back().empty() && (!nDepthInsideSensitive) && historyFilter.contains(QString::fromStdString(strArg), Qt::CaseInsensitive)) {
169 nDepthInsideSensitive = 1;
170 filter_begin_pos = chpos;
171 }
172 // Make sure stack is not empty before adding something
173 if (stack.empty()) {
174 stack.emplace_back();
175 }
176 stack.back().push_back(strArg);
177 };
178
179 auto close_out_params = [&]() {
180 if (nDepthInsideSensitive) {
181 if (!--nDepthInsideSensitive) {
182 assert(filter_begin_pos);
183 filter_ranges.emplace_back(filter_begin_pos, chpos);
184 filter_begin_pos = 0;
185 }
186 }
187 stack.pop_back();
188 };
189
190 std::string strCommandTerminated = strCommand;
191 if (strCommandTerminated.back() != '\n')
192 strCommandTerminated += "\n";
193 for (chpos = 0; chpos < strCommandTerminated.size(); ++chpos)
194 {
195 char ch = strCommandTerminated[chpos];
196 switch(state)
197 {
198 case STATE_COMMAND_EXECUTED_INNER:
199 case STATE_COMMAND_EXECUTED:
200 {
201 bool breakParsing = true;
202 switch(ch)
203 {
204 case '[': curarg.clear(); state = STATE_COMMAND_EXECUTED_INNER; break;
205 default:
206 if (state == STATE_COMMAND_EXECUTED_INNER)
207 {
208 if (ch != ']')
209 {
210 // append char to the current argument (which is also used for the query command)
211 curarg += ch;
212 break;
213 }
214 if (curarg.size() && fExecute)
215 {
216 // if we have a value query, query arrays with index and objects with a string key
217 UniValue subelement;
218 if (lastResult.isArray())
219 {
220 const auto parsed{ToIntegral<size_t>(curarg)};
221 if (!parsed) {
222 throw std::runtime_error("Invalid result query");
223 }
224 subelement = lastResult[parsed.value()];
225 }
226 else if (lastResult.isObject())
227 subelement = lastResult.find_value(curarg);
228 else
229 throw std::runtime_error("Invalid result query"); //no array or object: abort
230 lastResult = subelement;
231 }
232
233 state = STATE_COMMAND_EXECUTED;
234 break;
235 }
236 // don't break parsing when the char is required for the next argument
237 breakParsing = false;
238
239 // pop the stack and return the result to the current command arguments
240 close_out_params();
241
242 // don't stringify the json in case of a string to avoid doublequotes
243 if (lastResult.isStr())
244 curarg = lastResult.get_str();
245 else
246 curarg = lastResult.write(2);
247
248 // if we have a non empty result, use it as stack argument otherwise as general result
249 if (curarg.size())
250 {
251 if (stack.size())
252 add_to_current_stack(curarg);
253 else
254 strResult = curarg;
255 }
256 curarg.clear();
257 // assume eating space state
258 state = STATE_EATING_SPACES;
259 }
260 if (breakParsing)
261 break;
262 [[fallthrough]];
263 }
264 case STATE_ARGUMENT: // In or after argument
265 case STATE_EATING_SPACES_IN_ARG:
266 case STATE_EATING_SPACES_IN_BRACKETS:
267 case STATE_EATING_SPACES: // Handle runs of whitespace
268 switch(ch)
269 {
270 case '"': state = STATE_DOUBLEQUOTED; break;
271 case '\'': state = STATE_SINGLEQUOTED; break;
272 case '\\': state = STATE_ESCAPE_OUTER; break;
273 case '(': case ')': case '\n':
274 if (state == STATE_EATING_SPACES_IN_ARG)
275 throw std::runtime_error("Invalid Syntax");
276 if (state == STATE_ARGUMENT)
277 {
278 if (ch == '(' && stack.size() && stack.back().size() > 0)
279 {
280 if (nDepthInsideSensitive) {
281 ++nDepthInsideSensitive;
282 }
283 stack.emplace_back();
284 }
285
286 // don't allow commands after executed commands on baselevel
287 if (!stack.size())
288 throw std::runtime_error("Invalid Syntax");
289
290 add_to_current_stack(curarg);
291 curarg.clear();
292 state = STATE_EATING_SPACES_IN_BRACKETS;
293 }
294 if ((ch == ')' || ch == '\n') && stack.size() > 0 && stack.back().size() > 0)
295 {
296 if (fExecute) {
297 // Convert argument list to JSON objects in method-dependent way,
298 // and pass it along with the method name to the dispatcher.
299 UniValue params = RPCConvertValues(stack.back()[0], std::vector<std::string>(stack.back().begin() + 1, stack.back().end()));
300 std::string method = stack.back()[0];
301 std::string uri;
302 if (!wallet_name.isEmpty()) {
303 QByteArray encodedName = QUrl::toPercentEncoding(wallet_name);
304 uri = "/wallet/"+std::string(encodedName.constData(), encodedName.length());
305 }
306 assert(node);
307 lastResult = node->executeRpc(method, params, uri);
308 }
309
310 command_parsed = true;
311 state = STATE_COMMAND_EXECUTED;
312 curarg.clear();
313 }
314 break;
315 case ' ': case ',': case '\t':
316 if(state == STATE_EATING_SPACES_IN_ARG && curarg.empty() && ch == ',')
317 throw std::runtime_error("Invalid Syntax");
318
319 else if(state == STATE_ARGUMENT) // Space ends argument
320 {
321 add_to_current_stack(curarg);
322 curarg.clear();
323 }
324 if ((state == STATE_EATING_SPACES_IN_BRACKETS || state == STATE_ARGUMENT) && ch == ',')
325 {
326 state = STATE_EATING_SPACES_IN_ARG;
327 break;
328 }
329 state = STATE_EATING_SPACES;
330 break;
331 default: curarg += ch; state = STATE_ARGUMENT;
332 }
333 break;
334 case STATE_SINGLEQUOTED: // Single-quoted string
335 switch(ch)
336 {
337 case '\'': state = STATE_ARGUMENT; break;
338 default: curarg += ch;
339 }
340 break;
341 case STATE_DOUBLEQUOTED: // Double-quoted string
342 switch(ch)
343 {
344 case '"': state = STATE_ARGUMENT; break;
345 case '\\': state = STATE_ESCAPE_DOUBLEQUOTED; break;
346 default: curarg += ch;
347 }
348 break;
349 case STATE_ESCAPE_OUTER: // '\' outside quotes
350 curarg += ch; state = STATE_ARGUMENT;
351 break;
352 case STATE_ESCAPE_DOUBLEQUOTED: // '\' in double-quoted text
353 if(ch != '"' && ch != '\\') curarg += '\\'; // keep '\' for everything but the quote and '\' itself
354 curarg += ch; state = STATE_DOUBLEQUOTED;
355 break;
356 }
357 }
358 if (pstrFilteredOut) {
359 if (STATE_COMMAND_EXECUTED == state) {
360 assert(!stack.empty());
361 close_out_params();
362 }
363 *pstrFilteredOut = strCommand;
364 for (auto i = filter_ranges.rbegin(); i != filter_ranges.rend(); ++i) {
365 pstrFilteredOut->replace(i->first, i->second - i->first, "(…)");
366 }
367 }
368 switch(state) // final state
369 {
370 case STATE_COMMAND_EXECUTED:
371 if (lastResult.isStr())
372 strResult = lastResult.get_str();
373 else
374 strResult = lastResult.write(2);
375 [[fallthrough]];
376 case STATE_ARGUMENT:
377 return true;
378 case STATE_EATING_SPACES:
379 // Reaching this state without ever parsing a command means the line
380 // held no command name (e.g. ")", "()", "(", ","); treat it as invalid.
381 return command_parsed;
382 default: // ERROR to end in one of the other states
383 return false;
384 }
385}
386
387void RPCExecutor::request(const QString &command, const QString& wallet_name)
388{
389 try
390 {
391 std::string result;
392 std::string executableCommand = command.toStdString() + "\n";
393
394 // Catch the console-only-help command before RPC call is executed and reply with help text as-if a RPC reply.
395 if(executableCommand == "help-console\n") {
396 Q_EMIT reply(RPCConsole::CMD_REPLY, QString(("\n"
397 "This console accepts RPC commands using the standard syntax.\n"
398 " example: getblockhash 0\n\n"
399
400 "This console can also accept RPC commands using the parenthesized syntax.\n"
401 " example: getblockhash(0)\n\n"
402
403 "Commands may be nested when specified with the parenthesized syntax.\n"
404 " example: getblock(getblockhash(0) 1)\n\n"
405
406 "A space or a comma can be used to delimit arguments for either syntax.\n"
407 " example: getblockhash 0\n"
408 " getblockhash,0\n\n"
409
410 "Named results can be queried with a non-quoted key string in brackets using the parenthesized syntax.\n"
411 " example: getblock(getblockhash(0) 1)[tx]\n\n"
412
413 "Results without keys can be queried with an integer in brackets using the parenthesized syntax.\n"
414 " example: getblock(getblockhash(0),1)[tx][0]\n\n")));
415 return;
416 }
417 if (!RPCConsole::RPCExecuteCommandLine(m_node, result, executableCommand, nullptr, wallet_name)) {
418 Q_EMIT reply(RPCConsole::CMD_ERROR, QString("Parse error: unbalanced ' or \""));
419 return;
420 }
421
422 Q_EMIT reply(RPCConsole::CMD_REPLY, QString::fromStdString(result));
423 }
424 catch (UniValue& objError)
425 {
426 try // Nice formatting for standard-format error
427 {
428 int code = objError.find_value("code").getInt<int>();
429 std::string message = objError.find_value("message").get_str();
430 Q_EMIT reply(RPCConsole::CMD_ERROR, QString::fromStdString(message) + " (code " + QString::number(code) + ")");
431 }
432 catch (const std::runtime_error&) // raised when converting to invalid type, i.e. missing code or message
433 { // Show raw JSON object
434 Q_EMIT reply(RPCConsole::CMD_ERROR, QString::fromStdString(objError.write()));
435 }
436 }
437 catch (const std::exception& e)
438 {
439 Q_EMIT reply(RPCConsole::CMD_ERROR, QString("Error: ") + QString::fromStdString(e.what()));
440 }
441}
442
443RPCConsole::RPCConsole(interfaces::Node& node, const PlatformStyle *_platformStyle, QWidget *parent) :
444 QWidget(parent),
445 m_node(node),
446 ui(new Ui::RPCConsole),
447 platformStyle(_platformStyle)
448{
449 ui->setupUi(this);
450 QSettings settings;
451#ifdef ENABLE_WALLET
453 // RPCConsole widget is a window.
454 if (!restoreGeometry(settings.value("RPCConsoleWindowGeometry").toByteArray())) {
455 // Restore failed (perhaps missing setting), center the window
456 move(QGuiApplication::primaryScreen()->availableGeometry().center() - frameGeometry().center());
457 }
458 ui->splitter->restoreState(settings.value("RPCConsoleWindowPeersTabSplitterSizes").toByteArray());
459 } else
460#endif // ENABLE_WALLET
461 {
462 // RPCConsole is a child widget.
463 ui->splitter->restoreState(settings.value("RPCConsoleWidgetPeersTabSplitterSizes").toByteArray());
464 }
465
466 m_peer_widget_header_state = settings.value("PeersTabPeerHeaderState").toByteArray();
467 m_banlist_widget_header_state = settings.value("PeersTabBanlistHeaderState").toByteArray();
468
469 constexpr QChar nonbreaking_hyphen(8209);
470 const std::vector<QString> CONNECTION_TYPE_DOC{
471 //: Explanatory text for an inbound peer connection.
472 tr("Inbound: initiated by peer"),
473 /*: Explanatory text for an outbound peer connection that
474 relays all network information. This is the default behavior for
475 outbound connections. */
476 tr("Outbound Full Relay: default"),
477 /*: Explanatory text for an outbound peer connection that relays
478 network information about blocks and not transactions or addresses. */
479 tr("Outbound Block Relay: does not relay transactions or addresses"),
480 /*: Explanatory text for an outbound peer connection that was
481 established manually through one of several methods. The numbered
482 arguments are stand-ins for the methods available to establish
483 manual connections. */
484 tr("Outbound Manual: added using RPC %1 or %2/%3 configuration options")
485 .arg("addnode")
486 .arg(QString(nonbreaking_hyphen) + "addnode")
487 .arg(QString(nonbreaking_hyphen) + "connect"),
488 /*: Explanatory text for a short-lived outbound peer connection that
489 is used to test the aliveness of known addresses. */
490 tr("Outbound Feeler: short-lived, for testing addresses"),
491 /*: Explanatory text for a short-lived outbound peer connection that is used
492 to request addresses from a peer. */
493 tr("Outbound Address Fetch: short-lived, for soliciting addresses"),
494 /*: Explanatory text for a short-lived outbound peer connection that is used
495 to broadcast privacy-sensitive data (like our transactions). */
496 tr("Private broadcast: short-lived, for broadcasting privacy-sensitive transactions")};
497 const QString connection_types_list{"<ul><li>" + Join(CONNECTION_TYPE_DOC, QString("</li><li>")) + "</li></ul>"};
498 ui->peerConnectionTypeLabel->setToolTip(ui->peerConnectionTypeLabel->toolTip().arg(connection_types_list));
499 const std::vector<QString> TRANSPORT_TYPE_DOC{
500 //: Explanatory text for "detecting" transport type.
501 tr("detecting: peer could be v1 or v2"),
502 //: Explanatory text for v1 transport type.
503 tr("v1: unencrypted, plaintext transport protocol"),
504 //: Explanatory text for v2 transport type.
505 tr("v2: BIP324 encrypted transport protocol")};
506 const QString transport_types_list{"<ul><li>" + Join(TRANSPORT_TYPE_DOC, QString("</li><li>")) + "</li></ul>"};
507 ui->peerTransportTypeLabel->setToolTip(ui->peerTransportTypeLabel->toolTip().arg(transport_types_list));
508 const QString hb_list{"<ul><li>\""
509 + ts.to + "\" – " + tr("we selected the peer for high bandwidth relay") + "</li><li>\""
510 + ts.from + "\" – " + tr("the peer selected us for high bandwidth relay") + "</li><li>\""
511 + ts.no + "\" – " + tr("no high bandwidth relay selected") + "</li></ul>"};
512 ui->peerHighBandwidthLabel->setToolTip(ui->peerHighBandwidthLabel->toolTip().arg(hb_list));
513 ui->dataDir->setToolTip(ui->dataDir->toolTip().arg(QString(nonbreaking_hyphen) + "datadir"));
514 ui->blocksDir->setToolTip(ui->blocksDir->toolTip().arg(QString(nonbreaking_hyphen) + "blocksdir"));
515 ui->openDebugLogfileButton->setToolTip(ui->openDebugLogfileButton->toolTip().arg(CLIENT_NAME));
516
518 ui->openDebugLogfileButton->setIcon(platformStyle->SingleColorIcon(":/icons/export"));
519 }
520 ui->clearButton->setIcon(platformStyle->SingleColorIcon(":/icons/remove"));
521
522 ui->fontBiggerButton->setIcon(platformStyle->SingleColorIcon(":/icons/fontbigger"));
523 //: Main shortcut to increase the RPC console font size.
524 ui->fontBiggerButton->setShortcut(tr("Ctrl++"));
525 //: Secondary shortcut to increase the RPC console font size.
526 GUIUtil::AddButtonShortcut(ui->fontBiggerButton, tr("Ctrl+="));
527
528 ui->fontSmallerButton->setIcon(platformStyle->SingleColorIcon(":/icons/fontsmaller"));
529 //: Main shortcut to decrease the RPC console font size.
530 ui->fontSmallerButton->setShortcut(tr("Ctrl+-"));
531 //: Secondary shortcut to decrease the RPC console font size.
532 GUIUtil::AddButtonShortcut(ui->fontSmallerButton, tr("Ctrl+_"));
533
534 ui->promptIcon->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/prompticon")));
535
536 // Install event filter for up and down arrow
537 ui->lineEdit->installEventFilter(this);
538 ui->lineEdit->setMaxLength(16_MiB);
539 ui->messagesWidget->installEventFilter(this);
540
541 connect(ui->hidePeersDetailButton, &QAbstractButton::clicked, this, &RPCConsole::clearSelectedNode);
542 connect(ui->clearButton, &QAbstractButton::clicked, [this] { clear(); });
543 connect(ui->fontBiggerButton, &QAbstractButton::clicked, this, &RPCConsole::fontBigger);
544 connect(ui->fontSmallerButton, &QAbstractButton::clicked, this, &RPCConsole::fontSmaller);
545 connect(ui->btnClearTrafficGraph, &QPushButton::clicked, ui->trafficGraph, &TrafficGraphWidget::clear);
546
547 // disable the wallet selector by default
548 ui->WalletSelector->setVisible(false);
549 ui->WalletSelectorLabel->setVisible(false);
550
553
554 consoleFontSize = settings.value(fontSizeSettingsKey, QFont().pointSize()).toInt();
555 clear();
556
558
560}
561
563{
564 QSettings settings;
565#ifdef ENABLE_WALLET
567 // RPCConsole widget is a window.
568 settings.setValue("RPCConsoleWindowGeometry", saveGeometry());
569 settings.setValue("RPCConsoleWindowPeersTabSplitterSizes", ui->splitter->saveState());
570 } else
571#endif // ENABLE_WALLET
572 {
573 // RPCConsole is a child widget.
574 settings.setValue("RPCConsoleWidgetPeersTabSplitterSizes", ui->splitter->saveState());
575 }
576
577 settings.setValue("PeersTabPeerHeaderState", m_peer_widget_header_state);
578 settings.setValue("PeersTabBanlistHeaderState", m_banlist_widget_header_state);
579
580 delete ui;
581}
582
583bool RPCConsole::eventFilter(QObject* obj, QEvent *event)
584{
585 if(event->type() == QEvent::KeyPress) // Special key handling
586 {
587 QKeyEvent *keyevt = static_cast<QKeyEvent*>(event);
588 int key = keyevt->key();
589 Qt::KeyboardModifiers mod = keyevt->modifiers();
590 switch(key)
591 {
592 case Qt::Key_Up: if(obj == ui->lineEdit) { browseHistory(-1); return true; } break;
593 case Qt::Key_Down: if(obj == ui->lineEdit) { browseHistory(1); return true; } break;
594 case Qt::Key_PageUp: /* pass paging keys to messages widget */
595 case Qt::Key_PageDown:
596 if (obj == ui->lineEdit) {
597 QApplication::sendEvent(ui->messagesWidget, keyevt);
598 return true;
599 }
600 break;
601 case Qt::Key_Return:
602 case Qt::Key_Enter:
603 // forward these events to lineEdit
604 if (obj == autoCompleter->popup()) {
605 QApplication::sendEvent(ui->lineEdit, keyevt);
606 autoCompleter->popup()->hide();
607 return true;
608 }
609 break;
610 default:
611 // Typing in messages widget brings focus to line edit, and redirects key there
612 // Exclude most combinations and keys that emit no text, except paste shortcuts
613 if(obj == ui->messagesWidget && (
614 (!mod && !keyevt->text().isEmpty() && key != Qt::Key_Tab) ||
615 ((mod & Qt::ControlModifier) && key == Qt::Key_V) ||
616 ((mod & Qt::ShiftModifier) && key == Qt::Key_Insert)))
617 {
618 ui->lineEdit->setFocus();
619 QApplication::sendEvent(ui->lineEdit, keyevt);
620 return true;
621 }
622 }
623 }
624 return QWidget::eventFilter(obj, event);
625}
626
627void RPCConsole::setClientModel(ClientModel *model, int bestblock_height, int64_t bestblock_date, double verification_progress)
628{
629 clientModel = model;
630
631 bool wallet_enabled{false};
632#ifdef ENABLE_WALLET
633 wallet_enabled = WalletModel::isWalletEnabled();
634#endif // ENABLE_WALLET
635 if (model && !wallet_enabled) {
636 // Show warning, for example if this is a prerelease version
639 }
640
641 ui->trafficGraph->setClientModel(model);
643 // Keep up to date with client
646
647 setNumBlocks(bestblock_height, QDateTime::fromSecsSinceEpoch(bestblock_date), verification_progress, SyncType::BLOCK_SYNC);
649
652
654 updateTrafficStats(node.getTotalBytesRecv(), node.getTotalBytesSent());
656
658
659 // set up peer table
660 ui->peerWidget->setModel(model->peerTableSortProxy());
661 ui->peerWidget->verticalHeader()->hide();
662 ui->peerWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
663 ui->peerWidget->setSelectionMode(QAbstractItemView::ExtendedSelection);
664 ui->peerWidget->setContextMenuPolicy(Qt::CustomContextMenu);
665
666 if (!ui->peerWidget->horizontalHeader()->restoreState(m_peer_widget_header_state)) {
667 ui->peerWidget->setColumnWidth(PeerTableModel::Address, ADDRESS_COLUMN_WIDTH);
668 ui->peerWidget->setColumnWidth(PeerTableModel::Subversion, SUBVERSION_COLUMN_WIDTH);
669 ui->peerWidget->setColumnWidth(PeerTableModel::Ping, PING_COLUMN_WIDTH);
670 }
671 ui->peerWidget->horizontalHeader()->setSectionResizeMode(PeerTableModel::Age, QHeaderView::ResizeToContents);
672 ui->peerWidget->horizontalHeader()->setStretchLastSection(true);
673 ui->peerWidget->setItemDelegateForColumn(PeerTableModel::NetNodeId, new PeerIdViewDelegate(this));
674
675 // create peer table context menu
676 peersTableContextMenu = new QMenu(this);
677 //: Context menu action to copy the address of a peer.
678 peersTableContextMenu->addAction(tr("&Copy address"), [this] {
679 GUIUtil::copyEntryData(ui->peerWidget, PeerTableModel::Address, Qt::DisplayRole);
680 });
681 peersTableContextMenu->addSeparator();
682 peersTableContextMenu->addAction(tr("&Disconnect"), this, &RPCConsole::disconnectSelectedNode);
683 peersTableContextMenu->addAction(ts.ban_for + " " + tr("1 &hour"), [this] { banSelectedNode(60 * 60); });
684 peersTableContextMenu->addAction(ts.ban_for + " " + tr("1 d&ay"), [this] { banSelectedNode(60 * 60 * 24); });
685 peersTableContextMenu->addAction(ts.ban_for + " " + tr("1 &week"), [this] { banSelectedNode(60 * 60 * 24 * 7); });
686 peersTableContextMenu->addAction(ts.ban_for + " " + tr("1 &year"), [this] { banSelectedNode(60 * 60 * 24 * 365); });
687 connect(ui->peerWidget, &QTableView::customContextMenuRequested, this, &RPCConsole::showPeersTableContextMenu);
688
689 // peer table signal handling - update peer details when selecting new node
690 connect(ui->peerWidget->selectionModel(), &QItemSelectionModel::selectionChanged, this, &RPCConsole::updateDetailWidget);
691 connect(model->getPeerTableModel(), &QAbstractItemModel::dataChanged, [this] { updateDetailWidget(); });
692
693 // set up ban table
694 ui->banlistWidget->setModel(model->getBanTableModel());
695 ui->banlistWidget->verticalHeader()->hide();
696 ui->banlistWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
697 ui->banlistWidget->setSelectionMode(QAbstractItemView::SingleSelection);
698 ui->banlistWidget->setContextMenuPolicy(Qt::CustomContextMenu);
699
700 if (!ui->banlistWidget->horizontalHeader()->restoreState(m_banlist_widget_header_state)) {
701 ui->banlistWidget->setColumnWidth(BanTableModel::Address, BANSUBNET_COLUMN_WIDTH);
702 ui->banlistWidget->setColumnWidth(BanTableModel::Bantime, BANTIME_COLUMN_WIDTH);
703 }
704 ui->banlistWidget->horizontalHeader()->setSectionResizeMode(BanTableModel::Address, QHeaderView::ResizeToContents);
705 ui->banlistWidget->horizontalHeader()->setStretchLastSection(true);
706
707 // create ban table context menu
708 banTableContextMenu = new QMenu(this);
709 /*: Context menu action to copy the IP/Netmask of a banned peer.
710 IP/Netmask is the combination of a peer's IP address and its Netmask.
711 For IP address, see: https://en.wikipedia.org/wiki/IP_address. */
712 banTableContextMenu->addAction(tr("&Copy IP/Netmask"), [this] {
713 GUIUtil::copyEntryData(ui->banlistWidget, BanTableModel::Address, Qt::DisplayRole);
714 });
715 banTableContextMenu->addSeparator();
716 banTableContextMenu->addAction(tr("&Unban"), this, &RPCConsole::unbanSelectedNode);
717 connect(ui->banlistWidget, &QTableView::customContextMenuRequested, this, &RPCConsole::showBanTableContextMenu);
718
719 // ban table signal handling - clear peer details when clicking a peer in the ban table
720 connect(ui->banlistWidget, &QTableView::clicked, this, &RPCConsole::clearSelectedNode);
721 // ban table signal handling - ensure ban table is shown or hidden (if empty)
722 connect(model->getBanTableModel(), &BanTableModel::layoutChanged, this, &RPCConsole::showOrHideBanTableIfRequired);
724
725 // Provide initial values
726 ui->clientVersion->setText(model->formatFullVersion());
727 ui->clientUserAgent->setText(model->formatSubVersion());
728 ui->dataDir->setText(model->dataDir());
729 ui->blocksDir->setText(model->blocksDir());
730 ui->startupTime->setText(model->formatClientStartupTime());
731 ui->networkName->setText(QString::fromStdString(Params().GetChainTypeString()));
732
733 //Setup autocomplete and attach it
734 QStringList wordList;
735 std::vector<std::string> commandList = m_node.listRpcCommands();
736 for (size_t i = 0; i < commandList.size(); ++i)
737 {
738 wordList << commandList[i].c_str();
739 wordList << ("help " + commandList[i]).c_str();
740 }
741
742 wordList << "help-console";
743 wordList.sort();
744 autoCompleter = new QCompleter(wordList, this);
745 autoCompleter->setModelSorting(QCompleter::CaseSensitivelySortedModel);
746 // ui->lineEdit is initially disabled because running commands is only
747 // possible from now on.
748 ui->lineEdit->setEnabled(true);
749 ui->lineEdit->setCompleter(autoCompleter);
750 autoCompleter->popup()->installEventFilter(this);
751 // Start thread to execute RPC commands.
753 }
754 if (!model) {
755 // Client model is being set to 0, this means shutdown() is about to be called.
756 thread.quit();
757 thread.wait();
758 }
759}
760
761#ifdef ENABLE_WALLET
762void RPCConsole::addWallet(WalletModel * const walletModel)
763{
764 // use name for text and wallet model for internal data object (to allow to move to a wallet id later)
765 ui->WalletSelector->addItem(walletModel->getDisplayName(), QVariant::fromValue(walletModel));
766 if (ui->WalletSelector->count() == 2) {
767 // First wallet added, set to default to match wallet RPC behavior
768 ui->WalletSelector->setCurrentIndex(1);
769 }
770 if (ui->WalletSelector->count() > 2) {
771 ui->WalletSelector->setVisible(true);
772 ui->WalletSelectorLabel->setVisible(true);
773 }
774}
775
776void RPCConsole::removeWallet(WalletModel * const walletModel)
777{
778 ui->WalletSelector->removeItem(ui->WalletSelector->findData(QVariant::fromValue(walletModel)));
779 if (ui->WalletSelector->count() == 2) {
780 ui->WalletSelector->setVisible(false);
781 ui->WalletSelectorLabel->setVisible(false);
782 }
783}
784
785void RPCConsole::setCurrentWallet(WalletModel* const wallet_model)
786{
787 QVariant data = QVariant::fromValue(wallet_model);
788 ui->WalletSelector->setCurrentIndex(ui->WalletSelector->findData(data));
789}
790#endif
791
792static QString categoryClass(int category)
793{
794 switch(category)
795 {
796 case RPCConsole::CMD_REQUEST: return "cmd-request"; break;
797 case RPCConsole::CMD_REPLY: return "cmd-reply"; break;
798 case RPCConsole::CMD_ERROR: return "cmd-error"; break;
799 default: return "misc";
800 }
801}
802
804{
806}
807
809{
811}
812
813void RPCConsole::setFontSize(int newSize)
814{
815 QSettings settings;
816
817 //don't allow an insane font size
818 if (newSize < FONT_RANGE.width() || newSize > FONT_RANGE.height())
819 return;
820
821 // temp. store the console content
822 QString str = ui->messagesWidget->toHtml();
823
824 // replace font tags size in current content
825 str.replace(QString("font-size:%1pt").arg(consoleFontSize), QString("font-size:%1pt").arg(newSize));
826
827 // store the new font size
828 consoleFontSize = newSize;
829 settings.setValue(fontSizeSettingsKey, consoleFontSize);
830
831 // clear console (reset icon sizes, default stylesheet) and re-add the content
832 float oldPosFactor = 1.0 / ui->messagesWidget->verticalScrollBar()->maximum() * ui->messagesWidget->verticalScrollBar()->value();
833 clear(/*keep_prompt=*/true);
834 ui->messagesWidget->setHtml(str);
835 ui->messagesWidget->verticalScrollBar()->setValue(oldPosFactor * ui->messagesWidget->verticalScrollBar()->maximum());
836}
837
838void RPCConsole::clear(bool keep_prompt)
839{
840 ui->messagesWidget->clear();
841 if (!keep_prompt) ui->lineEdit->clear();
842 ui->lineEdit->setFocus();
843
844 // Add smoothly scaled icon images.
845 // (when using width/height on an img, Qt uses nearest instead of linear interpolation)
846 for(int i=0; ICON_MAPPING[i].url; ++i)
847 {
848 ui->messagesWidget->document()->addResource(
849 QTextDocument::ImageResource,
850 QUrl(ICON_MAPPING[i].url),
851 platformStyle->SingleColorImage(ICON_MAPPING[i].source).scaled(QSize(consoleFontSize*2, consoleFontSize*2), Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
852 }
853
854 // Set default style sheet
855#ifdef Q_OS_MACOS
856 QFontInfo fixedFontInfo(GUIUtil::fixedPitchFont(/*use_embedded_font=*/true));
857#else
858 QFontInfo fixedFontInfo(GUIUtil::fixedPitchFont());
859#endif
860 ui->messagesWidget->document()->setDefaultStyleSheet(
861 QString(
862 "table { }"
863 "td.time { color: #808080; font-size: %2; padding-top: 3px; } "
864 "td.message { font-family: %1; font-size: %2; white-space:pre-wrap; } "
865 "td.cmd-request { color: #006060; } "
866 "td.cmd-error { color: red; } "
867 ".secwarning { color: red; }"
868 "b { color: #006060; } "
869 ).arg(fixedFontInfo.family(), QString("%1pt").arg(consoleFontSize))
870 );
871
872 static const QString welcome_message =
873 /*: RPC console welcome message.
874 Placeholders %7 and %8 are style tags for the warning content, and
875 they are not space separated from the rest of the text intentionally. */
876 tr("Welcome to the %1 RPC console.\n"
877 "Use up and down arrows to navigate history, and %2 to clear screen.\n"
878 "Use %3 and %4 to increase or decrease the font size.\n"
879 "Type %5 for an overview of available commands.\n"
880 "For more information on using this console, type %6.\n"
881 "\n"
882 "%7WARNING: Scammers have been active, telling users to type"
883 " commands here, stealing their wallet contents. Do not use this console"
884 " without fully understanding the ramifications of a command.%8")
885 .arg(CLIENT_NAME,
886 "<b>" + ui->clearButton->shortcut().toString(QKeySequence::NativeText) + "</b>",
887 "<b>" + ui->fontBiggerButton->shortcut().toString(QKeySequence::NativeText) + "</b>",
888 "<b>" + ui->fontSmallerButton->shortcut().toString(QKeySequence::NativeText) + "</b>",
889 "<b>help</b>",
890 "<b>help-console</b>",
891 "<span class=\"secwarning\">",
892 "<span>");
893
894 message(CMD_REPLY, welcome_message, true);
895}
896
897void RPCConsole::keyPressEvent(QKeyEvent *event)
898{
899 if (windowType() != Qt::Widget && GUIUtil::IsEscapeOrBack(event->key())) {
900 close();
901 }
902}
903
905{
906 if (e->type() == QEvent::PaletteChange) {
907 ui->clearButton->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/remove")));
908 ui->fontBiggerButton->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/fontbigger")));
909 ui->fontSmallerButton->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/fontsmaller")));
910 ui->promptIcon->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/prompticon")));
911
912 for (int i = 0; ICON_MAPPING[i].url; ++i) {
913 ui->messagesWidget->document()->addResource(
914 QTextDocument::ImageResource,
915 QUrl(ICON_MAPPING[i].url),
916 platformStyle->SingleColorImage(ICON_MAPPING[i].source).scaled(QSize(consoleFontSize * 2, consoleFontSize * 2), Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
917 }
918 }
919
920 QWidget::changeEvent(e);
921}
922
923void RPCConsole::message(int category, const QString &message, bool html)
924{
925 QTime time = QTime::currentTime();
926 QString timeString = time.toString();
927 QString out;
928 out += "<table><tr><td class=\"time\" width=\"65\">" + timeString + "</td>";
929 out += "<td class=\"icon\" width=\"32\"><img src=\"" + categoryClass(category) + "\"></td>";
930 out += "<td class=\"message " + categoryClass(category) + "\" valign=\"middle\">";
931 if(html)
932 out += message;
933 else
935 out += "</td></tr></table>";
936 ui->messagesWidget->append(out);
937}
938
940{
941 if (!clientModel) return;
942 QString connections = QString::number(clientModel->getNumConnections()) + " (";
943 connections += tr("In:") + " " + QString::number(clientModel->getNumConnections(CONNECTIONS_IN)) + " / ";
944 connections += tr("Out:") + " " + QString::number(clientModel->getNumConnections(CONNECTIONS_OUT)) + ")";
945
947 connections += " (" + tr("Network activity disabled") + ")";
948 }
949
950 ui->numberOfConnections->setText(connections);
951
952 QString local_addresses;
953 std::map<CNetAddr, LocalServiceInfo> hosts = clientModel->getNetLocalAddresses();
954 for (const auto& [addr, info] : hosts) {
955 local_addresses += QString::fromStdString(addr.ToStringAddr());
956 if (!addr.IsI2P()) local_addresses += ":" + QString::number(info.nPort);
957 local_addresses += ", ";
958 }
959 local_addresses.chop(2); // remove last ", "
960 if (local_addresses.isEmpty()) local_addresses = tr("None");
961
962 ui->localAddresses->setText(local_addresses);
963}
964
966{
967 if (!clientModel)
968 return;
969
971}
972
973void RPCConsole::setNetworkActive(bool networkActive)
974{
976}
977
978void RPCConsole::setNumBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, SyncType synctype)
979{
980 if (synctype == SyncType::BLOCK_SYNC) {
981 ui->numberOfBlocks->setText(QString::number(count));
982 ui->lastBlockTime->setText(blockDate.toString());
983 }
984}
985
986void RPCConsole::setMempoolSize(long numberOfTxs, size_t dynUsage, size_t maxUsage)
987{
988 ui->mempoolNumberTxs->setText(QString::number(numberOfTxs));
989
990 const auto cur_usage_str = dynUsage < 1000000 ?
991 QObject::tr("%1 kB").arg(dynUsage / 1000.0, 0, 'f', 2) :
992 QObject::tr("%1 MB").arg(dynUsage / 1000000.0, 0, 'f', 2);
993 const auto max_usage_str = QObject::tr("%1 MB").arg(maxUsage / 1000000.0, 0, 'f', 2);
994
995 ui->mempoolSize->setText(cur_usage_str + " / " + max_usage_str);
996}
997
999{
1000 QString cmd = ui->lineEdit->text().trimmed();
1001
1002 if (cmd.isEmpty()) {
1003 return;
1004 }
1005
1006 std::string strFilteredCmd;
1007 try {
1008 std::string dummy;
1009 if (!RPCParseCommandLine(nullptr, dummy, cmd.toStdString(), false, &strFilteredCmd)) {
1010 // Failed to parse command, so we cannot even filter it for the history
1011 throw std::runtime_error("Invalid command line");
1012 }
1013 } catch (const std::exception& e) {
1014 QMessageBox::critical(this, "Error", QString("Error: ") + QString::fromStdString(e.what()));
1015 return;
1016 }
1017
1018 // A special case allows to request shutdown even a long-running command is executed.
1019 if (cmd == QLatin1String("stop")) {
1020 std::string dummy;
1021 RPCExecuteCommandLine(m_node, dummy, cmd.toStdString());
1022 return;
1023 }
1024
1025 if (m_is_executing) {
1026 return;
1027 }
1028
1029 ui->lineEdit->clear();
1030
1031 QString in_use_wallet_name;
1032#ifdef ENABLE_WALLET
1033 WalletModel* wallet_model = ui->WalletSelector->currentData().value<WalletModel*>();
1034 in_use_wallet_name = wallet_model ? wallet_model->getWalletName() : QString();
1035 if (m_last_wallet_model != wallet_model) {
1036 if (wallet_model) {
1037 message(CMD_REQUEST, tr("Executing command using \"%1\" wallet").arg(wallet_model->getWalletName()));
1038 } else {
1039 message(CMD_REQUEST, tr("Executing command without any wallet"));
1040 }
1041 m_last_wallet_model = wallet_model;
1042 }
1043#endif // ENABLE_WALLET
1044
1045 message(CMD_REQUEST, QString::fromStdString(strFilteredCmd));
1046 //: A console message indicating an entered command is currently being executed.
1047 message(CMD_REPLY, tr("Executing…"));
1048 m_is_executing = true;
1049
1050 QMetaObject::invokeMethod(m_executor, [this, cmd, in_use_wallet_name] {
1051 m_executor->request(cmd, in_use_wallet_name);
1052 });
1053
1054 cmd = QString::fromStdString(strFilteredCmd);
1055
1056 // Remove command, if already in history
1057 history.removeOne(cmd);
1058 // Append command to history
1059 history.append(cmd);
1060 // Enforce maximum history size
1061 while (history.size() > CONSOLE_HISTORY) {
1062 history.removeFirst();
1063 }
1064 // Set pointer to end of history
1065 historyPtr = history.size();
1066
1067 // Scroll console view to end
1068 scrollToEnd();
1069}
1070
1072{
1073 // store current text when start browsing through the history
1074 if (historyPtr == history.size()) {
1075 cmdBeforeBrowsing = ui->lineEdit->text();
1076 }
1077
1078 historyPtr += offset;
1079 if(historyPtr < 0)
1080 historyPtr = 0;
1081 if(historyPtr > history.size())
1082 historyPtr = history.size();
1083 QString cmd;
1084 if(historyPtr < history.size())
1085 cmd = history.at(historyPtr);
1086 else if (!cmdBeforeBrowsing.isNull()) {
1088 }
1089 ui->lineEdit->setText(cmd);
1090}
1091
1093{
1095 m_executor->moveToThread(&thread);
1096
1097 // Replies from executor object must go to this object
1098 connect(m_executor, &RPCExecutor::reply, this, [this](int category, const QString& command) {
1099 // Remove "Executing…" message.
1100 ui->messagesWidget->undo();
1101 message(category, command);
1102 scrollToEnd();
1103 m_is_executing = false;
1104 });
1105
1106 // Make sure executor object is deleted in its own thread
1107 connect(&thread, &QThread::finished, m_executor, &RPCExecutor::deleteLater);
1108
1109 // Default implementation of QThread::run() simply spins up an event loop in the thread,
1110 // which is what we want.
1111 thread.start();
1112 QTimer::singleShot(0, m_executor, []() {
1113 util::ThreadRename("qt-rpcconsole");
1114 });
1115}
1116
1118{
1119 if (ui->tabWidget->widget(index) == ui->tab_console) {
1120 ui->lineEdit->setFocus();
1121 }
1122}
1123
1125{
1127}
1128
1130{
1131 QScrollBar *scrollbar = ui->messagesWidget->verticalScrollBar();
1132 scrollbar->setValue(scrollbar->maximum());
1133}
1134
1136{
1137 const int multiplier = 5; // each position on the slider represents 5 min
1138 int mins = value * multiplier;
1140}
1141
1143{
1144 ui->trafficGraph->setGraphRange(std::chrono::minutes{mins});
1145 ui->lblGraphRange->setText(GUIUtil::formatDurationStr(std::chrono::minutes{mins}));
1146}
1147
1148void RPCConsole::updateTrafficStats(quint64 totalBytesIn, quint64 totalBytesOut)
1149{
1150 ui->lblBytesIn->setText(GUIUtil::formatBytes(totalBytesIn));
1151 ui->lblBytesOut->setText(GUIUtil::formatBytes(totalBytesOut));
1152}
1153
1155{
1156 const QList<QModelIndex> selected_peers = GUIUtil::getEntryData(ui->peerWidget, PeerTableModel::NetNodeId);
1157 if (!clientModel || !clientModel->getPeerTableModel() || selected_peers.size() != 1) {
1158 ui->peersTabRightPanel->hide();
1159 ui->peerHeading->setText(tr("Select a peer to view detailed information."));
1160 return;
1161 }
1162 const auto stats = selected_peers.first().data(PeerTableModel::StatsRole).value<CNodeCombinedStats*>();
1163 // update the detail ui with latest node information
1164 QString peerAddrDetails(QString::fromStdString(stats->nodeStats.m_addr_name) + " ");
1165 peerAddrDetails += tr("(peer: %1)").arg(QString::number(stats->nodeStats.nodeid));
1166 if (!stats->nodeStats.addrLocal.empty())
1167 peerAddrDetails += "<br />" + tr("via %1").arg(QString::fromStdString(stats->nodeStats.addrLocal));
1168 ui->peerHeading->setText(peerAddrDetails);
1169 QString bip152_hb_settings;
1170 if (stats->nodeStats.m_bip152_highbandwidth_to) bip152_hb_settings = ts.to;
1171 if (stats->nodeStats.m_bip152_highbandwidth_from) bip152_hb_settings += (bip152_hb_settings.isEmpty() ? ts.from : QLatin1Char('/') + ts.from);
1172 if (bip152_hb_settings.isEmpty()) bip152_hb_settings = ts.no;
1173 ui->peerHighBandwidth->setText(bip152_hb_settings);
1174 const auto now{NodeClock::now()};
1175 const auto time_now{GetTime<std::chrono::seconds>()};
1176 ui->peerConnTime->setText(GUIUtil::formatDurationStr(now - stats->nodeStats.m_connected));
1177 ui->peerLastBlock->setText(TimeDurationField(time_now, stats->nodeStats.m_last_block_time));
1178 ui->peerLastTx->setText(TimeDurationField(time_now, stats->nodeStats.m_last_tx_time));
1179 ui->peerLastSend->setText(TimeDurationField(now, stats->nodeStats.m_last_send));
1180 ui->peerLastRecv->setText(TimeDurationField(now, stats->nodeStats.m_last_recv));
1181 ui->peerBytesSent->setText(GUIUtil::formatBytes(stats->nodeStats.nSendBytes));
1182 ui->peerBytesRecv->setText(GUIUtil::formatBytes(stats->nodeStats.nRecvBytes));
1183 ui->peerPingTime->setText(GUIUtil::formatPingTime(stats->nodeStats.m_last_ping_time));
1184 ui->peerMinPing->setText(GUIUtil::formatPingTime(stats->nodeStats.m_min_ping_time));
1185 ui->peerVersion->setText(stats->nodeStats.nVersion ? QString::number(stats->nodeStats.nVersion) : ts.na);
1186 ui->peerSubversion->setText(!stats->nodeStats.cleanSubVer.empty() ? QString::fromStdString(stats->nodeStats.cleanSubVer) : ts.na);
1187 ui->peerConnectionType->setText(GUIUtil::ConnectionTypeToQString(stats->nodeStats.m_conn_type, /*prepend_direction=*/true));
1188 ui->peerTransportType->setText(QString::fromStdString(TransportTypeAsString(stats->nodeStats.m_transport_type)));
1189 if (stats->nodeStats.m_transport_type == TransportProtocolType::V2) {
1190 ui->peerSessionIdLabel->setVisible(true);
1191 ui->peerSessionId->setVisible(true);
1192 ui->peerSessionId->setText(QString::fromStdString(stats->nodeStats.m_session_id));
1193 } else {
1194 ui->peerSessionIdLabel->setVisible(false);
1195 ui->peerSessionId->setVisible(false);
1196 }
1197 ui->peerNetwork->setText(GUIUtil::NetworkToQString(stats->nodeStats.m_network));
1198 if (stats->nodeStats.m_permission_flags == NetPermissionFlags::None) {
1199 ui->peerPermissions->setText(ts.na);
1200 } else {
1201 QStringList permissions;
1202 for (const auto& permission : NetPermissions::ToStrings(stats->nodeStats.m_permission_flags)) {
1203 permissions.append(QString::fromStdString(permission));
1204 }
1205 ui->peerPermissions->setText(permissions.join(" & "));
1206 }
1207 ui->peerMappedAS->setText(stats->nodeStats.m_mapped_as != 0 ? QString::number(stats->nodeStats.m_mapped_as) : ts.na);
1208
1209 // This check fails for example if the lock was busy and
1210 // nodeStateStats couldn't be fetched.
1211 if (stats->fNodeStateStatsAvailable) {
1212 ui->timeoffset->setText(GUIUtil::formatTimeOffset(Ticks<std::chrono::seconds>(stats->nodeStateStats.time_offset)));
1213 ui->peerServices->setText(GUIUtil::formatServicesStr(stats->nodeStateStats.their_services));
1214 // Sync height is init to -1
1215 if (stats->nodeStateStats.nSyncHeight > -1) {
1216 ui->peerSyncHeight->setText(QString("%1").arg(stats->nodeStateStats.nSyncHeight));
1217 } else {
1218 ui->peerSyncHeight->setText(ts.unknown);
1219 }
1220 // Common height is init to -1
1221 if (stats->nodeStateStats.nCommonHeight > -1) {
1222 ui->peerCommonHeight->setText(QString("%1").arg(stats->nodeStateStats.nCommonHeight));
1223 } else {
1224 ui->peerCommonHeight->setText(ts.unknown);
1225 }
1226 ui->peerPingWait->setText(GUIUtil::formatPingTime(stats->nodeStateStats.m_ping_wait));
1227 ui->peerAddrRelayEnabled->setText(stats->nodeStateStats.m_addr_relay_enabled ? ts.yes : ts.no);
1228 ui->peerAddrProcessed->setText(QString::number(stats->nodeStateStats.m_addr_processed));
1229 ui->peerAddrRateLimited->setText(QString::number(stats->nodeStateStats.m_addr_rate_limited));
1230 ui->peerRelayTxes->setText(stats->nodeStateStats.m_relay_txs ? ts.yes : ts.no);
1231 }
1232
1233 ui->hidePeersDetailButton->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/remove")));
1234 ui->peersTabRightPanel->show();
1235}
1236
1237void RPCConsole::resizeEvent(QResizeEvent *event)
1238{
1239 QWidget::resizeEvent(event);
1240}
1241
1242void RPCConsole::showEvent(QShowEvent *event)
1243{
1244 QWidget::showEvent(event);
1245
1247 return;
1248
1249 // start PeerTableModel auto refresh
1251}
1252
1253void RPCConsole::hideEvent(QHideEvent *event)
1254{
1255 // It is too late to call QHeaderView::saveState() in ~RPCConsole(), as all of
1256 // the columns of QTableView child widgets will have zero width at that moment.
1257 m_peer_widget_header_state = ui->peerWidget->horizontalHeader()->saveState();
1258 m_banlist_widget_header_state = ui->banlistWidget->horizontalHeader()->saveState();
1259
1260 QWidget::hideEvent(event);
1261
1263 return;
1264
1265 // stop PeerTableModel auto refresh
1267}
1268
1270{
1271 QModelIndex index = ui->peerWidget->indexAt(point);
1272 if (index.isValid())
1273 peersTableContextMenu->exec(QCursor::pos());
1274}
1275
1277{
1278 QModelIndex index = ui->banlistWidget->indexAt(point);
1279 if (index.isValid())
1280 banTableContextMenu->exec(QCursor::pos());
1281}
1282
1284{
1285 // Get selected peer addresses
1286 QList<QModelIndex> nodes = GUIUtil::getEntryData(ui->peerWidget, PeerTableModel::NetNodeId);
1287 for(int i = 0; i < nodes.count(); i++)
1288 {
1289 // Get currently selected peer address
1290 NodeId id = nodes.at(i).data().toLongLong();
1291 // Find the node, disconnect it and clear the selected node
1292 if(m_node.disconnectById(id))
1294 }
1295}
1296
1298{
1299 if (!clientModel)
1300 return;
1301
1302 for (const QModelIndex& peer : GUIUtil::getEntryData(ui->peerWidget, PeerTableModel::NetNodeId)) {
1303 // Find possible nodes, ban it and clear the selected node
1304 const auto stats = peer.data(PeerTableModel::StatsRole).value<CNodeCombinedStats*>();
1305 if (stats) {
1306 m_node.ban(stats->nodeStats.addr, bantime);
1307 m_node.disconnectByAddress(stats->nodeStats.addr);
1308 }
1309 }
1312}
1313
1315{
1316 if (!clientModel)
1317 return;
1318
1319 // Get selected ban addresses
1320 QList<QModelIndex> nodes = GUIUtil::getEntryData(ui->banlistWidget, BanTableModel::Address);
1321 BanTableModel* ban_table_model{clientModel->getBanTableModel()};
1322 bool unbanned{false};
1323 for (const auto& node_index : nodes) {
1324 unbanned |= ban_table_model->unban(node_index);
1325 }
1326 if (unbanned) {
1327 ban_table_model->refresh();
1328 }
1329}
1330
1332{
1333 ui->peerWidget->selectionModel()->clearSelection();
1334 cachedNodeids.clear();
1336}
1337
1339{
1340 if (!clientModel)
1341 return;
1342
1343 bool visible = clientModel->getBanTableModel()->shouldShow();
1344 ui->banlistWidget->setVisible(visible);
1345 ui->banHeading->setVisible(visible);
1346}
1347
1349{
1350 ui->tabWidget->setCurrentIndex(int(tabType));
1351}
1352
1353QString RPCConsole::tabTitle(TabTypes tab_type) const
1354{
1355 return ui->tabWidget->tabText(int(tab_type));
1356}
1357
1358QKeySequence RPCConsole::tabShortcut(TabTypes tab_type) const
1359{
1360 switch (tab_type) {
1361 case TabTypes::INFO: return QKeySequence(tr("Ctrl+I"));
1362 case TabTypes::CONSOLE: return QKeySequence(tr("Ctrl+T"));
1363 case TabTypes::GRAPH: return QKeySequence(tr("Ctrl+N"));
1364 case TabTypes::PEERS: return QKeySequence(tr("Ctrl+P"));
1365 } // no default case, so the compiler can warn about missing cases
1366
1367 assert(false);
1368}
1369
1370void RPCConsole::updateAlerts(const QString& warnings)
1371{
1372 this->ui->label_alerts->setVisible(!warnings.isEmpty());
1373 this->ui->label_alerts->setText(warnings);
1374}
1375
1377{
1378 const ChainType chain = Params().GetChainType();
1379 if (chain == ChainType::MAIN) return;
1380
1381 const QString chainType = QString::fromStdString(Params().GetChainTypeString());
1382 const QString title = tr("Node window - [%1]").arg(chainType);
1383 this->setWindowTitle(title);
1384}
node::NodeContext m_node
Definition: bitcoin-gui.cpp:48
const auto cmd
const auto command
const CChainParams & Params()
Return the currently selected parameters.
ChainType
Definition: chaintype.h:12
Qt model providing information about banned peers, similar to the "getpeerinfo" RPC call.
Definition: bantablemodel.h:44
bool unban(const QModelIndex &index)
ChainType GetChainType() const
Return the chain type.
Definition: chainparams.h:111
Model for Bitcoin network client.
Definition: clientmodel.h:57
void bytesChanged(quint64 totalBytesIn, quint64 totalBytesOut)
QString blocksDir() const
QString getStatusBarWarnings() const
Return warnings to be displayed in status bar.
std::map< CNetAddr, LocalServiceInfo > getNetLocalAddresses() const
PeerTableModel * getPeerTableModel()
PeerTableSortProxy * peerTableSortProxy()
void numConnectionsChanged(int count)
QString formatClientStartupTime() const
int getNumConnections(unsigned int flags=CONNECTIONS_ALL) const
Return number of connections, default is in- and outbound (total)
Definition: clientmodel.cpp:84
BanTableModel * getBanTableModel()
void numBlocksChanged(int count, const QDateTime &blockDate, double nVerificationProgress, SyncType header, SynchronizationState sync_state)
void alertsChanged(const QString &warnings)
void mempoolSizeChanged(long count, size_t mempoolSizeInBytes, size_t mempoolMaxSizeInBytes)
QString dataDir() const
QString formatFullVersion() const
QString formatSubVersion() const
void networkActiveChanged(bool networkActive)
interfaces::Node & node() const
Definition: clientmodel.h:66
static std::vector< std::string > ToStrings(NetPermissionFlags flags)
QString displayText(const QVariant &value, const QLocale &locale) const override
Definition: rpcconsole.cpp:112
PeerIdViewDelegate(QObject *parent=nullptr)
Definition: rpcconsole.cpp:109
QIcon SingleColorIcon(const QString &filename) const
Colorize an icon (given filename) with the icon color.
bool getImagesOnButtons() const
Definition: platformstyle.h:21
QImage SingleColorImage(const QString &filename) const
Colorize an image (given filename) with the icon color.
Local Bitcoin RPC console.
Definition: rpcconsole.h:44
static bool RPCExecuteCommandLine(interfaces::Node &node, std::string &strResult, const std::string &strCommand, std::string *const pstrFilteredOut=nullptr, const QString &wallet_name={})
Definition: rpcconsole.h:52
QMenu * peersTableContextMenu
Definition: rpcconsole.h:171
RPCConsole(interfaces::Node &node, const PlatformStyle *platformStyle, QWidget *parent)
Definition: rpcconsole.cpp:443
struct RPCConsole::TranslatedStrings ts
void browseHistory(int offset)
Go forward or back in history.
QByteArray m_banlist_widget_header_state
Definition: rpcconsole.h:180
void fontSmaller()
Definition: rpcconsole.cpp:808
void on_lineEdit_returnPressed()
Definition: rpcconsole.cpp:998
QStringList history
Definition: rpcconsole.h:166
void message(int category, const QString &msg)
Append the message to the message widget.
Definition: rpcconsole.h:117
void setFontSize(int newSize)
Definition: rpcconsole.cpp:813
void updateTrafficStats(quint64 totalBytesIn, quint64 totalBytesOut)
update traffic statistics
void setTrafficGraphRange(int mins)
const PlatformStyle *const platformStyle
Definition: rpcconsole.h:170
void setMempoolSize(long numberOfTxs, size_t dynUsage, size_t maxUsage)
Set size (number of transactions and memory usage) of the mempool in the UI.
Definition: rpcconsole.cpp:986
void updateDetailWidget()
show detailed information on ui about selected node
void showEvent(QShowEvent *event) override
void resizeEvent(QResizeEvent *event) override
QString tabTitle(TabTypes tab_type) const
void updateNetworkState()
Update UI with latest network info from model.
Definition: rpcconsole.cpp:939
void clear(bool keep_prompt=false)
Definition: rpcconsole.cpp:838
void disconnectSelectedNode()
Disconnect a selected node on the Peers tab.
QString TimeDurationField(NodeClock::time_point now, NodeClock::time_point event) const
Format the duration between now and event as a string.
Definition: rpcconsole.h:186
@ BANTIME_COLUMN_WIDTH
Definition: rpcconsole.h:159
@ ADDRESS_COLUMN_WIDTH
Definition: rpcconsole.h:155
@ SUBVERSION_COLUMN_WIDTH
Definition: rpcconsole.h:156
@ PING_COLUMN_WIDTH
Definition: rpcconsole.h:157
@ BANSUBNET_COLUMN_WIDTH
Definition: rpcconsole.h:158
QCompleter * autoCompleter
Definition: rpcconsole.h:174
void hideEvent(QHideEvent *event) override
QKeySequence tabShortcut(TabTypes tab_type) const
void showPeersTableContextMenu(const QPoint &point)
Show custom context menu on Peers tab.
QList< NodeId > cachedNodeids
Definition: rpcconsole.h:169
bool m_is_executing
Definition: rpcconsole.h:178
interfaces::Node & m_node
Definition: rpcconsole.h:163
void unbanSelectedNode()
Unban a selected node on the Bans tab.
void updateAlerts(const QString &warnings)
void clearSelectedNode()
clear the selected node
void on_sldGraphRange_valueChanged(int value)
change the time range of the network traffic graph
int consoleFontSize
Definition: rpcconsole.h:173
void setNumConnections(int count)
Set number of connections shown in the UI.
Definition: rpcconsole.cpp:965
void setNumBlocks(int count, const QDateTime &blockDate, double nVerificationProgress, SyncType synctype)
Set number of blocks and last block date shown in the UI.
Definition: rpcconsole.cpp:978
ClientModel * clientModel
Definition: rpcconsole.h:165
void banSelectedNode(int bantime)
Ban a selected node on the Peers tab.
int historyPtr
Definition: rpcconsole.h:167
void scrollToEnd()
Scroll console view to end.
void keyPressEvent(QKeyEvent *) override
Definition: rpcconsole.cpp:897
void on_tabWidget_currentChanged(int index)
Ui::RPCConsole *const ui
Definition: rpcconsole.h:164
void startExecutor()
void setNetworkActive(bool networkActive)
Set network state shown in the UI.
Definition: rpcconsole.cpp:973
void updateWindowTitle()
void fontBigger()
Definition: rpcconsole.cpp:803
QString cmdBeforeBrowsing
Definition: rpcconsole.h:168
virtual bool eventFilter(QObject *obj, QEvent *event) override
Definition: rpcconsole.cpp:583
void on_openDebugLogfileButton_clicked()
open the debug.log from the current datadir
void showBanTableContextMenu(const QPoint &point)
Show custom context menu on Bans tab.
void setClientModel(ClientModel *model=nullptr, int bestblock_height=0, int64_t bestblock_date=0, double verification_progress=0.0)
Definition: rpcconsole.cpp:627
void setTabFocus(enum TabTypes tabType)
set which tab has the focus (is visible)
QByteArray m_peer_widget_header_state
Definition: rpcconsole.h:179
void changeEvent(QEvent *e) override
Definition: rpcconsole.cpp:904
WalletModel * m_last_wallet_model
Definition: rpcconsole.h:177
void showOrHideBanTableIfRequired()
Hides ban table if no bans are present.
RPCExecutor * m_executor
Definition: rpcconsole.h:176
QMenu * banTableContextMenu
Definition: rpcconsole.h:172
QThread thread
Definition: rpcconsole.h:175
static bool RPCParseCommandLine(interfaces::Node *node, std::string &strResult, const std::string &strCommand, bool fExecute, std::string *pstrFilteredOut=nullptr, const QString &wallet_name={})
Split shell command line into a list of arguments and optionally execute the command(s).
Definition: rpcconsole.cpp:142
void reply(int category, const QString &command)
RPCExecutor(interfaces::Node &node)
Definition: rpcconsole.cpp:93
interfaces::Node & m_node
Definition: rpcconsole.cpp:102
void request(const QString &command, const QString &wallet_name)
Definition: rpcconsole.cpp:387
void push_back(UniValue val)
Definition: univalue.cpp:103
const std::string & get_str() const
bool isArray() const
Definition: univalue.h:87
const UniValue & find_value(std::string_view key) const
Definition: univalue.cpp:232
std::string write(unsigned int prettyIndent=0, unsigned int indentLevel=0) const
bool isStr() const
Definition: univalue.h:85
Int getInt() const
Definition: univalue.h:143
bool isObject() const
Definition: univalue.h:88
Interface to Bitcoin wallet from Qt view code.
Definition: walletmodel.h:49
QString getDisplayName() const
static bool isWalletEnabled()
QString getWalletName() const
Top-level interface for a bitcoin node (bitcoind process).
Definition: node.h:66
virtual bool disconnectById(NodeId id)=0
Disconnect node by id.
virtual bool ban(const CNetAddr &net_addr, int64_t ban_time_offset)=0
Ban node.
virtual std::vector< std::string > listRpcCommands()=0
List rpc commands.
virtual bool getNetworkActive()=0
Get network active.
virtual bool disconnectByAddress(const CNetAddr &net_addr)=0
Disconnect node by address.
UniValue RPCConvertValues(const std::string &strMethod, const std::vector< std::string > &strParams)
Convert command lines arguments to params object when -named is disabled.
Definition: client.cpp:445
SyncType
Definition: clientmodel.h:42
@ CONNECTIONS_IN
Definition: clientmodel.h:50
@ CONNECTIONS_OUT
Definition: clientmodel.h:51
static const PrecomputedData data
Precomputed COutPoint and CCoins values.
std::string TransportTypeAsString(TransportProtocolType transport_type)
Convert TransportProtocolType enum to a string value.
@ V2
BIP324 protocol.
QString NetworkToQString(Network net)
Convert enum Network to QString.
Definition: guiutil.cpp:682
QString HtmlEscape(const QString &str, bool fMultiLine)
Definition: guiutil.cpp:249
QList< QModelIndex > getEntryData(const QAbstractItemView *view, int column)
Return a field of the currently selected entry as a QString.
Definition: guiutil.cpp:277
QFont fixedPitchFont(bool use_embedded_font)
Definition: guiutil.cpp:100
QString formatPingTime(NodeClock::duration ping_time)
Format a CNodeStats.m_last_ping_time/m_min_ping_time/m_ping_wait into a user-readable string if it ex...
Definition: guiutil.cpp:771
QString formatBytes(uint64_t bytes)
Definition: guiutil.cpp:820
void AddButtonShortcut(QAbstractButton *button, const QKeySequence &shortcut)
Connects an additional shortcut to a QAbstractButton.
Definition: guiutil.cpp:144
void handleCloseWindowShortcut(QWidget *w)
Definition: guiutil.cpp:426
QString formatDurationStr(std::chrono::nanoseconds dur)
Convert a duration into a QString with days, hours, mins, secs. This ignores sub-seconds.
Definition: guiutil.cpp:733
void copyEntryData(const QAbstractItemView *view, int column, int role)
Copy a field of the currently selected entry of a view to the clipboard.
Definition: guiutil.cpp:264
void openDebugLogfile()
Definition: guiutil.cpp:431
QString ConnectionTypeToQString(ConnectionType conn_type, bool prepend_direction)
Convert enum ConnectionType to QString.
Definition: guiutil.cpp:702
QString formatServicesStr(quint64 mask)
Format CNodeStats.nServices bitmask into a user-readable string.
Definition: guiutil.cpp:757
QString formatTimeOffset(int64_t time_offset)
Format a CNodeStateStats.time_offset into a user-readable string.
Definition: guiutil.cpp:778
bool IsEscapeOrBack(int key)
Definition: guiutil.h:440
Definition: messages.h:21
void ThreadRename(const std::string &)
Rename a thread both in terms of an internal (in-memory) name as well as its system thread name.
Definition: threadnames.cpp:66
auto Join(const C &container, const S &separator, UnaryOp unary_op)
Join all container items.
Definition: string.h:209
int64_t NodeId
Definition: net.h:105
const std::vector< std::string > CONNECTION_TYPE_DOC
Definition: net.cpp:52
const std::vector< std::string > TRANSPORT_TYPE_DOC
Definition: net.cpp:62
const int INITIAL_TRAFFIC_GRAPH_MINS
Definition: rpcconsole.cpp:57
const struct @8 ICON_MAPPING[]
const QSize FONT_RANGE(4, 40)
const int CONSOLE_HISTORY
Definition: rpcconsole.cpp:56
static QString categoryClass(int category)
Definition: rpcconsole.cpp:792
const char fontSizeSettingsKey[]
Definition: rpcconsole.cpp:59
const char * url
Definition: rpcconsole.cpp:62
const char * source
Definition: rpcconsole.cpp:63
static time_point now() noexcept
Return current system time or mocked time, if set.
Definition: time.cpp:38
static int count
assert(!tx.IsCoinBase())