Bitcoin Core  28.1.0
P2P Digital Currency
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>
13 #include <node/connection_types.h>
14 #include <qt/bantablemodel.h>
15 #include <qt/clientmodel.h>
16 #include <qt/guiutil.h>
17 #include <qt/peertablesortproxy.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 
51 using util::Join;
52 
53 const int CONSOLE_HISTORY = 50;
55 const QSize FONT_RANGE(4, 40);
56 const char fontSizeSettingsKey[] = "consoleFontSize";
57 
58 const 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 
69 namespace {
70 
71 // don't add private key handling cmd's to the history
72 const 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 */
86 class RPCExecutor : public QObject
87 {
88  Q_OBJECT
89 public:
91 
92 public Q_SLOTS:
93  void request(const QString &command, const WalletModel* wallet_model);
94 
95 Q_SIGNALS:
96  void reply(int category, const QString &command);
97 
98 private:
100 };
101 
105 class QtRPCTimerBase: public QObject, public RPCTimerBase
106 {
107  Q_OBJECT
108 public:
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;
117 private:
118  QTimer timer;
119  std::function<void()> func;
120 };
121 
123 {
124 public:
125  ~QtRPCTimerInterface() = default;
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 
133 class PeerIdViewDelegate : public QStyledItemDelegate
134 {
135  Q_OBJECT
136 public:
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 
170 bool 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 
412 void 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 
468 RPCConsole::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 
613 bool 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 
657 void 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
667  connect(model, &ClientModel::alertsChanged, this, &RPCConsole::updateAlerts);
669  }
670 
671  ui->trafficGraph->setClientModel(model);
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.
782  startExecutor();
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
792 void 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 
806 void 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 
815 void 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 
822 static 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 
834 {
836 }
837 
839 {
841 }
842 
843 void 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 
868 void 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 
927 void RPCConsole::keyPressEvent(QKeyEvent *event)
928 {
929  if (windowType() != Qt::Widget && GUIUtil::IsEscapeOrBack(event->key())) {
930  close();
931  }
932 }
933 
934 void RPCConsole::changeEvent(QEvent* e)
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 
953 void 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 
976  if(!clientModel->node().getNetworkActive()) {
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 
1003 void RPCConsole::setNetworkActive(bool networkActive)
1004 {
1006 }
1007 
1008 void 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 
1016 void 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 {
1124  m_executor = new RPCExecutor(m_node);
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 
1155 {
1157 }
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;
1169  setTrafficGraphRange(mins);
1170 }
1171 
1173 {
1174  ui->trafficGraph->setGraphRange(std::chrono::minutes{mins});
1175  ui->lblGraphRange->setText(GUIUtil::formatDurationStr(std::chrono::minutes{mins}));
1176 }
1177 
1178 void 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 
1271 void RPCConsole::resizeEvent(QResizeEvent *event)
1272 {
1273  QWidget::resizeEvent(event);
1274 }
1275 
1276 void RPCConsole::showEvent(QShowEvent *event)
1277 {
1278  QWidget::showEvent(event);
1279 
1281  return;
1282 
1283  // start PeerTableModel auto refresh
1285 }
1286 
1287 void 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 
1303 void RPCConsole::showPeersTableContextMenu(const QPoint& point)
1304 {
1305  QModelIndex index = ui->peerWidget->indexAt(point);
1306  if (index.isValid())
1307  peersTableContextMenu->exec(QCursor::pos());
1308 }
1309 
1310 void RPCConsole::showBanTableContextMenu(const QPoint& point)
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 
1387 QString RPCConsole::tabTitle(TabTypes tab_type) const
1388 {
1389  return ui->tabWidget->tabText(int(tab_type));
1390 }
1391 
1392 QKeySequence 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 
1404 void 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 }
void openDebugLogfile()
Definition: guiutil.cpp:436
QTableView * banlistWidget
QString formatClientStartupTime() const
QLabel * label_alerts
QTableView * peerWidget
QLabel * peerPingTime
bool isObject() const
Definition: univalue.h:86
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 push_back(UniValue val)
Definition: univalue.cpp:104
BIP324 protocol.
QString formatSubVersion() const
Local Bitcoin RPC console.
Definition: rpcconsole.h:41
RPC timer "driver".
Definition: server.h:57
QSplitter * splitter
QLabel * clientVersion
QString cmdBeforeBrowsing
Definition: rpcconsole.h:166
virtual bool getNetworkActive()=0
Get network active.
QLabel * peerAddrRelayEnabled
QLabel * lblGraphRange
assert(!tx.IsCoinBase())
QPushButton * openDebugLogfileButton
QLabel * peerLastRecv
static bool isWalletEnabled()
void on_lineEdit_returnPressed()
QLabel * numberOfConnections
RPCExecutor(interfaces::Node &node)
Definition: rpcconsole.cpp:90
void updateWindowTitle()
QLabel * peerServices
QToolButton * clearButton
QLabel * networkName
QString blocksDir() const
node::NodeContext m_node
Definition: bitcoin-gui.cpp:37
QLabel * peerTransportTypeLabel
void showPeersTableContextMenu(const QPoint &point)
Show custom context menu on Peers tab.
WalletModel * m_last_wallet_model
Definition: rpcconsole.h:176
void updateDetailWidget()
show detailed information on ui about selected node
virtual void rpcUnsetTimerInterface(RPCTimerInterface *iface)=0
Unset RPC timer interface.
void setClientModel(ClientModel *model=nullptr, int bestblock_height=0, int64_t bestblock_date=0, double verification_progress=0.0)
Definition: rpcconsole.cpp:657
QLabel * blocksDir
QStringList history
Definition: rpcconsole.h:164
QLabel * peerCommonHeight
QLabel * peerConnectionTypeLabel
QToolButton * hidePeersDetailButton
QTextEdit * messagesWidget
interfaces::Node & m_node
Definition: rpcconsole.h:161
QThread thread
Definition: rpcconsole.h:174
QLabel * banHeading
void setNetworkActive(bool networkActive)
Set network state shown in the UI.
void scrollToEnd()
Scroll console view to end.
QString formatBytes(uint64_t bytes)
Definition: guiutil.cpp:824
void networkActiveChanged(bool networkActive)
const auto cmd
static QString categoryClass(int category)
Definition: rpcconsole.cpp:822
void clearSelectedNode()
clear the selected node
#define PACKAGE_NAME
QLabel * peerPermissions
const struct @8 ICON_MAPPING[]
RPCConsole(interfaces::Node &node, const PlatformStyle *platformStyle, QWidget *parent)
Definition: rpcconsole.cpp:468
QLabel * peerHeading
const std::string & get_str() const
QIcon SingleColorIcon(const QString &filename) const
Colorize an icon (given filename) with the icon color.
bool isStr() const
Definition: univalue.h:83
QString HtmlEscape(const QString &str, bool fMultiLine)
Definition: guiutil.cpp:249
QLabel * peerVersion
void fontSmaller()
Definition: rpcconsole.cpp:838
QLabel * peerRelayTxes
QFont fixedPitchFont(bool use_embedded_font)
Definition: guiutil.cpp:100
void changeEvent(QEvent *e) override
Definition: rpcconsole.cpp:934
QLabel * startupTime
PeerTableModel * getPeerTableModel()
Int getInt() const
Definition: univalue.h:138
void disconnectSelectedNode()
Disconnect a selected node on the Peers tab.
QLabel * peerHighBandwidthLabel
void on_tabWidget_currentChanged(int index)
QLabel * peerHeight
void setupUi(QWidget *RPCConsole)
QLabel * clientUserAgent
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:57
void numConnectionsChanged(int count)
QString formatDurationStr(std::chrono::seconds dur)
Convert seconds into a QString with days, hours, mins, secs.
Definition: guiutil.cpp:736
QLabel * mempoolNumberTxs
virtual bool ban(const CNetAddr &net_addr, int64_t ban_time_offset)=0
Ban node.
QWidget * peersTabRightPanel
virtual void rpcSetTimerInterfaceIfUnset(RPCTimerInterface *iface)=0
Set RPC timer interface if unset.
QString getStatusBarWarnings() const
Return warnings to be displayed in status bar.
QString tabTitle(TabTypes tab_type) const
void alertsChanged(const QString &warnings)
void clear(bool keep_prompt=false)
Definition: rpcconsole.cpp:868
QLabel * peerConnTime
QPushButton * btnClearTrafficGraph
QLabel * timeoffset
UniValue RPCConvertValues(const std::string &strMethod, const std::vector< std::string > &strParams)
Convert positional arguments to command-specific RPC representation.
Definition: client.cpp:360
RPCExecutor * m_executor
Definition: rpcconsole.h:175
void bytesChanged(quint64 totalBytesIn, quint64 totalBytesOut)
std::string TransportTypeAsString(TransportProtocolType transport_type)
Convert TransportProtocolType enum to a string value.
const UniValue & find_value(std::string_view key) const
Definition: univalue.cpp:233
QLabel * peerSubversion
const PlatformStyle *const platformStyle
Definition: rpcconsole.h:168
const char * url
Definition: rpcconsole.cpp:59
void reply(int category, const QString &command)
struct RPCConsole::TranslatedStrings ts
QLabel * peerLastSend
QLabel * numberOfBlocks
QMenu * peersTableContextMenu
Definition: rpcconsole.h:170
QString NetworkToQString(Network net)
Convert enum Network to QString.
Definition: guiutil.cpp:687
QLabel * peerMinPing
const char * source
Definition: rpcconsole.cpp:60
void browseHistory(int offset)
Go forward or back in history.
void resizeEvent(QResizeEvent *event) override
ChainType
Definition: chaintype.h:11
void mempoolSizeChanged(long count, size_t mempoolSizeInBytes, size_t mempoolMaxSizeInBytes)
QLabel * WalletSelectorLabel
void message(int category, const QString &msg)
Append the message to the message widget.
Definition: rpcconsole.h:115
int getNumConnections(unsigned int flags=CONNECTIONS_ALL) const
Return number of connections, default is in- and outbound (total)
Definition: clientmodel.cpp:84
std::map< CNetAddr, LocalServiceInfo > getNetLocalAddresses() const
Class for handling RPC timers (used for e.g.
Definition: rpcconsole.cpp:105
QLabel * peerAddrProcessed
QLabel * peerTransportType
QByteArray m_banlist_widget_header_state
Definition: rpcconsole.h:179
const int CONSOLE_HISTORY
Definition: rpcconsole.cpp:53
void handleCloseWindowShortcut(QWidget *w)
Definition: guiutil.cpp:431
void setMempoolSize(long numberOfTxs, size_t dynUsage, size_t maxUsage)
Set size (number of transactions and memory usage) of the mempool in the UI.
QLabel * lblBytesOut
QPushButton * promptIcon
QLineEdit * lineEdit
QLabel * lastBlockTime
void setTabFocus(enum TabTypes tabType)
set which tab has the focus (is visible)
bool m_is_executing
Definition: rpcconsole.h:177
QToolButton * fontBiggerButton
QLabel * peerSyncHeight
void numBlocksChanged(int count, const QDateTime &blockDate, double nVerificationProgress, SyncType header, SynchronizationState sync_state)
QString ConnectionTypeToQString(ConnectionType conn_type, bool prepend_direction)
Convert enum ConnectionType to QString.
Definition: guiutil.cpp:707
BanTableModel * getBanTableModel()
interfaces::Node & node() const
Definition: clientmodel.h:66
QLabel * peerLastTx
int historyPtr
Definition: rpcconsole.h:165
std::string write(unsigned int prettyIndent=0, unsigned int indentLevel=0) const
RPCTimerInterface * rpcTimerInterface
Definition: rpcconsole.h:169
virtual bool disconnectByAddress(const CNetAddr &net_addr)=0
Disconnect node by address.
void updateNetworkState()
Update UI with latest network info from model.
Definition: rpcconsole.cpp:969
int64_t NodeId
Definition: net.h:97
QByteArray m_peer_widget_header_state
Definition: rpcconsole.h:178
QString displayText(const QVariant &value, const QLocale &locale) const override
Definition: rpcconsole.cpp:140
QLabel * localAddresses
QString getWalletName() const
~QtRPCTimerInterface()=default
void on_openDebugLogfileButton_clicked()
open the debug.log from the current datadir
const char * Name() override
Implementation name.
Definition: rpcconsole.cpp:126
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
QtRPCTimerBase(std::function< void()> &_func, int64_t millis)
Definition: rpcconsole.cpp:109
Model for Bitcoin network client.
Definition: clientmodel.h:56
void unbanSelectedNode()
Unban a selected node on the Bans tab.
QLabel * peerBytesRecv
QLabel * dataDir
ClientModel * clientModel
Definition: rpcconsole.h:163
TrafficGraphWidget * trafficGraph
QMenu * banTableContextMenu
Definition: rpcconsole.h:171
void setTrafficGraphRange(int mins)
SyncType
Definition: clientmodel.h:42
QKeySequence tabShortcut(TabTypes tab_type) const
Definition: messages.h:20
QLabel * peerBytesSent
bool IsEscapeOrBack(int key)
Definition: guiutil.h:430
void showOrHideBanTableIfRequired()
Hides ban table if no bans are present.
void fontBigger()
Definition: rpcconsole.cpp:833
QLabel * lblBytesIn
const std::vector< std::string > CONNECTION_TYPE_DOC
Definition: net.cpp:42
void updateTrafficStats(quint64 totalBytesIn, quint64 totalBytesOut)
update traffic statistics
QLabel * peerSessionId
QLabel * peerPingWait
QList< NodeId > cachedNodeids
Definition: rpcconsole.h:167
ChainType GetChainType() const
Return the chain type.
Definition: chainparams.h:115
void setFontSize(int newSize)
Definition: rpcconsole.cpp:843
std::function< void()> func
Definition: rpcconsole.cpp:119
PeerIdViewDelegate(QObject *parent=nullptr)
Definition: rpcconsole.cpp:137
const auto command
void setNumConnections(int count)
Set number of connections shown in the UI.
Definition: rpcconsole.cpp:995
void startExecutor()
QImage SingleColorImage(const QString &filename) const
Colorize an image (given filename) with the icon color.
const CChainParams & Params()
Return the currently selected parameters.
QToolButton * fontSmallerButton
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).
Definition: rpcconsole.cpp:170
interfaces::Node & m_node
Definition: rpcconsole.cpp:99
Interface to Bitcoin wallet from Qt view code.
Definition: walletmodel.h:47
virtual std::vector< std::string > listRpcCommands()=0
List rpc commands.
~QtRPCTimerBase()=default
bool unban(const QModelIndex &index)
QString formatServicesStr(quint64 mask)
Format CNodeStats.nServices bitmask into a user-readable string.
Definition: guiutil.cpp:761
void on_sldGraphRange_valueChanged(int value)
change the time range of the network traffic graph
Qt model providing information about banned peers, similar to the "getpeerinfo" RPC call...
Definition: bantablemodel.h:43
void banSelectedNode(int bantime)
Ban a selected node on the Peers tab.
QLabel * peerLastBlock
void setNumBlocks(int count, const QDateTime &blockDate, double nVerificationProgress, SyncType synctype)
Set number of blocks and last block date shown in the UI.
Ui::RPCConsole *const ui
Definition: rpcconsole.h:162
void updateAlerts(const QString &warnings)
Opaque base class for timers returned by NewTimerFunc.
Definition: server.h:48
QWidget * tab_console
QLabel * peerMappedAS
const int INITIAL_TRAFFIC_GRAPH_MINS
Definition: rpcconsole.cpp:54
QLabel * mempoolSize
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
static int count
const char fontSizeSettingsKey[]
Definition: rpcconsole.cpp:56
int consoleFontSize
Definition: rpcconsole.h:172
RPCTimerBase * NewTimer(std::function< void()> &func, int64_t millis) override
Factory function for timers.
Definition: rpcconsole.cpp:127
QString getDisplayName() const
void request(const QString &command, const WalletModel *wallet_model)
Definition: rpcconsole.cpp:412
const QSize FONT_RANGE(4, 40)
QLabel * peerNetwork
QString formatTimeOffset(int64_t time_offset)
Format a CNodeStateStats.time_offset into a user-readable string.
Definition: guiutil.cpp:782
QLabel * peerConnectionType
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 dataDir() const
QLabel * peerAddrRateLimited
void setGraphRange(std::chrono::minutes new_range)
const std::vector< std::string > TRANSPORT_TYPE_DOC
Definition: net.cpp:51
QLabel * peerSessionIdLabel
QTabWidget * tabWidget
PeerTableSortProxy * peerTableSortProxy()
virtual bool disconnectById(NodeId id)=0
Disconnect node by id.
QComboBox * WalletSelector
void showEvent(QShowEvent *event) override
QCompleter * autoCompleter
Definition: rpcconsole.h:173
auto Join(const C &container, const S &separator, UnaryOp unary_op)
Join all container items.
Definition: string.h:115
void AddButtonShortcut(QAbstractButton *button, const QKeySequence &shortcut)
Connects an additional shortcut to a QAbstractButton.
Definition: guiutil.cpp:144
Top-level interface for a bitcoin node (bitcoind process).
Definition: node.h:69
QLabel * peerHighBandwidth
bool isArray() const
Definition: univalue.h:85
bool getImagesOnButtons() const
Definition: platformstyle.h:21
void setClientModel(ClientModel *model)
void showBanTableContextMenu(const QPoint &point)
Show custom context menu on Bans tab.
void keyPressEvent(QKeyEvent *) override
Definition: rpcconsole.cpp:927
void hideEvent(QHideEvent *event) override
virtual bool eventFilter(QObject *obj, QEvent *event) override
Definition: rpcconsole.cpp:613
static std::vector< std::string > ToStrings(NetPermissionFlags flags)
QList< QModelIndex > getEntryData(const QAbstractItemView *view, int column)
Return a field of the currently selected entry as a QString.
Definition: guiutil.cpp:277
QString formatFullVersion() const