@@ -29,6 +29,8 @@ pub trait ConfigurableBlockchainTester<B: ConfigurableBlockchain>: Sized {
2929
3030 if self . config_with_stop_gap ( test_client, 0 ) . is_some ( ) {
3131 test_wallet_sync_with_stop_gaps ( test_client, self ) ;
32+ test_wallet_sync_fulfills_missing_script_cache ( test_client, self ) ;
33+ test_wallet_sync_self_transfer_tx ( test_client, self ) ;
3234 } else {
3335 println ! (
3436 "{}: Skipped tests requiring config_with_stop_gap." ,
@@ -113,16 +115,21 @@ where
113115 } else {
114116 max_balance
115117 } ;
118+ let details = format ! (
119+ "test_vector: [stop_gap: {}, actual_gap: {}, addrs_before: {}, addrs_after: {}]" ,
120+ stop_gap, actual_gap, addrs_before, addrs_after,
121+ ) ;
122+ println ! ( "{}" , details) ;
116123
117124 // perform wallet sync
118125 wallet. sync ( & blockchain, Default :: default ( ) ) . unwrap ( ) ;
119126
120127 let wallet_balance = wallet. get_balance ( ) . unwrap ( ) ;
121-
122- let details = format ! (
123- "test_vector: [stop_gap: {}, actual_gap: {}, addrs_before: {}, addrs_after: {}]" ,
124- stop_gap, actual_gap, addrs_before, addrs_after,
128+ println ! (
129+ "max: {}, min: {}, actual: {}" ,
130+ max_balance, min_balance, wallet_balance
125131 ) ;
132+
126133 assert ! (
127134 wallet_balance <= max_balance,
128135 "wallet balance is greater than received amount: {}" ,
@@ -138,3 +145,113 @@ where
138145 test_client. generate ( 1 , None ) ;
139146 }
140147}
148+
149+ /// With a `stop_gap` of x and every x addresses having a balance of 1000 (for y addresses),
150+ /// we expect `Wallet::sync` to correctly self-cache addresses, so that the resulting balance,
151+ /// after sync, should be y * 1000.
152+ fn test_wallet_sync_fulfills_missing_script_cache < T , B > ( test_client : & mut TestClient , tester : & T )
153+ where
154+ T : ConfigurableBlockchainTester < B > ,
155+ B : ConfigurableBlockchain ,
156+ {
157+ // wallet descriptor
158+ let descriptor = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/200/*)" ;
159+
160+ // amount in sats per tx
161+ const AMOUNT_PER_TX : u64 = 1000 ;
162+
163+ // addr constants
164+ const ADDR_COUNT : usize = 6 ;
165+ const ADDR_GAP : usize = 60 ;
166+
167+ let blockchain =
168+ B :: from_config ( & tester. config_with_stop_gap ( test_client, ADDR_GAP ) . unwrap ( ) ) . unwrap ( ) ;
169+
170+ let wallet = Wallet :: new ( descriptor, None , Network :: Regtest , MemoryDatabase :: new ( ) ) . unwrap ( ) ;
171+
172+ let expected_balance = ( 0 ..ADDR_COUNT ) . fold ( 0_u64 , |sum, i| {
173+ let addr_i = i * ADDR_GAP ;
174+ let address = wallet. get_address ( AddressIndex :: Peek ( addr_i as _ ) ) . unwrap ( ) ;
175+
176+ println ! (
177+ "tx: {} sats => [{}] {}" ,
178+ AMOUNT_PER_TX ,
179+ addr_i,
180+ address. to_string( )
181+ ) ;
182+
183+ test_client. receive ( testutils ! {
184+ @tx ( ( @addr address. address) => AMOUNT_PER_TX )
185+ } ) ;
186+ test_client. generate ( 1 , None ) ;
187+
188+ sum + AMOUNT_PER_TX
189+ } ) ;
190+ println ! ( "expected balance: {}, syncing..." , expected_balance) ;
191+
192+ // perform sync
193+ wallet. sync ( & blockchain, Default :: default ( ) ) . unwrap ( ) ;
194+ println ! ( "sync done!" ) ;
195+
196+ let balance = wallet. get_balance ( ) . unwrap ( ) ;
197+ assert_eq ! ( balance, expected_balance) ;
198+ }
199+
200+ /// Given a `stop_gap`, a wallet with a 2 transactions, one sending to `scriptPubKey` at derivation
201+ /// index of `stop_gap`, and the other spending from the same `scriptPubKey` into another
202+ /// `scriptPubKey` at derivation index of `stop_gap * 2`, we expect `Wallet::sync` to perform
203+ /// correctly, so that we detect the total balance.
204+ fn test_wallet_sync_self_transfer_tx < T , B > ( test_client : & mut TestClient , tester : & T )
205+ where
206+ T : ConfigurableBlockchainTester < B > ,
207+ B : ConfigurableBlockchain ,
208+ {
209+ const TRANSFER_AMOUNT : u64 = 10_000 ;
210+ const STOP_GAP : usize = 75 ;
211+
212+ let descriptor = "wpkh(tprv8i8F4EhYDMquzqiecEX8SKYMXqfmmb1Sm7deoA1Hokxzn281XgTkwsd6gL8aJevLE4aJugfVf9MKMvrcRvPawGMenqMBA3bRRfp4s1V7Eg3/*)" ;
213+
214+ let blockchain =
215+ B :: from_config ( & tester. config_with_stop_gap ( test_client, STOP_GAP ) . unwrap ( ) ) . unwrap ( ) ;
216+
217+ let wallet = Wallet :: new ( descriptor, None , Network :: Regtest , MemoryDatabase :: new ( ) ) . unwrap ( ) ;
218+
219+ let address1 = wallet
220+ . get_address ( AddressIndex :: Peek ( STOP_GAP as _ ) )
221+ . unwrap ( ) ;
222+ let address2 = wallet
223+ . get_address ( AddressIndex :: Peek ( ( STOP_GAP * 2 ) as _ ) )
224+ . unwrap ( ) ;
225+
226+ test_client. receive ( testutils ! {
227+ @tx ( ( @addr address1. address) => TRANSFER_AMOUNT )
228+ } ) ;
229+ test_client. generate ( 1 , None ) ;
230+
231+ wallet. sync ( & blockchain, Default :: default ( ) ) . unwrap ( ) ;
232+
233+ let mut builder = wallet. build_tx ( ) ;
234+ builder. add_recipient ( address2. script_pubkey ( ) , TRANSFER_AMOUNT / 2 ) ;
235+ let ( mut psbt, details) = builder. finish ( ) . unwrap ( ) ;
236+ assert ! ( wallet. sign( & mut psbt, Default :: default ( ) ) . unwrap( ) ) ;
237+ blockchain. broadcast ( & psbt. extract_tx ( ) ) . unwrap ( ) ;
238+
239+ test_client. generate ( 1 , None ) ;
240+
241+ // obtain what is expected
242+ let fee = details. fee . unwrap ( ) ;
243+ let expected_balance = TRANSFER_AMOUNT - fee;
244+ println ! ( "fee={}, expected_balance={}" , fee, expected_balance) ;
245+
246+ // actually test the wallet
247+ wallet. sync ( & blockchain, Default :: default ( ) ) . unwrap ( ) ;
248+ let balance = wallet. get_balance ( ) . unwrap ( ) ;
249+ assert_eq ! ( balance, expected_balance) ;
250+
251+ // now try with a fresh wallet
252+ let fresh_wallet =
253+ Wallet :: new ( descriptor, None , Network :: Regtest , MemoryDatabase :: new ( ) ) . unwrap ( ) ;
254+ fresh_wallet. sync ( & blockchain, Default :: default ( ) ) . unwrap ( ) ;
255+ let fresh_balance = fresh_wallet. get_balance ( ) . unwrap ( ) ;
256+ assert_eq ! ( fresh_balance, expected_balance) ;
257+ }
0 commit comments