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 let mut log_index: u64 = 0;
84
85 let mut loaded_first_tx_num = None;
89
90 for (receipt_idx, receipt) in receipts.iter().enumerate() {
92 let mut transaction_hash = None;
94
95 for log in receipt.logs() {
96 if filter.matches(log) {
97 if transaction_hash.is_none() {
99 transaction_hash = match &provider_or_block {
100 ProviderOrBlock::Block(block) => {
101 block.body().transactions().get(receipt_idx).map(|t| t.trie_hash())
102 }
103 ProviderOrBlock::Provider(provider) => {
104 let first_tx_num = match loaded_first_tx_num {
105 Some(num) => num,
106 None => {
107 let block_body_indices = provider
108 .block_body_indices(block_num_hash.number)?
109 .ok_or(ProviderError::BlockBodyIndicesNotFound(
110 block_num_hash.number,
111 ))?;
112 loaded_first_tx_num = Some(block_body_indices.first_tx_num);
113 block_body_indices.first_tx_num
114 }
115 };
116
117 let transaction_id = first_tx_num + receipt_idx as u64;
120 let transaction =
121 provider.transaction_by_id(transaction_id)?.ok_or_else(|| {
122 ProviderError::TransactionNotFound(transaction_id.into())
123 })?;
124
125 Some(transaction.trie_hash())
126 }
127 };
128 }
129
130 let log = Log {
131 inner: log.clone(),
132 block_hash: Some(block_num_hash.hash),
133 block_number: Some(block_num_hash.number),
134 transaction_hash,
135 transaction_index: Some(receipt_idx as u64),
137 log_index: Some(log_index),
138 removed,
139 block_timestamp: Some(block_timestamp),
140 };
141 all_logs.push(log);
142 }
143 log_index += 1;
144 }
145 }
146 Ok(())
147}
148
149pub fn get_filter_block_range(
153 from_block: Option<u64>,
154 to_block: Option<u64>,
155 start_block: u64,
156 info: ChainInfo,
157) -> Result<(u64, u64), FilterBlockRangeError> {
158 let from_block_number = from_block.unwrap_or(start_block);
159 let to_block_number = to_block.unwrap_or(info.best_number);
160
161 if from_block_number > to_block_number {
163 return Err(FilterBlockRangeError::InvalidBlockRange);
164 }
165
166 if to_block_number > info.best_number {
168 return Err(FilterBlockRangeError::BlockRangeExceedsHead);
169 }
170
171 Ok((from_block_number, to_block_number))
172}
173
174#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
178pub enum FilterBlockRangeError {
179 #[error("invalid block range params")]
181 InvalidBlockRange,
182 #[error("block range extends beyond current head block")]
184 BlockRangeExceedsHead,
185}
186
187#[cfg(test)]
188mod tests {
189 use alloy_rpc_types_eth::Filter;
190
191 use super::*;
192
193 #[test]
194 fn test_log_range_from_and_to() {
195 let from = 14000000u64;
196 let to = 14000100u64;
197 let info = ChainInfo { best_number: 15000000, ..Default::default() };
198 let range = get_filter_block_range(Some(from), Some(to), info.best_number, info).unwrap();
199 assert_eq!(range, (from, to));
200 }
201
202 #[test]
203 fn test_log_range_from() {
204 let from = 14000000u64;
205 let info = ChainInfo { best_number: 15000000, ..Default::default() };
206 let range = get_filter_block_range(Some(from), None, 0, info).unwrap();
207 assert_eq!(range, (from, info.best_number));
208 }
209
210 #[test]
211 fn test_log_range_to() {
212 let to = 14000000u64;
213 let start_block = 0u64;
214 let info = ChainInfo { best_number: 15000000, ..Default::default() };
215 let range = get_filter_block_range(None, Some(to), start_block, info).unwrap();
216 assert_eq!(range, (start_block, to));
217 }
218
219 #[test]
220 fn test_log_range_higher_error() {
221 let from = 15000001u64;
223 let to = 15000002u64;
224 let info = ChainInfo { best_number: 15000000, ..Default::default() };
225 let err = get_filter_block_range(Some(from), Some(to), info.best_number, info).unwrap_err();
226 assert_eq!(err, FilterBlockRangeError::BlockRangeExceedsHead);
227 }
228
229 #[test]
230 fn test_log_range_to_below_start_error() {
231 let to = 14000000u64;
233 let info = ChainInfo { best_number: 15000000, ..Default::default() };
234 let err = get_filter_block_range(None, Some(to), info.best_number, info).unwrap_err();
235 assert_eq!(err, FilterBlockRangeError::InvalidBlockRange);
236 }
237
238 #[test]
239 fn test_log_range_empty() {
240 let info = ChainInfo { best_number: 15000000, ..Default::default() };
241 let range = get_filter_block_range(None, None, info.best_number, info).unwrap();
242
243 assert_eq!(range, (info.best_number, info.best_number));
245 }
246
247 #[test]
248 fn test_invalid_block_range_error() {
249 let from = 100;
250 let to = 50;
251 let info = ChainInfo { best_number: 150, ..Default::default() };
252 let err = get_filter_block_range(Some(from), Some(to), 0, info).unwrap_err();
253 assert_eq!(err, FilterBlockRangeError::InvalidBlockRange);
254 }
255
256 #[test]
257 fn test_block_range_exceeds_head_error() {
258 let from = 100;
259 let to = 200;
260 let info = ChainInfo { best_number: 150, ..Default::default() };
261 let err = get_filter_block_range(Some(from), Some(to), 0, info).unwrap_err();
262 assert_eq!(err, FilterBlockRangeError::BlockRangeExceedsHead);
263 }
264
265 #[test]
266 fn parse_log_from_only() {
267 let s = r#"{"fromBlock":"0xf47a42","address":["0x7de93682b9b5d80d45cd371f7a14f74d49b0914c","0x0f00392fcb466c0e4e4310d81b941e07b4d5a079","0xebf67ab8cff336d3f609127e8bbf8bd6dd93cd81"],"topics":["0x0559884fd3a460db3073b7fc896cc77986f16e378210ded43186175bf646fc5f"]}"#;
268 let filter: Filter = serde_json::from_str(s).unwrap();
269
270 assert_eq!(filter.get_from_block(), Some(16022082));
271 assert!(filter.get_to_block().is_none());
272
273 let best_number = 17229427;
274 let info = ChainInfo { best_number, ..Default::default() };
275
276 let (from_block, to_block) = filter.block_option.as_range();
277
278 let start_block = info.best_number;
279
280 let (from_block_number, to_block_number) = get_filter_block_range(
281 from_block.and_then(alloy_rpc_types_eth::BlockNumberOrTag::as_number),
282 to_block.and_then(alloy_rpc_types_eth::BlockNumberOrTag::as_number),
283 start_block,
284 info,
285 )
286 .unwrap();
287 assert_eq!(from_block_number, 16022082);
288 assert_eq!(to_block_number, best_number);
289 }
290}