diff --git a/crates/esplora/tests/async_ext.rs b/crates/esplora/tests/async_ext.rs index 3b64d7be..38833f58 100644 --- a/crates/esplora/tests/async_ext.rs +++ b/crates/esplora/tests/async_ext.rs @@ -3,6 +3,7 @@ use electrsd::bitcoind::bitcoincore_rpc::RpcApi; use electrsd::bitcoind::{self, anyhow, BitcoinD}; use electrsd::{Conf, ElectrsD}; use esplora_client::{self, AsyncClient, Builder}; +use std::collections::{BTreeMap, HashSet}; use std::str::FromStr; use std::thread::sleep; use std::time::Duration; @@ -115,3 +116,121 @@ pub async fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> { assert_eq!(graph_update_txids, expected_txids); Ok(()) } + +/// Test the bounds of the address scan depending on the gap limit. +#[tokio::test] +pub async fn test_async_update_tx_graph_gap_limit() -> anyhow::Result<()> { + let env = TestEnv::new()?; + let _block_hashes = env.mine_blocks(101, None)?; + + // Now let's test the gap limit. First of all get a chain of 10 addresses. + let addresses = [ + "bcrt1qj9f7r8r3p2y0sqf4r3r62qysmkuh0fzep473d2ar7rcz64wqvhssjgf0z4", + "bcrt1qmm5t0ch7vh2hryx9ctq3mswexcugqe4atkpkl2tetm8merqkthas3w7q30", + "bcrt1qut9p7ej7l7lhyvekj28xknn8gnugtym4d5qvnp5shrsr4nksmfqsmyn87g", + "bcrt1qqz0xtn3m235p2k96f5wa2dqukg6shxn9n3txe8arlrhjh5p744hsd957ww", + "bcrt1q9c0t62a8l6wfytmf2t9lfj35avadk3mm8g4p3l84tp6rl66m48sqrme7wu", + "bcrt1qkmh8yrk2v47cklt8dytk8f3ammcwa4q7dzattedzfhqzvfwwgyzsg59zrh", + "bcrt1qvgrsrzy07gjkkfr5luplt0azxtfwmwq5t62gum5jr7zwcvep2acs8hhnp2", + "bcrt1qw57edarcg50ansq8mk3guyrk78rk0fwvrds5xvqeupteu848zayq549av8", + "bcrt1qvtve5ekf6e5kzs68knvnt2phfw6a0yjqrlgat392m6zt9jsvyxhqfx67ef", + "bcrt1qw03ddumfs9z0kcu76ln7jrjfdwam20qtffmkcral3qtza90sp9kqm787uk", + ]; + let addresses: Vec<_> = addresses + .into_iter() + .map(|s| Address::from_str(s).unwrap().assume_checked()) + .collect(); + let spks: Vec<_> = addresses + .iter() + .enumerate() + .map(|(i, addr)| (i as u32, addr.script_pubkey())) + .collect(); + let mut keychains = BTreeMap::new(); + keychains.insert(0, spks); + + // Then receive coins on the 4th address. + let txid_4th_addr = env.bitcoind.client.send_to_address( + &addresses[3], + Amount::from_sat(10000), + None, + None, + None, + None, + Some(1), + None, + )?; + let _block_hashes = env.mine_blocks(1, None)?; + while env.client.get_height().await.unwrap() < 103 { + sleep(Duration::from_millis(10)) + } + + // A scan with a gap limit of 2 won't find the transaction, but a scan with a gap limit of 3 + // will. + let (graph_update, active_indices) = env + .client + .scan_txs_with_keychains( + keychains.clone(), + vec![].into_iter(), + vec![].into_iter(), + 2, + 1, + ) + .await?; + assert!(graph_update.full_txs().next().is_none()); + assert!(active_indices.is_empty()); + let (graph_update, active_indices) = env + .client + .scan_txs_with_keychains( + keychains.clone(), + vec![].into_iter(), + vec![].into_iter(), + 3, + 1, + ) + .await?; + assert_eq!(graph_update.full_txs().next().unwrap().txid, txid_4th_addr); + assert_eq!(active_indices[&0], 3); + + // Now receive a coin on the last address. + let txid_last_addr = env.bitcoind.client.send_to_address( + &addresses[addresses.len() - 1], + Amount::from_sat(10000), + None, + None, + None, + None, + Some(1), + None, + )?; + let _block_hashes = env.mine_blocks(1, None)?; + while env.client.get_height().await.unwrap() < 104 { + sleep(Duration::from_millis(10)) + } + + // A scan with gap limit 4 won't find the second transaction, but a scan with gap limit 5 will. + // The last active indice won't be updated in the first case but will in the second one. + let (graph_update, active_indices) = env + .client + .scan_txs_with_keychains( + keychains.clone(), + vec![].into_iter(), + vec![].into_iter(), + 4, + 1, + ) + .await?; + let txs: HashSet<_> = graph_update.full_txs().map(|tx| tx.txid).collect(); + assert_eq!(txs.len(), 1); + assert!(txs.contains(&txid_4th_addr)); + assert_eq!(active_indices[&0], 3); + let (graph_update, active_indices) = env + .client + .scan_txs_with_keychains(keychains, vec![].into_iter(), vec![].into_iter(), 5, 1) + .await?; + let txs: HashSet<_> = graph_update.full_txs().map(|tx| tx.txid).collect(); + assert_eq!(txs.len(), 2); + assert!(txs.contains(&txid_4th_addr) && txs.contains(&txid_last_addr)); + assert_eq!(active_indices[&0], 9); + + Ok(()) +}