1use alloy_consensus::TxReceipt;
6use alloy_eips::{eip2718::Encodable2718, BlockNumHash};
7use alloy_primitives::TxHash;
8use alloy_rpc_types_eth::{Filter, 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;
14use thiserror::Error;
15
16pub fn matching_block_logs_with_tx_hashes<'a, I, R>(
18 filter: &Filter,
19 block_num_hash: BlockNumHash,
20 block_timestamp: u64,
21 tx_hashes_and_receipts: I,
22 removed: bool,
23) -> Vec<Log>
24where
25 I: IntoIterator<Item = (TxHash, &'a R)>,
26 R: TxReceipt<Log = alloy_primitives::Log> + 'a,
27{
28 if !filter.matches_block(&block_num_hash) {
29 return vec![];
30 }
31
32 let mut all_logs = Vec::new();
33 let mut log_index: u64 = 0;
35
36 for (receipt_idx, (tx_hash, receipt)) in tx_hashes_and_receipts.into_iter().enumerate() {
38 for log in receipt.logs() {
39 if filter.matches(log) {
40 let log = Log {
41 inner: log.clone(),
42 block_hash: Some(block_num_hash.hash),
43 block_number: Some(block_num_hash.number),
44 transaction_hash: Some(tx_hash),
45 transaction_index: Some(receipt_idx as u64),
47 log_index: Some(log_index),
48 removed,
49 block_timestamp: Some(block_timestamp),
50 };
51 all_logs.push(log);
52 }
53 log_index += 1;
54 }
55 }
56 all_logs
57}
58
59#[derive(Debug)]
61pub enum ProviderOrBlock<'a, P: BlockReader> {
62 Provider(&'a P),
64 Block(Arc<RecoveredBlock<ProviderBlock<P>>>),
66}
67
68pub fn append_matching_block_logs<P>(
71 all_logs: &mut Vec<Log>,
72 provider_or_block: ProviderOrBlock<'_, P>,
73 filter: &Filter,
74 block_num_hash: BlockNumHash,
75 receipts: &[P::Receipt],
76 removed: bool,
77 block_timestamp: u64,
78) -> Result<(), ProviderError>
79where
80 P: BlockReader<Transaction: SignedTransaction>,
81{
82 if !filter.matches_block(&block_num_hash) {
83 return Ok(());
84 }
85
86 let mut log_index: u64 = 0;
88
89 let mut loaded_first_tx_num = None;
93
94 for (receipt_idx, receipt) in receipts.iter().enumerate() {
96 let mut transaction_hash = None;
98
99 for log in receipt.logs() {
100 if filter.matches(log) {
101 if transaction_hash.is_none() {
103 transaction_hash = match &provider_or_block {
104 ProviderOrBlock::Block(block) => {
105 block.body().transactions().get(receipt_idx).map(|t| t.trie_hash())
106 }
107 ProviderOrBlock::Provider(provider) => {
108 let first_tx_num = match loaded_first_tx_num {
109 Some(num) => num,
110 None => {
111 let block_body_indices = provider
112 .block_body_indices(block_num_hash.number)?
113 .ok_or(ProviderError::BlockBodyIndicesNotFound(
114 block_num_hash.number,
115 ))?;
116 loaded_first_tx_num = Some(block_body_indices.first_tx_num);
117 block_body_indices.first_tx_num
118 }
119 };
120
121 let transaction_id = first_tx_num + receipt_idx as u64;
124 let transaction =
125 provider.transaction_by_id(transaction_id)?.ok_or_else(|| {
126 ProviderError::TransactionNotFound(transaction_id.into())
127 })?;
128
129 Some(transaction.trie_hash())
130 }
131 };
132 }
133
134 let log = Log {
135 inner: log.clone(),
136 block_hash: Some(block_num_hash.hash),
137 block_number: Some(block_num_hash.number),
138 transaction_hash,
139 transaction_index: Some(receipt_idx as u64),
141 log_index: Some(log_index),
142 removed,
143 block_timestamp: Some(block_timestamp),
144 };
145 all_logs.push(log);
146 }
147 log_index += 1;
148 }
149 }
150 Ok(())
151}
152
153pub fn get_filter_block_range(
157 from_block: Option<u64>,
158 to_block: Option<u64>,
159 start_block: u64,
160 info: ChainInfo,
161) -> Result<(u64, u64), FilterBlockRangeError> {
162 let from_block_number = from_block.unwrap_or(start_block);
163 let to_block_number = to_block.unwrap_or(info.best_number);
164
165 if from_block_number > to_block_number {
167 return Err(FilterBlockRangeError::InvalidBlockRange);
168 }
169
170 if to_block_number > info.best_number {
172 return Err(FilterBlockRangeError::BlockRangeExceedsHead {
173 requested: to_block_number,
174 head: info.best_number,
175 });
176 }
177
178 Ok((from_block_number, to_block_number))
179}
180
181#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
185pub enum FilterBlockRangeError {
186 #[error("invalid block range params")]
188 InvalidBlockRange,
189 #[error("block range extends beyond current head block: requested {requested}, head {head}")]
191 BlockRangeExceedsHead {
192 requested: u64,
194 head: u64,
196 },
197}
198
199#[cfg(test)]
200mod tests {
201 use alloy_rpc_types_eth::Filter;
202
203 use super::*;
204
205 #[test]
206 fn test_log_range_from_and_to() {
207 let from = 14000000u64;
208 let to = 14000100u64;
209 let info = ChainInfo { best_number: 15000000, ..Default::default() };
210 let range = get_filter_block_range(Some(from), Some(to), info.best_number, info).unwrap();
211 assert_eq!(range, (from, to));
212 }
213
214 #[test]
215 fn test_log_range_from() {
216 let from = 14000000u64;
217 let info = ChainInfo { best_number: 15000000, ..Default::default() };
218 let range = get_filter_block_range(Some(from), None, 0, info).unwrap();
219 assert_eq!(range, (from, info.best_number));
220 }
221
222 #[test]
223 fn test_log_range_to() {
224 let to = 14000000u64;
225 let start_block = 0u64;
226 let info = ChainInfo { best_number: 15000000, ..Default::default() };
227 let range = get_filter_block_range(None, Some(to), start_block, info).unwrap();
228 assert_eq!(range, (start_block, to));
229 }
230
231 #[test]
232 fn test_log_range_higher_error() {
233 let from = 15000001u64;
235 let to = 15000002u64;
236 let info = ChainInfo { best_number: 15000000, ..Default::default() };
237 let err = get_filter_block_range(Some(from), Some(to), info.best_number, info).unwrap_err();
238 assert_eq!(
239 err,
240 FilterBlockRangeError::BlockRangeExceedsHead { requested: to, head: info.best_number }
241 );
242 }
243
244 #[test]
245 fn test_log_range_to_below_start_error() {
246 let to = 14000000u64;
248 let info = ChainInfo { best_number: 15000000, ..Default::default() };
249 let err = get_filter_block_range(None, Some(to), info.best_number, info).unwrap_err();
250 assert_eq!(err, FilterBlockRangeError::InvalidBlockRange);
251 }
252
253 #[test]
254 fn test_log_range_empty() {
255 let info = ChainInfo { best_number: 15000000, ..Default::default() };
256 let range = get_filter_block_range(None, None, info.best_number, info).unwrap();
257
258 assert_eq!(range, (info.best_number, info.best_number));
260 }
261
262 #[test]
263 fn test_invalid_block_range_error() {
264 let from = 100;
265 let to = 50;
266 let info = ChainInfo { best_number: 150, ..Default::default() };
267 let err = get_filter_block_range(Some(from), Some(to), 0, info).unwrap_err();
268 assert_eq!(err, FilterBlockRangeError::InvalidBlockRange);
269 }
270
271 #[test]
272 fn test_block_range_exceeds_head_error() {
273 let from = 100;
274 let to = 200;
275 let info = ChainInfo { best_number: 150, ..Default::default() };
276 let err = get_filter_block_range(Some(from), Some(to), 0, info).unwrap_err();
277 assert_eq!(
278 err,
279 FilterBlockRangeError::BlockRangeExceedsHead { requested: to, head: info.best_number }
280 );
281 }
282
283 #[test]
284 fn parse_log_from_only() {
285 let s = r#"{"fromBlock":"0xf47a42","address":["0x7de93682b9b5d80d45cd371f7a14f74d49b0914c","0x0f00392fcb466c0e4e4310d81b941e07b4d5a079","0xebf67ab8cff336d3f609127e8bbf8bd6dd93cd81"],"topics":["0x0559884fd3a460db3073b7fc896cc77986f16e378210ded43186175bf646fc5f"]}"#;
286 let filter: Filter = serde_json::from_str(s).unwrap();
287
288 assert_eq!(filter.get_from_block(), Some(16022082));
289 assert!(filter.get_to_block().is_none());
290
291 let best_number = 17229427;
292 let info = ChainInfo { best_number, ..Default::default() };
293
294 let (from_block, to_block) = filter.block_option.as_range();
295
296 let start_block = info.best_number;
297
298 let (from_block_number, to_block_number) = get_filter_block_range(
299 from_block.and_then(alloy_rpc_types_eth::BlockNumberOrTag::as_number),
300 to_block.and_then(alloy_rpc_types_eth::BlockNumberOrTag::as_number),
301 start_block,
302 info,
303 )
304 .unwrap();
305 assert_eq!(from_block_number, 16022082);
306 assert_eq!(to_block_number, best_number);
307 }
308}