Support For Arbitrary Number (1+) of Keychains - #318
Conversation
7059451 to
0df1a2a
Compare
|
The next few commits:
|
524d4df to
6c50f00
Compare
There was a problem hiding this comment.
Quick review on 6c50f00: given your commit message I thought you were adding the Wallet::new constructor here, but the commit only contains the addition of the KeyRing to the Wallet type. Mind you this might be enough for this commit. Feel free to add a new commit with the constructor, or add it to this commit; whatever you think works best.
Also note that fa984e6 and 6c50f00 don't compile because of
impl AsRef<bdk_chain::tx_graph::TxGraph<ConfirmationBlockTime>> for Wallet {
fn as_ref(&self) -> &bdk_chain::tx_graph::TxGraph<ConfirmationBlockTime> {
self.keychain_tx_graph.graph()
}
}on line 2638 of wallet/mod.rs. You could comment it out with the rest to ensure everything builds.
| pub keyring: keyring::changeset::ChangeSet<K>, | ||
| /// Changes to the [`LocalChain`](local_chain::LocalChain). | ||
| pub local_chain: local_chain::ChangeSet, | ||
| pub chain: local_chain::ChangeSet, |
There was a problem hiding this comment.
I'm not sure if the renaming of this field is intentional and I don't have an opinion on whether it's a good idea or not yet, but it probably belongs in a different PR.
There was a problem hiding this comment.
I did the rename since the corresponding field in Wallet is so. reverted the rename in 588121b.
7c976e1 to
588121b
Compare
|
I am so sorry 😢 . I completely messed dividing the commits into two. Things should be fixed now! |
|
This one now needs a big ol' rebase @110CodingP. |
dfdcd83 to
4a3486e
Compare
|
Wow! this one was HUGE! For the first few commits I did something like |
|
Adding an example like this fails: // Simple KeyRing, allowing us to build a standard 2-descriptor wallet.
let external_descriptor: &str = "tr(tpubD6NzVbkrYhZ4WyC5VZLuSJQ14uwfUbus7oAFurAFkZA5N3groeQqtW65m8pG1TT1arPpfWu9RbBsc5rSBncrX2d84BAwJJHQfaRjnMCQwuT/86h/1h/0h/0/*)";
let internal_descriptor: &str = "tr(tpubD6NzVbkrYhZ4WyC5VZLuSJQ14uwfUbus7oAFurAFkZA5N3groeQqtW65m8pG1TT1arPpfWu9RbBsc5rSBncrX2d84BAwJJHQfaRjnMCQwuT/86h/1h/0h/1/*)";
let mut keyring: KeyRing<KeychainKind> = KeyRing::new(Network::Regtest, KeychainKind::External, external_descriptor);
keyring.add_descriptor(KeychainKind::Internal, internal_descriptor, false);
let mut wallet = Wallet::new(keyring);With a stacktrace containing: thread 'main' panicked at /Users/user/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/miniscript-12.3.5/src/descriptor/key.rs:687:14:
The key should not contain any wildcards at this pointAfter a bit of poking around I found that this is coming from the descriptor_id() method called inside insert_descriptor(): pub fn insert_descriptor(
&mut self,
keychain: K,
descriptor: Descriptor<DescriptorPublicKey>,
) -> Result<bool, InsertDescriptorError<K>> {
let did = descriptor.descriptor_id();I don't have time to finish the investigation today, but just pointing it out. I'm not sure yet why this worked well in the multi-keychain-wallet crate and not here, nor why the code for the constructor is so different than the other previous constructors like |
|
This almost scared me for a moment 😅 ! Using descriptors with unhardened paths seems to work. |
|
Total facepalm. Sorry @110CodingP! Ok so I simplified the example a little bit just to keep a really neat version of how to build a wallet as close as you can from the 2.0 approach. |
4403cdb to
16d6f8c
Compare
af0e9eb to
4177147
Compare
Completes first 2 of the Todos? |
for e33e007 : Also ig |
|
Also is |
ba0a282 to
5ce40ec
Compare
30de2ec to
db47943
Compare
115060d to
916bc1b
Compare
bdbad0b to
3d6a95a
Compare
3fe0ed5 to
5fb3a4a
Compare
5fb3a4a to
3fe0ed5
Compare
84adb7b to
e3ac74d
Compare
Added `keyring::ChangeSet`. The `add_descriptor` function returns a `KeyRing::Changeset` since this would eventually be required when we introducing APIs to add descriptors to `Wallet.keyring`. Also implemented `FromSql` and `ToSql` for `KeychainKind`. Also modified `DescriptorError` and added `KeyRingError` since information of whether a keychain/desc is already assigned should be with the wallet and thus belongs to its error types. Co-authored-by: thunderbiscuit <thunderbiscuit@protonmail.com> Co-authored-by: valued mammal <valuedmammal@protonmail.com>
Modified the `Wallet::ChangeSet` accordingly. Modified AddressInfo, add reveal_next_address Also added default version for address APIs. Modified Update to take in a generic Also modified `balance` method to incorporate `CanonicalView` and a more flexible `trust_predicate` which fixes incorrect trusted balance calculation by the `Wallet`. Modified `CreateParams` to incorporate the changes. Modified `persist_test_utils` to incorporate the new `Wallet`. Note: `persist_keychains` and `persist_keychain` are two separate tests keeping in mind that some users may not use > 1 keychain. Among all tests only `persist_keychains` assumes multiple keychains. Co-authored-by: thunderbiscuit <thunderbiscuit@protonmail.com> Co-authored-by: valued mammal <valuedmammal@protonmail.com>
Also fixed original examples. Co-authored-by: codingp110 <codingp110@gmail.com> Co-authored-by: valued mammal <valuedmammal@protonmail.com>
Co-authored-by: codingp110 <codingp110@gmail.com> Co-authored-by: valued mammal <valuedmammal@protonmail.com>
thunderbiscuit
left a comment
There was a problem hiding this comment.
I found some methods that are cut out from the codebase because of commit 5df393d, we should make sure we bring those back (or remove only the ones we really want to remove).
| /// | ||
| /// If `f` returns an error, then returns `E` of a type defined by the function | ||
| /// passed in. | ||
| pub fn events_helper<F, T, E>(&mut self, f: F) -> Result<Vec<WalletEvent>, E> |
There was a problem hiding this comment.
Some methods are removed entirely from the module, for example here Wallet::events_helper.
There was a problem hiding this comment.
Thanks for catching this! This shouldn't have happened though because this is just a rebase over master. Trying to figure out the reason.
How to preserve the automatic change derivation mechanismProblemIn the 2-keychain wallet, the change address can be automatically derived because we know which keychain is the "internal" one. However once we allow potentially many keychains we can no longer assume which keychain may be designated for change. It would be annoying, and arguably a regression, for the user to specify the change keychain on the PSBT params every time. Proposed solution is to add an internal flag to each wallet descriptor at wallet creation, persisted via the ChangeSet. Knowing which keychain(s) can be used for change not only allows us to derive the change script automatically, but match the change script to the dominant output script type (for privacy reasons). Example/// Represents a wallet descriptor
pub struct WalletDescriptor<D> {
descriptor: D,
internal: bool,
}
impl WalletDescriptor<D> {
/// A new wallet descriptor for generating receive addresses.
pub fn new(descriptor: D) -> Self { ... }
/// A new wallet descriptor with the internal flag set indicating it may be used to generate change addresses.
pub fn new_internal(descriptor: D) -> Self { ... }
}
impl KeyRing<K> {
/// Create new from (keychain, wallet_descriptor) tuples.
pub fn new<D>(descriptors: impl IntoIterator<Item = (K, WalletDescriptor<D>)>) -> Self
where
D: IntoWalletDescriptor,
{ ... }
} |
|
I like the idea of simplifying the work for users (particularly if they have a given keychain that's always the change keychain for a given descriptor, in which case they would as you say just always have to set it as their change keychain on every PSBT build). Looking at the pseudocode above though, I think it might need adjusting just tad. In this scenario (at least in my mind), the keychains should be provided in pairs (just a marker that says "this is internal" is not enough if you actually have 6 keychains, and some of them have an internal, sister-keychain but they're all different, some don't have change keychain altogether, etc.). Also tiny nit: I have seen enough users misunderstand the meaning of internal/external as to scare me for a lifetime. I don't know if this PR is the place to start this journey, but I think we should promote the receive/change taxonomy, or maybe just main/change. |
|
The main design hurdle is the ability of the wallet to derive a change address during transaction creation. For that we need to encode whether a keychain is eligible to "receive" change. In the process, to remove ambiguity in keychain selection, we should finally move away from using In the single-keychain case, the user can mark their only keychain as also eligible for change addresses and the process should "just work" for deriving addresses in all cases. Of course the user can override it by specifying the change script directly on The need to quickly access descriptor metadata in the wallet (e.g. the internal/change flag) again supports the argument for having |
Yep 100% agree!
Also agree. One thing we need to consider however, is that there is potentially multiple keychains marked as candidates for change, and then again you'd need a way to discriminate. A naive version of this could use keychain pairs, but then what happens when the wallet creates a transaction that pulls in UTXOs from different keychains? They each would potentially have a change keychain and then we haven't quite solved our issue. Maybe requiring users provide a change keychain on all transaction creation seems like a good basic first step, and then we can relax this by providing further shortcuts and helpers?
Darn! I kind of agree here, metadata is useful of course and we need access. But one of the weird aspects of the keyring as part of the wallet is that the descriptors then exist in two places (in the tx_graph and the keyring field). If there is a way to just use the keychains in the tx_graph I think that's maybe a good way to not duplicate or split the source of truth for the wallet. I've honestly been going back and forth on this (now after thinning the KeyRing as much as I have I kind of even find myself wondering if it's important enough to keep altogether... need to deep dive on this a bit more). Update on this PRI worked on the rebase of this yesterday all day and today, and it's become clear to me that the 15k green 15k red line diff is super hard to (1) manage + rebase, and (2) review (too much is just pre-keychain work and too little is actual API changes). I'm testing out a different approach:
|
Description
This PR allows the
Wallettype to track any number of keychains. It is a breaking change.Related Issues: #188, #227
Related PRs: #230, #226
Exploratory Crate: multi-keychain-wallet
Follow along the todos and PR development on this HackMD file.
Commits
Walletdependent code.KeyRingand related types.Walletto levergage theKeyRing.Todos
Search for the following pattern to find Todos in the codebase:
// TODO PR #318: We should fix this because...Changelog notice
TODO