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