1
- use std:: collections:: BTreeMap ;
1
+ use std:: collections:: { BTreeMap , BTreeSet } ;
2
2
use std:: path:: Path ;
3
3
4
4
use anyhow:: Context ;
5
5
use assert_matches:: assert_matches;
6
6
use bdk_chain:: {
7
7
keychain_txout:: DEFAULT_LOOKAHEAD , ChainPosition , ConfirmationBlockTime , DescriptorExt ,
8
8
} ;
9
+ use bdk_chain:: { DescriptorId , KeychainIndexed } ;
9
10
use bdk_wallet:: descriptor:: IntoWalletDescriptor ;
10
11
use bdk_wallet:: test_utils:: * ;
11
12
use bdk_wallet:: {
@@ -14,7 +15,9 @@ use bdk_wallet::{
14
15
use bitcoin:: constants:: ChainHash ;
15
16
use bitcoin:: hashes:: Hash ;
16
17
use 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
+ } ;
18
21
use miniscript:: { Descriptor , DescriptorPublicKey } ;
19
22
20
23
mod common;
@@ -24,6 +27,57 @@ const DB_MAGIC: &[u8] = &[0x21, 0x24, 0x48];
24
27
25
28
#[ test]
26
29
fn 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
+
27
81
fn run < Db , CreateDb , OpenDb > (
28
82
filename : & str ,
29
83
create_db : CreateDb ,
@@ -46,8 +100,18 @@ fn wallet_is_persisted() -> anyhow::Result<()> {
46
100
. network ( Network :: Testnet )
47
101
. use_spk_cache ( true )
48
102
. create_wallet ( & mut db) ?;
103
+
49
104
wallet. reveal_next_address ( KeychainKind :: External ) ;
50
105
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
+
51
115
// persist new wallet changes
52
116
assert ! ( wallet. persist( & mut db) ?, "must write" ) ;
53
117
wallet. spk_index ( ) . clone ( )
@@ -81,6 +145,7 @@ fn wallet_is_persisted() -> anyhow::Result<()> {
81
145
. 0
82
146
) ;
83
147
}
148
+
84
149
// Test SPK cache
85
150
{
86
151
let mut db = open_db ( & file_path) . context ( "failed to recover db" ) ?;
@@ -90,35 +155,32 @@ fn wallet_is_persisted() -> anyhow::Result<()> {
90
155
. load_wallet ( & mut db) ?
91
156
. expect ( "wallet must exist" ) ;
92
157
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
-
100
158
assert ! ( wallet. staged( ) . is_none( ) ) ;
101
159
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
+
113
170
// Clear the stage
114
171
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
+ ) ;
121
182
}
183
+
122
184
// SPK cache requires load params
123
185
{
124
186
let mut db = open_db ( & file_path) . context ( "failed to recover db" ) ?;
0 commit comments