@@ -3,6 +3,7 @@ use electrsd::bitcoind::bitcoincore_rpc::RpcApi;
33use electrsd:: bitcoind:: { self , anyhow, BitcoinD } ;
44use electrsd:: { Conf , ElectrsD } ;
55use esplora_client:: { self , AsyncClient , Builder } ;
6+ use std:: collections:: { BTreeMap , HashSet } ;
67use std:: str:: FromStr ;
78use std:: thread:: sleep;
89use std:: time:: Duration ;
@@ -115,3 +116,121 @@ pub async fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> {
115116 assert_eq ! ( graph_update_txids, expected_txids) ;
116117 Ok ( ( ) )
117118}
119+
120+ /// Test the bounds of the address scan depending on the gap limit.
121+ #[ tokio:: test]
122+ pub async fn test_async_update_tx_graph_gap_limit ( ) -> anyhow:: Result < ( ) > {
123+ let env = TestEnv :: new ( ) ?;
124+ let _block_hashes = env. mine_blocks ( 101 , None ) ?;
125+
126+ // Now let's test the gap limit. First of all get a chain of 10 addresses.
127+ let addresses = [
128+ "bcrt1qj9f7r8r3p2y0sqf4r3r62qysmkuh0fzep473d2ar7rcz64wqvhssjgf0z4" ,
129+ "bcrt1qmm5t0ch7vh2hryx9ctq3mswexcugqe4atkpkl2tetm8merqkthas3w7q30" ,
130+ "bcrt1qut9p7ej7l7lhyvekj28xknn8gnugtym4d5qvnp5shrsr4nksmfqsmyn87g" ,
131+ "bcrt1qqz0xtn3m235p2k96f5wa2dqukg6shxn9n3txe8arlrhjh5p744hsd957ww" ,
132+ "bcrt1q9c0t62a8l6wfytmf2t9lfj35avadk3mm8g4p3l84tp6rl66m48sqrme7wu" ,
133+ "bcrt1qkmh8yrk2v47cklt8dytk8f3ammcwa4q7dzattedzfhqzvfwwgyzsg59zrh" ,
134+ "bcrt1qvgrsrzy07gjkkfr5luplt0azxtfwmwq5t62gum5jr7zwcvep2acs8hhnp2" ,
135+ "bcrt1qw57edarcg50ansq8mk3guyrk78rk0fwvrds5xvqeupteu848zayq549av8" ,
136+ "bcrt1qvtve5ekf6e5kzs68knvnt2phfw6a0yjqrlgat392m6zt9jsvyxhqfx67ef" ,
137+ "bcrt1qw03ddumfs9z0kcu76ln7jrjfdwam20qtffmkcral3qtza90sp9kqm787uk" ,
138+ ] ;
139+ let addresses: Vec < _ > = addresses
140+ . into_iter ( )
141+ . map ( |s| Address :: from_str ( s) . unwrap ( ) . assume_checked ( ) )
142+ . collect ( ) ;
143+ let spks: Vec < _ > = addresses
144+ . iter ( )
145+ . enumerate ( )
146+ . map ( |( i, addr) | ( i as u32 , addr. script_pubkey ( ) ) )
147+ . collect ( ) ;
148+ let mut keychains = BTreeMap :: new ( ) ;
149+ keychains. insert ( 0 , spks) ;
150+
151+ // Then receive coins on the 4th address.
152+ let txid_4th_addr = env. bitcoind . client . send_to_address (
153+ & addresses[ 3 ] ,
154+ Amount :: from_sat ( 10000 ) ,
155+ None ,
156+ None ,
157+ None ,
158+ None ,
159+ Some ( 1 ) ,
160+ None ,
161+ ) ?;
162+ let _block_hashes = env. mine_blocks ( 1 , None ) ?;
163+ while env. client . get_height ( ) . await . unwrap ( ) < 103 {
164+ sleep ( Duration :: from_millis ( 10 ) )
165+ }
166+
167+ // A scan with a gap limit of 2 won't find the transaction, but a scan with a gap limit of 3
168+ // will.
169+ let ( graph_update, active_indices) = env
170+ . client
171+ . scan_txs_with_keychains (
172+ keychains. clone ( ) ,
173+ vec ! [ ] . into_iter ( ) ,
174+ vec ! [ ] . into_iter ( ) ,
175+ 2 ,
176+ 1 ,
177+ )
178+ . await ?;
179+ assert ! ( graph_update. full_txs( ) . next( ) . is_none( ) ) ;
180+ assert ! ( active_indices. is_empty( ) ) ;
181+ let ( graph_update, active_indices) = env
182+ . client
183+ . scan_txs_with_keychains (
184+ keychains. clone ( ) ,
185+ vec ! [ ] . into_iter ( ) ,
186+ vec ! [ ] . into_iter ( ) ,
187+ 3 ,
188+ 1 ,
189+ )
190+ . await ?;
191+ assert_eq ! ( graph_update. full_txs( ) . next( ) . unwrap( ) . txid, txid_4th_addr) ;
192+ assert_eq ! ( active_indices[ & 0 ] , 3 ) ;
193+
194+ // Now receive a coin on the last address.
195+ let txid_last_addr = env. bitcoind . client . send_to_address (
196+ & addresses[ addresses. len ( ) - 1 ] ,
197+ Amount :: from_sat ( 10000 ) ,
198+ None ,
199+ None ,
200+ None ,
201+ None ,
202+ Some ( 1 ) ,
203+ None ,
204+ ) ?;
205+ let _block_hashes = env. mine_blocks ( 1 , None ) ?;
206+ while env. client . get_height ( ) . await . unwrap ( ) < 104 {
207+ sleep ( Duration :: from_millis ( 10 ) )
208+ }
209+
210+ // A scan with gap limit 4 won't find the second transaction, but a scan with gap limit 5 will.
211+ // The last active indice won't be updated in the first case but will in the second one.
212+ let ( graph_update, active_indices) = env
213+ . client
214+ . scan_txs_with_keychains (
215+ keychains. clone ( ) ,
216+ vec ! [ ] . into_iter ( ) ,
217+ vec ! [ ] . into_iter ( ) ,
218+ 4 ,
219+ 1 ,
220+ )
221+ . await ?;
222+ let txs: HashSet < _ > = graph_update. full_txs ( ) . map ( |tx| tx. txid ) . collect ( ) ;
223+ assert_eq ! ( txs. len( ) , 1 ) ;
224+ assert ! ( txs. contains( & txid_4th_addr) ) ;
225+ assert_eq ! ( active_indices[ & 0 ] , 3 ) ;
226+ let ( graph_update, active_indices) = env
227+ . client
228+ . scan_txs_with_keychains ( keychains, vec ! [ ] . into_iter ( ) , vec ! [ ] . into_iter ( ) , 5 , 1 )
229+ . await ?;
230+ let txs: HashSet < _ > = graph_update. full_txs ( ) . map ( |tx| tx. txid ) . collect ( ) ;
231+ assert_eq ! ( txs. len( ) , 2 ) ;
232+ assert ! ( txs. contains( & txid_4th_addr) && txs. contains( & txid_last_addr) ) ;
233+ assert_eq ! ( active_indices[ & 0 ] , 9 ) ;
234+
235+ Ok ( ( ) )
236+ }
0 commit comments