@@ -12,10 +12,10 @@ use alloc::{
1212} ;
1313use bitcoin:: consensus:: { Decodable , Encodable } ;
1414use rusqlite;
15- use rusqlite:: named_params;
1615use rusqlite:: types:: { FromSql , FromSqlError , FromSqlResult , ToSql , ToSqlOutput , ValueRef } ;
1716use rusqlite:: OptionalExtension ;
1817use rusqlite:: Transaction ;
18+ use rusqlite:: { named_params, Connection } ;
1919
2020/// Table name for schemas.
2121pub 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) ) ]
664711mod 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