@@ -897,6 +897,20 @@ mod test {
897897
898898 use bdk_testenv:: { block_id, hash, utils} ;
899899
900+ #[ cfg( feature = "wallet" ) ]
901+ use bdk_wallet:: {
902+ KeychainKind , LoadError , LoadMismatch , LoadWithPersistError , Wallet ,
903+ bitcoin:: { constants:: ChainHash , key:: Secp256k1 } ,
904+ descriptor:: IntoWalletDescriptor ,
905+ test_utils:: {
906+ get_test_tr_single_sig_xprv, get_test_tr_single_sig_xprv_and_change_desc,
907+ get_test_wpkh, insert_anchor, insert_tx,
908+ } ,
909+ } ;
910+
911+ #[ cfg( feature = "wallet" ) ]
912+ use assert_matches:: assert_matches;
913+
900914 #[ cfg( feature = "wallet" ) ]
901915 const DESCRIPTORS : [ & str ; 4 ] = [
902916 "tr([5940b9b9/86'/0'/0']tpubDDVNqmq75GNPWQ9UNKfP43UwjaHU4GYfoPavojQbfpyfZp2KetWgjGBRRAy4tYCrAA6SB11mhQAkqxjh1VtQHyKwT4oYxpwLaGHvoKmtxZf/1/*)#ypcpw2dr" ,
@@ -2159,4 +2173,313 @@ mod test {
21592173 store2. read_wallet ( & mut changeset_read) . unwrap ( ) ;
21602174 assert_eq ! ( changeset_read, changeset2) ;
21612175 }
2176+
2177+ #[ cfg( feature = "wallet" ) ]
2178+ #[ test]
2179+ fn wallet_is_persisted ( ) {
2180+ use bdk_chain:: keychain_txout:: DEFAULT_LOOKAHEAD ;
2181+
2182+ let tmpfile = NamedTempFile :: new ( ) . unwrap ( ) ;
2183+ let db = Arc :: new ( create_db ( tmpfile. path ( ) ) ) ;
2184+ let ( external_desc, internal_desc) = get_test_tr_single_sig_xprv_and_change_desc ( ) ;
2185+
2186+ // create new wallet
2187+ let wallet_spk_index = {
2188+ let mut store = create_test_store ( db. clone ( ) , "wallet" ) ;
2189+ let mut wallet = Wallet :: create ( external_desc, internal_desc)
2190+ . network ( Network :: Testnet )
2191+ . use_spk_cache ( true )
2192+ . create_wallet ( & mut store)
2193+ . expect ( "should create wallet" ) ;
2194+ wallet. reveal_next_address ( KeychainKind :: External ) ;
2195+
2196+ // persist new wallet changes
2197+ assert ! (
2198+ wallet. persist( & mut store) . expect( "should persist" ) ,
2199+ "must write"
2200+ ) ;
2201+ wallet. spk_index ( ) . clone ( )
2202+ } ;
2203+
2204+ // recover wallet
2205+ {
2206+ let mut store = create_test_store ( db. clone ( ) , "wallet" ) ;
2207+ let wallet = Wallet :: load ( )
2208+ . descriptor ( KeychainKind :: External , Some ( external_desc) )
2209+ . descriptor ( KeychainKind :: Internal , Some ( internal_desc) )
2210+ . check_network ( Network :: Testnet )
2211+ . load_wallet ( & mut store)
2212+ . expect ( "should load wallet" )
2213+ . expect ( "wallet must exist" ) ;
2214+
2215+ assert_eq ! ( wallet. network( ) , Network :: Testnet ) ;
2216+ assert_eq ! (
2217+ wallet. spk_index( ) . keychains( ) . collect:: <Vec <_>>( ) ,
2218+ wallet_spk_index. keychains( ) . collect:: <Vec <_>>( )
2219+ ) ;
2220+ assert_eq ! (
2221+ wallet. spk_index( ) . last_revealed_indices( ) ,
2222+ wallet_spk_index. last_revealed_indices( )
2223+ ) ;
2224+ let secp = Secp256k1 :: new ( ) ;
2225+ assert_eq ! (
2226+ * wallet. public_descriptor( KeychainKind :: External ) ,
2227+ external_desc
2228+ . into_wallet_descriptor( & secp, wallet. network( ) )
2229+ . unwrap( )
2230+ . 0
2231+ ) ;
2232+ }
2233+ // Test SPK cache
2234+ {
2235+ let mut store = create_test_store ( db. clone ( ) , "wallet" ) ;
2236+ let mut wallet = Wallet :: load ( )
2237+ . check_network ( Network :: Testnet )
2238+ . use_spk_cache ( true )
2239+ . load_wallet ( & mut store)
2240+ . expect ( "should load wallet" )
2241+ . expect ( "wallet must exist" ) ;
2242+
2243+ let external_did = wallet
2244+ . public_descriptor ( KeychainKind :: External )
2245+ . descriptor_id ( ) ;
2246+ let internal_did = wallet
2247+ . public_descriptor ( KeychainKind :: Internal )
2248+ . descriptor_id ( ) ;
2249+
2250+ assert ! ( wallet. staged( ) . is_none( ) ) ;
2251+
2252+ let _addr = wallet. reveal_next_address ( KeychainKind :: External ) ;
2253+ let cs = wallet. staged ( ) . expect ( "we should have staged a changeset" ) ;
2254+ assert ! ( !cs. indexer. spk_cache. is_empty( ) , "failed to cache spks" ) ;
2255+ assert_eq ! ( cs. indexer. spk_cache. len( ) , 2 , "we persisted two keychains" ) ;
2256+ let spk_cache: & BTreeMap < u32 , ScriptBuf > =
2257+ cs. indexer . spk_cache . get ( & external_did) . unwrap ( ) ;
2258+ assert_eq ! ( spk_cache. len( ) as u32 , 1 + 1 + DEFAULT_LOOKAHEAD ) ;
2259+ assert_eq ! ( spk_cache. keys( ) . last( ) , Some ( & 26 ) ) ;
2260+ let spk_cache = cs. indexer . spk_cache . get ( & internal_did) . unwrap ( ) ;
2261+ assert_eq ! ( spk_cache. len( ) as u32 , DEFAULT_LOOKAHEAD ) ;
2262+ assert_eq ! ( spk_cache. keys( ) . last( ) , Some ( & 24 ) ) ;
2263+ // Clear the stage
2264+ let _ = wallet. take_staged ( ) ;
2265+ let _addr = wallet. reveal_next_address ( KeychainKind :: Internal ) ;
2266+ let cs = wallet. staged ( ) . unwrap ( ) ;
2267+ assert_eq ! ( cs. indexer. spk_cache. len( ) , 1 ) ;
2268+ let spk_cache = cs. indexer . spk_cache . get ( & internal_did) . unwrap ( ) ;
2269+ assert_eq ! ( spk_cache. len( ) , 1 ) ;
2270+ assert_eq ! ( spk_cache. keys( ) . next( ) , Some ( & 25 ) ) ;
2271+ }
2272+ // SPK cache requires load params
2273+ {
2274+ let mut store = create_test_store ( db. clone ( ) , "wallet" ) ;
2275+ let mut wallet = Wallet :: load ( )
2276+ . check_network ( Network :: Testnet )
2277+ // .use_spk_cache(false)
2278+ . load_wallet ( & mut store)
2279+ . expect ( "should load wallet" )
2280+ . expect ( "wallet must exist" ) ;
2281+
2282+ let internal_did = wallet
2283+ . public_descriptor ( KeychainKind :: Internal )
2284+ . descriptor_id ( ) ;
2285+
2286+ assert ! ( wallet. staged( ) . is_none( ) ) ;
2287+
2288+ let _addr = wallet. reveal_next_address ( KeychainKind :: Internal ) ;
2289+ let cs = wallet. staged ( ) . expect ( "we should have staged a changeset" ) ;
2290+ assert_eq ! ( cs. indexer. last_revealed. get( & internal_did) , Some ( & 0 ) ) ;
2291+ assert ! (
2292+ cs. indexer. spk_cache. is_empty( ) ,
2293+ "we didn't set `use_spk_cache`"
2294+ ) ;
2295+ }
2296+ }
2297+
2298+ #[ cfg( feature = "wallet" ) ]
2299+ #[ test]
2300+ fn wallet_load_checks ( ) {
2301+ let tmpfile = NamedTempFile :: new ( ) . unwrap ( ) ;
2302+ let db = Arc :: new ( create_db ( tmpfile. path ( ) ) ) ;
2303+ let network = Network :: Testnet ;
2304+ let ( external_desc, internal_desc) = get_test_tr_single_sig_xprv_and_change_desc ( ) ;
2305+
2306+ // create new wallet
2307+
2308+ let _ = Wallet :: create ( external_desc, internal_desc)
2309+ . network ( network)
2310+ . create_wallet ( & mut create_test_store ( db. clone ( ) , "wallet" ) )
2311+ . expect ( "wallet should get created" ) ;
2312+
2313+ assert_matches ! (
2314+ Wallet :: load( )
2315+ . check_network( Network :: Regtest )
2316+ . load_wallet( & mut create_test_store( db. clone( ) , "wallet" ) ) ,
2317+ Err ( LoadWithPersistError :: InvalidChangeSet ( LoadError :: Mismatch (
2318+ LoadMismatch :: Network {
2319+ loaded: Network :: Testnet ,
2320+ expected: Network :: Regtest ,
2321+ }
2322+ ) ) ) ,
2323+ "unexpected network check result: Regtest (check) is not Testnet (loaded)" ,
2324+ ) ;
2325+
2326+ let mainnet_hash = BlockHash :: from_byte_array ( ChainHash :: BITCOIN . to_bytes ( ) ) ;
2327+ assert_matches ! (
2328+ Wallet :: load( )
2329+ . check_genesis_hash( mainnet_hash)
2330+ . load_wallet( & mut create_test_store( db. clone( ) , "wallet" ) ) ,
2331+ Err ( LoadWithPersistError :: InvalidChangeSet ( LoadError :: Mismatch (
2332+ LoadMismatch :: Genesis { .. }
2333+ ) ) ) ,
2334+ "unexpected genesis hash check result: mainnet hash (check) is not testnet hash (loaded)" ,
2335+ ) ;
2336+ assert_matches ! (
2337+ Wallet :: load( )
2338+ . descriptor( KeychainKind :: External , Some ( internal_desc) )
2339+ . load_wallet( & mut create_test_store( db. clone( ) , "wallet" ) ) ,
2340+ Err ( LoadWithPersistError :: InvalidChangeSet ( LoadError :: Mismatch (
2341+ LoadMismatch :: Descriptor { .. }
2342+ ) ) ) ,
2343+ "unexpected descriptors check result" ,
2344+ ) ;
2345+ assert_matches ! (
2346+ Wallet :: load( )
2347+ . descriptor( KeychainKind :: External , Option :: <& str >:: None )
2348+ . load_wallet( & mut create_test_store( db. clone( ) , "wallet" ) ) ,
2349+ Err ( LoadWithPersistError :: InvalidChangeSet ( LoadError :: Mismatch (
2350+ LoadMismatch :: Descriptor { .. }
2351+ ) ) ) ,
2352+ "unexpected descriptors check result" ,
2353+ ) ;
2354+ // check setting keymaps
2355+ let ( _, external_keymap) =
2356+ <Descriptor < DescriptorPublicKey > >:: parse_descriptor ( & Secp256k1 :: new ( ) , external_desc)
2357+ . expect ( "failed to parse descriptor" ) ;
2358+ let ( _, internal_keymap) =
2359+ <Descriptor < DescriptorPublicKey > >:: parse_descriptor ( & Secp256k1 :: new ( ) , internal_desc)
2360+ . expect ( "failed to parse descriptor" ) ;
2361+ let wallet = Wallet :: load ( )
2362+ . keymap ( KeychainKind :: External , external_keymap)
2363+ . keymap ( KeychainKind :: Internal , internal_keymap)
2364+ . load_wallet ( & mut create_test_store ( db. clone ( ) , "wallet" ) )
2365+ . expect ( "db should not fail" )
2366+ . expect ( "wallet was persisted" ) ;
2367+ for keychain in [ KeychainKind :: External , KeychainKind :: Internal ] {
2368+ let keymap = wallet. get_signers ( keychain) . as_key_map ( wallet. secp_ctx ( ) ) ;
2369+ assert ! (
2370+ !keymap. is_empty( ) ,
2371+ "load should populate keymap for keychain {keychain:?}"
2372+ ) ;
2373+ }
2374+ }
2375+
2376+ #[ cfg( feature = "wallet" ) ]
2377+ #[ test]
2378+ fn wallet_should_persist_anchors_and_recover ( ) {
2379+ use bdk_chain:: ChainPosition ;
2380+ let tmpfile = NamedTempFile :: new ( ) . unwrap ( ) ;
2381+ let db = Arc :: new ( create_db ( tmpfile. path ( ) ) ) ;
2382+ let mut store = create_test_store ( db. clone ( ) , "wallet" ) ;
2383+
2384+ let desc = get_test_tr_single_sig_xprv ( ) ;
2385+ let mut wallet = Wallet :: create_single ( desc)
2386+ . network ( Network :: Testnet )
2387+ . create_wallet ( & mut store)
2388+ . unwrap ( ) ;
2389+
2390+ let small_output_tx = Transaction {
2391+ input : vec ! [ ] ,
2392+ output : vec ! [ TxOut {
2393+ script_pubkey: wallet
2394+ . next_unused_address( KeychainKind :: External )
2395+ . script_pubkey( ) ,
2396+ value: Amount :: from_sat( 25_000 ) ,
2397+ } ] ,
2398+ version : transaction:: Version :: non_standard ( 0 ) ,
2399+ lock_time : absolute:: LockTime :: ZERO ,
2400+ } ;
2401+ let txid = small_output_tx. compute_txid ( ) ;
2402+ insert_tx ( & mut wallet, small_output_tx) ;
2403+ let expected_anchor = ConfirmationBlockTime {
2404+ block_id : wallet. latest_checkpoint ( ) . block_id ( ) ,
2405+ confirmation_time : 200 ,
2406+ } ;
2407+ insert_anchor ( & mut wallet, txid, expected_anchor) ;
2408+ assert ! ( wallet. persist( & mut store) . unwrap( ) ) ;
2409+
2410+ // should recover persisted wallet
2411+ let secp = wallet. secp_ctx ( ) ;
2412+ let ( _, keymap) = <Descriptor < DescriptorPublicKey > >:: parse_descriptor ( secp, desc) . unwrap ( ) ;
2413+ assert ! ( !keymap. is_empty( ) ) ;
2414+ let wallet = Wallet :: load ( )
2415+ . descriptor ( KeychainKind :: External , Some ( desc) )
2416+ . extract_keys ( )
2417+ . load_wallet ( & mut store)
2418+ . unwrap ( )
2419+ . expect ( "must have loaded changeset" ) ;
2420+ // stored anchor should be retrieved in the same condition it was persisted
2421+ if let ChainPosition :: Confirmed {
2422+ anchor : obtained_anchor,
2423+ ..
2424+ } = wallet
2425+ . get_tx ( txid)
2426+ . expect ( "should retrieve stored tx" )
2427+ . chain_position
2428+ {
2429+ assert_eq ! ( obtained_anchor, expected_anchor)
2430+ } else {
2431+ panic ! ( "Should have got ChainPosition::Confirmed)" ) ;
2432+ }
2433+ }
2434+
2435+ #[ cfg( feature = "wallet" ) ]
2436+ #[ test]
2437+ fn single_descriptor_wallet_persist_and_recover ( ) {
2438+ use bdk_chain:: miniscript:: Descriptor ;
2439+ use bdk_chain:: miniscript:: DescriptorPublicKey ;
2440+
2441+ let tmpfile = NamedTempFile :: new ( ) . unwrap ( ) ;
2442+ let db = Arc :: new ( create_db ( tmpfile. path ( ) ) ) ;
2443+ let mut store = create_test_store ( db. clone ( ) , "wallet" ) ;
2444+
2445+ let desc = get_test_tr_single_sig_xprv ( ) ;
2446+ let mut wallet = Wallet :: create_single ( desc)
2447+ . network ( Network :: Testnet )
2448+ . create_wallet ( & mut store)
2449+ . unwrap ( ) ;
2450+ let _ = wallet. reveal_addresses_to ( KeychainKind :: External , 2 ) ;
2451+ assert ! ( wallet. persist( & mut store) . unwrap( ) ) ;
2452+
2453+ // should recover persisted wallet
2454+ let secp = wallet. secp_ctx ( ) ;
2455+ let ( _, keymap) = <Descriptor < DescriptorPublicKey > >:: parse_descriptor ( secp, desc) . unwrap ( ) ;
2456+ assert ! ( !keymap. is_empty( ) ) ;
2457+ let wallet = Wallet :: load ( )
2458+ . descriptor ( KeychainKind :: External , Some ( desc) )
2459+ . extract_keys ( )
2460+ . load_wallet ( & mut store)
2461+ . unwrap ( )
2462+ . expect ( "must have loaded changeset" ) ;
2463+ assert_eq ! ( wallet. derivation_index( KeychainKind :: External ) , Some ( 2 ) ) ;
2464+ // should have private key
2465+ assert_eq ! (
2466+ wallet. get_signers( KeychainKind :: External ) . as_key_map( secp) ,
2467+ keymap,
2468+ ) ;
2469+
2470+ // should error on wrong internal params
2471+ let desc = get_test_wpkh ( ) ;
2472+ let ( exp_desc, _) =
2473+ <Descriptor < DescriptorPublicKey > >:: parse_descriptor ( secp, desc) . unwrap ( ) ;
2474+ let err = Wallet :: load ( )
2475+ . descriptor ( KeychainKind :: Internal , Some ( desc) )
2476+ . extract_keys ( )
2477+ . load_wallet ( & mut store) ;
2478+ assert_matches ! (
2479+ err,
2480+ Err ( LoadWithPersistError :: InvalidChangeSet ( LoadError :: Mismatch ( LoadMismatch :: Descriptor { keychain, loaded, expected } ) ) )
2481+ if keychain == KeychainKind :: Internal && loaded. is_none( ) && expected == Some ( exp_desc) ,
2482+ "single descriptor wallet should refuse change descriptor param"
2483+ ) ;
2484+ }
21622485}
0 commit comments