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;
14
15pub fn matching_block_logs_with_tx_hashes<'a, I, R>(
17 filter: &Filter,
18 block_num_hash: BlockNumHash,
19 block_timestamp: u64,
20 tx_hashes_and_receipts: I,
21 removed: bool,
22) -> Vec<Log>
23where
24 I: IntoIterator<Item = (TxHash, &'a R)>,
25 R: TxReceipt<Log = alloy_primitives::Log> + 'a,
26{
27 if !filter.matches_block(&block_num_hash) {
28 return vec![];
29 }
30
31 let mut all_logs = Vec::new();
32 let mut log_index: u64 = 0;
34
35 for (receipt_idx, (tx_hash, receipt)) in tx_hashes_and_receipts.into_iter().enumerate() {
37 for log in receipt.logs() {
38 if filter.matches(log) {
39 let log = Log {
40 inner: log.clone(),
41 block_hash: Some(block_num_hash.hash),
42 block_number: Some(block_num_hash.number),
43 transaction_hash: Some(tx_hash),
44 transaction_index: Some(receipt_idx as u64),
46 log_index: Some(log_index),
47 removed,
48 block_timestamp: Some(block_timestamp),
49 };
50 all_logs.push(log);
51 }
52 log_index += 1;
53 }
54 }
55 all_logs
56}
57
58#[derive(Debug)]
60pub enum ProviderOrBlock<'a, P: BlockReader> {
61 Provider(&'a P),
63 Block(Arc<RecoveredBlock<ProviderBlock<P>>>),
65}
66
67pub fn append_matching_block_logs<P>(
70 all_logs: &mut Vec<Log>,
71 provider_or_block: ProviderOrBlock<'_, P>,
72 filter: &Filter,
73 block_num_hash: BlockNumHash,
74 receipts: &[P::Receipt],
75 removed: bool,
76 block_timestamp: u64,
77) -> Result<(), ProviderError>
78where
79 P: BlockReader<Transaction: SignedTransaction>,
80{
81 let mut log_index: u64 = 0;
83
84 let mut loaded_first_tx_num = None;
88
89 for (receipt_idx, receipt) in receipts.iter().enumerate() {
91 let mut transaction_hash = None;
93
94 for log in receipt.logs() {
95 if filter.matches(log) {
96 if transaction_hash.is_none() {
98 transaction_hash = match &provider_or_block {
99 ProviderOrBlock::Block(block) => {
100 block.body().transactions().get(receipt_idx).map(|t| t.trie_hash())
101 }
102 ProviderOrBlock::Provider(provider) => {
103 let first_tx_num = match loaded_first_tx_num {
104 Some(num) => num,
105 None => {
106 let block_body_indices = provider
107 .block_body_indices(block_num_hash.number)?
108 .ok_or(ProviderError::BlockBodyIndicesNotFound(
109 block_num_hash.number,
110 ))?;
111 loaded_first_tx_num = Some(block_body_indices.first_tx_num);
112 block_body_indices.first_tx_num
113 }
114 };
115
116 let transaction_id = first_tx_num + receipt_idx as u64;
119 let transaction =
120 provider.transaction_by_id(transaction_id)?.ok_or_else(|| {
121 ProviderError::TransactionNotFound(transaction_id.into())
122 })?;
123
124 Some(transaction.trie_hash())
125 }
126 };
127 }
128
129 let log = Log {
130 inner: log.clone(),
131 block_hash: Some(block_num_hash.hash),
132 block_number: Some(block_num_hash.number),
133 transaction_hash,
134 transaction_index: Some(receipt_idx as u64),
136 log_index: Some(log_index),
137 removed,
138 block_timestamp: Some(block_timestamp),
139 };
140 all_logs.push(log);
141 }
142 log_index += 1;
143 }
144 }
145 Ok(())
146}
147
148pub fn get_filter_block_range(
152 from_block: Option<u64>,
153 to_block: Option<u64>,
154 start_block: u64,
155 info: ChainInfo,
156) -> (u64, u64) {
157 let mut from_block_number = start_block;
158 let mut to_block_number = info.best_number;
159
160 if let Some(filter_from_block) = from_block {
164 from_block_number = start_block.min(filter_from_block)
165 }
166
167 if let Some(filter_to_block) = to_block {
170 to_block_number = info.best_number.min(filter_to_block);
171 }
172
173 (from_block_number, to_block_number)
174}
175
176#[cfg(test)]
177mod tests {
178 use alloy_rpc_types_eth::Filter;
179
180 use super::*;
181
182 #[test]
183 fn test_log_range_from_and_to() {
184 let from = 14000000u64;
185 let to = 14000100u64;
186 let info = ChainInfo { best_number: 15000000, ..Default::default() };
187 let range = get_filter_block_range(Some(from), Some(to), info.best_number, info);
188 assert_eq!(range, (from, to));
189 }
190
191 #[test]
192 fn test_log_range_higher() {
193 let from = 15000001u64;
194 let to = 15000002u64;
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, (info.best_number, info.best_number));
198 }
199
200 #[test]
201 fn test_log_range_from() {
202 let from = 14000000u64;
203 let info = ChainInfo { best_number: 15000000, ..Default::default() };
204 let range = get_filter_block_range(Some(from), None, info.best_number, info);
205 assert_eq!(range, (from, info.best_number));
206 }
207
208 #[test]
209 fn test_log_range_to() {
210 let to = 14000000u64;
211 let info = ChainInfo { best_number: 15000000, ..Default::default() };
212 let range = get_filter_block_range(None, Some(to), info.best_number, info);
213 assert_eq!(range, (info.best_number, to));
214 }
215
216 #[test]
217 fn test_log_range_empty() {
218 let info = ChainInfo { best_number: 15000000, ..Default::default() };
219 let range = get_filter_block_range(None, None, info.best_number, info);
220
221 assert_eq!(range, (info.best_number, info.best_number));
223 }
224
225 #[test]
226 fn parse_log_from_only() {
227 let s = r#"{"fromBlock":"0xf47a42","address":["0x7de93682b9b5d80d45cd371f7a14f74d49b0914c","0x0f00392fcb466c0e4e4310d81b941e07b4d5a079","0xebf67ab8cff336d3f609127e8bbf8bd6dd93cd81"],"topics":["0x0559884fd3a460db3073b7fc896cc77986f16e378210ded43186175bf646fc5f"]}"#;
228 let filter: Filter = serde_json::from_str(s).unwrap();
229
230 assert_eq!(filter.get_from_block(), Some(16022082));
231 assert!(filter.get_to_block().is_none());
232
233 let best_number = 17229427;
234 let info = ChainInfo { best_number, ..Default::default() };
235
236 let (from_block, to_block) = filter.block_option.as_range();
237
238 let start_block = info.best_number;
239
240 let (from_block_number, to_block_number) = get_filter_block_range(
241 from_block.and_then(alloy_rpc_types_eth::BlockNumberOrTag::as_number),
242 to_block.and_then(alloy_rpc_types_eth::BlockNumberOrTag::as_number),
243 start_block,
244 info,
245 );
246 assert_eq!(from_block_number, 16022082);
247 assert_eq!(to_block_number, best_number);
248 }
249}