-
Notifications
You must be signed in to change notification settings - Fork 320
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
Backwards compatibility of bdk::ChangeSet
data structure
#1103
Comments
Note that it would also be nice to consider whether even forwards compatibility could be guaranteed. It may make things a bit more complicated, but it's worth exploring, especially since these properties are transitive for any downstream users (e.g., LDK Node can only guarantee what BDK guarantees...).
Mh, but then you'd need to define explicit migration functions to allow user migrating from |
@tnull so you want an older version of BDK to understand changesets that a newer version of BDK outputs. Can you elaborate why this would be useful? Thank you |
Forwards compatibility is really useful to allow downgrades for various reasons: say a user upgrades to a newer BDK version and then a bug is discovered. If the upgrade breaks serialization forwards compatibility they couldn't downgrade to get going again and would be stuck until BDK ships a fix. Similarly, it would be necessary to be a bit more forgiving when restoring from backups or when having multiple apps that operate on the same data set. In LDK Node's case we for example plan to allow multi-device support synced via a VSS backend. Not having forwards compatibility of the serialized data would require our users to upgrade all apps and devices in lockstep as otherwise an outdated device would refuse to read any data written by the already upgraded devices. As said above, I'm aware that it may complicate things, but it's at least worth considering, i.e., it should be a dedicated design choice not to provide these guarantees. |
Thanks for bringing up this point. I think we should never change this for structures in
I'm not sure if we can apply the same policy to I think forwards compatibility is possible but this depends on the application serializing each changeset in a particular way. Note in |
Yeah, I think even independently from (forwards) compatibility guarantees, having some TLV-style encoding or at the very least adding the length sounds like a very good idea. Not having this really limits how you can make necessary changes to the data model in the future. |
Do we really need forward and backwards compatibility baked into the data structures for persisted data? My understanding is that the data BDK persists should always able to be regenerated from the wallet/app descriptors and blockchain client. So in the case of a roll forward/back should be able to just do a fresh full scan. Optionally some persistence modules like SQL based ones should be able to at least rolling forward new schema changes to work with new |
I added this to the alpha.4 milestone to decide at least if we're going to do this or not since it's a biggish functional change. |
I mean generally forwards compat. is a "nice-to-have", not a hard requirement. Still, ... nice to have it. ;)
Mh, but this is really about the data model itself, not the persistence layer. Of course the persistence layer could always provide migrations, but the idea compat. guarantees is exactly that the data model does not need an explicit migration step to go forwards/backwards. |
I just realized this suggestion doesn't make sense since we're using bincode. You indeed have to do what @evanlinjin suggested in the OP for backwards compat. |
Lib team call: It changes the API so we should include it before cutting off for beta release |
I suggest we go ahead with this and do what is described in the ticket description. |
We are now using the So for example, if we add some field like @tnull does this work for you? I believe it's what you suggested in our last call. If so I suggest we only need to document this restriction in the docs for |
Yes, it sounds like this could work, especially if newly added fields are |
@notmandatory some serialization formats would not work with what's suggested (i.e. bincode) when the entire changeset is persisted. I think we should create a new type (i.e. We still need to ensure new fields added have sane defaults (as @tnull pointed out). However, I can't imagine a scenario where something added wouldn't have a sane default. P.s. I'm working on this right now! |
Can you elaborate on why serialization schemes like bincode wouldn't work with the idea of making all fields, and new ones in particular, optional (or potentially empty)? doesn't any serialization we use have to have the property that serdes change sets from/to regular rust structs and that can be appended as we do now? |
In theory bincode could work like this if you added a length prefix to each changeset but in practice there doesn't seem to be APIs to "decode this thing and leave the remaining fields as I think it would be possible to get both if we were willing to do a custom serde derserializer that did versioning but if the version is higher than the current impl, then just try deserializing anyway but ignore trailing bytes (note that bincode can ignore trailing bytes). This would be a little better since it doesn't require making the enum. I wonder if there's a way we could do this in bdk file persist where we just do the version prefixing there and you provide a callback to the bdk_file_persist telling it how to decode each older version. This seems like the cleanest because it puts the solution where the actual problem is (bincode) and doesn't preclude forwards compatibility for other formats (e.g. cbor, json etc). This would turn it into a TLV kind of thing where each entry is in the form: struct Entry<V> {
version: V,
data: Vec<u8>,
} Then you'd provide a callback like:
Where you would actually do the bincode decoding, looking at the |
@LLFourn I agree that handling empty fields only needs to be handled right now in the file store. I don't expect versions will change that often, so rather than making a whole general purpose framework for handling this can we just take the simple enum approach in that crate? I'm hoping we don't add new fields that often, something like this: pub enum VersionedCombinedChangeSet<K,A> {
V1 {
chain: crate::local_chain::ChangeSet,
indexed_tx_graph: crate::indexed_tx_graph::ChangeSet<A, crate::keychain::ChangeSet<K>>,
network: Option<bitcoin::Network>,
},
V2 {
chain: crate::local_chain::ChangeSet,
indexed_tx_graph: crate::indexed_tx_graph::ChangeSet<A, crate::keychain::ChangeSet<K>>,
network: Option<bitcoin::Network>,
new_info: Option<u32>,
}
}
impl<K,A> From<VersionedCombinedChangeSet<K,A>> for CombinedChangeSet<K,A> {
fn from(versioned: VersionedCombinedChangeSet<K, A>) -> Self {
match versioned {
VersionedCombinedChangeSet::V1 { chain, indexed_tx_graph, network } => Self {
chain,
indexed_tx_graph,
network,
// missing fields get default
..Default::default()
}
VersionedCombinedChangeSet::V2 { chain, indexed_tx_graph, network, new_info } => Self {
chain,
indexed_tx_graph,
network,
new_info,
}
}
}
}
impl<K,A> From<CombinedChangeSet<K,A>> for VersionedCombinedChangeSet<K,A> {
fn from(changeset: CombinedChangeSet<K,A>) -> Self {
// current latest version
VersionedCombinedChangeSet::V2 {
chain: changeset.chain,
indexed_tx_graph: changeset.indexed_tx_graph,
network: changeset.network,
new_info: changeset.new_info,
}
}
} |
To add some context to this conversation, I'll touch on all proposed future changes that will affect Add another tx timestamp for when wallet created tx
Wallet birthday
Script pubkey birthday
Monotone
|
Changes which only adds new fields are easily backwards and forwards compatible. Changes which change fields (monotone For monotone For block metadata, we can have forwards and backwards compatibility if |
I think we should have the following:
I'm a fan of flattening the |
@evanlinjin per recent discord chat the changes that need to be done are:
Do we need separate issues and/or PRs for these or want to do them all together? Can I help by doing a PR for 1? |
@notmandatory one more breaking change here: #1333 I would like more feedback on this. I'm also currently working on 1 & 2 (should have a PR ready tomorrow). |
fixed by #1514 |
Describe the enhancement
The
bdk_wallet::ChangeSet
is what the persistence interfaces with. As suggested by @tnull, this structure should be backwards compatible (in case we make changes to this structure).An easy solution is to make
bdk_wallet::ChangeSet
a enum:The text was updated successfully, but these errors were encountered: