From ad0dd07047b0860fedaf6376a90c65c0d03bf470 Mon Sep 17 00:00:00 2001 From: Hernan Marino Date: Sun, 25 Dec 2022 22:51:50 -0300 Subject: [PATCH] qt: add generate command to gui console --- src/qt/rpcconsole.cpp | 159 ++++++++++++++++++++++++++++++------------ 1 file changed, 116 insertions(+), 43 deletions(-) diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index a07686ab2b7..f3434246315 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -49,6 +49,7 @@ #include + const int CONSOLE_HISTORY = 50; const int INITIAL_TRAFFIC_GRAPH_MINS = 30; const QSize FONT_RANGE(4, 40); @@ -96,6 +97,8 @@ public Q_SLOTS: private: interfaces::Node& m_node; + std::vector parseHelper(const std::string& strCommand); + bool executeConsoleOnlyCommand(const std::string& command, const WalletModel* wallet_model); }; /** Class for handling RPC timers @@ -408,59 +411,129 @@ bool RPCConsole::RPCParseCommandLine(interfaces::Node* node, std::string &strRes } } -void RPCExecutor::request(const QString &command, const WalletModel* wallet_model) +/** + * Small and fast parser supporting console command syntax, with limited functionality. + * Splits a command line string into a vector with command at position 0, and parameters after + * + * @param[in] strCommand Command line to parse + * + * @return a vector of strings with command and parameters + */ + +std::vector RPCExecutor::parseHelper(const std::string& strCommand) { - try - { - std::string result; - std::string executableCommand = command.toStdString() + "\n"; + std::vector vec = SplitString(strCommand, " (),\n"); + auto should_remove = [](const std::string& str) { return str.empty(); }; + vec.erase(std::remove_if(vec.begin(), vec.end(), should_remove), vec.end()); + return vec; +} - // Catch the console-only-help command before RPC call is executed and reply with help text as-if a RPC reply. - if(executableCommand == "help-console\n") { +/** + * Catches console-only command before a RPC call is executed + * + * @param[in] command Command line to execute + * @param[in] wallet_model Wallet model to use + * @return true if command was handled by this method (even on errors), false otherwise + * + */ + +bool RPCExecutor::executeConsoleOnlyCommand(const std::string& command, const WalletModel* wallet_model) +{ + std::string result; + std::string address; + std::string executableCommand; + std::vector parsedCommand{parseHelper(command)}; + + // Catch the console-only generate command with 2 or less parameters before RPC call is executed . + if (!parsedCommand.empty() && parsedCommand[0] == "generate" && parsedCommand.size() <= 3) { + // Default number of blocks is 1 if missing + const std::string nblocks{parsedCommand.size() > 1 ? parsedCommand[1] : "1"}; + // An empty maxtries parameters will be modified by generatetoaddress' default value + const std::string maxtries{parsedCommand.size() > 2 ? parsedCommand[2] : ""}; + + // Generate address + if (!RPCConsole::RPCExecuteCommandLine(m_node, address, "getnewaddress\n", nullptr, wallet_model)) { + Q_EMIT reply(RPCConsole::CMD_ERROR, QString("Error: could not generate new address")); + } else { + executableCommand = "generatetoaddress " + nblocks + " \"" + address + "\" " + maxtries; + if (!RPCConsole::RPCExecuteCommandLine(m_node, result, "generatetoaddress " + nblocks + " " + address + " " + maxtries + "\n", nullptr, wallet_model)) { + Q_EMIT reply(RPCConsole::CMD_ERROR, QString("Error: could not generate blocks")); + } else { + std::string answer = "{\n \"address\": \"" + address + "\",\n \"blocks\": " + result + "\n}"; + Q_EMIT reply(RPCConsole::CMD_REPLY, QString::fromStdString("\n" + answer + "\n\n")); + } + } + return true; + } + // Catch the console-only "help generate" command when requested, or when "generate is called with wrong parameters. + if ((parsedCommand.size() >= 2 && parsedCommand[0] == "help" && parsedCommand[1] == "generate") || (parsedCommand.size() > 3 && parsedCommand[0] == "generate")) { Q_EMIT reply(RPCConsole::CMD_REPLY, QString(("\n" - "This console accepts RPC commands using the standard syntax.\n" - " example: getblockhash 0\n\n" + "Generate blocks, equivalent to RPC getnewaddress followed by RPC generatetoaddress.\n" + "Optional integer arguments are number of blocks to generate and maximum iterations to try.\n" + "Equivalent to RPC generatetoaddress nblocks and maxtries arguments.\n" + " example: generate\n" + " example: generate 4\n" + " example: generate 3 6000\n\n"))); - "This console can also accept RPC commands using the parenthesized syntax.\n" - " example: getblockhash(0)\n\n" + return true; + } + // Catch the console-only-help command before RPC call is executed and reply with help text as-if a RPC reply. + if (!parsedCommand.empty() && parsedCommand[0] == "help-console") { + Q_EMIT reply(RPCConsole::CMD_REPLY, QString(("\n" + "This console accepts RPC commands using the standard syntax.\n" + " example: getblockhash 0\n\n" - "Commands may be nested when specified with the parenthesized syntax.\n" - " example: getblock(getblockhash(0) 1)\n\n" + "This console can also accept RPC commands using the parenthesized syntax.\n" + " example: getblockhash(0)\n\n" - "A space or a comma can be used to delimit arguments for either syntax.\n" - " example: getblockhash 0\n" - " getblockhash,0\n\n" + "Commands may be nested when specified with the parenthesized syntax.\n" + " example: getblock(getblockhash(0) 1)\n\n" - "Named results can be queried with a non-quoted key string in brackets using the parenthesized syntax.\n" - " example: getblock(getblockhash(0) 1)[tx]\n\n" + "A space or a comma can be used to delimit arguments for either syntax.\n" + " example: getblockhash 0\n" + " getblockhash,0\n\n" - "Results without keys can be queried with an integer in brackets using the parenthesized syntax.\n" - " example: getblock(getblockhash(0),1)[tx][0]\n\n"))); - return; - } - if (!RPCConsole::RPCExecuteCommandLine(m_node, result, executableCommand, nullptr, wallet_model)) { - Q_EMIT reply(RPCConsole::CMD_ERROR, QString("Parse error: unbalanced ' or \"")); - return; - } + "Named results can be queried with a non-quoted key string in brackets using the parenthesized syntax.\n" + " example: getblock(getblockhash(0) 1)[tx]\n\n" - Q_EMIT reply(RPCConsole::CMD_REPLY, QString::fromStdString(result)); - } - catch (UniValue& objError) - { - try // Nice formatting for standard-format error - { - int code = find_value(objError, "code").getInt(); - std::string message = find_value(objError, "message").get_str(); - Q_EMIT reply(RPCConsole::CMD_ERROR, QString::fromStdString(message) + " (code " + QString::number(code) + ")"); - } - catch (const std::runtime_error&) // raised when converting to invalid type, i.e. missing code or message - { // Show raw JSON object - Q_EMIT reply(RPCConsole::CMD_ERROR, QString::fromStdString(objError.write())); - } + "Results without keys can be queried with an integer in brackets using the parenthesized syntax.\n" + " example: getblock(getblockhash(0),1)[tx][0]\n\n"))); + return true; } - catch (const std::exception& e) - { - Q_EMIT reply(RPCConsole::CMD_ERROR, QString("Error: ") + QString::fromStdString(e.what())); + + + // Returns false if no command was caught by previous code, so that it can be sent to RPC + return false; +} + +void RPCExecutor::request(const QString& command, const WalletModel* wallet_model) +{ + try { + std::string result; + std::string executableCommand = command.toStdString() + "\n"; + + // Attempt to execute console-only commands + if (!RPCExecutor::executeConsoleOnlyCommand(executableCommand, wallet_model)) { + // Send to the RPC command parser if not console-only + if (!RPCConsole::RPCExecuteCommandLine(m_node, result, executableCommand, nullptr, wallet_model)) { + Q_EMIT reply(RPCConsole::CMD_ERROR, QString("Parse error: unbalanced ' or \"")); + return; + } + Q_EMIT reply(RPCConsole::CMD_REPLY, QString::fromStdString(result)); + } + + } catch (UniValue& objError) { + try // Nice formatting for standard-format error + { + int code = find_value(objError, "code").getInt(); + std::string message = find_value(objError, "message").get_str(); + Q_EMIT reply(RPCConsole::CMD_ERROR, QString::fromStdString(message) + " (code " + QString::number(code) + ")"); + } catch (const std::runtime_error&) // raised when converting to invalid type, i.e. missing code or message + { // Show raw JSON object + Q_EMIT reply(RPCConsole::CMD_ERROR, QString::fromStdString(objError.write())); + } + } catch (const std::exception& e) { + Q_EMIT reply(RPCConsole::CMD_ERROR, QString("Error: ") + QString::fromStdString(e.what())); } }