From 655146d5e7a90ba049dc7aabe203d77abd515bb2 Mon Sep 17 00:00:00 2001 From: "W. J. van der Laan" Date: Mon, 5 Apr 2021 15:31:30 +0200 Subject: [PATCH] Merge #21302: wallet: createwallet examples for descriptor wallets 5039e0e55a7c9dd63a38c12fa60d244592de69e0 test: HelpExampleCliNamed and HelpExampleRpcNamed (Ivan Metlushko) 591735ef0bf13b94643b794518406f981fa5dcb7 rpc: Add HelpExampleCliNamed and use it for `createwallet` doc (Wladimir J. van der Laan) 5d5a90e819d23a302f9bec6b995a3116ead6ae94 rpc: Add HelpExampleRpcNamed (Ivan Metlushko) Pull request description: Rationale: make descriptor wallets more visible and just a bit easier to setup `bitcoin-cli help createwallet` **Before**: ``` Examples: > bitcoin-cli createwallet "testwallet" > curl --user myusername --data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "createwallet", "params": ["testwallet"]}' -H 'content-type: text/plain;' http://127.0.0.1:8332/ ``` **After** ``` Examples: > bitcoin-cli createwallet "testwallet" > curl --user myusername --data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "createwallet", "params": ["testwallet"]}' -H 'content-type: text/plain;' http://127.0.0.1:8332/ > bitcoin-cli createwallet "descriptors" false false "" true true true > curl --user myusername --data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "createwallet", "params": ["descriptors", false, false, "", true, true, true]}' -H 'content-type: text/plain;' http://127.0.0.1:8332/ ``` ACKs for top commit: laanwj: Tested ACK 5039e0e55a7c9dd63a38c12fa60d244592de69e0 Tree-SHA512: d37210e6ce639addee881377092d8f6fb2a537a60a259c561899e24cf68a0254d7ff45a213573c938f626677e46770cd21113aae5974f26c66b9a2e137699c14 --- src/rpc/util.cpp | 63 ++++++++++++++++++++++++++++++++++++++++ src/rpc/util.h | 4 +++ src/test/rpc_tests.cpp | 35 ++++++++++++++++++++++ src/wallet/rpcwallet.cpp | 2 ++ 4 files changed, 104 insertions(+) diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index e850a17879..80abf1418a 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -158,11 +158,63 @@ bool ParseBoolV(const UniValue& v, const std::string &strName) throw JSONRPCError(RPC_INVALID_PARAMETER, strName+" must be true, false, yes, no, 1 or 0 (not '"+strBool+"')"); } +namespace { + +/** + * Quote an argument for shell. + * + * @note This is intended for help, not for security-sensitive purposes. + */ +std::string ShellQuote(const std::string& s) +{ + std::string result; + result.reserve(s.size() * 2); + for (const char ch: s) { + if (ch == '\'') { + result += "'\''"; + } else { + result += ch; + } + } + return "'" + result + "'"; +} + +/** + * Shell-quotes the argument if it needs quoting, else returns it literally, to save typing. + * + * @note This is intended for help, not for security-sensitive purposes. + */ +std::string ShellQuoteIfNeeded(const std::string& s) +{ + for (const char ch: s) { + if (ch == ' ' || ch == '\'' || ch == '"') { + return ShellQuote(s); + } + } + + return s; +} + +} + std::string HelpExampleCli(const std::string& methodname, const std::string& args) { return "> dash-cli " + methodname + " " + args + "\n"; } +std::string HelpExampleCliNamed(const std::string& methodname, const RPCArgList& args) +{ + std::string result = "> dash-cli -named " + methodname; + for (const auto& argpair: args) { + const auto& value = argpair.second.isStr() + ? argpair.second.get_str() + : argpair.second.write(); + result += " " + argpair.first + "=" + ShellQuoteIfNeeded(value); + } + result += "\n"; + return result; +} + std::string HelpExampleRpc(const std::string& methodname, const std::string& args) { return "> curl --user myusername --data-binary '{\"jsonrpc\": \"1.0\", \"id\": \"curltest\", " @@ -170,6 +222,17 @@ std::string HelpExampleRpc(const std::string& methodname, const std::string& arg " http://127.0.0.1:9998/\n"; } +std::string HelpExampleRpcNamed(const std::string& methodname, const RPCArgList& args) +{ + UniValue params(UniValue::VOBJ); + for (const auto& param: args) { + params.pushKV(param.first, param.second); + } + + return "> curl --user myusername --data-binary '{\"jsonrpc\": \"1.0\", \"id\": \"curltest\", " + "\"method\": \"" + methodname + "\", \"params\": " + params.write() + "}' -H 'content-type: text/plain;' http://127.0.0.1:8332/\n"; +} + // Converts a hex string to a public key if possible CPubKey HexToPubKey(const std::string& hex_in) { diff --git a/src/rpc/util.h b/src/rpc/util.h index 768131795a..77c9c39ec4 100644 --- a/src/rpc/util.h +++ b/src/rpc/util.h @@ -85,8 +85,12 @@ extern double ParseDoubleV(const UniValue& v, const std::string &strName); extern bool ParseBoolV(const UniValue& v, const std::string &strName); extern CAmount AmountFromValue(const UniValue& value); + +using RPCArgList = std::vector>; extern std::string HelpExampleCli(const std::string& methodname, const std::string& args); +extern std::string HelpExampleCliNamed(const std::string& methodname, const RPCArgList& args); extern std::string HelpExampleRpc(const std::string& methodname, const std::string& args); +extern std::string HelpExampleRpcNamed(const std::string& methodname, const RPCArgList& args); CPubKey HexToPubKey(const std::string& hex_in); CPubKey AddrToPubKey(const FillableSigningProvider& keystore, const std::string& addr_in); diff --git a/src/test/rpc_tests.cpp b/src/test/rpc_tests.cpp index 3eeb868de9..5e89889a61 100644 --- a/src/test/rpc_tests.cpp +++ b/src/test/rpc_tests.cpp @@ -481,6 +481,41 @@ BOOST_AUTO_TEST_CASE(rpc_getblockstats_calculate_percentiles_by_size) } } +BOOST_AUTO_TEST_CASE(help_example) +{ + // test different argument types + const RPCArgList& args = {{"foo", "bar"}, {"b", true}, {"n", 1}}; + BOOST_CHECK_EQUAL(HelpExampleCliNamed("test", args), "> dash-cli -named test foo=bar b=true n=1\n"); + BOOST_CHECK_EQUAL(HelpExampleRpcNamed("test", args), "> curl --user myusername --data-binary '{\"jsonrpc\": \"1.0\", \"id\": \"curltest\", \"method\": \"test\", \"params\": {\"foo\":\"bar\",\"b\":true,\"n\":1}}' -H 'content-type: text/plain;' http://127.0.0.1:8332/\n"); + + // test shell escape + BOOST_CHECK_EQUAL(HelpExampleCliNamed("test", {{"foo", "b'ar"}}), "> dash-cli -named test foo='b'''ar'\n"); + BOOST_CHECK_EQUAL(HelpExampleCliNamed("test", {{"foo", "b\"ar"}}), "> dash-cli -named test foo='b\"ar'\n"); + BOOST_CHECK_EQUAL(HelpExampleCliNamed("test", {{"foo", "b ar"}}), "> dash-cli -named test foo='b ar'\n"); + + // test object params + UniValue obj_value(UniValue::VOBJ); + obj_value.pushKV("foo", "bar"); + obj_value.pushKV("b", false); + obj_value.pushKV("n", 1); + BOOST_CHECK_EQUAL(HelpExampleCliNamed("test", {{"name", obj_value}}), "> dash-cli -named test name='{\"foo\":\"bar\",\"b\":false,\"n\":1}'\n"); + BOOST_CHECK_EQUAL(HelpExampleRpcNamed("test", {{"name", obj_value}}), "> curl --user myusername --data-binary '{\"jsonrpc\": \"1.0\", \"id\": \"curltest\", \"method\": \"test\", \"params\": {\"name\":{\"foo\":\"bar\",\"b\":false,\"n\":1}}}' -H 'content-type: text/plain;' http://127.0.0.1:8332/\n"); + + // test array params + UniValue arr_value(UniValue::VARR); + arr_value.push_back("bar"); + arr_value.push_back(false); + arr_value.push_back(1); + BOOST_CHECK_EQUAL(HelpExampleCliNamed("test", {{"name", arr_value}}), "> dash-cli -named test name='[\"bar\",false,1]'\n"); + BOOST_CHECK_EQUAL(HelpExampleRpcNamed("test", {{"name", arr_value}}), "> curl --user myusername --data-binary '{\"jsonrpc\": \"1.0\", \"id\": \"curltest\", \"method\": \"test\", \"params\": {\"name\":[\"bar\",false,1]}}' -H 'content-type: text/plain;' http://127.0.0.1:8332/\n"); + + // test types don't matter for shell + BOOST_CHECK_EQUAL(HelpExampleCliNamed("foo", {{"arg", true}}), HelpExampleCliNamed("foo", {{"arg", "true"}})); + + // test types matter for Rpc + BOOST_CHECK_NE(HelpExampleRpcNamed("foo", {{"arg", true}}), HelpExampleRpcNamed("foo", {{"arg", "true"}})); +} + BOOST_AUTO_TEST_CASE(rpc_bls) { UniValue r; diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index a3e7fccba7..e3987f0b96 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -2992,6 +2992,8 @@ static RPCHelpMan createwallet() RPCExamples{ HelpExampleCli("createwallet", "\"testwallet\"") + HelpExampleRpc("createwallet", "\"testwallet\"") + + HelpExampleCliNamed("createwallet", {{"wallet_name", "descriptors"}, {"avoid_reuse", true}, {"descriptors", true}, {"load_on_startup", true}}) + + HelpExampleRpcNamed("createwallet", {{"wallet_name", "descriptors"}, {"avoid_reuse", true}, {"descriptors", true}, {"load_on_startup", true}}) }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue {