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

[GUI] RPC-Console support nested commands and simple value queries #2282

Merged
merged 3 commits into from
Apr 7, 2021
Merged
Show file tree
Hide file tree
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
7 changes: 4 additions & 3 deletions src/Makefile.qt.include
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ QT_MOC_CPP = \
qt/moc_qvaluecombobox.cpp \
qt/moc_recentrequeststablemodel.cpp \
qt/moc_rpcconsole.cpp \
qt/moc_rpcexecutor.cpp \
qt/moc_trafficgraphwidget.cpp \
qt/moc_transactionfilterproxy.cpp \
qt/moc_transactiontablemodel.cpp \
Expand Down Expand Up @@ -162,9 +163,7 @@ BITCOIN_MM = \

QT_MOC = \
qt/pivx.moc \
qt/intro.moc \
qt/rpcconsole.moc \
qt/pivx/settings/moc_settingsconsolewidget.cpp
qt/intro.moc

QT_QRC_CPP = qt/qrc_pivx.cpp
QT_QRC = qt/pivx.qrc
Expand Down Expand Up @@ -206,6 +205,7 @@ BITCOIN_QT_H = \
qt/qvaluecombobox.h \
qt/recentrequeststablemodel.h \
qt/rpcconsole.h \
qt/rpcexecutor.h \
qt/trafficgraphwidget.h \
qt/transactionfilterproxy.h \
qt/transactionrecord.h \
Expand Down Expand Up @@ -518,6 +518,7 @@ BITCOIN_QT_BASE_CPP = \
qt/qvalidatedlineedit.cpp \
qt/qvaluecombobox.cpp \
qt/rpcconsole.cpp \
qt/rpcexecutor.cpp \
qt/trafficgraphwidget.cpp \
qt/utilitydialog.cpp

Expand Down
8 changes: 1 addition & 7 deletions src/qt/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ SET(QT_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/qvalidatedlineedit.cpp
${CMAKE_CURRENT_SOURCE_DIR}/qvaluecombobox.cpp
${CMAKE_CURRENT_SOURCE_DIR}/rpcconsole.cpp
${CMAKE_CURRENT_SOURCE_DIR}/rpcexecutor.cpp
${CMAKE_CURRENT_SOURCE_DIR}/trafficgraphwidget.cpp
${CMAKE_CURRENT_SOURCE_DIR}/utilitydialog.cpp
${CMAKE_CURRENT_SOURCE_DIR}/addressbookpage.cpp
Expand Down Expand Up @@ -174,13 +175,6 @@ SET(QT_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/pivx/splash.cpp
)

# Workaround until the old rpcconsole UI window is fully removed
set_property(SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/pivx/settings/settingsconsolewidget.cpp PROPERTY SKIP_AUTOMOC ON)
execute_process(
COMMAND ${MOC_BIN} -o moc_settingsconsolewidget.cpp settingsconsolewidget.h
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/pivx/settings
)

execute_process(
COMMAND ${MOC_BIN} -o moc_pfborderimage.cpp pfborderimage.h
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/pivx
Expand Down
227 changes: 9 additions & 218 deletions src/qt/pivx/settings/settingsconsolewidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@

#include "qt/pivx/settings/settingsconsolewidget.h"
#include "qt/pivx/settings/forms/ui_settingsconsolewidget.h"
#include "qt/pivx/settings/moc_settingsconsolewidget.cpp"

#include "qt/pivx/qtutils.h"
#include "qt/rpcexecutor.h"

#include "clientmodel.h"
#include "guiutil.h"

#include "chainparams.h"
#include "rpc/client.h"
#include "rpc/server.h"
#include "sapling/key_io_sapling.h"
#include "util.h"
#include "utilitydialog.h"
Expand All @@ -37,7 +37,6 @@
#include <QSignalMapper>
#include <QThread>
#include <QTime>
#include <QTimer>
#include <QStringList>

const int CONSOLE_HISTORY = 50;
Expand All @@ -52,193 +51,6 @@ const struct {
{"misc", ":/icons/ic-transaction-staked"},
{NULL, NULL}};

/* Object for executing console RPC commands in a separate thread.
*/
class RPCExecutor : public QObject
{
Q_OBJECT

public Q_SLOTS:
void requestCommand(const QString& command);

Q_SIGNALS:
void reply(int category, const QString& command);
};

/** Class for handling RPC timers
* (used for e.g. re-locking the wallet after a timeout)
*/
class QtRPCTimerBase: public QObject, public RPCTimerBase
{
Q_OBJECT
public:
QtRPCTimerBase(std::function<void(void)>& _func, int64_t millis):
func(_func)
{
timer.setSingleShot(true);
connect(&timer, &QTimer::timeout, [this]{ func(); });
timer.start(millis);
}
~QtRPCTimerBase() {}

private:
QTimer timer;
std::function<void(void)> func;
};

class QtRPCTimerInterface: public RPCTimerInterface
{
public:
~QtRPCTimerInterface() {}
const char *Name() { return "Qt"; }
RPCTimerBase* NewTimer(std::function<void(void)>& func, int64_t millis)
{
return new QtRPCTimerBase(func, millis);
}
};

#include "qt/pivx/settings/moc_settingsconsolewidget.cpp"

/**
* Split shell command line into a list of arguments. Aims to emulate \c bash and friends.
*
* - Arguments are delimited with whitespace
* - Extra whitespace at the beginning and end and between arguments will be ignored
* - Text can be "double" or 'single' quoted
* - The backslash \c \ is used as escape character
* - Outside quotes, any character can be escaped
* - Within double quotes, only escape \c " and backslashes before a \c " or another backslash
* - Within single quotes, no escaping is possible and no special interpretation takes place
*
* @param[out] args Parsed arguments will be appended to this list
* @param[in] strCommand Command line to split
*/
bool parseCommandLineSettings(std::vector<std::string>& args, const std::string& strCommand)
{
enum CmdParseState {
STATE_EATING_SPACES,
STATE_ARGUMENT,
STATE_SINGLEQUOTED,
STATE_DOUBLEQUOTED,
STATE_ESCAPE_OUTER,
STATE_ESCAPE_DOUBLEQUOTED
} state = STATE_EATING_SPACES;
std::string curarg;
for (char ch : strCommand) {
switch (state) {
case STATE_ARGUMENT: // In or after argument
case STATE_EATING_SPACES: // Handle runs of whitespace
switch (ch) {
case '"':
state = STATE_DOUBLEQUOTED;
break;
case '\'':
state = STATE_SINGLEQUOTED;
break;
case '\\':
state = STATE_ESCAPE_OUTER;
break;
case ' ':
case '\n':
case '\t':
if (state == STATE_ARGUMENT) // Space ends argument
{
args.push_back(curarg);
curarg.clear();
}
state = STATE_EATING_SPACES;
break;
default:
curarg += ch;
state = STATE_ARGUMENT;
}
break;
case STATE_SINGLEQUOTED: // Single-quoted string
switch (ch) {
case '\'':
state = STATE_ARGUMENT;
break;
default:
curarg += ch;
}
break;
case STATE_DOUBLEQUOTED: // Double-quoted string
switch (ch) {
case '"':
state = STATE_ARGUMENT;
break;
case '\\':
state = STATE_ESCAPE_DOUBLEQUOTED;
break;
default:
curarg += ch;
}
break;
case STATE_ESCAPE_OUTER: // '\' outside quotes
curarg += ch;
state = STATE_ARGUMENT;
break;
case STATE_ESCAPE_DOUBLEQUOTED: // '\' in double-quoted text
if (ch != '"' && ch != '\\') curarg += '\\'; // keep '\' for everything but the quote and '\' itself
curarg += ch;
state = STATE_DOUBLEQUOTED;
break;
}
}
switch (state) // final state
{
case STATE_EATING_SPACES:
return true;
case STATE_ARGUMENT:
args.push_back(curarg);
return true;
default: // ERROR to end in one of the other states
return false;
}
}

void RPCExecutor::requestCommand(const QString& command)
{
std::vector<std::string> args;
if (!parseCommandLineSettings(args, command.toStdString())) {
Q_EMIT reply(SettingsConsoleWidget::CMD_ERROR, QString("Parse error: unbalanced ' or \""));
return;
}
if (args.empty())
return; // Nothing to do
try {
std::string strPrint;
// Convert argument list to JSON objects in method-dependent way,
// and pass it along with the method name to the dispatcher.
JSONRPCRequest req;
req.params = RPCConvertValues(args[0], std::vector<std::string>(args.begin() + 1, args.end()));
req.strMethod = args[0];
UniValue result = tableRPC.execute(req);

// Format result reply
if (result.isNull())
strPrint = "";
else if (result.isStr())
strPrint = result.get_str();
else
strPrint = result.write(2);

Q_EMIT reply(SettingsConsoleWidget::CMD_REPLY, QString::fromStdString(strPrint));
} catch (UniValue& objError) {
try // Nice formatting for standard-format error
{
int code = find_value(objError, "code").get_int();
std::string message = find_value(objError, "message").get_str();
Q_EMIT reply(SettingsConsoleWidget::CMD_ERROR, QString::fromStdString(message) + " (code " + QString::number(code) + ")");
} catch (std::runtime_error&) // raised when converting to invalid type, i.e. missing code or message
{ // Show raw JSON object
Q_EMIT reply(SettingsConsoleWidget::CMD_ERROR, QString::fromStdString(objError.write()));
}
} catch (std::exception& e) {
Q_EMIT reply(SettingsConsoleWidget::CMD_ERROR, QString("Error: ") + QString::fromStdString(e.what()));
}
}

SettingsConsoleWidget::SettingsConsoleWidget(PIVXGUI* _window, QWidget *parent) :
PWidget(_window,parent),
ui(new Ui::SettingsConsoleWidget)
Expand Down Expand Up @@ -383,20 +195,6 @@ void SettingsConsoleWidget::showEvent(QShowEvent *event)
if (ui->lineEdit) ui->lineEdit->setFocus();
}

static QString categoryClass(int category)
{
switch (category) {
case SettingsConsoleWidget::CMD_REQUEST:
return "cmd-request";
case SettingsConsoleWidget::CMD_REPLY:
return "cmd-reply";
case SettingsConsoleWidget::CMD_ERROR:
return "cmd-error";
default:
return "misc";
}
}

void SettingsConsoleWidget::clear(bool clearHistory)
{
ui->messagesWidget->clear();
Expand Down Expand Up @@ -425,7 +223,7 @@ void SettingsConsoleWidget::clear(bool clearHistory)
QString clsKey = "Ctrl-L";
#endif

messageInternal(CMD_REPLY, (tr("Welcome to the PIVX RPC console.") + "<br>" +
messageInternal(RPCExecutor::CMD_REPLY, (tr("Welcome to the PIVX RPC console.") + "<br>" +
tr("Use up and down arrows to navigate history, and %1 to clear screen.").arg("<b>"+clsKey+"</b>") + "<br>" +
tr("Type <b>help</b> for an overview of available commands.") +
"<br><span class=\"secwarning\"><br>" +
Expand All @@ -440,8 +238,8 @@ void SettingsConsoleWidget::messageInternal(int category, const QString& message
QString timeString = time.toString();
QString out;
out += "<table><tr><td class=\"time\" width=\"65\">" + timeString + "</td>";
out += "<td class=\"icon\" width=\"32\"><img src=\"" + categoryClass(category) + "\"></td>";
out += "<td class=\"message " + categoryClass(category) + "\" valign=\"middle\">";
out += "<td class=\"icon\" width=\"32\"><img src=\"" + RPCExecutor::categoryClass(category) + "\"></td>";
out += "<td class=\"message " + RPCExecutor::categoryClass(category) + "\" valign=\"middle\">";
if (html)
out += message;
else
Expand All @@ -457,18 +255,11 @@ static bool PotentiallyDangerousCommand(const QString& cmd)
return true;
}
if (cmd.size() >= 13 && cmd.leftRef(11) == "dumpprivkey") {
// valid PIVX Transparent Address
std::vector<std::string> args;
parseCommandLineSettings(args, cmd.toStdString());
return (args.size() == 2 && IsValidDestinationString(args[1], false));
return true;
}
if (cmd.size() >= 18 && cmd.leftRef(16) == "exportsaplingkey") {
// valid PIVX Shield Address
std::vector<std::string> args;
parseCommandLineSettings(args, cmd.toStdString());
return (args.size() == 2 && KeyIO::IsValidPaymentAddressString(args[1]));
return true;
}

return false;
}

Expand All @@ -485,7 +276,7 @@ void SettingsConsoleWidget::on_lineEdit_returnPressed()
return;
}

messageInternal(CMD_REQUEST, cmd);
messageInternal(RPCExecutor::CMD_REQUEST, cmd);
Q_EMIT cmdCommandRequest(cmd);
// Remove command, if already in history
history.removeOne(cmd);
Expand Down Expand Up @@ -524,7 +315,7 @@ void SettingsConsoleWidget::startExecutor()
// Replies from executor object must go to this object
connect(executor, &RPCExecutor::reply, this, &SettingsConsoleWidget::response);
// Requests from this object must go to executor
connect(this, &SettingsConsoleWidget::cmdCommandRequest, executor, &RPCExecutor::requestCommand);
connect(this, &SettingsConsoleWidget::cmdCommandRequest, executor, &RPCExecutor::request);

// On stopExecutor signal
// - queue executor for deletion (in execution thread)
Expand Down
8 changes: 0 additions & 8 deletions src/qt/pivx/settings/settingsconsolewidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,6 @@ class SettingsConsoleWidget : public PWidget
void loadClientModel() override;
void showEvent(QShowEvent *event) override;

enum MessageClass {
MC_ERROR,
MC_DEBUG,
CMD_REQUEST,
CMD_REPLY,
CMD_ERROR
};

public Q_SLOTS:
void clear(bool clearHistory = true);
void response(int category, const QString &message) { messageInternal(category, message); };
Expand Down
Loading