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(
150 from_block: Option<u64>,
151 to_block: Option<u64>,
152 start_block: u64,
153 info: ChainInfo,
154) -> (u64, u64) {
155 let mut from_block_number = start_block;
156 let mut to_block_number = info.best_number;
157
158 if let Some(filter_from_block) = from_block {
162 from_block_number = start_block.min(filter_from_block)
163 }
164
165 if let Some(filter_to_block) = to_block {
168 to_block_number = info.best_number.min(filter_to_block);
169 }
170
171 (from_block_number, to_block_number)
172}
173
174#[cfg(test)]
175mod tests {
176 use alloy_rpc_types_eth::Filter;
177
178 use super::*;
179
180 #[test]
181 fn test_log_range_from_and_to() {
182 let from = 14000000u64;
183 let to = 14000100u64;
184 let info = ChainInfo { best_number: 15000000, ..Default::default() };
185 let range = get_filter_block_range(Some(from), Some(to), info.best_number, info);
186 assert_eq!(range, (from, to));
187 }
188
189 #[test]
190 fn test_log_range_higher() {
191 let from = 15000001u64;
192 let to = 15000002u64;
193 let info = ChainInfo { best_number: 15000000, ..Default::default() };
194 let range = get_filter_block_range(Some(from), Some(to), info.best_number, info);
195 assert_eq!(range, (info.best_number, info.best_number));
196 }
197
198 #[test]
199 fn test_log_range_from() {
200 let from = 14000000u64;
201 let info = ChainInfo { best_number: 15000000, ..Default::default() };
202 let range = get_filter_block_range(Some(from), None, info.best_number, info);
203 assert_eq!(range, (from, info.best_number));
204 }
205
206 #[test]
207 fn test_log_range_to() {
208 let to = 14000000u64;
209 let info = ChainInfo { best_number: 15000000, ..Default::default() };
210 let range = get_filter_block_range(None, Some(to), info.best_number, info);
211 assert_eq!(range, (info.best_number, to));
212 }
213
214 #[test]
215 fn test_log_range_empty() {
216 let info = ChainInfo { best_number: 15000000, ..Default::default() };
217 let range = get_filter_block_range(None, None, info.best_number, info);
218
219 assert_eq!(range, (info.best_number, info.best_number));
221 }
222
223 #[test]
224 fn parse_log_from_only() {
225 let s = r#"{"fromBlock":"0xf47a42","address":["0x7de93682b9b5d80d45cd371f7a14f74d49b0914c","0x0f00392fcb466c0e4e4310d81b941e07b4d5a079","0xebf67ab8cff336d3f609127e8bbf8bd6dd93cd81"],"topics":["0x0559884fd3a460db3073b7fc896cc77986f16e378210ded43186175bf646fc5f"]}"#;
226 let filter: Filter = serde_json::from_str(s).unwrap();
227
228 assert_eq!(filter.get_from_block(), Some(16022082));
229 assert!(filter.get_to_block().is_none());
230
231 let best_number = 17229427;
232 let info = ChainInfo { best_number, ..Default::default() };
233
234 let (from_block, to_block) = filter.block_option.as_range();
235
236 let start_block = info.best_number;
237
238 let (from_block_number, to_block_number) = get_filter_block_range(
239 from_block.and_then(alloy_rpc_types_eth::BlockNumberOrTag::as_number),
240 to_block.and_then(alloy_rpc_types_eth::BlockNumberOrTag::as_number),
241 start_block,
242 info,
243 );
244 assert_eq!(from_block_number, 16022082);
245 assert_eq!(to_block_number, best_number);
246 }
247}