diff --git a/crates/chain/src/keychain/txout_index.rs b/crates/chain/src/keychain/txout_index.rs index da6a1e25..79f98fad 100644 --- a/crates/chain/src/keychain/txout_index.rs +++ b/crates/chain/src/keychain/txout_index.rs @@ -326,12 +326,17 @@ impl KeychainTxOutIndex { self.lookahead } - /// Store lookahead scripts until `target_index`. + /// Store lookahead scripts until `target_index` (inclusive). /// - /// This does not change the `lookahead` setting. + /// This does not change the global `lookahead` setting. pub fn lookahead_to_target(&mut self, keychain: &K, target_index: u32) { - let next_index = self.next_store_index(keychain); - if let Some(temp_lookahead) = target_index.checked_sub(next_index).filter(|&v| v > 0) { + let (next_index, _) = self.next_index(keychain); + + let temp_lookahead = (target_index + 1) + .checked_sub(next_index) + .filter(|&index| index > 0); + + if let Some(temp_lookahead) = temp_lookahead { self.replenish_lookahead(keychain, temp_lookahead); } } diff --git a/crates/chain/tests/test_keychain_txout_index.rs b/crates/chain/tests/test_keychain_txout_index.rs index 0bbc3739..9ce15f4b 100644 --- a/crates/chain/tests/test_keychain_txout_index.rs +++ b/crates/chain/tests/test_keychain_txout_index.rs @@ -386,3 +386,98 @@ fn test_non_wildcard_derivations() { 1, ); } + +/// Check that calling `lookahead_to_target` stores the expected spks. +#[test] +fn lookahead_to_target() { + #[derive(Default)] + struct TestCase { + lookahead: u32, // Global lookahead value + external_last_revealed: Option, // Last revealed index for external keychain + internal_last_revealed: Option, // Last revealed index for internal keychain + external_target: Option, // Call `lookahead_to_target(External, u32)` + internal_target: Option, // Call `lookahead_to_target(Internal, u32)` + } + + let test_cases = &[ + TestCase { + lookahead: 0, + external_target: Some(100), + ..Default::default() + }, + TestCase { + lookahead: 10, + internal_target: Some(99), + ..Default::default() + }, + TestCase { + lookahead: 100, + internal_target: Some(9), + external_target: Some(10), + ..Default::default() + }, + TestCase { + lookahead: 12, + external_last_revealed: Some(2), + internal_last_revealed: Some(2), + internal_target: Some(15), + external_target: Some(13), + }, + TestCase { + lookahead: 13, + external_last_revealed: Some(100), + internal_last_revealed: Some(21), + internal_target: Some(120), + external_target: Some(130), + }, + ]; + + for t in test_cases { + let (mut index, _, _) = init_txout_index(t.lookahead); + + if let Some(last_revealed) = t.external_last_revealed { + let _ = index.reveal_to_target(&TestKeychain::External, last_revealed); + } + if let Some(last_revealed) = t.internal_last_revealed { + let _ = index.reveal_to_target(&TestKeychain::Internal, last_revealed); + } + + let keychain_test_cases = [ + ( + TestKeychain::External, + t.external_last_revealed, + t.external_target, + ), + ( + TestKeychain::Internal, + t.internal_last_revealed, + t.internal_target, + ), + ]; + for (keychain, last_revealed, target) in keychain_test_cases { + if let Some(target) = target { + let original_last_stored_index = match last_revealed { + Some(last_revealed) => Some(last_revealed + t.lookahead), + None => t.lookahead.checked_sub(1), + }; + let exp_last_stored_index = match original_last_stored_index { + Some(original_last_stored_index) => { + Ord::max(target, original_last_stored_index) + } + None => target, + }; + index.lookahead_to_target(&keychain, target); + let keys = index + .inner() + .all_spks() + .range((keychain.clone(), 0)..=(keychain.clone(), u32::MAX)) + .map(|(k, _)| k.clone()) + .collect::>(); + let exp_keys = core::iter::repeat(keychain) + .zip(0_u32..=exp_last_stored_index) + .collect::>(); + assert_eq!(keys, exp_keys); + } + } + } +}