8000 wallet: Migrate legacy wallets to descriptor wallets by achow101 · Pull Request #19602 · bitcoin/bitcoin · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

wallet: Migrate legacy wallets to descriptor wallets #19602

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

Merged
merged 11 commits into from
Sep 1, 2022

Conversation

achow101
Copy link
Member
@achow101 achow101 commented Jul 27, 2020

This PR adds a new migratewallet RPC which migrates a legacy wallet to a descriptor wallet. Migrated wallets will need a new backup. If a wallet has watchonly stuff in it, a new watchonly descriptor wallet will be created containing those watchonly things. The related transactions, labels, and descriptors for those watchonly things will be removed from the original wallet. Migrated wallets will not have any of the legacy things be available for fetching from getnewaddress or getrawchangeaddress. Wallets that have private keys enabled will have newly generated descriptors. Wallets with private keys disabled will not have any active ScriptPubKeyMans.

For the basic HD wallet case of just generated keys, in addition to the standard descriptor wallet descriptors using the master key derived from the pre-existing hd seed, the migration will also create 3 descriptors for each HD chain in: a ranged combo external, a ranged combo internal, and a single key combo for the seed (the seed is a valid key that we can receive coins at!). The migrated wallet will then have newly generated descriptors as the active ScriptPubKeyMans. This is equivalent to creating a new descriptor wallet and importing the 3 descriptors for each HD chain. For wallets containing non-HD keys, each key will have its own combo descriptor.

There are also tests.

@DrahtBot
Copy link
Contributor
DrahtBot commented Jul 27, 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:

  • #25957 (wallet: fast rescan with BIP157 block filters for descriptor wallets by theStack)
  • #25907 (wallet: rpc to add automatically generated descriptors by achow101)
  • #25297 (wallet: group independent db writes on single batched db transaction by furszy)
  • #23417 (wallet, spkm: Move key management from DescriptorScriptPubKeyMan to wallet level KeyManager by achow101)
  • #22838 (descriptors: Be able to specify change and receiving in a single descriptor string by achow101)
  • #22341 (rpc: add getxpub by Sjors)
  • #20205 (wallet: Properly support a wallet id 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.

@hebasto
Copy link
Member
hebasto commented Jul 29, 2020

@achow101
It seems this PR has problems with linters.

@achow101 achow101 force-pushed the descriptor-wallet-migration branch 3 times, most recently from 5bb71b2 to ac5549a Compare July 29, 2020 18:48
@luke-jr
Copy link
Member
luke-jr commented Jul 31, 2020

Why doesn't this extend upgradewallet?

@achow101
Copy link
Member Author

Why doesn't this extend upgradewallet?

It didn't feel like it fit upgradewallet as I see that more for changing the wallet version number and using legacy wallet features. But this is changing a legacy wallet to something completely new.

This is also pretty invasive and fundamentally changes how the wallet is working, so I wanted to keep it separate from something that people might still want to use on legacy wallets.

@laanwj
Copy link
Member
laanwj commented Aug 12, 2020

I think we should only add this functionality after sqlite wallets? Otherwise you'd keep migrating.

@achow101
Copy link
Member Author

I think we should only add this functionality after sqlite wallets? Otherwise you'd keep migrating.

I think there's a question of whether we want to keep sqlite separate from descriptors. We might want to allow legacy wallets to migrate to sqlite without migrating to descriptors since they are orthogonal.

Copy link
Member
@maflcko maflcko left a comment

Choose a reason for hiding this comment

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

Left a style-nit. Feel free to ignore.

@meshcollider
Copy link
Contributor

Do you know what the performance of a wallet would be like, in the case of a non-HD wallet being migrated?

@achow101
Copy link
Member Author

Do you know what the performance of a wallet would be like, in the case of a non-HD wallet being migrated?

Probably worse.

@meshcollider
Copy link
Contributor

Probably worse.

Almost certainly, that's why I'm asking :) It would be nice to know how much worse it would be

Copy link
Member
@maflcko maflcko left a comment

Choose a reason for hiding this comment

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

I think it is expected that you run into deadlocks, given that the thread sanitizer doesn't pass CI.

Also, the Windows compilation fails, see u8string suggestion.

Copy link
Member
@Sjors Sjors left a comment

Choose a reason for hiding this comment

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

As you said:

Multisigs are special because we have addmultisigaddress which add the multisig but does not watch them.

The test could use some comments around that behavior. It correctly demonstrates it though.


addr = ms_info["address"]
txid = default.sendtoaddress(addr, 10)
multisig1.importaddress(addr)
Copy link
Member
@Sjors Sjors Aug 29, 2022

Choose a reason for hidin 8000 g this comment

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

fb5e095: why do you need to call importaddress after addmultisigaddress? Is that makes it marked as watch-only (as oppose to the second address)? Why?

(update: see above, but I still don't understand why)

Copy link
Member Author

Choose a reason for hiding this comment

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

So that this particular address is being watched and will end up in the _watchonly wallet.

@Sjors
67E6 Copy link
Member
Sjors commented Aug 29, 2022

I was confused about the difference between solvable and watch-only.

  • solvable: anything we have any (public) keys for and we know who to spend (e.g. a multisig for which we have one public key, and we know the other two because we have the full redeemscript)
  • watch-only: may or may not be solvable, e.g. an unsolvable multisig is one for which we only know the address (scriptPubKey), not the full script, i.e. we don't know which keys it needs

A solvable address is not automatically watch-only, but all import methods except importmulti ensure it is.

importmulti adds a redeemscript so it's solvable, but it's not marked watch-only. The net-effect of that is that we see the full script with getaddressinfo, the wallet ignores transactions to it, but we can sign them. This may be useful if you're a co-signer and you don't want those transactions to show up.

@Sjors
Copy link
Member
Sjors commented Aug 29, 2022

I also noticed the label given by addmultisigaddress is not migrated to the solvables wallet. In fact it stays behind, as seen by getaddressinfo. The other info does move.

We should consider making the solvables wallet legacy for now. Because right now it's just another watch-only wallet, except without transaction history. But future transaction will show up and rescan will reveal the old ones. This particular use case of having scripts but not seeing their transactions, seems a legacy feature we can't migrate yet. And in that case, we might as well move or copy corresponding private keys over too. Unless I totally misunderstand this multisig use case, you need to keep those around.

@achow101 achow101 force-pushed the descriptor-wallet-migration branch 2 times, most recently from 11f19f9 to a2278c2 Compare August 29, 2022 16:33
@achow101
Copy link
Member Author

The issues with locks and hanging should be resolved now. I've changed it to just unload a wallet before migrating, then loading it but without connecting all of the signals, etc. for the migration itself. This should fix the issues with locks and hanging (which was caused by waiting for the shared_ptr to be released by the GUI).

We should consider making the solvables wallet legacy for now.

I don't think we should. The point is to make it so that migrated wallets do not have any dependency on legacy stuff at all so that we can remove the legacy wallet. I agree that it can be confusing, but I don't think it is helpful or less confusing to make a new legacy wallet when the user is expecting everything to go to descriptors.

@Sjors
Copy link
Member
Sjors commented Aug 29, 2022

The problem is that the solvables wallet is completely useless. It can't sign things the legacy wallet could sign. You might as well put these things in the watch-only wallet, unless I'm missing something.

Another approach could be to handle the special (and only?) case of a multisig wallet more gracefully. The best substitute we can make for how that worked before, is to create a blank descriptor wallet and then import the (inferred) descriptor with the private keys that we have. They'll lose the "feature" of not seeing transactions, but they'll retain the ability to sign, which is more important.


The locking issue seems resolved. It's also no longer freezing the UI while the migration is in progress. Labels

It's quite a slow call (several minutes), so a nice followup improvement would be a way to abort it and maybe a progress bar. It's a one-off task, but probably one that makes users very nervous.

Notice one glitch: if you call this from GUI console, since it closes the wallet, you'll suddenly see a different wallet selected in the console, even though the command is still in progress. That's confusing, though maybe it's enough for now to just to warn the user to ignore that. It also crashes if you touch the dropdown though.

@achow101
Copy link
Member Author

The problem is that the solvables wallet is completely useless. It can't sign things the legacy wallet could sign. You might as well put these things in the watch-only wallet, unless I'm missing something.

The solvables wallet can update a PSBT with the UTXO, scripts, and pubkeys, for inputs that spend the any of the scripts it contains. Because the user had not watched those scripts when the wallet is a legacy wallet, it does not make sense to have them be in the watchonly wallet.

@Sjors
Copy link
Member
Sjors commented Aug 29, 2022

The slowness seems to be macOS thing, or even particular to one of my machines. The exact same wallet takes ~9 minutes to upgrade on one on my macOS machines, and less than a minute on the other and on Ubuntu. When the upgrade is fast, the labels are preserved fine too.


It would be useful to know how people use(d) legacy multisig wallets, especially before PSBT. It's not clear to me with which wallet they would craft the transaction.

A solveable-only legacy wallet can sign them and add more metadata, but because it lacks transaction history, they can't craft them. Presumably the user would have different wallet for that purpose, which they may or may not be upgrading separately. As long as that other wallet can produce a PSBT, then our upgraded main wallet can sign it just fine. The _solvable wallet's only purpose then is to backup the redeemscript. They should never need it.

If their other wallet can't produce a psbt, then the upgrade process breaks their ability to sign, since the private keys are now no longer in the same wallet as the redeemscript. So we should warn about that. And we could a utility that takes a raw unsigned transaction and transform it into a PSBT to address that potential problem.

Knowing these use cases would also help us write upgrade documentation for them, to explain what new workflows they should consider.

}

std::optional<MigrationData> res = legacy_spkm->MigrateToDescriptor();
if (res == std::nullopt) {
Copy link
Member
@furszy furszy Aug 29, 2022

Choose a reason for hiding this comment

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

You're assuming here that the reason std::nullopt is returned is that the wallet is locked, rather than some other failure.

The RPC handler is ensuring that the wallet is unlocked (third line there). This check is redundant for now, and might be useful when a migration button is implemented on the GUI.

achow101 and others added 6 commits August 29, 2022 17:30
They don't have any private data and they can't be nested so they
should return false for ToPrivateString.
Co-Authored-By: furszy <matiasfurszyfer@protonmail.com>
@Sjors
Copy link
Member
Sjors commented Aug 31, 2022

tACK 53e7ed0

I think this is good enough for an experimental RPC. Hopefully we can figure out why some macOS machines have difficulty with the RPC, but if not, we can just mention that in the release notes. Similarly we can revisit the way solvable multisig is handled; anyone using that feature will know how to restore the backup.

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.

reACK 53e7ed0

@achow101 achow101 merged commit 7921026 into bitcoin:master Sep 1, 2022
sidhujag pushed a commit to syscoin/syscoin that referenced this pull request Sep 1, 2022
hebasto added a commit to bitcoin-core/gui that referenced this pull request Sep 14, 2022
6725030 qt: Update translation source file for string freeze (round 2) (Hennadii Stepanov)

Pull request description:

  On the day of [translation string freeze](bitcoin/bitcoin#24987 (comment)), it happened that #660 did not include new strings from bitcoin/bitcoin#19602.

  This PR includes all recent updates.

  As a Transifex translator, I believe it is enough time for all translators to handle a few new strings by a release date. Also a Transifex check failure has been [fixed](/pull/664).

ACKs for top commit:
  jarolrod:
    ACK 6725030

Tree-SHA512: d57b841e87e389d31ec4ae9067b83f7f209e168399bc088c3234c2c66b34772739cb801f04b5038d55de115083d022d603bc976374bfd537b8ea10c10a545183
for (const auto& addr_pair : m_address_book) {
// Labels applied to receiving addresses should go based on IsMine
if (addr_pair.second.purpose == "receive") {
if (!IsMine(addr_pair.first)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

In commit "Implement MigrateLegacyToDescriptor" (0bf7b38)

It seems like this should say IsMine not !IsMine. Also I'm not sure why addr_pair.second.purpose == "receive" check is necessary. It seems like just checking IsMine should be enough, and it would be less robust to rely on purpose field being present.

Copy link
Member
@furszy furszy Mar 13, 2023

Choose a reason for hiding this comment

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

It's because the migration process divides data between different wallets.
It first deletes and unloads the legacy spkm from the main wallet (check the beginning of the function), and setup new descriptors. Then moves the data to two possible wallets; a solvable and a watch-only wallet.

So the !IsMine means that the record in the addressbook is from the legacy spkm (not loaded anymore), so it needs migration to the new wallets.

It also took me a while to get it while was rewriting it for #26836.
(sorry for the double PR mention, saw late your response in the other PR)

@bitcoin bitcoin locked and limited conversation to collaborators Mar 12, 2024
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.

0