Join GitHub today
GitHub is home to over 20 million developers working together to host and review code, manage projects, and build software together.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
Already on GitHub? Sign in to your account
[Qt] RPC-Console: support nested commands and simple value queries #7783
Conversation
jonasschnelli
added
the
GUI
label
Apr 1, 2016
|
Isn't this going a bit overboard for debugging tools? (OTOH, it's only about 150 LOC...) |
|
I think this is useful. An alternative proposed was to use variables |
It is a "luxury extension", right. But given the time some of us have spent in the console repeating and copy-pasting commands out- and input, I think it worth taking this in. IMO we should also extend bitcoin-cli to support nested commands. It simply increases productivity with that tool. |
|
I like this concept. I initially had @luke-jr 's concern as well. But only a bit of code added, and it's well-contained. It's not just luxury: it's useful for cases like #7599 where someone wants to insert the output of a previous command into a new one, but it's too long for copy pasting.
Not sure there. For the GUI debug console, which is essentially it's own I'm all for a fancy ncurses-based interactive client, but that should not be |
|
On the other hand, many advices read 'Run getsomething' and so. This will bring another "fork" - you have to also add that you have to run this in Debug console or via Concept ACK (I'd also like to see this in |
|
Almost tempting to make it server-side, if we're using long output-inputs... but this seems fine (Concept ACK) as-is; further improvement can wait for another PR. |
|
Would it be possible to abstract out this functionality in a separate commit (including the existing RPC parsing logic from the Qt console) and move it to rpc/server.cpp, as an actual RPC call that just takes a string argument with a command to parse? That would make it both more usable (by exposing it as RPC, bitcoin-cli and other tools can use it too), and more testable (we can have RPC tests for it), without complicating the change much. |
|
I have though about that but I wasn't sure if we should delegate the parsing/executing of nested command to the server. This PR would do the parsing "client side". We could also factor out the parsing and use it client-side (Qt / bitcoin-cli). But I agree, it could be useful server-side. |
|
Yes, I agree having it client side is useful. My main reason for suggesting abstracting it out it because I don't think it's very hard, and would make the parsing logic much easier to test. |
UniQredit
commented
Jul 19, 2016
|
This would save a hell lot of time and copy/pasting |
This could now be simply extended to the RPC server, although, nested commands could be resource and time hungry. |
MarcoFalke
commented on an outdated diff
Jul 19, 2016
| + std::string result2; | ||
| + RPCExecuteCommandLine(result, "getblockchaininfo()[chain]"); //simple result filtering with path | ||
| + BOOST_CHECK_EQUAL(result, "main"); | ||
| + | ||
| + BOOST_CHECK_NO_THROW(RPCExecuteCommandLine(result, "getblock(getbestblockhash())")); //simple 2 level nesting | ||
| + BOOST_CHECK_NO_THROW(RPCExecuteCommandLine(result, "getblock(getblock(getbestblockhash())[hash], true)")); | ||
| + | ||
| + BOOST_CHECK_NO_THROW(RPCExecuteCommandLine(result, "getblock( getblock( getblock(getbestblockhash())[hash] )[hash], true)")); //4 level nesting with whitespace, filtering path and boolean parameter | ||
| + | ||
| + BOOST_CHECK_NO_THROW(RPCExecuteCommandLine(result, "getblockchaininfo")); | ||
| + BOOST_CHECK_EQUAL(result.substr(0,1), "{"); | ||
| + | ||
| + BOOST_CHECK_NO_THROW(RPCExecuteCommandLine(result, "getblockchaininfo()")); | ||
| + BOOST_CHECK_EQUAL(result.substr(0,1), "{"); | ||
| + | ||
| + BOOST_CHECK_NO_THROW(RPCExecuteCommandLine(result, "getblockchaininfo")); //whitespace at the end will be tolerated |
|
|
MarcoFalke
commented on an outdated diff
Jul 19, 2016
| + BOOST_CHECK_NO_THROW(RPCExecuteCommandLine(result, "getblock(getblock(getbestblockhash())[hash], true)")); | ||
| + | ||
| + BOOST_CHECK_NO_THROW(RPCExecuteCommandLine(result, "getblock( getblock( getblock(getbestblockhash())[hash] )[hash], true)")); //4 level nesting with whitespace, filtering path and boolean parameter | ||
| + | ||
| + BOOST_CHECK_NO_THROW(RPCExecuteCommandLine(result, "getblockchaininfo")); | ||
| + BOOST_CHECK_EQUAL(result.substr(0,1), "{"); | ||
| + | ||
| + BOOST_CHECK_NO_THROW(RPCExecuteCommandLine(result, "getblockchaininfo()")); | ||
| + BOOST_CHECK_EQUAL(result.substr(0,1), "{"); | ||
| + | ||
| + BOOST_CHECK_NO_THROW(RPCExecuteCommandLine(result, "getblockchaininfo")); //whitespace at the end will be tolerated | ||
| + BOOST_CHECK_EQUAL(result.substr(0,1), "{"); | ||
| + | ||
| + BOOST_CHECK_THROW(RPCExecuteCommandLine(result, "getblockchaininfo() .\n"), runtime_error); //invalid syntax | ||
| + BOOST_CHECK_THROW(RPCExecuteCommandLine(result, "getblockchaininfo() getblockchaininfo()"), runtime_error); //invalid syntax | ||
| + BOOST_CHECK_NO_THROW(RPCExecuteCommandLine(result, "getblockchaininfo(")); //tollerate non closing brackets if we have no arguments |
|
|
|
Fixed nits. Had to add |
Sorry to be contrary, but IMO, functionality related to parsing and not dispatching should be in (another reason that it doesn't belong server-side is that it doesn't act on univalue/JSON objects, but on command line strings. Wouldn't want string parsing on the server side, as this makes the JSON-RPC API unclear, and introduces potential vulnerabilities and DoS possibilities etc) |
laanwj
commented on an outdated diff
Aug 12, 2016
| @@ -181,6 +181,7 @@ libbitcoin_server_a_SOURCES = \ | ||
| pow.cpp \ | ||
| rest.cpp \ | ||
| rpc/blockchain.cpp \ | ||
| + rpc/client.cpp \ |
laanwj
Owner
|
|
Having it in |
|
Server-side nested commands are not part of the JSON-RPC standard. It is an interesting thought but that would be a completely different proposal, and I don't think it would share any code with this. I'd imagine it would work something akin to batching (but w/ nested structures), not by parsing/formatting expression strings. Seeing how little even simple batching is used, I'm also not sure there is enough demand for that kind of advanced behavior, but that aside. Edit: Looked it up a bit, 'nested remote function call' in RPC protocols is commonly implemented in the form of 'promise pipelining', a strategy to reduce round-trips. A call can return a handle, which is essentially a temporary variable, which can be passed as argument to other calls before the result is known. This allows more versatile manipulation than just nesting (e.g. a DAG instead of a tree). In any case this is something to be found in the more advanced RPC frameworks, I couldn't find anyone having bolted it into JSON-RPC. As said, an issue for another time :) Edit.2: Had a try at a proposal here: #8457 (comment) |
|
Agreed with keeping parsing client-side. Let's not tangle up the dependencies. I really like this idea btw. |
laanwj
referenced this pull request
Aug 13, 2016
Closed
Add block height support in rpc call getblock #8457
|
Removed all changes from the core classes. |
|
qt-test fail on travis, apparently. |
|
Fixed the travis Qt-Test issue. |
UdjinM6
commented on an outdated diff
Aug 23, 2016
| { | ||
| - args.push_back(curarg); | ||
| - curarg.clear(); | ||
| + case '[': curarg.clear(); state = STATE_COMMAND_EXECUTED_INNER; break; | ||
| + default: | ||
| + if (state == STATE_COMMAND_EXECUTED_INNER) | ||
| + { | ||
| + if (ch == ']') | ||
| + { | ||
| + if (curarg.size()) | ||
| + { | ||
| + // if we have a value query, query arrays with index and objects with a string key | ||
| + UniValue subelement; | ||
| + if (curarg.size() && lastResult.isArray()) |
|
|
UdjinM6
commented on an outdated diff
Aug 23, 2016
| + if (state == STATE_COMMAND_EXECUTED_INNER) | ||
| + { | ||
| + if (ch == ']') | ||
| + { | ||
| + if (curarg.size()) | ||
| + { | ||
| + // if we have a value query, query arrays with index and objects with a string key | ||
| + UniValue subelement; | ||
| + if (curarg.size() && lastResult.isArray()) | ||
| + { | ||
| + for(char argch: curarg) | ||
| + if (!std::isdigit(argch)) | ||
| + throw std::runtime_error("Invalid result query"); | ||
| + subelement = lastResult[atoi(curarg.c_str())]; | ||
| + } | ||
| + else if (curarg.size() && lastResult.isObject()) |
|
|
UdjinM6
commented on an outdated diff
Aug 23, 2016
| { | ||
| - args.push_back(curarg); | ||
| - curarg.clear(); | ||
| + case '[': curarg.clear(); state = STATE_COMMAND_EXECUTED_INNER; break; | ||
| + default: | ||
| + if (state == STATE_COMMAND_EXECUTED_INNER) | ||
| + { | ||
| + if (ch == ']') |
UdjinM6
Contributor
|
MarcoFalke
commented on an outdated diff
Aug 23, 2016
| + // don't stringify the json in case of a string to avoid doublequotes | ||
| + if (lastResult.isStr()) | ||
| + curarg = lastResult.get_str(); | ||
| + else | ||
| + curarg = lastResult.write(2); | ||
| + | ||
| + // if we have a non empty result, use it as stack argument otherwise as general result | ||
| + if (curarg.size()) | ||
| + { | ||
| + if (stack.size()) | ||
| + stack.back().push_back(curarg); | ||
| + else | ||
| + strResult = curarg; | ||
| + } | ||
| + curarg.clear(); | ||
| + // assume easting space state |
|
|
UdjinM6
commented on the diff
Aug 23, 2016
| - // and pass it along with the method name to the dispatcher. | ||
| - UniValue result = tableRPC.execute( | ||
| - args[0], | ||
| - RPCConvertValues(args[0], std::vector<std::string>(args.begin() + 1, args.end()))); | ||
| - | ||
| - // Format result reply | ||
| - if (result.isNull()) | ||
| - strPrint = ""; | ||
| - else if (result.isStr()) | ||
| - strPrint = result.get_str(); | ||
| - else | ||
| - strPrint = result.write(2); | ||
| - | ||
| - Q_EMIT reply(RPCConsole::CMD_REPLY, QString::fromStdString(strPrint)); | ||
| + std::string result; | ||
| + std::string executableCommand = command.toStdString() + "\n"; |
UdjinM6
Contributor
|
MarcoFalke
commented on an outdated diff
Aug 23, 2016
| +#include "util.h" | ||
| + | ||
| +#include <QDir> | ||
| + | ||
| +#include <boost/filesystem.hpp> | ||
| + | ||
| +void RPCNestedTests::rpcNestedTests() | ||
| +{ | ||
| + UniValue jsonRPCError; | ||
| + | ||
| + // do some test setup | ||
| + // could be moved to a more generic place when we add more tests on QT level | ||
| + const CChainParams& chainparams = Params(); | ||
| + RegisterAllCoreRPCCommands(tableRPC); | ||
| + ClearDatadirCache(); | ||
| + std::string path = QDir::tempPath().toStdString() + "/" + strprintf("test_bitcoin_%lu_%i", (unsigned long)GetTime(), (int)(GetRand(100000))); |
|
|
|
Fixed nits, added cleanup of Qt test data. |
|
concept ACK |
|
utACK. I do think this needs documentation. Not necessarily in this pull, but currently the debug console help consists of two lines "Use up and down arrows to navigate history, and Ctrl-L to clear screen. Type help for an overview of available commands.". Maybe add a debug-console-only command like |
Good point. I try something. Maybe not in this PR. |
|
|
|
@laanwj: I guess you need to use |
|
@jonasschnelli awesome, that works. So that's why we need documentation :) |
|
@laanwj: Agree on the documentation. The dropped Allowing the double-quotes (ignore them while parsing) could be a useful addition. |
|
For testing this it's useful to add an diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp
index c14d9d6..4e09249 100644
--- a/src/rpc/client.cpp
+++ b/src/rpc/client.cpp
@@ -109,6 +109,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "setban", 3 },
{ "getmempoolancestors", 1 },
{ "getmempooldescendants", 1 },
+ { "echon", 0}, { "echon", 1}, { "echon", 2}, { "echon", 3}, { "echon", 4}, { "echon", 5}, { "echon", 6}, { "echon", 7}, { "echon", 8}, { "echon", 9},
};
class CRPCConvertTable
diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp
index 5afcf63..e3b4550 100644
--- a/src/rpc/misc.cpp
+++ b/src/rpc/misc.cpp
@@ -417,6 +417,17 @@ UniValue signmessagewithprivkey(const UniValue& params, bool fHelp)
return EncodeBase64(&vchSig[0], vchSig.size());
}
+UniValue echo(const UniValue& params, bool fHelp)
+{
+ if (fHelp)
+ throw runtime_error(
+ "echo \"message\" ...\n"
+ "\nSimply echo back the input arguments\n"
+ );
+
+ return params;
+}
+
UniValue setmocktime(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() != 1)
@@ -454,6 +465,8 @@ static const CRPCCommand commands[] =
{ // category name actor (function) okSafeMode
// --------------------- ------------------------ ----------------------- ----------
{ "control", "getinfo", &getinfo, true }, /* uses wallet if enabled */
+ { "control", "echo", &echo, true },
+ { "control", "echon", &echo, true },
{ "util", "validateaddress", &validateaddress, true }, /* uses wallet if enabled */
{ "util", "createmultisig", &createmultisig, true },
{ "util", "verifymessage", &verifymessage, true },
Yes, would be useful to add, maybe in a later pull. Somehow I keep typing [] accidentally, and being surprised I get 'null'. |
laanwj
merged commit 1586044
into
bitcoin:master
Sep 20, 2016
1 check passed
|
ACK 2ca6b9d |
added a commit
that referenced
this pull request
Sep 20, 2016
| * - 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[out] result stringified Result from the executed command(chain) |
| + std::string executableCommand = command.toStdString() + "\n"; | ||
| + if(!RPCConsole::RPCExecuteCommandLine(result, executableCommand)) | ||
| + { | ||
| + Q_EMIT reply(RPCConsole::CMD_ERROR, QString("Parse error: unbalanced ' or \"")); |
| + QVERIFY_EXCEPTION_THROWN(RPCConsole::RPCExecuteCommandLine(result, "getblockchaininfo() .\n"), std::runtime_error); //invalid syntax | ||
| + QVERIFY_EXCEPTION_THROWN(RPCConsole::RPCExecuteCommandLine(result, "getblockchaininfo() getblockchaininfo()"), std::runtime_error); //invalid syntax | ||
| + (RPCConsole::RPCExecuteCommandLine(result, "getblockchaininfo(")); //tolerate non closing brackets if we have no arguments | ||
| + (RPCConsole::RPCExecuteCommandLine(result, "getblockchaininfo()()()")); //tolerate non command brackts |
MarcoFalke
Sep 20, 2016
Member
Nits: Can be moved out of the guard, typo brackts, no wrapping brackets needed.
jonasschnelli commentedApr 1, 2016
this is currently limited to the QT RPC Console, but I'm happy to extend/refactor this to make it useable in bitcoin-cli
Commands can be executed with bracket syntax, example:
getwalletinfo().Commands can be nested, example:
sendtoaddress(getnewaddress(), 10).Simple queries are possible:
listunspent()[0][txid]Object values are accessed with a non-quoted string, example:
[txid].Fully backward compatible.
generate 101is identical togenerate(101)Result value queries indicated with
[]require the new brackets syntax.Comma as argument separator is now also possible:
sendtoaddress,<address>,<amount>Space as argument separator works also with the bracket syntax, example:
sendtoaddress(getnewaddress() 10)No dept limitation, complex commands are possible:
decoderawtransaction(getrawtransaction(getblock(getbestblockhash())[tx][0]))[vout][0][value]