|
| 1 | +use bdk_chain::{ |
| 2 | + indexed_tx_graph, keychain_txout, local_chain, tx_graph, ConfirmationBlockTime, DescriptorId, |
| 3 | + Merge, |
| 4 | +}; |
| 5 | +use bitcoin::Network; |
| 6 | +use miniscript::{Descriptor, DescriptorPublicKey}; |
| 7 | +use serde::{Deserialize, Serialize}; |
| 8 | + |
| 9 | +use crate::multi_keychain::keyring; |
| 10 | + |
| 11 | +/// Change set. |
| 12 | +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] |
| 13 | +pub struct ChangeSet<K: Ord> { |
| 14 | + /// Keyring changeset. |
| 15 | + pub keyring: keyring::ChangeSet<K>, |
| 16 | + /// Changes to the [`LocalChain`](local_chain::LocalChain). |
| 17 | + pub local_chain: local_chain::ChangeSet, |
| 18 | + /// Changes to [`TxGraph`](tx_graph::TxGraph). |
| 19 | + pub tx_graph: tx_graph::ChangeSet<ConfirmationBlockTime>, |
| 20 | + /// Changes to [`KeychainTxOutIndex`](keychain_txout::KeychainTxOutIndex). |
| 21 | + pub indexer: keychain_txout::ChangeSet, |
| 22 | +} |
| 23 | + |
| 24 | +impl<K: Ord> Default for ChangeSet<K> { |
| 25 | + fn default() -> Self { |
| 26 | + Self { |
| 27 | + keyring: Default::default(), |
| 28 | + local_chain: Default::default(), |
| 29 | + tx_graph: Default::default(), |
| 30 | + indexer: Default::default(), |
| 31 | + } |
| 32 | + } |
| 33 | +} |
| 34 | + |
| 35 | +impl<K: Ord> Merge for ChangeSet<K> { |
| 36 | + fn merge(&mut self, other: Self) { |
| 37 | + // merge keyring |
| 38 | + self.keyring.merge(other.keyring); |
| 39 | + |
| 40 | + // merge local chain, tx-graph, indexer |
| 41 | + Merge::merge(&mut self.local_chain, other.local_chain); |
| 42 | + Merge::merge(&mut self.tx_graph, other.tx_graph); |
| 43 | + Merge::merge(&mut self.indexer, other.indexer); |
| 44 | + } |
| 45 | + |
| 46 | + fn is_empty(&self) -> bool { |
| 47 | + self.keyring.is_empty() |
| 48 | + && self.local_chain.is_empty() |
| 49 | + && self.tx_graph.is_empty() |
| 50 | + && self.indexer.is_empty() |
| 51 | + } |
| 52 | +} |
| 53 | + |
| 54 | +#[cfg(feature = "rusqlite")] |
| 55 | +use bdk_chain::rusqlite; |
| 56 | + |
| 57 | +#[cfg(feature = "rusqlite")] |
| 58 | +impl ChangeSet<DescriptorId> { |
| 59 | + /// Schema name for wallet. |
| 60 | + pub const WALLET_SCHEMA_NAME: &'static str = "bdk_wallet"; |
| 61 | + /// Name of table to store wallet metainformation. |
| 62 | + pub const WALLET_TABLE_NAME: &'static str = "bdk_wallet"; |
| 63 | + /// Name of table to store wallet descriptors. |
| 64 | + pub const DESCRIPTORS_TABLE_NAME: &'static str = "bdk_descriptor"; |
| 65 | + |
| 66 | + /// Get v0 sqlite [ChangeSet] schema. |
| 67 | + pub fn schema_v0() -> alloc::string::String { |
| 68 | + format!( |
| 69 | + "CREATE TABLE {} ( \ |
| 70 | + id INTEGER PRIMARY KEY NOT NULL, \ |
| 71 | + network TEXT NOT NULL \ |
| 72 | + ); \ |
| 73 | + CREATE TABLE {} ( \ |
| 74 | + descriptor_id TEXT PRIMARY KEY NOT NULL, \ |
| 75 | + descriptor BLOB NOT NULL \ |
| 76 | + );", |
| 77 | + Self::WALLET_TABLE_NAME, |
| 78 | + Self::DESCRIPTORS_TABLE_NAME, |
| 79 | + ) |
| 80 | + } |
| 81 | + |
| 82 | + /// Initializes tables and returns the aggregate data if the database is non-empty |
| 83 | + /// otherwise returns `Ok(None)`. |
| 84 | + pub fn initialize(db_tx: &rusqlite::Transaction) -> rusqlite::Result<Option<Self>> { |
| 85 | + Self::init_sqlite_tables(db_tx)?; |
| 86 | + let changeset = Self::from_sqlite(db_tx)?; |
| 87 | + |
| 88 | + if changeset.is_empty() { |
| 89 | + Ok(None) |
| 90 | + } else { |
| 91 | + Ok(Some(changeset)) |
| 92 | + } |
| 93 | + } |
| 94 | + |
| 95 | + /// Initialize SQLite tables. |
| 96 | + fn init_sqlite_tables(db_tx: &rusqlite::Transaction) -> rusqlite::Result<()> { |
| 97 | + crate::rusqlite_impl::migrate_schema( |
| 98 | + db_tx, |
| 99 | + Self::WALLET_SCHEMA_NAME, |
| 100 | + &[&Self::schema_v0()], |
| 101 | + )?; |
| 102 | + |
| 103 | + local_chain::ChangeSet::init_sqlite_tables(db_tx)?; |
| 104 | + tx_graph::ChangeSet::<ConfirmationBlockTime>::init_sqlite_tables(db_tx)?; |
| 105 | + keychain_txout::ChangeSet::init_sqlite_tables(db_tx)?; |
| 106 | + |
| 107 | + Ok(()) |
| 108 | + } |
| 109 | + |
| 110 | + /// Construct self by reading all of the SQLite data. This should succeed |
| 111 | + /// even if attempting to read an empty database. |
| 112 | + fn from_sqlite(db_tx: &rusqlite::Transaction) -> rusqlite::Result<Self> { |
| 113 | + use bdk_chain::Impl; |
| 114 | + use rusqlite::OptionalExtension; |
| 115 | + let mut changeset = Self::default(); |
| 116 | + |
| 117 | + let mut keyring = keyring::ChangeSet::default(); |
| 118 | + |
| 119 | + // Read network |
| 120 | + let mut network_stmt = db_tx.prepare(&format!( |
| 121 | + "SELECT network FROM {} WHERE id = 0", |
| 122 | + Self::WALLET_TABLE_NAME, |
| 123 | + ))?; |
| 124 | + let row = network_stmt |
| 125 | + .query_row([], |row| row.get::<_, Impl<Network>>("network")) |
| 126 | + .optional()?; |
| 127 | + if let Some(Impl(network)) = row { |
| 128 | + keyring.network = Some(network); |
| 129 | + } |
| 130 | + |
| 131 | + // Read descriptors |
| 132 | + let mut descriptor_stmt = db_tx.prepare(&format!( |
| 133 | + "SELECT descriptor_id, descriptor FROM {}", |
| 134 | + Self::DESCRIPTORS_TABLE_NAME |
| 135 | + ))?; |
| 136 | + let rows = descriptor_stmt.query_map([], |row| { |
| 137 | + Ok(( |
| 138 | + row.get::<_, Impl<DescriptorId>>("descriptor_id")?, |
| 139 | + row.get::<_, Impl<Descriptor<DescriptorPublicKey>>>("descriptor")?, |
| 140 | + )) |
| 141 | + })?; |
| 142 | + for row in rows { |
| 143 | + let (Impl(did), Impl(descriptor)) = row?; |
| 144 | + keyring.descriptors.insert(did, descriptor); |
| 145 | + } |
| 146 | + |
| 147 | + changeset.keyring = keyring; |
| 148 | + changeset.local_chain = local_chain::ChangeSet::from_sqlite(db_tx)?; |
| 149 | + changeset.tx_graph = tx_graph::ChangeSet::from_sqlite(db_tx)?; |
| 150 | + changeset.indexer = keychain_txout::ChangeSet::from_sqlite(db_tx)?; |
| 151 | + |
| 152 | + Ok(changeset) |
| 153 | + } |
| 154 | + |
| 155 | + /// Persist self to SQLite. |
| 156 | + pub fn persist_to_sqlite(&self, db_tx: &rusqlite::Transaction) -> rusqlite::Result<()> { |
| 157 | + use chain::rusqlite::named_params; |
| 158 | + use chain::Impl; |
| 159 | + |
| 160 | + let keyring = &self.keyring; |
| 161 | + |
| 162 | + // Write network |
| 163 | + let mut network_stmt = db_tx.prepare_cached(&format!( |
| 164 | + "REPLACE INTO {}(id, network) VALUES(:id, :network)", |
| 165 | + Self::WALLET_TABLE_NAME, |
| 166 | + ))?; |
| 167 | + if let Some(network) = keyring.network { |
| 168 | + network_stmt.execute(named_params! { |
| 169 | + ":id": 0, |
| 170 | + ":network": Impl(network), |
| 171 | + })?; |
| 172 | + } |
| 173 | + |
| 174 | + // Write descriptors |
| 175 | + let mut descriptor_stmt = db_tx.prepare_cached(&format!( |
| 176 | + "INSERT OR IGNORE INTO {}(descriptor_id, descriptor) VALUES(:descriptor_id, :descriptor)", |
| 177 | + Self::DESCRIPTORS_TABLE_NAME, |
| 178 | + ))?; |
| 179 | + for (&did, descriptor) in &keyring.descriptors { |
| 180 | + descriptor_stmt.execute(named_params! { |
| 181 | + ":descriptor_id": Impl(did), |
| 182 | + ":descriptor": Impl(descriptor.clone()), |
| 183 | + })?; |
| 184 | + } |
| 185 | + |
| 186 | + self.local_chain.persist_to_sqlite(db_tx)?; |
| 187 | + self.tx_graph.persist_to_sqlite(db_tx)?; |
| 188 | + self.indexer.persist_to_sqlite(db_tx)?; |
| 189 | + |
| 190 | + Ok(()) |
| 191 | + } |
| 192 | +} |
| 193 | + |
| 194 | +impl<K: Ord> From<local_chain::ChangeSet> for ChangeSet<K> { |
| 195 | + fn from(local_chain: local_chain::ChangeSet) -> Self { |
| 196 | + Self { |
| 197 | + local_chain, |
| 198 | + ..Default::default() |
| 199 | + } |
| 200 | + } |
| 201 | +} |
| 202 | + |
| 203 | +impl<K: Ord> From<indexed_tx_graph::ChangeSet<ConfirmationBlockTime, keychain_txout::ChangeSet>> |
| 204 | + for ChangeSet<K> |
| 205 | +{ |
| 206 | + fn from( |
| 207 | + indexed_tx_graph: indexed_tx_graph::ChangeSet< |
| 208 | + ConfirmationBlockTime, |
| 209 | + keychain_txout::ChangeSet, |
| 210 | + >, |
| 211 | + ) -> Self { |
| 212 | + Self { |
| 213 | + tx_graph: indexed_tx_graph.tx_graph, |
| 214 | + indexer: indexed_tx_graph.indexer, |
| 215 | + ..Default::default() |
| 216 | + } |
| 217 | + } |
| 218 | +} |
| 219 | + |
| 220 | +impl<K: Ord> From<keychain_txout::ChangeSet> for ChangeSet<K> { |
| 221 | + fn from(indexer: keychain_txout::ChangeSet) -> Self { |
| 222 | + Self { |
| 223 | + indexer, |
| 224 | + ..Default::default() |
| 225 | + } |
| 226 | + } |
| 227 | +} |
0 commit comments