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