bdk_wallet/wallet/
changeset.rs

1use bdk_chain::{
2    indexed_tx_graph, keychain_txout, local_chain, tx_graph, ConfirmationBlockTime, Merge,
3};
4use miniscript::{Descriptor, DescriptorPublicKey};
5use serde::{Deserialize, Serialize};
6
7type IndexedTxGraphChangeSet =
8    indexed_tx_graph::ChangeSet<ConfirmationBlockTime, keychain_txout::ChangeSet>;
9
10/// A change set for [`Wallet`]
11///
12/// ## Definition
13///
14/// The change set is responsible for transmiting data between the persistent storage layer and the
15/// core library components. Specifically, it serves two primary functions:
16///
17/// 1) Recording incremental changes to the in-memory representation that need to be persisted to
18///    disk
19/// 2) Applying aggregate changes from the persistence layer to the in-memory representation at
20///    startup
21///
22/// ## Contract
23///
24/// The change set maintains and enforces the following properties:
25///
26/// * Change sets must implement [`Serialize`] and [`Deserialize`] to meet the definition from
27///   above.
28/// * Change sets must implement [`Default`] as a way of instantiating new empty objects.
29/// * Change sets must implement [`Merge`] so that many instances can be aggregated into a single
30///   instance.
31/// * A change set is composed of a number of individual "sub-change sets" that adhere to the same
32///   rules as above. This is for increased modularity and portability. For example the core modules
33///   each have their own change set (`tx_graph`, `local_chain`, etc).
34///
35/// ## Members and required fields
36///
37/// The change set has certain required fields without which a `Wallet` cannot function.
38/// These include the [`descriptor`] and the [`network`] in use. These are required to be non-empty
39/// *in the aggregate*, meaning the field must be present and non-null in the union of all
40/// persisted changes, but may be empty in any one change set, where "empty" is defined by the
41/// [`Merge`](Merge::is_empty) implementation of that change set. This requirement also applies to
42/// the [`local_chain`] field in that the aggregate change set must include a genesis block.
43///
44/// For example, the descriptor and network are present in the first change set after wallet
45/// creation, but are usually omitted in subsequent updates, as they are not permitted to change
46/// at any point thereafter.
47///
48/// Other fields of the change set are not required to be non-empty, that is they may be empty even
49/// in the aggregate. However in practice they should contain the data needed to recover a wallet
50/// state between sessions. These include:
51/// * [`tx_graph`](Self::tx_graph)
52/// * [`indexer`](Self::indexer)
53///
54/// The [`change_descriptor`] is special in that its presence is optional, however the value of the
55/// change descriptor should be defined at wallet creation time and respected for the life of the
56/// wallet, meaning that if a change descriptor is originally defined, it must also be present in
57/// the aggregate change set.
58///
59/// ## Staging
60///
61/// For greater efficiency the [`Wallet`] is able to *stage* the to-be-persisted changes. Many
62/// operations result in staged changes which require persistence on the part of the user. These
63/// include address revelation, applying an [`Update`], and introducing transactions and chain
64/// data to the wallet. To get the staged changes see [`Wallet::staged`] and similar methods. Once
65/// the changes are committed to the persistence layer the contents of the stage should be
66/// discarded.
67///
68/// Users should persist early and often generally speaking, however in principle there is no
69/// limit to the number or type of changes that can be staged prior to persisting or the order in
70/// which they're staged. This is because change sets are designed to be [merged]. The change
71/// that is ultimately persisted will encompass the combined effect of each change individually.
72///
73/// ## Extensibility
74///
75/// Existing fields may be extended in the future with additional sub-fields. New top-level fields
76/// are likely to be added as new features and core components are implemented. Existing fields may
77/// be removed in future versions of the library.
78///
79/// The authors reserve the right to make breaking changes to the [`ChangeSet`] structure in
80/// a major version release. API changes affecting the types of data persisted will display
81/// prominently in the release notes. Users are advised to look for such changes and update their
82/// application accordingly.
83///
84/// The resulting interface is designed to give the user more control of what to persist and when
85/// to persist it. Custom implementations should consider and account for the possibility of
86/// partial or repeat writes, the atomicity of persistence operations, and the order of reads and
87/// writes among the fields of the change set. BDK comes with support for [SQLite] that handles
88/// the details for you and is recommended for many users. If implementing your own persistence,
89/// please refer to the documentation for [`WalletPersister`] and [`PersistedWallet`] for more
90/// information.
91///
92/// [`change_descriptor`]: Self::change_descriptor
93/// [`descriptor`]: Self::descriptor
94/// [`local_chain`]: Self::local_chain
95/// [merged]: bdk_chain::Merge
96/// [`network`]: Self::network
97/// [`PersistedWallet`]: crate::PersistedWallet
98/// [SQLite]: bdk_chain::rusqlite_impl
99/// [`Update`]: crate::Update
100/// [`WalletPersister`]: crate::WalletPersister
101/// [`Wallet::staged`]: crate::Wallet::staged
102/// [`Wallet`]: crate::Wallet
103#[derive(Default, Debug, Clone, PartialEq, Deserialize, Serialize)]
104pub struct ChangeSet {
105    /// Descriptor for recipient addresses.
106    pub descriptor: Option<Descriptor<DescriptorPublicKey>>,
107    /// Descriptor for change addresses.
108    pub change_descriptor: Option<Descriptor<DescriptorPublicKey>>,
109    /// Stores the network type of the transaction data.
110    pub network: Option<bitcoin::Network>,
111    /// Changes to the [`LocalChain`](local_chain::LocalChain).
112    pub local_chain: local_chain::ChangeSet,
113    /// Changes to [`TxGraph`](tx_graph::TxGraph).
114    pub tx_graph: tx_graph::ChangeSet<ConfirmationBlockTime>,
115    /// Changes to [`KeychainTxOutIndex`](keychain_txout::KeychainTxOutIndex).
116    pub indexer: keychain_txout::ChangeSet,
117}
118
119impl Merge for ChangeSet {
120    /// Merge another [`ChangeSet`] into itself.
121    fn merge(&mut self, other: Self) {
122        if other.descriptor.is_some() {
123            debug_assert!(
124                self.descriptor.is_none() || self.descriptor == other.descriptor,
125                "descriptor must never change"
126            );
127            self.descriptor = other.descriptor;
128        }
129        if other.change_descriptor.is_some() {
130            debug_assert!(
131                self.change_descriptor.is_none()
132                    || self.change_descriptor == other.change_descriptor,
133                "change descriptor must never change"
134            );
135            self.change_descriptor = other.change_descriptor;
136        }
137        if other.network.is_some() {
138            debug_assert!(
139                self.network.is_none() || self.network == other.network,
140                "network must never change"
141            );
142            self.network = other.network;
143        }
144
145        Merge::merge(&mut self.local_chain, other.local_chain);
146        Merge::merge(&mut self.tx_graph, other.tx_graph);
147        Merge::merge(&mut self.indexer, other.indexer);
148    }
149
150    fn is_empty(&self) -> bool {
151        self.descriptor.is_none()
152            && self.change_descriptor.is_none()
153            && self.network.is_none()
154            && self.local_chain.is_empty()
155            && self.tx_graph.is_empty()
156            && self.indexer.is_empty()
157    }
158}
159
160#[cfg(feature = "rusqlite")]
161impl ChangeSet {
162    /// Schema name for wallet.
163    pub const WALLET_SCHEMA_NAME: &'static str = "bdk_wallet";
164    /// Name of table to store wallet descriptors and network.
165    pub const WALLET_TABLE_NAME: &'static str = "bdk_wallet";
166
167    /// Get v0 sqlite [ChangeSet] schema
168    pub fn schema_v0() -> alloc::string::String {
169        format!(
170            "CREATE TABLE {} ( \
171                id INTEGER PRIMARY KEY NOT NULL CHECK (id = 0), \
172                descriptor TEXT, \
173                change_descriptor TEXT, \
174                network TEXT \
175                ) STRICT;",
176            Self::WALLET_TABLE_NAME,
177        )
178    }
179
180    /// Initialize sqlite tables for wallet tables.
181    pub fn init_sqlite_tables(db_tx: &chain::rusqlite::Transaction) -> chain::rusqlite::Result<()> {
182        crate::rusqlite_impl::migrate_schema(
183            db_tx,
184            Self::WALLET_SCHEMA_NAME,
185            &[&Self::schema_v0()],
186        )?;
187
188        bdk_chain::local_chain::ChangeSet::init_sqlite_tables(db_tx)?;
189        bdk_chain::tx_graph::ChangeSet::<ConfirmationBlockTime>::init_sqlite_tables(db_tx)?;
190        bdk_chain::keychain_txout::ChangeSet::init_sqlite_tables(db_tx)?;
191
192        Ok(())
193    }
194
195    /// Recover a [`ChangeSet`] from sqlite database.
196    pub fn from_sqlite(db_tx: &chain::rusqlite::Transaction) -> chain::rusqlite::Result<Self> {
197        use chain::rusqlite::OptionalExtension;
198        use chain::Impl;
199
200        let mut changeset = Self::default();
201
202        let mut wallet_statement = db_tx.prepare(&format!(
203            "SELECT descriptor, change_descriptor, network FROM {}",
204            Self::WALLET_TABLE_NAME,
205        ))?;
206        let row = wallet_statement
207            .query_row([], |row| {
208                Ok((
209                    row.get::<_, Option<Impl<Descriptor<DescriptorPublicKey>>>>("descriptor")?,
210                    row.get::<_, Option<Impl<Descriptor<DescriptorPublicKey>>>>(
211                        "change_descriptor",
212                    )?,
213                    row.get::<_, Option<Impl<bitcoin::Network>>>("network")?,
214                ))
215            })
216            .optional()?;
217        if let Some((desc, change_desc, network)) = row {
218            changeset.descriptor = desc.map(Impl::into_inner);
219            changeset.change_descriptor = change_desc.map(Impl::into_inner);
220            changeset.network = network.map(Impl::into_inner);
221        }
222
223        changeset.local_chain = local_chain::ChangeSet::from_sqlite(db_tx)?;
224        changeset.tx_graph = tx_graph::ChangeSet::<_>::from_sqlite(db_tx)?;
225        changeset.indexer = keychain_txout::ChangeSet::from_sqlite(db_tx)?;
226
227        Ok(changeset)
228    }
229
230    /// Persist [`ChangeSet`] to sqlite database.
231    pub fn persist_to_sqlite(
232        &self,
233        db_tx: &chain::rusqlite::Transaction,
234    ) -> chain::rusqlite::Result<()> {
235        use chain::rusqlite::named_params;
236        use chain::Impl;
237
238        let mut descriptor_statement = db_tx.prepare_cached(&format!(
239            "INSERT INTO {}(id, descriptor) VALUES(:id, :descriptor) ON CONFLICT(id) DO UPDATE SET descriptor=:descriptor",
240            Self::WALLET_TABLE_NAME,
241        ))?;
242        if let Some(descriptor) = &self.descriptor {
243            descriptor_statement.execute(named_params! {
244                ":id": 0,
245                ":descriptor": Impl(descriptor.clone()),
246            })?;
247        }
248
249        let mut change_descriptor_statement = db_tx.prepare_cached(&format!(
250            "INSERT INTO {}(id, change_descriptor) VALUES(:id, :change_descriptor) ON CONFLICT(id) DO UPDATE SET change_descriptor=:change_descriptor",
251            Self::WALLET_TABLE_NAME,
252        ))?;
253        if let Some(change_descriptor) = &self.change_descriptor {
254            change_descriptor_statement.execute(named_params! {
255                ":id": 0,
256                ":change_descriptor": Impl(change_descriptor.clone()),
257            })?;
258        }
259
260        let mut network_statement = db_tx.prepare_cached(&format!(
261            "INSERT INTO {}(id, network) VALUES(:id, :network) ON CONFLICT(id) DO UPDATE SET network=:network",
262            Self::WALLET_TABLE_NAME,
263        ))?;
264        if let Some(network) = self.network {
265            network_statement.execute(named_params! {
266                ":id": 0,
267                ":network": Impl(network),
268            })?;
269        }
270
271        self.local_chain.persist_to_sqlite(db_tx)?;
272        self.tx_graph.persist_to_sqlite(db_tx)?;
273        self.indexer.persist_to_sqlite(db_tx)?;
274        Ok(())
275    }
276}
277
278impl From<local_chain::ChangeSet> for ChangeSet {
279    fn from(chain: local_chain::ChangeSet) -> Self {
280        Self {
281            local_chain: chain,
282            ..Default::default()
283        }
284    }
285}
286
287impl From<IndexedTxGraphChangeSet> for ChangeSet {
288    fn from(indexed_tx_graph: IndexedTxGraphChangeSet) -> Self {
289        Self {
290            tx_graph: indexed_tx_graph.tx_graph,
291            indexer: indexed_tx_graph.indexer,
292            ..Default::default()
293        }
294    }
295}
296
297impl From<tx_graph::ChangeSet<ConfirmationBlockTime>> for ChangeSet {
298    fn from(tx_graph: tx_graph::ChangeSet<ConfirmationBlockTime>) -> Self {
299        Self {
300            tx_graph,
301            ..Default::default()
302        }
303    }
304}
305
306impl From<keychain_txout::ChangeSet> for ChangeSet {
307    fn from(indexer: keychain_txout::ChangeSet) -> Self {
308        Self {
309            indexer,
310            ..Default::default()
311        }
312    }
313}