@@ -12,7 +12,7 @@ use alloc::{
1212} ;
1313use bitcoin:: consensus:: { Decodable , Encodable } ;
1414use rusqlite;
15- use rusqlite:: named_params;
15+ use rusqlite:: { named_params, Connection } ;
1616use rusqlite:: types:: { FromSql , FromSqlError , FromSqlResult , ToSql , ToSqlOutput , ValueRef } ;
1717use rusqlite:: OptionalExtension ;
1818use 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) ) ]
664710mod 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