zcash_client_sqlite/wallet/init/
migrations.rs1mod add_account_birthdays;
11mod add_account_uuids;
12mod add_transaction_views;
13mod add_utxo_account;
14mod addresses_table;
15mod ensure_default_transparent_address;
16mod ensure_orchard_ua_receiver;
17mod ephemeral_addresses;
18mod fix_bad_change_flagging;
19mod fix_broken_commitment_trees;
20mod fix_transparent_received_outputs;
21mod full_account_ids;
22mod initial_setup;
23mod nullifier_map;
24mod orchard_received_notes;
25mod orchard_shardtree;
26mod received_notes_nullable_nf;
27mod receiving_key_scopes;
28mod sapling_memo_consistency;
29mod sent_notes_to_internal;
30mod shardtree_support;
31mod spend_key_available;
32mod support_legacy_sqlite;
33mod transparent_gap_limit_handling;
34mod tx_retrieval_queue;
35mod ufvk_support;
36mod utxos_table;
37mod utxos_to_txos;
38mod v_sapling_shard_unscanned_ranges;
39mod v_transactions_additional_totals;
40mod v_transactions_net;
41mod v_transactions_note_uniqueness;
42mod v_transactions_shielding_balance;
43mod v_transactions_transparent_history;
44mod v_tx_outputs_use_legacy_false;
45mod wallet_summaries;
46
47use std::{rc::Rc, sync::Mutex};
48
49use rand_core::RngCore;
50use rusqlite::{named_params, OptionalExtension};
51use schemerz_rusqlite::RusqliteMigration;
52use secrecy::SecretVec;
53use uuid::Uuid;
54use zcash_address::unified::{Encoding as _, Ufvk};
55use zcash_protocol::consensus;
56
57use crate::util::Clock;
58
59use super::WalletMigrationError;
60
61pub(super) fn all_migrations<
62 P: consensus::Parameters + 'static,
63 C: Clock + Clone + 'static,
64 R: RngCore + Clone + 'static,
65>(
66 params: &P,
67 clock: C,
68 rng: R,
69 seed: Option<Rc<SecretVec<u8>>>,
70) -> Vec<Box<dyn RusqliteMigration<Error = WalletMigrationError>>> {
71 let rng = Rc::new(Mutex::new(rng));
116 vec![
117 Box::new(initial_setup::Migration {}),
118 Box::new(utxos_table::Migration {}),
119 Box::new(ufvk_support::Migration {
120 params: params.clone(),
121 seed: seed.clone(),
122 }),
123 Box::new(addresses_table::Migration {
124 params: params.clone(),
125 }),
126 Box::new(add_utxo_account::Migration {
127 _params: params.clone(),
128 }),
129 Box::new(sent_notes_to_internal::Migration {}),
130 Box::new(add_transaction_views::Migration),
131 Box::new(v_transactions_net::Migration),
132 Box::new(received_notes_nullable_nf::Migration),
133 Box::new(shardtree_support::Migration {
134 params: params.clone(),
135 }),
136 Box::new(nullifier_map::Migration),
137 Box::new(sapling_memo_consistency::Migration {
138 params: params.clone(),
139 }),
140 Box::new(add_account_birthdays::Migration {
141 params: params.clone(),
142 }),
143 Box::new(v_sapling_shard_unscanned_ranges::Migration {
144 params: params.clone(),
145 }),
146 Box::new(wallet_summaries::Migration),
147 Box::new(v_transactions_transparent_history::Migration),
148 Box::new(v_tx_outputs_use_legacy_false::Migration),
149 Box::new(v_transactions_shielding_balance::Migration),
150 Box::new(v_transactions_note_uniqueness::Migration),
151 Box::new(receiving_key_scopes::Migration {
152 params: params.clone(),
153 }),
154 Box::new(full_account_ids::Migration {
155 seed,
156 params: params.clone(),
157 }),
158 Box::new(orchard_shardtree::Migration {
159 params: params.clone(),
160 }),
161 Box::new(orchard_received_notes::Migration),
162 Box::new(ensure_orchard_ua_receiver::Migration {
163 params: params.clone(),
164 }),
165 Box::new(utxos_to_txos::Migration),
166 Box::new(ephemeral_addresses::Migration {
167 params: params.clone(),
168 }),
169 Box::new(spend_key_available::Migration),
170 Box::new(tx_retrieval_queue::Migration {
171 _params: params.clone(),
172 }),
173 Box::new(support_legacy_sqlite::Migration),
174 Box::new(fix_broken_commitment_trees::Migration {
175 params: params.clone(),
176 }),
177 Box::new(fix_bad_change_flagging::Migration),
178 Box::new(add_account_uuids::Migration),
179 Box::new(v_transactions_additional_totals::Migration),
180 Box::new(transparent_gap_limit_handling::Migration {
181 params: params.clone(),
182 _clock: clock.clone(),
183 _rng: rng.clone(),
184 }),
185 Box::new(ensure_default_transparent_address::Migration {
186 _params: params.clone(),
187 }),
188 Box::new(fix_transparent_received_outputs::Migration),
189 ]
190}
191
192#[allow(dead_code)]
198const PUBLIC_MIGRATION_STATES: &[&[Uuid]] = &[
199 V_0_4_0, V_0_6_0, V_0_8_0, V_0_9_0, V_0_10_0, V_0_10_3, V_0_11_0, V_0_11_1, V_0_11_2, V_0_12_0,
200 V_0_13_0, V_0_14_0, V_0_15_0, V_0_16_0, V_0_16_2,
201];
202
203pub const V_0_4_0: &[Uuid] = &[add_transaction_views::MIGRATION_ID];
205
206pub const V_0_6_0: &[Uuid] = &[v_transactions_net::MIGRATION_ID];
208
209pub const V_0_8_0: &[Uuid] = &[
211 nullifier_map::MIGRATION_ID,
212 v_transactions_note_uniqueness::MIGRATION_ID,
213 wallet_summaries::MIGRATION_ID,
214];
215
216pub const V_0_9_0: &[Uuid] = &[
218 nullifier_map::MIGRATION_ID,
219 receiving_key_scopes::MIGRATION_ID,
220 v_transactions_note_uniqueness::MIGRATION_ID,
221 wallet_summaries::MIGRATION_ID,
222];
223
224pub const V_0_10_0: &[Uuid] = &[
226 nullifier_map::MIGRATION_ID,
227 orchard_received_notes::MIGRATION_ID,
228 orchard_shardtree::MIGRATION_ID,
229];
230
231pub const V_0_10_3: &[Uuid] = &[
233 ensure_orchard_ua_receiver::MIGRATION_ID,
234 nullifier_map::MIGRATION_ID,
235 orchard_shardtree::MIGRATION_ID,
236];
237
238pub const V_0_11_0: &[Uuid] = &[
240 ensure_orchard_ua_receiver::MIGRATION_ID,
241 ephemeral_addresses::MIGRATION_ID,
242 nullifier_map::MIGRATION_ID,
243 orchard_shardtree::MIGRATION_ID,
244 spend_key_available::MIGRATION_ID,
245 tx_retrieval_queue::MIGRATION_ID,
246];
247
248pub const V_0_11_1: &[Uuid] = &[tx_retrieval_queue::MIGRATION_ID];
250
251pub const V_0_11_2: &[Uuid] = &[support_legacy_sqlite::MIGRATION_ID];
253
254pub const V_0_12_0: &[Uuid] = &[fix_broken_commitment_trees::MIGRATION_ID];
256
257pub const V_0_13_0: &[Uuid] = &[fix_bad_change_flagging::MIGRATION_ID];
259
260pub const V_0_14_0: &[Uuid] = &[
262 fix_bad_change_flagging::MIGRATION_ID,
263 add_account_uuids::MIGRATION_ID,
264];
265
266pub const V_0_15_0: &[Uuid] = &[
268 fix_bad_change_flagging::MIGRATION_ID,
269 v_transactions_additional_totals::MIGRATION_ID,
270];
271
272const V_0_16_0: &[Uuid] = &[
274 fix_bad_change_flagging::MIGRATION_ID,
275 v_transactions_additional_totals::MIGRATION_ID,
276 transparent_gap_limit_handling::MIGRATION_ID,
277];
278
279const V_0_16_2: &[Uuid] = &[
281 fix_bad_change_flagging::MIGRATION_ID,
282 v_transactions_additional_totals::MIGRATION_ID,
283 ensure_default_transparent_address::MIGRATION_ID,
284];
285
286pub(super) fn verify_network_compatibility<P: consensus::Parameters>(
287 conn: &rusqlite::Connection,
288 params: &P,
289) -> Result<(), WalletMigrationError> {
290 let has_ufvk = conn
294 .query_row(
295 &format!(
296 "SELECT 1 FROM {} WHERE id = :migration_id",
297 super::MIGRATIONS_TABLE
298 ),
299 named_params![":migration_id": &ufvk_support::MIGRATION_ID.as_bytes()[..]],
300 |row| row.get::<_, bool>(0),
301 )
302 .optional()?
303 == Some(true);
304
305 if has_ufvk {
306 let mut fvks_stmt = conn.prepare("SELECT ufvk FROM accounts")?;
307 let mut rows = fvks_stmt.query([])?;
308 while let Some(row) = rows.next()? {
309 let ufvk_str = row.get::<_, String>(0)?;
310 let (network, _) = Ufvk::decode(&ufvk_str).map_err(|e| {
311 WalletMigrationError::CorruptedData(format!("Unable to parse UFVK: {e}"))
312 })?;
313
314 if network != params.network_type() {
315 let network_name = |n| match n {
316 consensus::NetworkType::Main => "mainnet",
317 consensus::NetworkType::Test => "testnet",
318 consensus::NetworkType::Regtest => "regtest",
319 };
320 return Err(WalletMigrationError::CorruptedData(format!(
321 "Network type mismatch: account UFVK is for {} but attempting to initialize for {}.",
322 network_name(network),
323 network_name(params.network_type())
324 )));
325 }
326 }
327 }
328
329 Ok(())
330}
331
332#[cfg(test)]
333mod tests {
334 use std::collections::HashSet;
335
336 use rusqlite::Connection;
337 use secrecy::Secret;
338 use tempfile::NamedTempFile;
339 use uuid::Uuid;
340 use zcash_protocol::consensus::Network;
341
342 use crate::{
343 testing::db::{test_clock, test_rng},
344 wallet::init::WalletMigrator,
345 WalletDb,
346 };
347
348 pub(crate) fn test_migrate(migrations: &[Uuid]) {
351 let data_file = NamedTempFile::new().unwrap();
352 let mut db_data = WalletDb::for_path(
353 data_file.path(),
354 Network::TestNetwork,
355 test_clock(),
356 test_rng(),
357 )
358 .unwrap();
359
360 let seed = [0xab; 32];
361 assert_matches!(
362 WalletMigrator::new()
363 .with_seed(Secret::new(seed.to_vec()))
364 .ignore_seed_relevance()
365 .init_or_migrate_to(&mut db_data, migrations),
366 Ok(_)
367 );
368 }
369
370 #[test]
371 fn migrate_between_releases_without_data() {
372 let data_file = NamedTempFile::new().unwrap();
373 let mut db_data = WalletDb::for_path(
374 data_file.path(),
375 Network::TestNetwork,
376 test_clock(),
377 test_rng(),
378 )
379 .unwrap();
380
381 let seed = [0xab; 32].to_vec();
382
383 let mut prev_state = HashSet::new();
384 let mut ensure_migration_state_changed = |conn: &Connection| {
385 let new_state = conn
386 .prepare_cached("SELECT * FROM schemer_migrations")
387 .unwrap()
388 .query_map([], |row| row.get::<_, [u8; 16]>(0).map(Uuid::from_bytes))
389 .unwrap()
390 .collect::<Result<HashSet<Uuid>, _>>()
391 .unwrap();
392 assert!(prev_state != new_state);
393 prev_state = new_state;
394 };
395
396 let mut prev_leaves: &[Uuid] = &[];
397 for migrations in super::PUBLIC_MIGRATION_STATES {
398 assert_matches!(
399 WalletMigrator::new()
400 .with_seed(Secret::new(seed.clone()))
401 .ignore_seed_relevance()
402 .init_or_migrate_to(&mut db_data, migrations),
403 Ok(_)
404 );
405
406 if migrations.iter().any(|m| !prev_leaves.contains(m)) {
410 ensure_migration_state_changed(&db_data.conn);
411 }
412
413 prev_leaves = *migrations;
414 }
415
416 assert_matches!(
419 WalletMigrator::new()
420 .with_seed(Secret::new(seed))
421 .ignore_seed_relevance()
422 .init_or_migrate(&mut db_data),
423 Ok(_)
424 );
425 }
427}