1use alloy_consensus::TxReceipt;
6use alloy_eips::{eip2718::Encodable2718, BlockNumHash};
7use alloy_primitives::TxHash;
8use alloy_rpc_types_eth::{FilteredParams, Log};
9use reth_chainspec::ChainInfo;
10use reth_errors::ProviderError;
11use reth_primitives_traits::{BlockBody, RecoveredBlock, SignedTransaction};
12use reth_storage_api::{BlockReader, ProviderBlock};
13use std::sync::Arc;
14
15pub fn matching_block_logs_with_tx_hashes<'a, I, R>(
17 filter: &FilteredParams,
18 block_num_hash: BlockNumHash,
19 tx_hashes_and_receipts: I,
20 removed: bool,
21) -> Vec<Log>
22where
23 I: IntoIterator<Item = (TxHash, &'a R)>,
24 R: TxReceipt<Log = alloy_primitives::Log> + 'a,
25{
26 let mut all_logs = Vec::new();
27 let mut log_index: u64 = 0;
29 for (receipt_idx, (tx_hash, receipt)) in tx_hashes_and_receipts.into_iter().enumerate() {
31 for log in receipt.logs() {
32 if log_matches_filter(block_num_hash, log, filter) {
33 let log = Log {
34 inner: log.clone(),
35 block_hash: Some(block_num_hash.hash),
36 block_number: Some(block_num_hash.number),
37 transaction_hash: Some(tx_hash),
38 transaction_index: Some(receipt_idx as u64),
40 log_index: Some(log_index),
41 removed,
42 block_timestamp: None,
43 };
44 all_logs.push(log);
45 }
46 log_index += 1;
47 }
48 }
49 all_logs
50}
51
52#[derive(Debug)]
54pub enum ProviderOrBlock<'a, P: BlockReader> {
55 Provider(&'a P),
57 Block(Arc<RecoveredBlock<ProviderBlock<P>>>),
59}
60
61pub fn append_matching_block_logs<P>(
64 all_logs: &mut Vec<Log>,
65 provider_or_block: ProviderOrBlock<'_, P>,
66 filter: &FilteredParams,
67 block_num_hash: BlockNumHash,
68 receipts: &[P::Receipt],
69 removed: bool,
70 block_timestamp: u64,
71) -> Result<(), ProviderError>
72where
73 P: BlockReader<Transaction: SignedTransaction>,
74{
75 let mut log_index: u64 = 0;
77
78 let mut loaded_first_tx_num = None;
82
83 for (receipt_idx, receipt) in receipts.iter().enumerate() {
85 let mut transaction_hash = None;
87
88 for log in receipt.logs() {
89 if log_matches_filter(block_num_hash, log, filter) {
90 if transaction_hash.is_none() {
92 transaction_hash = match &provider_or_block {
93 ProviderOrBlock::Block(block) => {
94 block.body().transactions().get(receipt_idx).map(|t| t.trie_hash())
95 }
96 ProviderOrBlock::Provider(provider) => {
97 let first_tx_num = match loaded_first_tx_num {
98 Some(num) => num,
99 None => {
100 let block_body_indices = provider
101 .block_body_indices(block_num_hash.number)?
102 .ok_or(ProviderError::BlockBodyIndicesNotFound(
103 block_num_hash.number,
104 ))?;
105 loaded_first_tx_num = Some(block_body_indices.first_tx_num);
106 block_body_indices.first_tx_num
107 }
108 };
109
110 let transaction_id = first_tx_num + receipt_idx as u64;
113 let transaction =
114 provider.transaction_by_id(transaction_id)?.ok_or_else(|| {
115 ProviderError::TransactionNotFound(transaction_id.into())
116 })?;
117
118 Some(transaction.trie_hash())
119 }
120 };
121 }
122
123 let log = Log {
124 inner: log.clone(),
125 block_hash: Some(block_num_hash.hash),
126 block_number: Some(block_num_hash.number),
127 transaction_hash,
128 transaction_index: Some(receipt_idx as u64),
130 log_index: Some(log_index),
131 removed,
132 block_timestamp: Some(block_timestamp),
133 };
134 all_logs.push(log);
135 }
136 log_index += 1;
137 }
138 }
139 Ok(())
140}
141
142pub fn log_matches_filter(
144 block: BlockNumHash,
145 log: &alloy_primitives::Log,
146 params: &FilteredParams,
147) -> bool {
148 if params.filter.is_some() &&
149 (!params.filter_block_range(block.number) ||
150 !params.filter_block_hash(block.hash) ||
151 !params.filter_address(&log.address) ||
152 !params.filter_topics(log.topics()))
153 {
154 return false
155 }
156 true
157}
158
159pub fn get_filter_block_range(
161 from_block: Option<u64>,
162 to_block: Option<u64>,
163 start_block: u64,
164 info: ChainInfo,
165) -> (u64, u64) {
166 let mut from_block_number = start_block;
167 let mut to_block_number = info.best_number;
168
169 if let Some(filter_from_block) = from_block {
173 from_block_number = start_block.min(filter_from_block)
174 }
175
176 if let Some(filter_to_block) = to_block {
179 to_block_number = info.best_number.min(filter_to_block);
180 }
181
182 (from_block_number, to_block_number)
183}
184
185#[cfg(test)]
186mod tests {
187 use alloy_rpc_types_eth::Filter;
188
189 use super::*;
190
191 #[test]
192 fn test_log_range_from_and_to() {
193 let from = 14000000u64;
194 let to = 14000100u64;
195 let info = ChainInfo { best_number: 15000000, ..Default::default() };
196 let range = get_filter_block_range(Some(from), Some(to), info.best_number, info);
197 assert_eq!(range, (from, to));
198 }
199
200 #[test]
201 fn test_log_range_higher() {
202 let from = 15000001u64;
203 let to = 15000002u64;
204 let info = ChainInfo { best_number: 15000000, ..Default::default() };
205 let range = get_filter_block_range(Some(from), Some(to), info.best_number, info);
206 assert_eq!(range, (info.best_number, info.best_number));
207 }
208
209 #[test]
210 fn test_log_range_from() {
211 let from = 14000000u64;
212 let info = ChainInfo { best_number: 15000000, ..Default::default() };
213 let range = get_filter_block_range(Some(from), None, info.best_number, info);
214 assert_eq!(range, (from, info.best_number));
215 }
216
217 #[test]
218 fn test_log_range_to() {
219 let to = 14000000u64;
220 let info = ChainInfo { best_number: 15000000, ..Default::default() };
221 let range = get_filter_block_range(None, Some(to), info.best_number, info);
222 assert_eq!(range, (info.best_number, to));
223 }
224
225 #[test]
226 fn test_log_range_empty() {
227 let info = ChainInfo { best_number: 15000000, ..Default::default() };
228 let range = get_filter_block_range(None, None, info.best_number, info);
229
230 assert_eq!(range, (info.best_number, info.best_number));
232 }
233
234 #[test]
235 fn parse_log_from_only() {
236 let s = r#"{"fromBlock":"0xf47a42","address":["0x7de93682b9b5d80d45cd371f7a14f74d49b0914c","0x0f00392fcb466c0e4e4310d81b941e07b4d5a079","0xebf67ab8cff336d3f609127e8bbf8bd6dd93cd81"],"topics":["0x0559884fd3a460db3073b7fc896cc77986f16e378210ded43186175bf646fc5f"]}"#;
237 let filter: Filter = serde_json::from_str(s).unwrap();
238
239 assert_eq!(filter.get_from_block(), Some(16022082));
240 assert!(filter.get_to_block().is_none());
241
242 let best_number = 17229427;
243 let info = ChainInfo { best_number, ..Default::default() };
244
245 let (from_block, to_block) = filter.block_option.as_range();
246
247 let start_block = info.best_number;
248
249 let (from_block_number, to_block_number) = get_filter_block_range(
250 from_block.and_then(alloy_rpc_types_eth::BlockNumberOrTag::as_number),
251 to_block.and_then(alloy_rpc_types_eth::BlockNumberOrTag::as_number),
252 start_block,
253 info,
254 );
255 assert_eq!(from_block_number, 16022082);
256 assert_eq!(to_block_number, best_number);
257 }
258}