LCOV - code coverage report
Current view: top level - src/qt - rpcconsole.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 127 703 18.1 %
Date: 2020-09-26 01:30:44 Functions: 4 75 5.3 %

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

Generated by: LCOV version 1.15