-
Notifications
You must be signed in to change notification settings - Fork 280
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
Include the descriptor in keychain::Changeset
#1203
Include the descriptor in keychain::Changeset
#1203
Conversation
keychain_txout_index::Changeset
7c4fea5
to
54f7975
Compare
keychain_txout_index::Changeset
keychain::Changeset
54f7975
to
c14504e
Compare
c14504e
to
fd85943
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for taking this forward. This is my initial round of comments!
Oh and it seems like you need to rebase. |
Hey @evanlinjin and @LLFourn, thanks a lot for your reviews. I've given some thoughts about them:
It seems to me that it would be optimal to have |
Just quoting @LLFourn's response to adding an error to
|
What do we think about using The |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've been trying to think of how to keep ChangeSet<K>
monotone and I think we should not change the keychain::ChangeSet and instead enforce that descriptors don't change at the bdk::wallet::ChangeSet level. This puts responsibility on the devs who who don't use bdk::wallet to keep their data consistent. They can either follow how we do it in Wallet
or go their own way if they know what they're doing. I created #1234 to demonstrate this approach, would love some feed back.
This is actually becoming my intuition too but have an idea a bit different #1234. I think the descriptor has to be in the keychain changeset because that's where it's stored in memory and it has to be there to produce new addresses. Having it elsewhere adds friction and removing that is the point of this effort. We can keep So here's my suggestion:
#[derive(Clone, Debug)]
pub struct DescriptorTxOutIndex {
inner: SpkTxOutIndex<(DescriptorId, u32)>,
// descriptors of each keychain
descriptors: BTreeMap<DescriptorId, Descriptor<DescriptorPublicKey>>,
// last revealed indexes
last_revealed: BTreeMap<DescriptorId, u32>,
// lookahead settings for each descriptor
lookahead: BTreeMap<DescriptorId, u32>,
}
#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct ChangeSet {
revealed: BTreeMap<DescriptorId, u32>
added: BTreeSet<Descriptor<DescriptorPublicKey>
} Methods in This changeset is monotone and error free and gives us most of the useful things that So here's what /// The changes made to a wallet by applying an [`Update`].
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, Default)]
pub struct ChangeSet {
/// Changes to the [`LocalChain`].
///
/// [`LocalChain`]: local_chain::LocalChain
pub chain: local_chain::ChangeSet,
/// Changes to [`IndexedTxGraph`].
///
/// [`IndexedTxGraph`]: bdk_chain::indexed_tx_graph::IndexedTxGraph
pub indexed_tx_graph: indexed_tx_graph::ChangeSet<
ConfirmationTimeHeightAnchor,
descriptor::ChangeSet,
>,
/// Stores they keychains of the wallet and their descriptors.
/// This should only be non-empty in the first changeset of the wallet.
keychains: BTreeMap<KeychainKind, DescriptorId>.
/// Stores the network type of the wallet.
pub network: Option<Network>,
} Of course this is non-monotone but it's better to have these hacks up here than down in We'll probably want to add a Thoughts? |
So after sleeping on it I have changed my view a little bit. The design above is the most in line with the overall design philosophy and removes all possible invalid representations at the #[derive(Clone, Debug)]
pub struct KeychainTxOutIndex<K> {
inner: SpkTxOutIndex<(DescriptorId, u32)>,
// descriptors of each keychain
descriptors: BTreeMap<DescriptorId, Descriptor<DescriptorPublicKey>>,
// last revealed indexes
last_revealed: BTreeMap<DescriptorId, u32>,
// lookahead settings for each descriptor
lookahead: BTreeMap<DescriptorId, u32>,
keychains: BTreeMap<K, DescriptorId>
}
#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct ChangeSet<K> {
revealed: BTreeMap<DescriptorId, u32>,
keychains_added: BTreeMap<K, Descriptor<DescriptorPublicKey>
} This means the non-montonicity is restricted to |
@LLFourn Thanks for the detailed explanation. It helped clarify for me why persisting descriptor data at the wallet level is perhaps not enough. I think this direction is well aligned with the related effort of separating out a "header" changeset. |
|
Please rebase now that #1183 has been merged. |
@LLFourn very nice! |
I fixed some new CI issues with #1247, it just needs to be ACKd and merged and then this PR rebased. |
…wallet::ChangeSet
...secret keys in the wallet in Wallet::load
..enabled, enable it by default
We only need to loop though entries of `other`. The logic before was wasteful because we were also looping though all entries of `self` even if we do not need to modify the `self` entry.
This fixes the bug with changesets not being monotone. Previously, the result of applying changesets individually v.s. applying the aggregate of changesets may result in different `KeychainTxOutIndex` states. The nature of the changeset allows different keychain types to share the same descriptor. However, the previous design did not take this into account. To do this properly, we should keep track of all keychains currently associated with a given descriptor. However, the API only allows returning one keychain per spk/txout/outpoint (which is a good API). Therefore, we rank keychain variants by `Ord`. Earlier keychain variants have a higher rank, and the first keychain will be returned.
078a932
to
f3c463a
Compare
After a call with @evanlinjin we decided to go with his solution of using Then, either of us will open an issue in order to discuss further improvements (I don't like this solution that much and proposed one where we'd return only the latest keychain added, using a Vec instead of a set, but it still needs some discussion). At the moment, replacing descriptors is still allowed, and having multiple keychains for the same descriptor is still allowed, even when using |
f3c463a
to
9822040
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK 86711d4
/// a situation where a descriptor has no associated keychain(s), and relevant [`TxOut`]s, | ||
/// [`OutPoint`]s and [`Script`]s (of that descriptor) will not be return by [`KeychainTxOutIndex`]. | ||
/// Therefore, reassigning the descriptor of a single keychain is not recommended. | ||
/// |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 good explanation!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK b990293
Great job everyone sticking with this one! it was a real journey.
Daniella and Evan have incorporated review comments.
keychains: BTreeMap<K, Descriptor<DescriptorPublicKey>>, | ||
inner: SpkTxOutIndex<(DescriptorId, u32)>, | ||
// keychain -> (descriptor, descriptor id) map | ||
keychains_to_descriptors: BTreeMap<K, (DescriptorId, Descriptor<DescriptorPublicKey>)>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Descriptor
does not need to be here. You can just look it up in descriptor_ids_to_descriptors
.
POST merge ACK. Thanks for just one of the non-perfect options and pushing this one through. Let's see how we feel about it after using it. I left one comment that I think should be addressed. |
Fixes #1101
keychain/txout_index.rs
as now theChangeSet
depends on miniscriptinstead of K. The DescriptorId -> K translation is made at the
KeychainTxOutIndex level.
revealed indexes, and one for newly added keychains and their
descriptor.
Changelog notice
API changes in bdk:
impl Iterator
instead ofBTreeMap
API changes in bdk_chain:
ChangeSet
is now a struct, which includes a map for last revealedindexes, and one for keychains and descriptors.
KeychainTxOutIndex::inner
returns aSpkIterator<(DescriptorId, u32)>
KeychainTxOutIndex::outpoints
returns aBTreeSet
instead of&BTreeSet
KeychainTxOutIndex::keychains
returns aimpl Iterator
instead of&BTreeMap
KeychainTxOutIndex::txouts
doesn't return a ExactSizeIterator anymoreKeychainTxOutIndex::last_revealed_indices
returns aBTreeMap
instead of
&BTreeMap
KeychainTxOutIndex::add_keychain
has been renamed toKeychainTxOutIndex::insert_descriptor
, and now it returns a ChangeSetKeychainTxOutIndex::reveal_next_spk
returns OptionKeychainTxOutIndex::next_unused_spk
returns OptionKeychainTxOutIndex::unbounded_spk_iter
returns OptionKeychainTxOutIndex::next_index
returns OptionKeychainTxOutIndex::reveal_to_target
returns OptionKeychainTxOutIndex::revealed_keychain_spks
returns OptionKeychainTxOutIndex::unused_keychain_spks
returns OptionKeychainTxOutIndex::last_revealed_index
returns OptionKeychainTxOutIndex::keychain_outpoints
returns OptionKeychainTxOutIndex::keychain_outpoints_in_range
returns OptionKeychainTxOutIndex::last_used_index
returns None if the keychain has never been used, or if it doesn't existChecklists
All Submissions:
cargo fmt
andcargo clippy
before committingNew Features: