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