1- use std:: collections:: BTreeMap ;
1+ use std:: collections:: { BTreeMap , BTreeSet } ;
22use std:: path:: Path ;
33
44use anyhow:: Context ;
55use assert_matches:: assert_matches;
66use bdk_chain:: {
77 keychain_txout:: DEFAULT_LOOKAHEAD , ChainPosition , ConfirmationBlockTime , DescriptorExt ,
88} ;
9+ use bdk_chain:: { DescriptorId , KeychainIndexed } ;
910use bdk_wallet:: descriptor:: IntoWalletDescriptor ;
1011use bdk_wallet:: test_utils:: * ;
1112use bdk_wallet:: {
@@ -14,7 +15,9 @@ use bdk_wallet::{
1415use bitcoin:: constants:: ChainHash ;
1516use bitcoin:: hashes:: Hash ;
1617use bitcoin:: key:: Secp256k1 ;
17- use bitcoin:: { absolute, transaction, Amount , BlockHash , Network , ScriptBuf , Transaction , TxOut } ;
18+ use bitcoin:: {
19+ absolute, secp256k1, transaction, Amount , BlockHash , Network , ScriptBuf , Transaction , TxOut ,
20+ } ;
1821use miniscript:: { Descriptor , DescriptorPublicKey } ;
1922
2023mod common;
@@ -24,6 +27,57 @@ const DB_MAGIC: &[u8] = &[0x21, 0x24, 0x48];
2427
2528#[ test]
2629fn wallet_is_persisted ( ) -> anyhow:: Result < ( ) > {
30+ type SpkCacheChangeSet = BTreeMap < DescriptorId , BTreeMap < u32 , ScriptBuf > > ;
31+
32+ /// Check whether the spk-cache field of the changeset contains the expected spk indices.
33+ fn check_cache_cs (
34+ cache_cs : & SpkCacheChangeSet ,
35+ expected : impl IntoIterator < Item = ( KeychainKind , impl IntoIterator < Item = u32 > ) > ,
36+ msg : impl AsRef < str > ,
37+ ) {
38+ let secp = secp256k1:: Secp256k1 :: new ( ) ;
39+ let ( external, internal) = get_test_tr_single_sig_xprv_and_change_desc ( ) ;
40+ let ( external_desc, _) = external
41+ . into_wallet_descriptor ( & secp, Network :: Testnet )
42+ . unwrap ( ) ;
43+ let ( internal_desc, _) = internal
44+ . into_wallet_descriptor ( & secp, Network :: Testnet )
45+ . unwrap ( ) ;
46+ let external_did = external_desc. descriptor_id ( ) ;
47+ let internal_did = internal_desc. descriptor_id ( ) ;
48+
49+ let cache_cmp = cache_cs
50+ . iter ( )
51+ . map ( |( did, spks) | {
52+ let kind: KeychainKind ;
53+ if did == & external_did {
54+ kind = KeychainKind :: External ;
55+ } else if did == & internal_did {
56+ kind = KeychainKind :: Internal ;
57+ } else {
58+ unreachable ! ( ) ;
59+ }
60+ let spk_indices = spks. keys ( ) . copied ( ) . collect :: < BTreeSet < u32 > > ( ) ;
61+ ( kind, spk_indices)
62+ } )
63+ . filter ( |( _, spk_indices) | !spk_indices. is_empty ( ) )
64+ . collect :: < BTreeMap < KeychainKind , BTreeSet < u32 > > > ( ) ;
65+
66+ let expected_cmp = expected
67+ . into_iter ( )
68+ . map ( |( kind, indices) | ( kind, indices. into_iter ( ) . collect :: < BTreeSet < u32 > > ( ) ) )
69+ . filter ( |( _, spk_indices) | !spk_indices. is_empty ( ) )
70+ . collect :: < BTreeMap < KeychainKind , BTreeSet < u32 > > > ( ) ;
71+
72+ assert_eq ! ( cache_cmp, expected_cmp, "{}" , msg. as_ref( ) ) ;
73+ }
74+
75+ fn staged_cache ( wallet : & Wallet ) -> SpkCacheChangeSet {
76+ wallet. staged ( ) . map_or ( SpkCacheChangeSet :: default ( ) , |cs| {
77+ cs. indexer . spk_cache . clone ( )
78+ } )
79+ }
80+
2781 fn run < Db , CreateDb , OpenDb > (
2882 filename : & str ,
2983 create_db : CreateDb ,
@@ -46,8 +100,18 @@ fn wallet_is_persisted() -> anyhow::Result<()> {
46100 . network ( Network :: Testnet )
47101 . use_spk_cache ( true )
48102 . create_wallet ( & mut db) ?;
103+
49104 wallet. reveal_next_address ( KeychainKind :: External ) ;
50105
106+ check_cache_cs (
107+ & staged_cache ( & wallet) ,
108+ [
109+ ( KeychainKind :: External , 0 ..DEFAULT_LOOKAHEAD + 1 ) ,
110+ ( KeychainKind :: Internal , 0 ..DEFAULT_LOOKAHEAD ) ,
111+ ] ,
112+ "cache cs must return initial set + the external index that was just derived" ,
113+ ) ;
114+
51115 // persist new wallet changes
52116 assert ! ( wallet. persist( & mut db) ?, "must write" ) ;
53117 wallet. spk_index ( ) . clone ( )
@@ -81,6 +145,7 @@ fn wallet_is_persisted() -> anyhow::Result<()> {
81145 . 0
82146 ) ;
83147 }
148+
84149 // Test SPK cache
85150 {
86151 let mut db = open_db ( & file_path) . context ( "failed to recover db" ) ?;
@@ -90,35 +155,32 @@ fn wallet_is_persisted() -> anyhow::Result<()> {
90155 . load_wallet ( & mut db) ?
91156 . expect ( "wallet must exist" ) ;
92157
93- let external_did = wallet
94- . public_descriptor ( KeychainKind :: External )
95- . descriptor_id ( ) ;
96- let internal_did = wallet
97- . public_descriptor ( KeychainKind :: Internal )
98- . descriptor_id ( ) ;
99-
100158 assert ! ( wallet. staged( ) . is_none( ) ) ;
101159
102- let _addr = wallet. reveal_next_address ( KeychainKind :: External ) ;
103- let cs = wallet. staged ( ) . expect ( "we should have staged a changeset" ) ;
104- assert ! ( !cs. indexer. spk_cache. is_empty( ) , "failed to cache spks" ) ;
105- assert_eq ! ( cs. indexer. spk_cache. len( ) , 2 , "we persisted two keychains" ) ;
106- let spk_cache: & BTreeMap < u32 , ScriptBuf > =
107- cs. indexer . spk_cache . get ( & external_did) . unwrap ( ) ;
108- assert_eq ! ( spk_cache. len( ) as u32 , 1 + 1 + DEFAULT_LOOKAHEAD ) ;
109- assert_eq ! ( spk_cache. keys( ) . last( ) , Some ( & 26 ) ) ;
110- let spk_cache = cs. indexer . spk_cache . get ( & internal_did) . unwrap ( ) ;
111- assert_eq ! ( spk_cache. len( ) as u32 , DEFAULT_LOOKAHEAD ) ;
112- assert_eq ! ( spk_cache. keys( ) . last( ) , Some ( & 24 ) ) ;
160+ let revealed_external_addr = wallet. reveal_next_address ( KeychainKind :: External ) ;
161+ check_cache_cs (
162+ & staged_cache ( & wallet) ,
163+ [ (
164+ KeychainKind :: External ,
165+ [ revealed_external_addr. index + DEFAULT_LOOKAHEAD ] ,
166+ ) ] ,
167+ "must only persist the revealed+LOOKAHEAD indexed external spk" ,
168+ ) ;
169+
113170 // Clear the stage
114171 let _ = wallet. take_staged ( ) ;
115- let _addr = wallet. reveal_next_address ( KeychainKind :: Internal ) ;
116- let cs = wallet. staged ( ) . unwrap ( ) ;
117- assert_eq ! ( cs. indexer. spk_cache. len( ) , 1 ) ;
118- let spk_cache = cs. indexer . spk_cache . get ( & internal_did) . unwrap ( ) ;
119- assert_eq ! ( spk_cache. len( ) , 1 ) ;
120- assert_eq ! ( spk_cache. keys( ) . next( ) , Some ( & 25 ) ) ;
172+
173+ let revealed_internal_addr = wallet. reveal_next_address ( KeychainKind :: Internal ) ;
174+ check_cache_cs (
175+ & staged_cache ( & wallet) ,
176+ [ (
177+ KeychainKind :: Internal ,
178+ [ revealed_internal_addr. index + DEFAULT_LOOKAHEAD ] ,
179+ ) ] ,
180+ "must only persist the revealed+LOOKAHEAD indexed internal spk" ,
181+ ) ;
121182 }
183+
122184 // SPK cache requires load params
123185 {
124186 let mut db = open_db ( & file_path) . context ( "failed to recover db" ) ?;
0 commit comments