Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Debug Console implementation of generate method #692

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 157 additions & 29 deletions src/qt/rpcconsole.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
#include <util/strencodings.h>
#include <util/string.h>
#include <util/threadnames.h>

#include <util/chaintype.h>
#include <univalue.h>

#include <QAbstractButton>
Expand Down Expand Up @@ -96,8 +96,36 @@ public Q_SLOTS:

private:
interfaces::Node& m_node;
bool executeConsoleGenerate(const std::vector<std::string>& parsed_command, const WalletModel* wallet_model, const bool exec_help = false);
void executeConsoleHelpGenerate();
bool executeConsoleHelpConsole(const std::vector<std::string>& 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<std::string, bool (RPCExecutor::*)(const std::vector<std::string>&, 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function iterates over the entire input command string twice. It isn't really "fast".
Could remove this line altogether.

* 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<std::string> parseHelper(const std::string& strCommand)
{
// Split while recognizing the several characters that can be used as separators in the GUI console
std::vector<std::string> vec{SplitString(strCommand, " (),")};
hernanmarino marked this conversation as resolved.
Show resolved Hide resolved
// 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)
*/
Expand Down Expand Up @@ -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));
Comment on lines +447 to +453
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is an indentation issue here.

}

Q_EMIT reply(RPCConsole::CMD_REPLY, QString::fromStdString(result));
}
catch (UniValue& objError)
{
Expand All @@ -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<std::string>& 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) {
Copy link
Member

@Sjors Sjors Oct 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to fail on all networks except regtest. It might be possible to mine on a custom signet too, but anyone who knows how to make such a network also knows how to use the RPC.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, I'll change it soon

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To access the chain type, use the node interface. See 03d67301e081ecf3123372901b115ee5e29d7c79. So we don't violate the layers division.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should pass through the model.

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<int>(nblocks)};
const auto maxtries_value{ToIntegral<int>(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 .
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above, this shouldn't be here. It is explaining the caller's workflow inside executeConsoleGenerate. There is no other RPC calls at this point.

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<std::string>& 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't be here. The function's docstrings shouldn't explain the caller workflow behavior.
Something like "Executes gui-console-only commands" would be preferable.

*
* @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)
hernanmarino marked this conversation as resolved.
Show resolved Hide resolved
{
// Parse command line into a vector of strings
const std::vector<std::string> 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),
Expand Down