Multi descriptor: Decouple transaction building logic#647
Multi descriptor: Decouple transaction building logic#647evanlinjin wants to merge 16 commits intobitcoindevkit:masterfrom
Conversation
5a0e85a to
14d2e99
Compare
TxParams for multi-descriptor capabilityc27a900 to
5e3afe5
Compare
ac2f4ed to
574dcc4
Compare
617c2b2 to
5aaf2bf
Compare
Store policy paths in a map with ExtendedDescriptor as key. This way we can re-use the TxParams struct in multi-descriptor/multi-wallet contexts.
Use `ExtendedDescriptor` instead of `Keychain`
This is a WIP. Explanation for my approach (will be / is) provided in the PR's description box.
4c6bf84 to
ae9a331
Compare
* Added `MultiTracker::latest_blockheight` method. * Update documentation to reflect changes.
ae9a331 to
e3ca1ce
Compare
afilini
left a comment
There was a problem hiding this comment.
I still feel like I'm trying to wrap my head around this, but conceptually I think it makes sense and I really like how this allows us to slowly move away from Wallet by making it "backward compatible" with MultiTracker.
| pub(crate) internal_policy_path: Option<BTreeMap<String, Vec<usize>>>, | ||
| pub(crate) external_policy_path: Option<BTreeMap<String, Vec<usize>>>, | ||
| pub(crate) descriptor_policy_paths: | ||
| BTreeMap<&'a ExtendedDescriptor, BTreeMap<String, Vec<usize>>>, |
There was a problem hiding this comment.
Maybe we can avoid the lifetime by using something else as a key? like the descriptor's checksum?
There was a problem hiding this comment.
I will change to use checksum once #671 is approved and merged :)
| /// * Confirmed height. | ||
| /// * Descriptor + Child index. | ||
| /// * Satisfaction weight of scriptPubKey (weight of corresponding scriptSig/witness data). | ||
| fn iter_unspent(&self) -> Result<Box<dyn Iterator<Item = (LocalUtxo, usize)> + '_>, Error>; |
There was a problem hiding this comment.
We actually have a WeightedUtxo struct that is basically a utxo + the weight, maybe that's more convenient here
There was a problem hiding this comment.
However, WeightedUtxo can be of a foreign Utxo so it is not as "direct" as using an union here (since this method is about iterating unspent, and owned UTXOs). Should I keep it as is?
src/wallet/multi_tracker.rs
Outdated
|
|
||
| let secp = SecpCtx::new(); | ||
|
|
||
| for index in 0..2100_u32 { |
There was a problem hiding this comment.
Wait, that's a lot! Deriving all the descriptors up to index 2100 may take a very long time. Do you think it makes sense to add an extra argument for a "max index"? Most of the time I guess the caller would only need to derive up to 100/200
There was a problem hiding this comment.
This is the default implementation of the method and I intended it just to be there for convenience. But you are right, so I've changed it to check for 200 iterations, and added a comment saying that this method should be overloaded. Would that be enough? It seems inelegant to add another input imo.
| // TODO: Add as struct field | ||
| let desc_map = self | ||
| .iter_descriptors()? | ||
| .map(|desc| (desc.descriptor.to_string(), desc)) |
There was a problem hiding this comment.
I think here again you can just use the checksum which is shorter
|
|
||
| /// Implements [`MultiTracker`] with one external descriptor and an optional internal descriptor. | ||
| // #[derive(Clone)] | ||
| pub struct LegacyTracker<'a, D> { |
There was a problem hiding this comment.
Is there a specific reason why you chose to not let this struct own the descriptors and the database and instead use references?
There was a problem hiding this comment.
Is it to make this easier to clone for example? I see that in the TxBuilder you have to clone this, in that case it makes sense, but then the question is: why can't you store a reference in the TxBuilder like we used to do before with the Wallet?
There was a problem hiding this comment.
@afilini It is just to avoid copying data in memory. The assumption is that one would only use LegacyTracker with the existence of Wallet (which is a good assumption imo). Since, the whole point of LegacyTracker is to get Wallet to "implement" MultiTracker, since transaction-building and signing logic is to be wrapped around MultiTracker. Right now this tx logic uses static functions of Wallet (which is confusing), but will avoid annoying git as much as possible. Those static functions, responsible for tx logic, should eventually be moved out of Wallet when the time is right.
| /// | ||
| /// For an example see [this module](crate::wallet::coin_selection)'s documentation. | ||
| pub trait CoinSelectionAlgorithm<D: Database>: std::fmt::Debug { | ||
| pub trait CoinSelectionAlgorithm: std::fmt::Debug { |
There was a problem hiding this comment.
I'm trying to figure out how these changes affect my original idea for the coin selection: the reason why the D generic was defined here was that we wanted to offer a way to use custom databases in a custom coin selection without being restricted on the API in any way.
For example, let's say I have my custom database that exposes some kind of metric that I'd like to use for the coin selection, then I can implement CoinSelectionAlgorithm<MyCustomDb> and this would make the cs get a &MyCustomDb (not a generic &dyn Database), which would allow me to use any custom API I have.
It's not the end of the world to lose this ability because again it was something that probably nobody used, but I think it was kind of cool and I'm wondering if we can somehow preserve this even with the MultiTracker.
There was a problem hiding this comment.
But since CoinSelectionAlgorithm is a trait, we can have a struct implement CoinSelectionAlgorithm (with special databases and parameters as fields) so we can still do those custom algorithms without the need for the generic type. Let me know if I've missed something though
It should return `Ok(None)` when tx is not found, but return `Err` when `OutPoint.vout` is invalid.
6db5b4a Introduce `get_checksum_bytes` method and improvements (志宇) Pull request description: ### Description `get_checksum_bytes()` returns a descriptor checksum as `[u8; 8]` instead of `String`, potentially improving performance and memory usage. In addition to this, since descriptors only use characters that fit within a UTF-8 8-bit code unit ([US-ASCII](https://www.charset.org/charsets/us-ascii)), there is no need to use the `char` type (which is 4 bytes). This can also potentially bring in some performance and memory-usage benefits. ### Notes to the reviewers This is useful because we will be using descriptor checksums for indexing operations in the near future (multi-descriptor wallets #486 ). Refer to comments by @afilini : * #647 (comment) * #647 (comment) * #654 (comment) ### Checklists #### All Submissions: * [x] I've signed all my commits * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md) * [x] I ran `cargo fmt` and `cargo clippy` before committing #### New Features: * [x] I've added tests for the new feature * [x] I've added docs for the new feature * [x] I've updated `CHANGELOG.md` ACKs for top commit: afilini: ACK 6db5b4a Tree-SHA512: 1cecc3a1514a3ec3ac0a50775f6b3c4dd9785e3606390ceba57cc6248b8ff19c4023add0643c48dd9d84984341c506c036c4880fca4a4358ce1b54ccb4c56687
2c02a44 Test: No address reuse for single descriptor (志宇) Pull request description: ### Description Just a simple new test. This test is to ensure there are no regressions when we later change internal logic of `Wallet`. A single descriptor wallet should always get a new address with `AddressIndex::New` even if we alternate grabbing internal/external keychains. I thought of adding this during work on #647 ### Checklists #### All Submissions: * [x] I've signed all my commits * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md) * [x] I ran `cargo fmt` and `cargo clippy` before committing ACKs for top commit: danielabrozzoni: tACK 2c02a44 rajarshimaitra: tACK 2c02a44 Tree-SHA512: d065ae0979dc3ef7c26d6dfc19c88498e4bf17cc908e4f5677dcbf62ee59162e666cb00eb87b96d4c2557310960e3677eec7b6d907a5a4860cb7d2d74dba07b0
|
This work will be replaced with BDK Core/Chain. |
Description
Closes bitcoindevkit/bdk_wallet#188
The end goal of this PR is to have the ability to a single wallet with multiple descriptors.
Notes to the reviewers
In the Discord channel @
#multi-descriptor-wallets, @afilini suggested a roadmap:The "database factories" structure is already implemented in a separate PR: #654 (And can be used later).
This PR mainly addresses the initial portion of step 2.
My Approach
The most difficult aspect of adding multi-descriptor capability is in the transaction creation. Everything else from retrieving address, UTXOs, signing, transactions, balances and syncing are trivial tasks. Hence, my approach is to first decouple the transaction building logic from
Walletso that it can be reused for multi-descriptor (or even for users of BDK that don't want a wallet). In that being said, I have kept what @afilini said in mind (paraphrasing)...Walletwith it immediately.Therefore, I have ensured the following things for my approach:
Instead of moving logic out of
Wallet, I have turned various member functions into static functions (renamed to start with_). The original functions will call these static functions. This way, we do not change the "external interface" ofWalletand more-or-less keep these bits and pieces in the same place (so hopefully git will not complain so much when we merge).I have introduced a new trait
SpendableCollectionto replace the functionality ofWallet/DatabasewithinTxBuilder. To keepWalletworking with the new modifiedTxBuilder, a structSpendablesFromDbis formed which implementsSpendableCollection, and wrapsDatabase(and some fields ofWallet).Noteworthy changes
Not in any particular order...
SpendableCollectiontrait (andSpendableCollectionInnerwhich defines it's "explicitly required" methods). These traits represent a collection of owned and spendable UTXO's, Descriptors and their associated transactions. Note that it's membersSpendableCollectionInner::get_{external|internal}_descriptorsreturnsVecs (so we can support multiple descriptor implementations in the very near future).CoinSelectionAlgorithmtrait's type constraint has been moved to it's function definition (the constrained type was only used as input anyway) - I hope this change is okay?KeychainKind, I have changed to use theExtendedDescriptordirectly (again, for muti-descriptors in the future). This change has some small caveats...bdk/src/wallet/spendable_collection.rs
Lines 73 to 101 in 574dcc4
bdk/src/wallet/mod.rs
Lines 5279 to 5283 in 574dcc4
TxBuilder, other than needing theBatchDatabaseconstraint withSpendableCollection, uses static members fromWallet(as mentioned above). Hence, we needed to add another phantom constraint imposed by the constraints of thoseWalletmethods (this can be removed once we ACTUALLY move stuff out ofWallet).Tests
It seems that I have not broken the functionality of
Walletand all the non-doc tests continue to pass.Next steps
Once @afilini and everyone else is happy with my approach thus far, I will create a multi-descriptor implementation of
SpendableCollectionand do tests to see if multi-descriptor transaction creation works as expected.Since
CoinSelectionAlgorithmnow takes aSpendableCollectionas an input (instead of aBatchDatabase, I need to ensure thatSpendableCollectionis satisfactory for all coin selection algorithms.I also still need to decouple Fee-bumping logic.
Checklists
All Submissions:
cargo fmtandcargo clippybefore committingNew Features:
CHANGELOG.mdBugfixes: