|
| 1 | +use crate::wallet_interface::FilterMatchKey; |
| 2 | +use alloc::vec::Vec; |
| 3 | +use dashcore::bip158::BlockFilter; |
| 4 | +use dashcore::Address; |
| 5 | +use rayon::prelude::{IntoParallelIterator, ParallelIterator}; |
| 6 | +use std::collections::{BTreeSet, HashMap}; |
| 7 | + |
| 8 | +pub type FilterMatchInput = HashMap<FilterMatchKey, BlockFilter>; |
| 9 | +pub type FilterMatchOutput = BTreeSet<FilterMatchKey>; |
| 10 | + |
| 11 | +/// Check compact filters for addresses and return the keys that matched. |
| 12 | +pub fn check_compact_filters_for_addresses( |
| 13 | + input: FilterMatchInput, |
| 14 | + addresses: Vec<Address>, |
| 15 | +) -> FilterMatchOutput { |
| 16 | + let script_pubkey_bytes: Vec<Vec<u8>> = |
| 17 | + addresses.iter().map(|address| address.script_pubkey().to_bytes()).collect(); |
| 18 | + |
| 19 | + input |
| 20 | + .into_par_iter() |
| 21 | + .filter_map(|(key, filter)| { |
| 22 | + filter |
| 23 | + .match_any(&key.block_hash, script_pubkey_bytes.iter().map(|v| v.as_slice())) |
| 24 | + .unwrap_or(false) |
| 25 | + .then_some(key) |
| 26 | + }) |
| 27 | + .collect() |
| 28 | +} |
| 29 | + |
| 30 | +#[cfg(test)] |
| 31 | +mod tests { |
| 32 | + use super::*; |
| 33 | + use dashcore::blockdata::script::ScriptBuf; |
| 34 | + use dashcore_test_utils::{ |
| 35 | + create_filter_for_block, create_test_block, create_test_transaction_to_script, test_address, |
| 36 | + }; |
| 37 | + |
| 38 | + #[test] |
| 39 | + fn test_empty_input_returns_empty() { |
| 40 | + let result = check_compact_filters_for_addresses(FilterMatchInput::new(), vec![]); |
| 41 | + assert!(result.is_empty()); |
| 42 | + } |
| 43 | + |
| 44 | + #[test] |
| 45 | + fn test_empty_addresses_returns_empty() { |
| 46 | + let tx = create_test_transaction_to_script(ScriptBuf::new()); |
| 47 | + let block = create_test_block(100, vec![tx]); |
| 48 | + let filter = create_filter_for_block(&block); |
| 49 | + let key = FilterMatchKey::new(100, block.block_hash()); |
| 50 | + |
| 51 | + let mut input = FilterMatchInput::new(); |
| 52 | + input.insert(key.clone(), filter); |
| 53 | + |
| 54 | + let output = check_compact_filters_for_addresses(input, vec![]); |
| 55 | + assert!(!output.contains(&key)); |
| 56 | + } |
| 57 | + |
| 58 | + #[test] |
| 59 | + fn test_matching_filter() { |
| 60 | + let address = test_address(0); |
| 61 | + |
| 62 | + let tx = create_test_transaction_to_script(address.script_pubkey()); |
| 63 | + let block = create_test_block(100, vec![tx]); |
| 64 | + let filter = create_filter_for_block(&block); |
| 65 | + let key = FilterMatchKey::new(100, block.block_hash()); |
| 66 | + |
| 67 | + let mut input = FilterMatchInput::new(); |
| 68 | + input.insert(key.clone(), filter); |
| 69 | + |
| 70 | + let output = check_compact_filters_for_addresses(input, vec![address]); |
| 71 | + assert!(output.contains(&key)); |
| 72 | + } |
| 73 | + |
| 74 | + #[test] |
| 75 | + fn test_non_matching_filter() { |
| 76 | + let address = test_address(0); |
| 77 | + let other_address = test_address(1); |
| 78 | + |
| 79 | + let tx = create_test_transaction_to_script(other_address.script_pubkey()); |
| 80 | + let block = create_test_block(100, vec![tx]); |
| 81 | + let filter = create_filter_for_block(&block); |
| 82 | + let key = FilterMatchKey::new(100, block.block_hash()); |
| 83 | + |
| 84 | + let mut input = FilterMatchInput::new(); |
| 85 | + input.insert(key.clone(), filter); |
| 86 | + |
| 87 | + let output = check_compact_filters_for_addresses(input, vec![address]); |
| 88 | + assert!(!output.contains(&key)); |
| 89 | + } |
| 90 | + |
| 91 | + #[test] |
| 92 | + fn test_batch_mixed_results() { |
| 93 | + let address1 = test_address(0); |
| 94 | + let address2 = test_address(1); |
| 95 | + let unrelated_address = test_address(2); |
| 96 | + |
| 97 | + let tx1 = create_test_transaction_to_script(address1.script_pubkey()); |
| 98 | + let block1 = create_test_block(100, vec![tx1]); |
| 99 | + let filter1 = create_filter_for_block(&block1); |
| 100 | + let key1 = FilterMatchKey::new(100, block1.block_hash()); |
| 101 | + |
| 102 | + let tx2 = create_test_transaction_to_script(address2.script_pubkey()); |
| 103 | + let block2 = create_test_block(200, vec![tx2]); |
| 104 | + let filter2 = create_filter_for_block(&block2); |
| 105 | + let key2 = FilterMatchKey::new(200, block2.block_hash()); |
| 106 | + |
| 107 | + let tx3 = create_test_transaction_to_script(unrelated_address.script_pubkey()); |
| 108 | + let block3 = create_test_block(300, vec![tx3]); |
| 109 | + let filter3 = create_filter_for_block(&block3); |
| 110 | + let key3 = FilterMatchKey::new(300, block3.block_hash()); |
| 111 | + |
| 112 | + let mut input = FilterMatchInput::new(); |
| 113 | + input.insert(key1.clone(), filter1); |
| 114 | + input.insert(key2.clone(), filter2); |
| 115 | + input.insert(key3.clone(), filter3); |
| 116 | + |
| 117 | + let output = check_compact_filters_for_addresses(input, vec![address1, address2]); |
| 118 | + assert_eq!(output.len(), 2); |
| 119 | + assert!(output.contains(&key1)); |
| 120 | + assert!(output.contains(&key2)); |
| 121 | + assert!(!output.contains(&key3)); |
| 122 | + } |
| 123 | +} |
0 commit comments