Skip to content

Conversation

jonasschnelli
Copy link
Contributor

Would fix #18715.
See discussion in #18453.

Currently, batching RPC requests across wallets does not work due to the fact that each wallet has its own endpoint.

This PR allows using a simple wildcard approach for the wallet endpoint (/wallet/*/).
Using bitcoin-cli -rpcwallet="*" or -rpcwallet="pattern* works with this PR.

This PR is a simple approach that allows detailed control on a per call basis. Some calls probably need special logic (rescanblockchain). Some calls make not much sense across wallets (dumpwallet or backupwallet as example).

The output (or error/s) will be bundles as json array with a dictionary for each wallet:

[
  {
    "walletname": "test",
    "result": 0.00000000
  },
  {
    "walletname": "wallet",
    "result": 0.00000000
  },
  {
    "walletname": "test2",
    "result": 0.00000000
  }
]

Alternatives:

  • implementing a generic approach on our httpserver/rpcserver layer (complicates layering)
  • try to add it to the CRPCTable (overhead for non wallet calls)
  • a dedicated "foreachwallet" RPC meta call

Todo:

  • tests
  • documentation

Copy link
Contributor

@promag promag left a comment

Choose a reason for hiding this comment

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

I still don't get what are the use cases beside the obvious getbalances. Not sure about mutation calls, like send.

Does it make sense to have one unified view in qt instead? That view could show all loaded wallets + their balances, flags etc.

@@ -355,6 +411,10 @@ static CTransactionRef SendMoney(interfaces::Chain::Lock& locked_chain, CWallet

static UniValue sendtoaddress(const JSONRPCRequest& request)
{
if (IsMultiwalletJSONRPCRequest(request)) {
return ExecForeachWalletJSONRPCRequest(request, &sendtoaddress);
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we want to incur in double payments?

@maflcko
Copy link
Member

maflcko commented Apr 22, 2020

I think @promag has a good point. Apart from get*, list* and keypoolrefill there is not really a use case for this endpoint.

@jonasschnelli
Copy link
Contributor Author

I think @promag has a good point. Apart from get*, list* and keypoolrefill there is not really a use case for this endpoint.

Fair point. I think this is why I would favour this approach which can be implemented per call. I guess there are some edge cases for other calls like set* (like settxfee), import*. But even only supporting the list* and get* calls,... it would be around half of the wallet calls.

@maflcko
Copy link
Member

maflcko commented Apr 22, 2020

Instead of the not-quite-regex-but-still-a-pattern I'd prefer the caller to explicitly list the wallets. I think requiring that the user calls listtransactions once to get the wallet names is not too much to ask.

Also, the returned array could be a json dict with keys being the wallet name. That is easier to parse at the client.

@jonasschnelli
Copy link
Contributor Author

jonasschnelli commented Apr 22, 2020

Instead of the not-quite-regex-but-still-a-pattern I'd prefer the caller to explicitly list the wallets. I think requiring that the user calls listtransactions once to get the wallet names is not too much to ask.

Probably meh to not support a pure "*" (all wallets). Maybe "*" or a cs-list of wallets?

Also, the returned array could be a json dict with keys being the wallet name. That is easier to parse at the client.

Good point. A downside could be the identifiability of an error (you already don't get the http response code).

@DrahtBot
Copy link
Contributor

DrahtBot commented Apr 22, 2020

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Conflicts

Reviewers, this pull request conflicts with the following ones:

If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

@jb55
Copy link
Contributor

jb55 commented Apr 22, 2020

@promag:

I still don't get what are the use cases beside the obvious getbalances.

listing transactions, coins. This what I currently do in scripts like btc-coins, but the round-trips are a bit slow with many wallets

Not sure about mutation calls, like send.

good point, that seems like a footgun. seems like this is more useful for not-mutating calls

Does it make sense to have one unified view in qt instead? That view could show all loaded wallets + their balances, flags etc.

I think that would be useful, but runs into similar issues when creating transactions where you probably want to know what wallet you're creating from. So maybe it would make sense to limit it to balances and maybe transactions in a unified qt view? The logic for this could be tricky.

@MarcoFalke:

Instead of the not-quite-regex-but-still-a-pattern I'd prefer the caller to explicitly list the wallets. I think requiring that the user calls listtransactions once to get the wallet names is not too much to ask.

👍

Also, the returned array could be a json dict with keys being the wallet name. That is easier to parse at the client.

👍

Comment on lines +95 to +97
if (IsMultiwalletJSONRPCRequest(request)) {
return ExecForeachWalletJSONRPCRequest(request, &importprivkey);
}
Copy link
Member

Choose a reason for hiding this comment

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

Can we avoid adding this to every wallet function?

Comment on lines +703 to +705
if (IsMultiwalletJSONRPCRequest(request)) {
return ExecForeachWalletJSONRPCRequest(request, &dumpprivkey);
}
Copy link
Member

Choose a reason for hiding this comment

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

Something like dumpprivkey should probably just return the first result it gets (and ignore all the failures)?

@luke-jr
Copy link
Member

luke-jr commented Apr 24, 2020

Any reason not to return

{
    "walletname": result,
    ...
}
```?

Copy link
Member

@jonatack jonatack left a comment

Choose a reason for hiding this comment

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

Tested Concept ACK 57b449d rebased on current master

  • I really like the rpcwallet=* wildcard feature; it would be my default use as a human user.

  • The ability to specify multiple wallets with rpcwallet= would be good also, especially for RPC clients/scripts, as a logical follow-up.

  • Agree with the idea of initially adding this only for get*, list*, and maybe keypoolrefill calls.

  • Agree with the suggestions to try returning this as a JSON dict of objects with wallet names as keys.

UniValue ExecForeachWalletJSONRPCRequest(JSONRPCRequest mutable_request, const rpcfn_type& func) {
std::string search_string = URL_DECODE(mutable_request.URI.substr(WALLET_ENDPOINT_BASE.size()));
const size_t pos = search_string.find('*');
assert(pos != std::string::npos); // we must have checked earlier for an asterisk
Copy link
Member

Choose a reason for hiding this comment

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

Needed to appease test/lint/lint-assertions.sh

-    assert(pos != std::string::npos); // we must have checked earlier for an asterisk
+    CHECK_NONFATAL(pos != std::string::npos); // we must have checked earlier for an asterisk

@fanquake
Copy link
Member

fanquake commented Jun 9, 2021

Going to close this as "Up for Grabs" for now.

@fanquake fanquake closed this Jun 9, 2021
@bitcoin bitcoin locked as resolved and limited conversation to collaborators Aug 18, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Allow JSON RPC "batches" for multiwallet
8 participants