diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index ceaa3ac46be..aeb43b12464 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -24,7 +24,7 @@ #include #include #include - +#include #include #include @@ -96,8 +96,36 @@ public Q_SLOTS: private: interfaces::Node& m_node; + bool executeConsoleGenerate(const std::vector& parsed_command, const WalletModel* wallet_model, const bool exec_help = false); + void executeConsoleHelpGenerate(); + bool executeConsoleHelpConsole(const std::vector& parsed_command, const WalletModel* wallet_model, const bool exec_help = false); + bool executeConsoleOnlyCommand(const std::string& command, const WalletModel* wallet_model); + // std::map mapping strings to methods member of RPCExecutor class + // Keys must be strings with commands and (optionally) parameters in "canonical" form (separated by single space) + // Keys should match the beggining of user input commands (user commands can have more parameters than the key) + std::map&, const WalletModel*, const bool)> m_method_map{ + {"generate", &RPCExecutor::executeConsoleGenerate}, + {"help-console", &RPCExecutor::executeConsoleHelpConsole}}; }; +/** + * 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 parseHelper(const std::string& strCommand) +{ + // Split while recognizing the several characters that can be used as separators in the GUI console + std::vector vec{SplitString(strCommand, " (),")}; + // Remove empty strings produced by consecutive separators + 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; +} + /** Class for handling RPC timers * (used for e.g. re-locking the wallet after a timeout) */ @@ -415,35 +443,15 @@ void RPCExecutor::request(const QString &command, const WalletModel* wallet_mode std::string result; std::string executableCommand = command.toStdString() + "\n"; - // 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") { - Q_EMIT reply(RPCConsole::CMD_REPLY, QString(("\n" - "This console accepts RPC commands using the standard syntax.\n" - " example: getblockhash 0\n\n" - - "This console can also accept RPC commands using the parenthesized 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" - - "A space or a comma can be used to delimit arguments for either syntax.\n" - " example: getblockhash 0\n" - " getblockhash,0\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" - - "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; + // Attempt to execute console-only commands + if (!RPCExecutor::executeConsoleOnlyCommand(command.toStdString(), 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)); } - - Q_EMIT reply(RPCConsole::CMD_REPLY, QString::fromStdString(result)); } catch (UniValue& objError) { @@ -464,6 +472,126 @@ void RPCExecutor::request(const QString &command, const WalletModel* wallet_mode } } +/** + * @brief Executes the console-only command "generate". + * @param parsed_command A vector of strings with command and parameters, usually generated by RPCExecutor::parseHelper + * @param wallet_model WalletModel to use for the command + * @return True if the command was executed, false otherwise. + */ +bool RPCExecutor::executeConsoleGenerate(const std::vector& parsed_command, const WalletModel* wallet_model, const bool exec_help) +{ + // Initialize default parameters if missing + const std::string nblocks{parsed_command.size() > 1 ? parsed_command[1] : "1"}; + const std::string maxtries{parsed_command.size() > 2 ? parsed_command[2] : "1000000"}; + + // Handle some special cases... + // Default to console help generate if more than 3 parameters or if "help generate" was called + if (parsed_command.size() > 3 || exec_help) { + executeConsoleHelpGenerate(); + return true; + } + // Fail if we are on mainnet, to avoid generating addresses for blocks that will not be generated + if (Params().GetChainType() == ChainType::MAIN) { + Q_EMIT reply(RPCConsole::CMD_ERROR, QString("Error: generate is not available on mainnet")); + return true; + } + // Fail if parameters are not positive integers + const auto nblocks_value{ToIntegral(nblocks)}; + const auto maxtries_value{ToIntegral(maxtries)}; + if (!nblocks_value || !maxtries_value || nblocks_value.value() <= 0 || maxtries_value.value() <= 0) { + Q_EMIT reply(RPCConsole::CMD_ERROR, QString("Error: parameters must be positive integers")); + return true; + } + + // Catch the console-only generate command with 2 or less parameters before RPC call is executed . + std::string blocks; + std::string address; + if (!RPCConsole::RPCExecuteCommandLine(m_node, address, "getnewaddress\n", /*pstrFilteredOut=*/nullptr, wallet_model)) { + Q_EMIT reply(RPCConsole::CMD_ERROR, QString("Error: could not generate new address")); + } else { + if (!RPCConsole::RPCExecuteCommandLine(m_node, blocks, "generatetoaddress " + nblocks + " " + address + " " + maxtries + "\n", /*pstrFilteredOut=*/nullptr, wallet_model)) { + Q_EMIT reply(RPCConsole::CMD_ERROR, QString("Error: could not generate blocks")); + } else { + UniValue result{UniValue::VOBJ}; + UniValue blocks_object{UniValue::VOBJ}; + blocks_object.read(blocks); + result.pushKV("address", address); + result.pushKV("blocks", blocks_object); + Q_EMIT reply(RPCConsole::CMD_REPLY, QString::fromStdString("\n" + result.write(2) + "\n\n")); + } + } + return true; +} + +/** + * @brief Executes the console-only command "help generate". + */ +void RPCExecutor::executeConsoleHelpGenerate() +{ + // Execute the console-only "help generate" command. + Q_EMIT reply(RPCConsole::CMD_REPLY, + QString("\n" + "Generate blocks, equivalent to RPC getnewaddress followed by RPC generatetoaddress.\n" + "Optional positive 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")); +} + +/** + * @brief Executes the console-only command "help-console". + * @param parsed_command A vector of strings with command and parameters, usually generated by RPCExecutor::parseHelper + * @param wallet_model WalletModel to use for the command + * @return True if the command was executed, false otherwise. + */ +bool RPCExecutor::executeConsoleHelpConsole(const std::vector& parsed_command, const WalletModel* wallet_model, const bool exec_help) +{ + // Reply with help text as-if a RPC reply. + Q_EMIT reply(RPCConsole::CMD_REPLY, + QString("\n" + "This console accepts RPC commands using the standard syntax.\n" + " example: getblockhash 0\n\n" + "This console can also accept RPC commands using the parenthesized 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" + "A space or a comma can be used to delimit arguments for either syntax.\n" + " example: getblockhash 0\n" + " getblockhash,0\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" + "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; +} + +/** + * 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) +{ + // Parse command line into a vector of strings + const std::vector parsed_command{parseHelper(command)}; + + if (parsed_command.empty()) return false; + + std::string method = parsed_command[0]; + bool exec_help = false; + if (method == "help" && parsed_command.size() > 1) { + exec_help = true; + method = parsed_command[1]; + } + auto it_method = m_method_map.find(method); + if (it_method == m_method_map.end()) return false; // method not found + return (this->*(it_method->second))(parsed_command, wallet_model, exec_help); +} + RPCConsole::RPCConsole(interfaces::Node& node, const PlatformStyle *_platformStyle, QWidget *parent) : QWidget(parent), m_node(node),