Skip to content

Commit 4751fac

Browse files
committed
feat[rusqlite]: add get_pre_1_wallet_keychains migration helper
1 parent 8f8a8e9 commit 4751fac

File tree

2 files changed

+139
-0
lines changed

2 files changed

+139
-0
lines changed

src/wallet/migration.rs

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
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+
}

src/wallet/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ mod persisted;
5959
pub mod signer;
6060
pub mod tx_builder;
6161
pub(crate) mod utils;
62+
pub mod migration;
6263

6364
use crate::collections::{BTreeMap, HashMap, HashSet};
6465
use crate::descriptor::{

0 commit comments

Comments
 (0)