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}