Skip to content

Commit fde4e87

Browse files
committed
feat[rusqlite]: add get_pre_1_wallet_keychains migration helper
1 parent be356d5 commit fde4e87

File tree

1 file changed

+110
-1
lines changed

1 file changed

+110
-1
lines changed

crates/chain/src/rusqlite_impl.rs

Lines changed: 110 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ use alloc::{
1212
};
1313
use bitcoin::consensus::{Decodable, Encodable};
1414
use rusqlite;
15-
use rusqlite::named_params;
1615
use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
1716
use rusqlite::OptionalExtension;
1817
use rusqlite::Transaction;
18+
use rusqlite::{named_params, Connection};
1919

2020
/// Table name for schemas.
2121
pub const SCHEMAS_TABLE_NAME: &str = "bdk_schemas";
@@ -659,6 +659,53 @@ impl keychain_txout::ChangeSet {
659659
}
660660
}
661661

662+
// pre-1.0 sqlite database migration helper functions
663+
664+
/// `Pre1WalletKeychain` represents a structure that holds the keychain details
665+
/// and metadata required for managing a wallet's keys.
666+
#[derive(Debug)]
667+
pub struct Pre1WalletKeychain {
668+
/// The name of the wallet keychains, "External" or "Internal".
669+
pub keychain: String,
670+
/// The index of the last derived key in the wallet keychain.
671+
pub last_derivation_index: u32,
672+
/// Checksum of the keychain descriptor, it must match the corresponding post-1.0 bdk wallet
673+
/// descriptor checksum.
674+
pub checksum: Vec<u8>,
675+
}
676+
677+
/// Retrieves a list of [`Pre1WalletKeychain`] objects from a pre-1.0 bdk SQLite database.
678+
///
679+
/// This function uses a connection to a pre-1.0 bdk wallet SQLite database to execute a query that
680+
/// retrieves data from two tables (`last_derivation_indices` and `checksums`) and maps the
681+
/// resulting rows to a list of `Pre1WalletKeychain` objects.
682+
pub fn get_pre_1_wallet_keychains(
683+
conn: &mut Connection,
684+
) -> Result<Vec<Pre1WalletKeychain>, rusqlite::Error> {
685+
let db_tx = conn.transaction()?;
686+
let mut statement = db_tx.prepare(
687+
"SELECT idx.keychain AS keychain, value, checksum FROM last_derivation_indices AS idx \
688+
JOIN checksums AS chk ON idx.keychain = chk.keychain",
689+
)?;
690+
let row_iter = statement.query_map([], |row| {
691+
Ok((
692+
row.get::<_, String>("keychain")?,
693+
row.get::<_, u32>("value")?,
694+
row.get::<_, Vec<u8>>("checksum")?,
695+
))
696+
})?;
697+
let mut keychains = vec![];
698+
for row in row_iter {
699+
let (keychain, value, checksum) = row?;
700+
keychains.push(Pre1WalletKeychain {
701+
keychain: keychain.trim_matches('"').to_string(),
702+
last_derivation_index: value,
703+
checksum,
704+
})
705+
}
706+
Ok(keychains)
707+
}
708+
662709
#[cfg(test)]
663710
#[cfg_attr(coverage_nightly, coverage(off))]
664711
mod test {
@@ -903,4 +950,66 @@ mod test {
903950

904951
Ok(())
905952
}
953+
954+
#[test]
955+
fn test_get_pre_1_wallet_keychains() -> anyhow::Result<()> {
956+
let mut conn = rusqlite::Connection::open_in_memory()?;
957+
let external_checksum = vec![0x01u8, 0x02u8, 0x03u8, 0x04u8];
958+
let internal_checksum = vec![0x05u8, 0x06u8, 0x07u8, 0x08u8];
959+
// Init tables
960+
{
961+
// Create pre-1.0 bdk sqlite schema
962+
conn.execute_batch(
963+
"CREATE TABLE last_derivation_indices (keychain TEXT, value INTEGER);
964+
CREATE UNIQUE INDEX idx_indices_keychain ON last_derivation_indices(keychain);
965+
CREATE TABLE checksums (keychain TEXT, checksum BLOB);
966+
CREATE INDEX idx_checksums_keychain ON checksums(keychain);",
967+
)?;
968+
// Insert test data
969+
conn.execute(
970+
"INSERT INTO last_derivation_indices (keychain, value) VALUES (?, ?)",
971+
rusqlite::params!["\"External\"", 42],
972+
)?;
973+
conn.execute(
974+
"INSERT INTO checksums (keychain, checksum) VALUES (?, ?)",
975+
rusqlite::params!["\"External\"", external_checksum],
976+
)?;
977+
conn.execute(
978+
"INSERT INTO last_derivation_indices (keychain, value) VALUES (?, ?)",
979+
rusqlite::params!["\"Internal\"", 21],
980+
)?;
981+
conn.execute(
982+
"INSERT INTO checksums (keychain, checksum) VALUES (?, ?)",
983+
rusqlite::params!["\"Internal\"", internal_checksum],
984+
)?;
985+
}
986+
// test with a 2 keychain wallet
987+
let result = get_pre_1_wallet_keychains(&mut conn)?;
988+
assert_eq!(result.len(), 2);
989+
assert_eq!(result[0].keychain, "External");
990+
assert_eq!(result[0].last_derivation_index, 42);
991+
assert_eq!(result[0].checksum, external_checksum);
992+
assert_eq!(result[1].keychain, "Internal");
993+
assert_eq!(result[1].last_derivation_index, 21);
994+
assert_eq!(result[1].checksum, internal_checksum);
995+
// delete "Internal" descriptor
996+
{
997+
conn.execute(
998+
"DELETE FROM last_derivation_indices WHERE keychain = ?",
999+
rusqlite::params!["\"Internal\""],
1000+
)?;
1001+
conn.execute(
1002+
"DELETE FROM checksums WHERE keychain = ?",
1003+
rusqlite::params!["\"Internal\""],
1004+
)?;
1005+
}
1006+
// test with a 1 keychain wallet
1007+
let result = get_pre_1_wallet_keychains(&mut conn)?;
1008+
assert_eq!(result.len(), 1);
1009+
assert_eq!(result[0].keychain, "External");
1010+
assert_eq!(result[0].last_derivation_index, 42);
1011+
assert_eq!(result[0].checksum, external_checksum);
1012+
1013+
Ok(())
1014+
}
9061015
}

0 commit comments

Comments
 (0)