zcash_client_sqlite/wallet/init/
migrations.rs

1//! Migrations for the `zcash_client_sqlite` wallet database.
2//!
3//! The constants in this module cover all states of the migration DAG that have been
4//! exposed in a public crate release, in the order that crate users would have
5//! encountered them.
6//!
7//! Omitted versions had the same migration state as the first prior version that is
8//! included.
9
10mod 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    //                                   initial_setup
72    //                                   /           \
73    //                          utxos_table         ufvk_support
74    //                             |                 /         \
75    //                             |    addresses_table   sent_notes_to_internal
76    //                             |          /                /
77    //                           add_utxo_account             /
78    //                                        \              /
79    //                                     add_transaction_views
80    //                                               |
81    //                                       v_transactions_net
82    //                                               |
83    //                                            received_notes_nullable_nf----------------------
84    //                                            /           |                                   \
85    //                                           /            |                                    \
86    //           --------------- shardtree_support    sapling_memo_consistency                    nullifier_map
87    //          /                     /           \                       \                                  |
88    // orchard_shardtree   add_account_birthdays   receiving_key_scopes   v_transactions_transparent_history |
89    //   |                    |                 \            |                     |                         |
90    //   |   v_sapling_shard_unscanned_ranges    \           |       v_tx_outputs_use_legacy_false           |
91    //   |                    |                   \          |                     |                         |
92    //   |            wallet_summaries             \         |      v_transactions_shielding_balance         |
93    //   |                    \                     \        |                     |                        /
94    //    \                    \                     \       |      v_transactions_note_uniqueness         /
95    //     \                    \                     \      |        /                                   /
96    //      \                    -------------------- full_account_ids                                   /
97    //       \                                        /               \                                 /
98    //        \                         orchard_received_notes        spend_key_available              /
99    //         \                             /         \                      /                       /
100    //          \     ensure_orchard_ua_receiver     utxos_to_txos           /                       /
101    //           \                          \              |                /                       /
102    //            \                          \     ephemeral_addresses     /                       /
103    //             \                          \            |              /                       /
104    //              ------------------------------ tx_retrieval_queue ----------------------------
105    //                                                     |
106    //                                            support_legacy_sqlite
107    //                                               /              \
108    //                         fix_broken_commitment_trees         add_account_uuids
109    //                             /                                /             \
110    //          fix_bad_change_flagging      transparent_gap_limit_handling    v_transactions_additional_totals
111    //                             \                       |                      /
112    //                              \      ensure_default_transparent_address    /
113    //                               \                     |                    /
114    //                                `---- fix_transparent_received_outputs --'
115    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/// All states of the migration DAG that have been exposed in a public crate release, in
193/// the order that crate users would have encountered them.
194///
195/// Omitted versions had the same migration state as the first prior version that is
196/// included.
197#[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
203/// Leaf migrations in the 0.4.0 release.
204pub const V_0_4_0: &[Uuid] = &[add_transaction_views::MIGRATION_ID];
205
206/// Leaf migrations in the 0.6.0 release.
207pub const V_0_6_0: &[Uuid] = &[v_transactions_net::MIGRATION_ID];
208
209/// Leaf migrations in the 0.8.0 release.
210pub 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
216/// Leaf migrations in the 0.9.0 release.
217pub 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
224/// Leaf migrations in the 0.10.0 release.
225pub const V_0_10_0: &[Uuid] = &[
226    nullifier_map::MIGRATION_ID,
227    orchard_received_notes::MIGRATION_ID,
228    orchard_shardtree::MIGRATION_ID,
229];
230
231/// Leaf migrations in the 0.10.3 release.
232pub 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
238/// Leaf migrations in the 0.11.0 release.
239pub 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
248/// Leaf migrations in the 0.11.1 release.
249pub const V_0_11_1: &[Uuid] = &[tx_retrieval_queue::MIGRATION_ID];
250
251/// Leaf migrations in the 0.11.2 release.
252pub const V_0_11_2: &[Uuid] = &[support_legacy_sqlite::MIGRATION_ID];
253
254/// Leaf migrations in the 0.12.0 release.
255pub const V_0_12_0: &[Uuid] = &[fix_broken_commitment_trees::MIGRATION_ID];
256
257/// Leaf migrations in the 0.13.0 release.
258pub const V_0_13_0: &[Uuid] = &[fix_bad_change_flagging::MIGRATION_ID];
259
260/// Leaf migrations in the 0.14.0 release.
261pub const V_0_14_0: &[Uuid] = &[
262    fix_bad_change_flagging::MIGRATION_ID,
263    add_account_uuids::MIGRATION_ID,
264];
265
266/// Leaf migrations in the 0.15.0 release.
267pub const V_0_15_0: &[Uuid] = &[
268    fix_bad_change_flagging::MIGRATION_ID,
269    v_transactions_additional_totals::MIGRATION_ID,
270];
271
272/// Leaf migrations in the 0.16.0 release.
273const 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
279/// Leaf migrations in the 0.16.2 release.
280const 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    // Ensure that the `ufvk_support` migration has been applied; if it hasn't, we won't be able to
291    // validate that the UFVKs in the wallet correspond to the network type that the wallet is
292    // being migrated for.
293    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    /// Tests that we can migrate from a completely empty wallet database to the target
349    /// migrations.
350    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 we have any new leaves, ensure the migration state changed. This lets us
407            // represent releases that changed the graph edges without introducing any new
408            // migrations.
409            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        // Now check that we can migrate from the last public release to the current
417        // migration state in this branch.
418        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        // We don't ensure that the migration state changed, because it may not have.
426    }
427}