|
| 1 | +// Bitcoin Dev Kit |
| 2 | +// |
| 3 | +// Copyright (c) 2020-2026 Bitcoin Dev Kit Developers |
| 4 | +// |
| 5 | +// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE |
| 6 | +// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
| 7 | +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option. |
| 8 | +// You may not use this file except in accordance with one or both of these |
| 9 | +// licenses. |
| 10 | + |
| 11 | +//! Migration utilities for upgrading between BDK library versions. |
| 12 | +//! |
| 13 | +//! This module provides helper functions and types to assist users in migrating wallet data |
| 14 | +//! when upgrading between major versions of the `bdk_wallet` crate. |
| 15 | +
|
| 16 | +#[cfg(feature = "rusqlite")] |
| 17 | +use crate::rusqlite::{self, Connection}; |
| 18 | +#[cfg(feature = "rusqlite")] |
| 19 | +use alloc::{vec::Vec, string::String, string::ToString}; |
| 20 | + |
| 21 | +// pre-1.0 sqlite database migration helper functions |
| 22 | + |
| 23 | +/// `Pre1WalletKeychain` represents a structure that holds the keychain details |
| 24 | +/// and metadata required for managing a wallet's keys. |
| 25 | +#[cfg(feature = "rusqlite")] |
| 26 | +#[derive(Debug)] |
| 27 | +pub struct Pre1WalletKeychain { |
| 28 | + /// The name of the wallet keychains, "External" or "Internal". |
| 29 | + pub keychain: String, |
| 30 | + /// The index of the last derived key in the wallet keychain. |
| 31 | + pub last_derivation_index: u32, |
| 32 | + /// Checksum of the keychain descriptor, it must match the corresponding post-1.0 bdk wallet |
| 33 | + /// descriptor checksum. |
| 34 | + pub checksum: Vec<u8>, |
| 35 | +} |
| 36 | + |
| 37 | +/// Retrieves a list of [`Pre1WalletKeychain`] objects from a pre-1.0 bdk SQLite database. |
| 38 | +/// |
| 39 | +/// This function uses a connection to a pre-1.0 bdk wallet SQLite database to execute a query that |
| 40 | +/// retrieves data from two tables (`last_derivation_indices` and `checksums`) and maps the |
| 41 | +/// resulting rows to a list of `Pre1WalletKeychain` objects. |
| 42 | +#[cfg(feature = "rusqlite")] |
| 43 | +pub fn get_pre_1_wallet_keychains( |
| 44 | + conn: &mut Connection, |
| 45 | +) -> Result<Vec<Pre1WalletKeychain>, rusqlite::Error> { |
| 46 | + let db_tx = conn.transaction()?; |
| 47 | + let mut statement = db_tx.prepare( |
| 48 | + "SELECT idx.keychain AS keychain, value, checksum FROM last_derivation_indices AS idx \ |
| 49 | + JOIN checksums AS chk ON idx.keychain = chk.keychain", |
| 50 | + )?; |
| 51 | + let row_iter = statement.query_map([], |row| { |
| 52 | + Ok(( |
| 53 | + row.get::<_, String>("keychain")?, |
| 54 | + row.get::<_, u32>("value")?, |
| 55 | + row.get::<_, Vec<u8>>("checksum")?, |
| 56 | + )) |
| 57 | + })?; |
| 58 | + let mut keychains = vec![]; |
| 59 | + for row in row_iter { |
| 60 | + let (keychain, value, checksum) = row?; |
| 61 | + keychains.push(Pre1WalletKeychain { |
| 62 | + keychain: keychain.trim_matches('"').to_string(), |
| 63 | + last_derivation_index: value, |
| 64 | + checksum, |
| 65 | + }) |
| 66 | + } |
| 67 | + Ok(keychains) |
| 68 | +} |
| 69 | + |
| 70 | +#[cfg(test)] |
| 71 | +mod test { |
| 72 | + #[cfg(feature = "rusqlite")] |
| 73 | + use crate::rusqlite::{self, Connection}; |
| 74 | + |
| 75 | + #[cfg(feature = "rusqlite")] |
| 76 | + #[test] |
| 77 | + fn test_get_pre_1_wallet_keychains() -> anyhow::Result<()> { |
| 78 | + let mut conn = Connection::open_in_memory()?; |
| 79 | + let external_checksum = vec![0x01u8, 0x02u8, 0x03u8, 0x04u8]; |
| 80 | + let internal_checksum = vec![0x05u8, 0x06u8, 0x07u8, 0x08u8]; |
| 81 | + // Init tables |
| 82 | + { |
| 83 | + // Create pre-1.0 bdk sqlite schema |
| 84 | + conn.execute_batch( |
| 85 | + "CREATE TABLE last_derivation_indices (keychain TEXT, value INTEGER); |
| 86 | + CREATE UNIQUE INDEX idx_indices_keychain ON last_derivation_indices(keychain); |
| 87 | + CREATE TABLE checksums (keychain TEXT, checksum BLOB); |
| 88 | + CREATE INDEX idx_checksums_keychain ON checksums(keychain);", |
| 89 | + )?; |
| 90 | + // Insert test data |
| 91 | + conn.execute( |
| 92 | + "INSERT INTO last_derivation_indices (keychain, value) VALUES (?, ?)", |
| 93 | + rusqlite::params!["\"External\"", 42], |
| 94 | + )?; |
| 95 | + conn.execute( |
| 96 | + "INSERT INTO checksums (keychain, checksum) VALUES (?, ?)", |
| 97 | + rusqlite::params!["\"External\"", external_checksum], |
| 98 | + )?; |
| 99 | + conn.execute( |
| 100 | + "INSERT INTO last_derivation_indices (keychain, value) VALUES (?, ?)", |
| 101 | + rusqlite::params!["\"Internal\"", 21], |
| 102 | + )?; |
| 103 | + conn.execute( |
| 104 | + "INSERT INTO checksums (keychain, checksum) VALUES (?, ?)", |
| 105 | + rusqlite::params!["\"Internal\"", internal_checksum], |
| 106 | + )?; |
| 107 | + } |
| 108 | + |
| 109 | + // test with a 2 keychain wallet |
| 110 | + let result = super::get_pre_1_wallet_keychains(&mut conn)?; |
| 111 | + assert_eq!(result.len(), 2); |
| 112 | + assert_eq!(result[0].keychain, "External"); |
| 113 | + assert_eq!(result[0].last_derivation_index, 42); |
| 114 | + assert_eq!(result[0].checksum, external_checksum); |
| 115 | + assert_eq!(result[1].keychain, "Internal"); |
| 116 | + assert_eq!(result[1].last_derivation_index, 21); |
| 117 | + assert_eq!(result[1].checksum, internal_checksum); |
| 118 | + // delete "Internal" descriptor |
| 119 | + { |
| 120 | + conn.execute( |
| 121 | + "DELETE FROM last_derivation_indices WHERE keychain = ?", |
| 122 | + rusqlite::params!["\"Internal\""], |
| 123 | + )?; |
| 124 | + conn.execute( |
| 125 | + "DELETE FROM checksums WHERE keychain = ?", |
| 126 | + rusqlite::params!["\"Internal\""], |
| 127 | + )?; |
| 128 | + } |
| 129 | + // test with a 1 keychain wallet |
| 130 | + let result = super::get_pre_1_wallet_keychains(&mut conn)?; |
| 131 | + assert_eq!(result.len(), 1); |
| 132 | + assert_eq!(result[0].keychain, "External"); |
| 133 | + assert_eq!(result[0].last_derivation_index, 42); |
| 134 | + assert_eq!(result[0].checksum, external_checksum); |
| 135 | + |
| 136 | + Ok(()) |
| 137 | + } |
| 138 | +} |
0 commit comments