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 }
174
175 Ok((from_block_number, to_block_number))
176}
177
178#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
182pub enum FilterBlockRangeError {
183 #[error("invalid block range params")]
185 InvalidBlockRange,
186 #[error("block range extends beyond current head block")]
188 BlockRangeExceedsHead,
189}
190
191#[cfg(test)]
192mod tests {
193 use alloy_rpc_types_eth::Filter;
194
195 use super::*;
196
197 #[test]
198 fn test_log_range_from_and_to() {
199 let from = 14000000u64;
200 let to = 14000100u64;
201 let info = ChainInfo { best_number: 15000000, ..Default::default() };
202 let range = get_filter_block_range(Some(from), Some(to), info.best_number, info).unwrap();
203 assert_eq!(range, (from, to));
204 }
205
206 #[test]
207 fn test_log_range_from() {
208 let from = 14000000u64;
209 let info = ChainInfo { best_number: 15000000, ..Default::default() };
210 let range = get_filter_block_range(Some(from), None, 0, info).unwrap();
211 assert_eq!(range, (from, info.best_number));
212 }
213
214 #[test]
215 fn test_log_range_to() {
216 let to = 14000000u64;
217 let start_block = 0u64;
218 let info = ChainInfo { best_number: 15000000, ..Default::default() };
219 let range = get_filter_block_range(None, Some(to), start_block, info).unwrap();
220 assert_eq!(range, (start_block, to));
221 }
222
223 #[test]
224 fn test_log_range_higher_error() {
225 let from = 15000001u64;
227 let to = 15000002u64;
228 let info = ChainInfo { best_number: 15000000, ..Default::default() };
229 let err = get_filter_block_range(Some(from), Some(to), info.best_number, info).unwrap_err();
230 assert_eq!(err, FilterBlockRangeError::BlockRangeExceedsHead);
231 }
232
233 #[test]
234 fn test_log_range_to_below_start_error() {
235 let to = 14000000u64;
237 let info = ChainInfo { best_number: 15000000, ..Default::default() };
238 let err = get_filter_block_range(None, Some(to), info.best_number, info).unwrap_err();
239 assert_eq!(err, FilterBlockRangeError::InvalidBlockRange);
240 }
241
242 #[test]
243 fn test_log_range_empty() {
244 let info = ChainInfo { best_number: 15000000, ..Default::default() };
245 let range = get_filter_block_range(None, None, info.best_number, info).unwrap();
246
247 assert_eq!(range, (info.best_number, info.best_number));
249 }
250
251 #[test]
252 fn test_invalid_block_range_error() {
253 let from = 100;
254 let to = 50;
255 let info = ChainInfo { best_number: 150, ..Default::default() };
256 let err = get_filter_block_range(Some(from), Some(to), 0, info).unwrap_err();
257 assert_eq!(err, FilterBlockRangeError::InvalidBlockRange);
258 }
259
260 #[test]
261 fn test_block_range_exceeds_head_error() {
262 let from = 100;
263 let to = 200;
264 let info = ChainInfo { best_number: 150, ..Default::default() };
265 let err = get_filter_block_range(Some(from), Some(to), 0, info).unwrap_err();
266 assert_eq!(err, FilterBlockRangeError::BlockRangeExceedsHead);
267 }
268
269 #[test]
270 fn parse_log_from_only() {
271 let s = r#"{"fromBlock":"0xf47a42","address":["0x7de93682b9b5d80d45cd371f7a14f74d49b0914c","0x0f00392fcb466c0e4e4310d81b941e07b4d5a079","0xebf67ab8cff336d3f609127e8bbf8bd6dd93cd81"],"topics":["0x0559884fd3a460db3073b7fc896cc77986f16e378210ded43186175bf646fc5f"]}"#;
272 let filter: Filter = serde_json::from_str(s).unwrap();
273
274 assert_eq!(filter.get_from_block(), Some(16022082));
275 assert!(filter.get_to_block().is_none());
276
277 let best_number = 17229427;
278 let info = ChainInfo { best_number, ..Default::default() };
279
280 let (from_block, to_block) = filter.block_option.as_range();
281
282 let start_block = info.best_number;
283
284 let (from_block_number, to_block_number) = get_filter_block_range(
285 from_block.and_then(alloy_rpc_types_eth::BlockNumberOrTag::as_number),
286 to_block.and_then(alloy_rpc_types_eth::BlockNumberOrTag::as_number),
287 start_block,
288 info,
289 )
290 .unwrap();
291 assert_eq!(from_block_number, 16022082);
292 assert_eq!(to_block_number, best_number);
293 }
294}