-
Notifications
You must be signed in to change notification settings - Fork 37.7k
Multiwallet: add RPC endpoint support #10650
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
Multiwallet: add RPC endpoint support #10650
Conversation
d47c387
to
6c4b1ba
Compare
This would make much more sense as |
No. Please no query string. POST data to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
utACK 6c4b1ba5d05295b68f91fda74fc1841d76ce01fc with whatever link fixes are needed for travis.
This is a nice, clean change, and I personally think it is more user friendly than requiring an -rpcauth
setup in order to use multiwallet like #10615 (although auth wallet selection and endpoint wallet selection are basically compatible each other and both PRs could be merged).
I would agree with luke and prefer /?wallet=<name>
to /wallet/<name>
because former is more extensible and would allow more query options to be added in the future (like custom timeouts), while leaving the top level url path free for a more traditional use like API versioning.
src/bitcoin-cli.cpp
Outdated
// check if we should use a special wallet endpoint | ||
std::string endpoint = "/"; | ||
if (GetArg("-wallet", "") != "") { | ||
endpoint = "/wallet/"+GetArg("-wallet", ""); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In commit "Add -wallet endpoint support to bitcoin-cli":
Probably should url encode in case filename contains spaces, reserved characters, or unicode (utf8) https://en.wikipedia.org/wiki/Percent-encoding#Percent-encoding_reserved_characters
Corresponding decode would be needed in GetWalletForJSONRPCRequest.
method = self._service_name | ||
elements = self._service_name.split(".") | ||
if len(elements) > 1: | ||
urlAdd = "/wallet/" + '.'.join(elements[:-1]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In commit "[QA] Add support for wallet endpoints in Authproxy"
It seems hacky to me for AuthServiceProxy to be aware of multiwallet and to be munging urls and service name strings this way.
I think it would be better if AuthServiceProxy didn't know anything about bitcoin urls and just let the caller control the request. There are many ways to allow this, but a simple one might be to define:
def __idiv__(self, relative_uri):
return AuthServiceProxy("{}/{}".format(self.__service_url, relative_uri), self._service_name, connection=self.__conn)
Then you could call methods on individual wallets with:
w1 = self.nodes[0] / "wallet/w1.dat"
w1.getwalletinfo()
w1.getbalance()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this actually should use __truediv__
not __idiv__
,
which is for python 2.x (https://docs.python.org/3/reference/datamodel.html?highlight=truediv#object.__truediv__)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree - we should try to make minimal changes to authproxy.
Later, once TestNode is merged, it'll be very straightforward to add wallet methods to the TestNode class, which is where I think they should live - not in the authproxy layer.
src/bitcoin-cli.cpp
Outdated
int r = evhttp_make_request(evcon.get(), req.get(), EVHTTP_REQ_POST, "/"); | ||
// check if we should use a special wallet endpoint | ||
std::string endpoint = "/"; | ||
if (GetArg("-wallet", "") != "") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In commit "Add -wallet endpoint support to bitcoin-cli":
You should update HelpMessageCli
to mention the new argument.
Since I started making a similar change (but didn't get very far) you could steal my help string:
strUsage += HelpMessageOpt("-wallet=<file>", _("Send RPC for non-default wallet on RPC server (argument is wallet filename in bitcoind directory)"));
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In commit "Add -wallet endpoint support to bitcoin-cli"
Previous comment not addressed (#10650 (comment)).
Concept ACK, will review.
I tend to agree:
|
I guess personally I don't see how If we want to go down the road of encoding request metadata in path segments instead of query parameters maybe we should put more thought into what the path hierarchy should be? It isn't obvious to me that you'd want to put wallet selection at the very top of the path hierarchy instead of something else like an API version version number. |
src/wallet/rpcwallet.cpp
Outdated
} | ||
} | ||
// no wallet found with the given endpoint | ||
throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Requested wallet not found ("+SanitizeString(requestedWallet)+")"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In commit "Select wallet based on the given endpoint"
Maybe change error message to something like "Wallet file does not exist or is not loaded" to avoid implying that the wallet doesn't exist when it might just not be loaded.
Also, since this is a URI, sanitize call should be changed to allow %
character and probably all reserved and unreserved characters from RFC3986 ("%-_.~!*'();:@&=+$,/?#[]"
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In commit "Select wallet based on the given endpoint"
Previous comment not addressed (#10650 (comment))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thread #10650 (comment)
Error string cleanup not (yet) addressed.
My initial impression was "I thought we were going to put an RPC api version in the path" too. FWIW. I don't know what the norms are in this kind of rpc thing. Are there parallels in hosted wallet service APIs in the Bitcoin space already? |
Yes. Versioning makes probably sense at this point. I could think of The query string makes sense if we expect multiple non-hierarchical inputs. If we start to use a URI query-string == non-hierarchical inputs, where would be the main difference between the query-string key/value level and the JSON key/value level? I don't think there are huge advantages/disadvantages between query-string vs path. It's probably a design choice, matter of taste. |
I think for simplicity we should almost always prefer using JSON-RPC parameters for passing request options. If I were designing a bitcoin JSON-RPC API from scratch, I'd require a fixed, basic, future-proof request-uri like But in this case we're talking about updating an existing JSON-RPC API with dozens of commands that already have their own ways of interpreting JSON-RPC parameters and that all assume they'll be interacting with a single wallet. So for backwards compatability, I think it's a good idea to allow an out-of-band I think if you want to come up with complicated new URL schemes or authorization schemes to provide more structured and controlled access to the API, that could be great, but it goes beyond what you need for basic backward-compatible multiwallet support, which is a simple |
Another multiwallet RPC implemention for discussion: #10653 |
88ee520
to
1140b58
Compare
Added the /v1/ path element. @ryanofsky pointed out that we should urlencode/decode the walletID in the path element. The walletID should in future not be a representation of the filename. User should be able to manually choose the walletID. Ideally, we write the walletID into the wallet database. There we could only allow alphanumeric chars. |
What's this about? I think I missed this point...
I don't think I understand the advantages of adding a "walletID." The user already has to choose one name for the wallet when they make a filename, so what's the advantage of being able to choose two potentially different names for the same wallet? It seems like this would only add confusion. Also, even you only allow alphanumerics, unless you restrict to english, you still need percent encoding for unicode. |
If we would decouple the wallet completely, a specific endpoint could probably be the better approach to switch between processes or/and even implementations.
Yeah. Not sure if custom wallet names is a good idea. AFAIK most multiwallet application offer a custom wallet naming.
Indeed. API designUsing the HTTP request path makes sense to me. URLEncode/Decode would then be required if we use the path approach (not required if we embed the walletID in the JSON payload). Lets have a look at GitHub's API (I don't say its good just because it's GitHub): For me, that makes perfect sense. Same for HTTP posts: {
"title": "new title",
"body": "updated body",
"state": "open",
"base": "master"
} |
To summarize current state of things, we have 3 (nonexclusive) choices for allowing existing RPC methods work on multiple wallets:
|
Long term, here's one suggestion of how this could work:
I think that achieves the multi-user functionality from #10615 without the drawbacks:
Short-term, we can implement this PR, either with query options or a request-path scheme. When the wallet is separated, the interface doesn't have to change, since this is (3) from the design above. My preference is for a query option, since this seems more flexible and doesn't restrict the API scheme if we want to use a different request-path in future. If it's going to take time to get this PR merged, I think we should merge #10653 as a short-term measure, with warnings that the API will change. |
src/utilstrencodings.cpp
Outdated
@@ -701,3 +702,28 @@ bool ParseFixedPoint(const std::string &val, int decimals, int64_t *amount_out) | |||
return true; | |||
} | |||
|
|||
std::string urlEncode(const std::string &urlPart) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use evhttp_uriencode()
?
src/utilstrencodings.cpp
Outdated
return escaped.str(); | ||
} | ||
|
||
std::string urlDecode(const std::string &urlEncoded) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use evhttp_uridecode()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh! Very good point. Will do that.
@@ -235,7 +235,13 @@ UniValue CallRPC(const std::string& strMethod, const UniValue& params) | |||
assert(output_buffer); | |||
evbuffer_add(output_buffer, strRequest.data(), strRequest.size()); | |||
|
|||
int r = evhttp_make_request(evcon.get(), req.get(), EVHTTP_REQ_POST, "/"); | |||
// check if we should use a special wallet endpoint | |||
std::string endpoint = "/"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use evhttp_uri
and it's primitives?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess for the endpoint a plain std::string is okay.
src/wallet/wallet.h
Outdated
@@ -1122,6 +1124,8 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface | |||
caller must ensure the current wallet version is correct before calling | |||
this function). */ | |||
bool SetHDMasterKey(const CPubKey& key); | |||
|
|||
const std::string GetWalletID() { return walletID; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const.
35dab3f
to
386c299
Compare
Rebased and overhauled. What can be done outside – this already large – PR:
Passed travis now (see circular dependency fix 386c299 [ping @theuni]). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that /v1/node is never registered as an HTTP handler, so calls to v1/node fail.
src/wallet/rpcwallet.cpp
Outdated
@@ -43,7 +43,7 @@ CWallet *GetWalletForJSONRPCRequest(const JSONRPCRequest& request) | |||
} | |||
else if (request.URI.substr(0, WALLET_ENDPOINT_BASE.size()) == WALLET_ENDPOINT_BASE) { | |||
// wallet endpoint was used | |||
std::string requestedWallet = request.URI.substr(WALLET_ENDPOINT_BASE.size()); | |||
std::string requestedWallet = urlDecode(request.URI.substr(WALLET_ENDPOINT_BASE.size())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that "Add -wallet endpoint support to bitcoin-cli" doesnt build as urlDecode is undefined here (you need to swap commit ordering).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In commit "Add -wallet endpoint support to bitcoin-cli"
This change belongs in commit "Select wallet based on the given endpoint" not in this bitcoin-cli commit. Also the commit introducing url encode/decode functions needs to be pushed back earlier in the history like Matt said for this to compile.
Alternately you could consolidate some of the commits. I don't think having 12 commits in this PR accomplishes very much when many of the commits introduce functionality that isn't called anywhere and doesn't make sense without context from later commits.
src/utilstrencodings.cpp
Outdated
@@ -10,6 +10,7 @@ | |||
#include <cstdlib> | |||
#include <cstring> | |||
#include <errno.h> | |||
#include <iomanip> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wait, why?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh,. yes. This is a relict of a custom URIencode/decode implementation. Will remove.
@@ -226,6 +226,11 @@ static bool InitRPCAuthentication() | |||
return true; | |||
} | |||
|
|||
void RegisterJSONEndpoint(const std::string& endpoint, bool exactMatch) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems superfluous. Why not just either auto-register based on the endpoint in the commands table or just register everything explicitly in httprpc.cpp (its only 4 things).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In commit "Expose JSON endpoint registration"
I don't see the reasoning for this either... If you decide to keep this, maybe document the function with a comment to explain why the urls shouldn't be listed in a single place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a) I think we don't want an #ifdef ENABLE_WALLE
in httpserver.cpp
b) Using RegisterHTTPHandler(endpoint, exactMatch, HTTPReq_JSONRPC);
from the point where we can register based on RPC-tables endpoints would result in exposing RegisterHTTPHandler
and HTTPReq_JSONRPC
which seems unideal.
But the idea of register based on the tables endpoints makes sense, will implement but will also keep the RegisterJSONEndpoint
function.
src/rpc/blockchain.cpp
Outdated
{ "blockchain", "verifychain", &verifychain, true, {"checklevel","nblocks"} }, | ||
|
||
{ "blockchain", "preciousblock", &preciousblock, true, {"blockhash"} }, | ||
{ "blockchain", "/v1/node/", "getblockchaininfo", &getblockchaininfo, true, {} }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use some constant for this instead of duplicating the string everyhwere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most important changes requested:
- ~~~Make it an error to a request a wallet name if
vpwallets
is empty, instead of silently ignoring the requested wallet, #10650 (comment)~~~ - Make it an error not to explicitly specify wallet name when more than one wallet is loaded, #10650 (comment)
- ~~~Add documentation for new bitcoin-cli
-wallet
option, #10650 (comment), and for new URL scheme.~~~ - ~~~Fix broken commit history, or consolidate commits.~~~
@@ -226,6 +226,11 @@ static bool InitRPCAuthentication() | |||
return true; | |||
} | |||
|
|||
void RegisterJSONEndpoint(const std::string& endpoint, bool exactMatch) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In commit "Expose JSON endpoint registration"
I don't see the reasoning for this either... If you decide to keep this, maybe document the function with a comment to explain why the urls shouldn't be listed in a single place.
src/wallet/rpcwallet.cpp
Outdated
@@ -3044,3 +3048,11 @@ void RegisterWalletRPCCommands(CRPCTable &t) | |||
for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++) | |||
t.appendCommand(commands[vcidx].name, &commands[vcidx]); | |||
} | |||
|
|||
void RegisterWalletRPCEndpoints() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In commit "Register /wallet/* endpoint in RPC server"
Name MaybeRegisterWalletRPCEndpoints
might be more descriptive since nothing happens if wallet is disabled.
src/wallet/rpcwallet.cpp
Outdated
} | ||
} | ||
// no wallet found with the given endpoint | ||
throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Requested wallet not found ("+SanitizeString(requestedWallet)+")"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In commit "Select wallet based on the given endpoint"
Previous comment not addressed (#10650 (comment))
src/wallet/rpcwallet.cpp
Outdated
return vpwallets.empty() ? nullptr : vpwallets[0]; | ||
LOCK(cs_main); // for now, protect vpwallets via cs_main | ||
|
||
if (vpwallets.empty()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In commit "Select wallet based on the given endpoint"
This logic doesn't seem right because it will silently ignore the requestedWallet
value when vpwallets
is empty. It would be better to trigger the "Requested wallet not found" error if requestedWallet
is nonempty when vpwallets
is empty instead of silently ignoring the requestedWallet
value.
You could fix this by just deleting this early return and changing the last line to return vpwallets.empty() ? nullptr : vpwallets[0];
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thread #10650 (comment)
Silently ignoring invalid requestedWallet values not (yet) addressed.
src/wallet/rpcwallet.cpp
Outdated
} | ||
else { | ||
// default endpoint, use first wallet | ||
return vpwallets[0]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In commit "Select wallet based on the given endpoint"
Per @laanwj's comment at #10653 (comment), I think it would be a good idea for this to raise an error if vpwallets.size() is not 1. If somebody has loaded multiple wallets, they should have to explicitly specify which wallet they want to call. Otherwise, it's easy to imagine writing a script that makes many rpc calls, and forgets to add the -wallet=
option in one call, and suddenly winds up using funds or balances from the wrong wallet, causing dangerous or difficult to debug errors.
Having a default wallet is needed for backwards compatibility when there is 1 wallet, but otherwise it just seems like a dangerous and not very useful idea to want to support right now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thread #10650 (comment)
Unsafe fallthrough to default wallet not (yet) addressed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure about that one.
The GUI also has a default wallet. Wouldn't throwing an exception also not work when someone runs with -disablewallet
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thread #10650 (comment)
I think you could change this line to something like return vpwallets.size() == 1 ? vpwallets[0] : nullptr;
without bad side effects for the gui or disablewallet. This would also let you drop the early return for vpwallet.empty() above, which would make sure the requestedWallet
value is always properly validated (#10650 (comment)).
src/httpserver.cpp
Outdated
@@ -665,3 +665,14 @@ void UnregisterHTTPHandler(const std::string &prefix, bool exactMatch) | |||
} | |||
} | |||
|
|||
std::string urlDecode(const std::string &urlEncoded) { | |||
std::string res = ""; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In commit "Add urlencode/decode via libevent2"
More idiomatic / efficient to just use the default string constructor (write std::string res;
).
src/bitcoin-cli.cpp
Outdated
// always use v1 endpoint if -wallet has been provided | ||
endpoint = "/v1/wallet/"+urlEncode(GetArg("-wallet", "")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In commit "Add urlencode/decode via libevent2"
Something seems to have gone a little haywire in the rebased history. This commit seems to be inlining a nonexistent urlEncode
function, which I think would be a step backwards from having a reusable urlEncode
function that complements urlDecode
.
Also this commit has unrelated changes to src/utilstrencodings.cpp
and src/wallet/rpcwallet.cpp
which should be reverted.
method = self._service_name | ||
elements = self._service_name.split(".") | ||
if len(elements) > 1: | ||
urlAdd = "/v1/wallet/" + '.'.join(elements[:-1]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In commit "[QA] Add support for wallet endpoints in Authproxy"
It's fragile, over complicated, and not necessary to add multiwallet code and hardcoded url fragments in authproxy. I suggested a simpler approach here: #10650 (comment), adding a one-line __idiv__
method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thread #10650 (comment)
Test framework comment not (yet) addressed.
src/rpc/server.cpp
Outdated
@@ -493,7 +493,12 @@ UniValue CRPCTable::execute(const JSONRPCRequest &request) const | |||
const CRPCCommand *pcmd = tableRPC[request.strMethod]; | |||
if (!pcmd) | |||
throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Method not found"); | |||
|
|||
if (request.URI != "" && request.URI != "/") { | |||
// enforce correct endpoint usage |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In commit "Split node / wallet RPC calls based on the endpoint"
I don't see what this has to do with multiwallet support and I think this commit and previous commit should go into a separate PR if we want to introduce a more heavily embroidered URL scheme beyond /v1/wallet/filename
.
Also if there's going to be a JSON-RPC URL scheme, it needs to be documented and explained somewhere (maybe a new doc/JSONRPC-interface.md
file), and should probably get a some more discussion. Personally I don't see why people seem to be in love with convoluted URL schemes. We are not implementing a REST API which is centered around URLs. We are implementing a RPC API where you call a method, pass JSON arguments, and get a JSON return value. I don't think it's good to be adding restrictions and side-channels around the JSON requests without explicit reason.
Supporting a ?wallet=filename
query option or /wallet/filename
path selector makes a certain amount of sense if we think JSON-RPC named arguments are too awkward, or because we want to implement a multiprocess framework like the one John described where the RPC handler has "superuser access" to all wallets and forwards calls to them (#10650 (comment)).
Supporting /v1
, /v2
path selectors maybe also makes sense if we want to be able to make breaking changes to RPC methods. And maybe requiring node / wallet URL endpoints also makes sense for reasons that I don't understand, but I definitely think those reasons deserve to be discussed, and to get some documentation, and not to be shoehorned into the last 2 commits of a tangentially related PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the last weeks IRC meeting I had the feeling we had consensus about splitting the calls into node non node.
IMO the v1 approach (while still supporting / [v0]) gives us the chance to eventually clean up some APIs (accounts?!). Not sure if we are going to do this but at least we would have the chance.
This is why I think we should mark the V1 api as "experimental" and "unstable" which then allows us to remove/add things without caring to much about API compatibility.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thread #10650 (comment)
But why do those two commits need to be in this PR? All I am saying is move these to a separate PR. These changes aren't needed for multiwallet, add a bunch of boilerplate to the code and complexity to review, and introduce a bunch of rules and design details (even if marked "experimental") that have never been discussed anywhere and aren't documented at all.
We are right before the deadline for 0.15 features and trying to get some kind of multiwallet support in, which only requires support for /v1/wallet/filename
urls which the other 10 commits in your PR implement. The 2 commits which expand the URL scheme and change the way RPC calls are dispatched should be moved to a separate PR and not rushed in before 0.15 because they greatly increase complexity of the PR and are not needed for multiwallet RPC access.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They are in this PR because I thought we have decided to not support node command on /v1/wallet
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thread #10650 (comment)
They are in this PR because I thought we have decided to not support node command on /v1/wallet.
Interesting. So will wallet-optional methods like validateaddress and createmultisig then only work on vpwallet[0] and not other wallets?
In any case, I am trying to suggest that you can significantly simplify this PR and focus it to the task at hand (adding multiwallet RPC support) by not doing this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For what it's worth, I agree with @ryanofsky here. It'd be good to keep this PR to the minimal functionality needed for RPC multiwallet access. We can easily add /v1/node
later if people want it.
will wallet-optional methods like validateaddress and createmultisig...
Hopefully those will all be split into wallet/non-wallet methods by 0.16. See #10583, #10570, etc
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thread #10650 (comment)
I'm not going to keep objecting, but will just note that I'd still prefer to see this commit ("Split node / wallet RPC calls based on the endpoint") dropped from the PR because of the complexity it adds. (Also because of my general failure to understand why people seem to want to overlay a REST-style url scheme on top of an RPC-style API, #10650 (comment).)
src/wallet/rpcwallet.cpp
Outdated
@@ -2434,6 +2434,7 @@ UniValue getwalletinfo(const JSONRPCRequest& request) | |||
UniValue obj(UniValue::VOBJ); | |||
|
|||
size_t kpExternalSize = pwallet->KeypoolCountExternalKeys(); | |||
obj.push_back(Pair("walletid", pwallet->GetWalletID())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jonasschnelli build change looks fine. |
Concept ACK on this approach for multiwallet, but needs rebase and addressing of comments. |
There is no default wallet. There should be no functional difference between wallets when multiple wallets are loaded. |
@jnewbery ok let me rephrase: non-endpoint calls will still use that wallet(just like before). |
I think this PR started off bad and has gotten worse, and I'm writing another post to go into what I think the new problems are. But because I think there's a good chance that post will be ignored or disputed, I want to suggest 2 more changes that I think would make this PR less bad.
|
Huh? No. That's the point. We want to avoid RPCs defaulting to any wallet when they're called without endpoints. Having a 'default' wallet risks users accidentally calling a method on the wrong wallet. |
CWallet *GetWalletForJSONRPCRequest(const JSONRPCRequest& request) | ||
{ | ||
// TODO: Some way to access secondary wallets | ||
return vpwallets.empty() ? nullptr : vpwallets[0]; | ||
if (request.URI.substr(0, WALLET_ENDPOINT_BASE.size()) == WALLET_ENDPOINT_BASE) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (request.URI.compare(0, WALLET_ENDPOINT_BASE.size(), WALLET_ENDPOINT_BASE) == 0)
// no wallet found with the given endpoint | ||
throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Wallet file does not exist or is not loaded"); | ||
} | ||
else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The } else {
can be removed since the if
blocks always exists the function.
if (decoded) { | ||
res = std::string(decoded); | ||
free(decoded); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} else {
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess there is no need for an else, it will just return an empty string if URLencode failed which is fine IMO.
@@ -665,3 +669,14 @@ void UnregisterHTTPHandler(const std::string &prefix, bool exactMatch) | |||
} | |||
} | |||
|
|||
std::string urlDecode(const std::string &urlEncoded) { | |||
std::string res; | |||
if (!urlEncoded.empty()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
evhttp_uridecode
works with an empty string, as such this if
can be removed.
@@ -494,6 +498,13 @@ UniValue CRPCTable::execute(const JSONRPCRequest &request) const | |||
if (!pcmd) | |||
throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Method not found"); | |||
|
|||
// enfore endpoint only for non / and "" endpoint (the later is used by the GUI) | |||
if (request.URI != "" && request.URI != "/") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (!request.URI.empty() && ...)
@jnewbery My mistake, I probably iterated too fast through options. Can confirm that it gives |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
utACK 759dc37
Thanks for doing this!
I think @ryanofsky has some good points, but it's really not the fault of this PR, it's important to get this in for 0.15 and for better or worse we decided on this approach as a project.
Regarding his 2 suggested changes, I think both would be improvements but aren't particularly necessary if there is disagreement.
@@ -648,7 +648,11 @@ HTTPRequest::RequestMethod HTTPRequest::GetRequestMethod() | |||
void RegisterHTTPHandler(const std::string &prefix, bool exactMatch, const HTTPRequestHandler &handler) | |||
{ | |||
LogPrint(BCLog::HTTP, "Registering HTTP handler for %s (exactmatch %d)\n", prefix, exactMatch); | |||
pathHandlers.push_back(HTTPPathHandler(prefix, exactMatch, handler)); | |||
HTTPPathHandler pathHandler(prefix, exactMatch, handler); | |||
if (std::find_if(pathHandlers.begin(), pathHandlers.end(), [pathHandler](const HTTPPathHandler a){ return (a.prefix == pathHandler.prefix && a.exactMatch == pathHandler.exactMatch); }) == pathHandlers.end()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: should your lambda capture list and argument be references?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about something along these lines:
// only add handlers if they do not exists yet
for (auto handler : pathHandlers) {
if (handler.prefix == prefix && handler.exactMatch == exactMath) {
return;
}
}
pathHandlers.push_back(HTTPPathHandler(prefix, exactMatch, handler));
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another option is to implement HTTPPathHandler::operator==
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@promag: I think your solution would be a slower find algorithm (but doesn't matter). I had the ==
operator in an earlier version but @ryanofsky said (and I agreed) that this may be dangerous if we not check all of the instance variables (including the handler).
@morcos: Yes, Should be referenced.
This PR is looking worse than before. This is bad engineering and interface design, which creates usability problems with confusing errors and restrictions, and will lead to hacks, workarounds, and churn in the future. All of which is pointless because we have a much simpler alternative in master...ryanofsky:pr/multiparam which has none of these problems and is 100% compatible with endpoints if we want to add them later. Previously and with master...ryanofsky:pr/multiparam, to perform an operation you would call a RPC method on the server, pass it a set of JSON parameters, and get back a JSON result. This is a simple, straightforward paradigm familiar to anyone with any background in programming. Now, for no one's benefit, we want to overlay complexity and restrictions on top of this when we haven't actually figured out what the restrictions should be and are still discussing different possible workarounds for the usability problems this change causes. Instead of having method calls with simple parameters, we are now throwing a REST URL scheme into the mix (without adopting other REST principles, which don't make sense for a non-REST RPC interface to begin with), so instead of (method + params -> result) we now have (method + params + uri-path with some redundant information already derivable from method and params and incomplete and changing consistency requirements -> result). But this has been my objection all along, so here is why I now think this change is worse than before:
I'm maybe just failing to understand what is motivating this change. I think there is a boiling frog thing going on because this PR started off simpler without encoding, and node endpoints, and default wallet restrictions, and the |
Thanks @ryanofsky - I think those are all good points, and I'm sympathetic to them. At this point, I think we should go ahead with merging this PR. It's had lots of review, and already has 4 ACKs. We still have some freedom with the interface after this is merged. The feature is going to be marked as experimental, and anyone who uses multiwallet should be prepared for the interface to be tweaked as the feature becomes more mature. For example, in bitcoin.cli, we could just ignore any So in order to get something merged which is now very well reviewed and tested, we should go ahead with this PR and tidy up the rough edges later. |
Ok but these changes are never going to happen later if they don't get done before merging this PR: #10650 (comment), so please say something if there is a problem with |
|
I might have misunderstood what was meant by the But if "experimental" means that behavior of the endpoint is unstable and can change without concern for backward compatibility then it definitely should be marked |
@ryanofsky's commit looks so simple that I'm inclined at this point to go with that, and push the v1 API to a later version... |
It looks like this implementation is still too controversial to make it into 0.15, so removing the milestone (sorry, this is a very painful decision for me too). |
6b9faf7 [QA] add basic multiwallet test (Jonas Schnelli) 979d0b8 [tests] [wallet] Add wallet endpoint support to authproxy (John Newbery) 76603b1 Select wallet based on the given endpoint (Jonas Schnelli) 32c9710 Fix test_bitcoin circular dependency issue (Jonas Schnelli) 31e0720 Add wallet endpoint support to bitcoin-cli (-usewallet) (Jonas Schnelli) dd2185c Register wallet endpoint (Jonas Schnelli) Pull request description: Alternative for #10829 and #10650. It adds the most simplest form of wallet based endpoint support (`/wallet/<filename>`). No v1 and no node/wallet endpoint split. Tree-SHA512: 23de1fd2f9b48d94682928b582fb6909e16ca507c2ee19e1f989d5a4f3aa706194c4b1fe8854d1d79ba531b7092434239776cae1ae715ff536e829424f59f9be
Should be closed or rebased. One part of this PR which I think is good and didn't make it in with #10849 was addition of the RPC_WALLET_NOT_FOUND error code. |
Closing for now... seems no longer required (or at least controversial). |
6b9faf7 [QA] add basic multiwallet test (Jonas Schnelli) 979d0b8 [tests] [wallet] Add wallet endpoint support to authproxy (John Newbery) 76603b1 Select wallet based on the given endpoint (Jonas Schnelli) 32c9710 Fix test_bitcoin circular dependency issue (Jonas Schnelli) 31e0720 Add wallet endpoint support to bitcoin-cli (-usewallet) (Jonas Schnelli) dd2185c Register wallet endpoint (Jonas Schnelli) Pull request description: Alternative for bitcoin#10829 and bitcoin#10650. It adds the most simplest form of wallet based endpoint support (`/wallet/<filename>`). No v1 and no node/wallet endpoint split. Tree-SHA512: 23de1fd2f9b48d94682928b582fb6909e16ca507c2ee19e1f989d5a4f3aa706194c4b1fe8854d1d79ba531b7092434239776cae1ae715ff536e829424f59f9be
6b9faf7 [QA] add basic multiwallet test (Jonas Schnelli) 979d0b8 [tests] [wallet] Add wallet endpoint support to authproxy (John Newbery) 76603b1 Select wallet based on the given endpoint (Jonas Schnelli) 32c9710 Fix test_bitcoin circular dependency issue (Jonas Schnelli) 31e0720 Add wallet endpoint support to bitcoin-cli (-usewallet) (Jonas Schnelli) dd2185c Register wallet endpoint (Jonas Schnelli) Pull request description: Alternative for bitcoin#10829 and bitcoin#10650. It adds the most simplest form of wallet based endpoint support (`/wallet/<filename>`). No v1 and no node/wallet endpoint split. Tree-SHA512: 23de1fd2f9b48d94682928b582fb6909e16ca507c2ee19e1f989d5a4f3aa706194c4b1fe8854d1d79ba531b7092434239776cae1ae715ff536e829424f59f9be
6b9faf7 [QA] add basic multiwallet test (Jonas Schnelli) 979d0b8 [tests] [wallet] Add wallet endpoint support to authproxy (John Newbery) 76603b1 Select wallet based on the given endpoint (Jonas Schnelli) 32c9710 Fix test_bitcoin circular dependency issue (Jonas Schnelli) 31e0720 Add wallet endpoint support to bitcoin-cli (-usewallet) (Jonas Schnelli) dd2185c Register wallet endpoint (Jonas Schnelli) Pull request description: Alternative for bitcoin#10829 and bitcoin#10650. It adds the most simplest form of wallet based endpoint support (`/wallet/<filename>`). No v1 and no node/wallet endpoint split. Tree-SHA512: 23de1fd2f9b48d94682928b582fb6909e16ca507c2ee19e1f989d5a4f3aa706194c4b1fe8854d1d79ba531b7092434239776cae1ae715ff536e829424f59f9be
This adds a
/wallet/*
endpoint to the RPC server (optional).The wallet RPC functions will try to find the selected wallet by comparing the given endpoint with all available wallets/
walletID
s.The
walletID
is currently defined by the filename (-wallet=<filename>
), ideally, we later add a wallet RPC call to set the walletID (should be written to the wallet database).This also includes endpoint support for
bitcoin-cli
.There is a new argument
-wallet=<walletID>
. If set, the given walletID will be used to call the endpoint with a scheme of/wallet/< walletID >
.QA's
authproxy
is also extended to allow calls likeself.nodes[0].<walletID>.<method>
.There is finally also a basic multiwallet RPC test.
Contains #10649.