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