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

RPC: allow to track coins by parent descriptors #25504

Merged
merged 5 commits into from Aug 16, 2022

Conversation

darosior
Copy link
Member

Wallet descriptors are useful for applications using the Bitcoin Core wallet as a backend for tracking coins, as they allow to track coins for multiple descriptors in a single wallet. However there is no information currently given for such applications to link a coin with an imported descriptor, severely limiting the possibilities for such applications of using multiple descriptors in a single wallet. This PR outputs the matching imported descriptor(s) for a given received coin in listsinceblock (and friends).

It comes from a need for an application i'm working on, but i think it's something any software using bitcoind to track multiple descriptors in a single wallet would have eventually. For instance i'm thinking about the BDK project. Currently, the way to achieve this is to import raw addresses with labels and to have your application be responsible for wallet things like the gap limit.

I'll add this to the output of listunspent too if this gets a few Concept ACKs.

{
std::vector<WalletDescriptor> descs;
for (const auto spk_man: GetScriptPubKeyMans(script)) {
if (const auto desc_spk_man = dynamic_cast<DescriptorScriptPubKeyMan*>(spk_man)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can this cast be avoided, or a static cast used instead?

Copy link
Member Author

Choose a reason for hiding this comment

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

The point here is to filter only descriptor spkmans, which can only be done dynamically.

src/wallet/receive.cpp Outdated Show resolved Hide resolved
Copy link
Contributor

@w0xlt w0xlt left a comment

Choose a reason for hiding this comment

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

Approach ACK

src/wallet/wallet.cpp Show resolved Hide resolved
@DrahtBot
Copy link
Contributor

DrahtBot commented Jun 30, 2022

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

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #25722 (refactor: Use util::Result class for wallet loading by ryanofsky)
  • #25158 (rpc, wallet: add abandoned field for all categories of transaction in ListTransaction by brunoerg)
  • #20892 (tests: Run both descriptor and legacy tests within a single test invocation by achow101)

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.

@kristapsk
Copy link
Contributor

Concept ACK. JoinMarket currently imports each address into watchonly manually and assigns same label (JM wallet id) to them. So this is needed for JoinMarket to support ranged descriptors in future instead.

@achow101
Copy link
Member

To be clear, this affects more than just imported descriptors. It outputs the parent descriptor for each scriptPubKey, and so it will as well for the automatically generated descriptors.

src/wallet/rpc/transactions.cpp Outdated Show resolved Hide resolved
src/wallet/rpc/transactions.cpp Outdated Show resolved Hide resolved
src/wallet/rpc/transactions.cpp Outdated Show resolved Hide resolved
@jb55
Copy link
Contributor

jb55 commented Jun 30, 2022

Concept ACK

@darosior
Copy link
Member Author

darosior commented Jul 5, 2022

Good to see others have an interest in this. :)
I've addressed the review comments and added the same field to the listunspent entries.

Note however that there is another barrier to tracking coins by descriptor on the RPC interface, if you want to track multiple descriptors on the same wallet. listsinceblock will not add entries for outputs it considers being change, so if you have two descriptors A and B imported on your wallet and you send a coin from A to B it will not be shown as a deposit for B. I think the only reasonable workaround would be to have yet another argument to listsinceblock instructing to not treat change outputs differently (like listunspent does).
Ideas welcome, but i'd like to tackle this other issue in another PR.

@darosior darosior force-pushed the rpc_track_coins_by_descriptor branch from 8976cd8 to 053f19a Compare July 5, 2022 13:26
Copy link
Member

@furszy furszy left a comment

Choose a reason for hiding this comment

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

As ListTransactions receives the wtx, instead of adding a copy of the scriptPubKey inside COutputEntry to later use it to call PushWalletDescriptors(wallet, r.script_pubkey, entry), what if you just get the output directly?

In other words, drop 9bfd9a9 and change d8b1e37 (line 398) in this way:

PushWalletDescriptors(wallet, wtx.tx->vout.at(r.vout).scriptPubKey, entry);

@darosior
Copy link
Member Author

darosior commented Jul 6, 2022

Heh, indeed it's much cleaner. Thanks, done.

(Force pushed again to re-kick CI which was blocked on "Agent is not responding!")

src/wallet/rpc/util.cpp Outdated Show resolved Hide resolved
@darosior darosior force-pushed the rpc_track_coins_by_descriptor branch from 83b8bdd to 3447b80 Compare July 15, 2022 09:16
We currently expose a method to get the signing providers, which allows
to infer a descriptor from the scriptPubKey. But in order to identify
"on" what descriptor a coin was received, we need access to the
descriptors that were imported to the wallet.
@darosior darosior force-pushed the rpc_track_coins_by_descriptor branch from 3447b80 to dc148f3 Compare July 15, 2022 11:06
@darosior
Copy link
Member Author

I added a new commit to make listsinceblock optionally list change outputs as well, and added release notes for this PR. Also rebased due to conflict between the new commit and master.

@darosior darosior changed the title RPC: allow to track coins by imported descriptors RPC: allow to track coins by parent descriptors Jul 15, 2022
@darosior darosior force-pushed the rpc_track_coins_by_descriptor branch from dc148f3 to 02cc302 Compare July 15, 2022 15:52
src/wallet/rpc/transactions.cpp Outdated Show resolved Hide resolved
src/wallet/receive.cpp Outdated Show resolved Hide resolved
The descriptor wallets allow an application to track coins of multiple
descriptors in a single wallet. However, such an application would not
previously be able to (easily) tell what received coin "belongs" to what
descriptor.

This commit tackles this issues by adding a "wallet_desc" entry to the
entries for received coins in 'listsinceblock'.
@darosior
Copy link
Member Author

Friendly ping to reviewers, as i'd love to have this in the next release. :)

@maflcko maflcko added this to the 24.0 milestone Aug 10, 2022
@achow101
Copy link
Member

ACK 9e78836

self.generate(self.nodes[2], 1)
coins = self.nodes[2].listsinceblock()["transactions"]
assert_equal(len([c for c in coins if c["address"] == addr]), 0)
coins = self.nodes[2].listsinceblock(include_change=True)["transactions"]
Copy link
Member

Choose a reason for hiding this comment

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

would this have two entries since you have the send(to change), and the normal change output as well?

I think the test should be explicit about it if so

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes. What would you suggest? Just asserting there are two entries?

Copy link
Member

Choose a reason for hiding this comment

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

Extract both outputs, assert they are change addresses, I suspect is the best.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done. This needed a couple re-arrangements since listsinceblock lists also the "sent" coins, and we only want coins since the last block.

I couldn't resist a few drive-by cosmetic changes to the tests, including removing the unneeded block generation.

doc/release-notes-25504.md Outdated Show resolved Hide resolved
It's useful for an external application tracking coins to not be limited
by our change detection. For instance, for a watchonly wallet with two
descriptors a transaction from one to the other would be considered a
change output and not be included in the result (if the address was not
generated by this wallet).
@darosior darosior force-pushed the rpc_track_coins_by_descriptor branch from 9e78836 to a6b0c1f Compare August 16, 2022 16:41
@instagibbs
Copy link
Member

ACK a6b0c1f

@achow101
Copy link
Member

re-ACK a6b0c1f

@achow101 achow101 merged commit c336f81 into bitcoin:master Aug 16, 2022
@darosior darosior deleted the rpc_track_coins_by_descriptor branch August 16, 2022 18:04
sidhujag pushed a commit to syscoin/syscoin that referenced this pull request Aug 16, 2022
@@ -623,6 +632,7 @@ RPCHelpMan listsinceblock()
}

bool include_removed = (request.params[3].isNull() || request.params[3].get_bool());
bool include_change = (!request.params[4].isNull() && request.params[4].get_bool());
Copy link
Member

Choose a reason for hiding this comment

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

I find it ugly to encode default argument values in the code syntax, as opposed to simply name them with a literal. If the default were to change, one would have to change the code syntax as opposed to simply changing a literal.

const bool include_change{p.isNull()?false:p.get_bool()}; // should be clearer now and in the future if the default was changed or refactored out

@S3RK
Copy link
Contributor

S3RK commented Aug 23, 2022

listsinceblock will not add entries for outputs it considers being change, so if you have two descriptors A and B imported on your wallet and you send a coin from A to B it will not be shown as a deposit for B.

@darosior I thought I fixed that in #22929. The test you added also specifically generates change address and not receiving address. Could you elaborate on the use-case here since it differs from the test? listchange parameter still could be useful, but I'd like to understand when and if deposits are not listed correctly.

@darosior
Copy link
Member Author

Sure, the use case is using Bitcoin Core to track coins for multiple descriptors, and any descriptor. There exists a number of applications doing that (and in my opinion, there should be more :p).

#22929 was a good fix for users of the wallet as such, but it was limited to a single active descriptor per output type. It's not a viable solutions for applications that want "just an index of transactions per descriptors". Such an application should not care about the wallet internals: whether the descriptor is active or not, the spkman internal or not, the address in the book or not. No, they just want to know the transactions related to a set of descriptor in a given block range. We can provide this information, and that's what this PR is permitting.

Regarding the test, i generated a change address to simulate an address that was externally generated (not in the address book), and not necessarily from a descriptor that was imported as "active" in the watchonly wallet (therefore may be considered internal, ie change).

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.

None yet