Skip to content

Commit cdc74eb

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

File tree

1 file changed

+95
-1
lines changed

1 file changed

+95
-1
lines changed

crates/chain/src/rusqlite_impl.rs

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use alloc::{
1212
};
1313
use bitcoin::consensus::{Decodable, Encodable};
1414
use rusqlite;
15-
use rusqlite::named_params;
15+
use rusqlite::{named_params, Connection};
1616
use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
1717
use rusqlite::OptionalExtension;
1818
use rusqlite::Transaction;
@@ -659,6 +659,52 @@ 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+
let row_iter = statement.query_map([], |row| {
690+
Ok((
691+
row.get::<_, String>("keychain")?,
692+
row.get::<_, u32>("value")?,
693+
row.get::<_, Vec<u8>>("checksum")?,
694+
))
695+
})?;
696+
let mut keychains = vec![];
697+
for row in row_iter {
698+
let (keychain, value, checksum) = row?;
699+
keychains.push(Pre1WalletKeychain {
700+
keychain,
701+
last_derivation_index: value,
702+
checksum,
703+
})
704+
}
705+
Ok(keychains)
706+
}
707+
662708
#[cfg(test)]
663709
#[cfg_attr(coverage_nightly, coverage(off))]
664710
mod test {
@@ -903,4 +949,52 @@ mod test {
903949

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

0 commit comments

Comments
 (0)