tiders_ingest/
evm.rs

1//! EVM query types and field selection for the ingest layer.
2
3#[cfg(feature = "pyo3")]
4use anyhow::Context;
5use serde::{Deserialize, Serialize};
6
7/// EVM blockchain data query specifying block range, filters, and field selections.
8#[derive(Default, Debug, Clone)]
9#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
10pub struct Query {
11    pub from_block: u64,
12    pub to_block: Option<u64>,
13    pub include_all_blocks: bool,
14    pub transactions: Vec<TransactionRequest>,
15    pub logs: Vec<LogRequest>,
16    pub traces: Vec<TraceRequest>,
17    pub fields: Fields,
18}
19
20/// A 32-byte hash value (block hash, transaction hash).
21#[derive(Debug, Clone, Copy)]
22pub struct Hash(pub [u8; 32]);
23
24/// A 20-byte EVM address.
25#[derive(Debug, Clone, Copy)]
26pub struct Address(pub [u8; 20]);
27
28/// A 4-byte function selector (first 4 bytes of keccak256 of the function signature).
29#[derive(Debug, Clone, Copy)]
30pub struct Sighash(pub [u8; 4]);
31
32/// A 32-byte event log topic value.
33#[derive(Debug, Clone, Copy)]
34pub struct Topic(pub [u8; 32]);
35
36#[cfg(feature = "pyo3")]
37fn extract_hex<const N: usize>(ob: &pyo3::Bound<'_, pyo3::PyAny>) -> pyo3::PyResult<[u8; N]> {
38    use pyo3::types::PyAnyMethods;
39
40    let s: &str = ob.extract()?;
41    let s = s.strip_prefix("0x").context("strip 0x prefix")?;
42    let mut out = [0; N];
43    faster_hex::hex_decode(s.as_bytes(), &mut out).context("decode hex")?;
44
45    Ok(out)
46}
47
48#[cfg(feature = "pyo3")]
49impl<'py> pyo3::FromPyObject<'py> for Hash {
50    fn extract_bound(ob: &pyo3::Bound<'py, pyo3::PyAny>) -> pyo3::PyResult<Self> {
51        let out = extract_hex(ob)?;
52        Ok(Self(out))
53    }
54}
55
56#[cfg(feature = "pyo3")]
57impl<'py> pyo3::FromPyObject<'py> for Address {
58    fn extract_bound(ob: &pyo3::Bound<'py, pyo3::PyAny>) -> pyo3::PyResult<Self> {
59        let out = extract_hex(ob)?;
60        Ok(Self(out))
61    }
62}
63
64#[cfg(feature = "pyo3")]
65impl<'py> pyo3::FromPyObject<'py> for Sighash {
66    fn extract_bound(ob: &pyo3::Bound<'py, pyo3::PyAny>) -> pyo3::PyResult<Self> {
67        let out = extract_hex(ob)?;
68        Ok(Self(out))
69    }
70}
71
72#[cfg(feature = "pyo3")]
73impl<'py> pyo3::FromPyObject<'py> for Topic {
74    fn extract_bound(ob: &pyo3::Bound<'py, pyo3::PyAny>) -> pyo3::PyResult<Self> {
75        let out = extract_hex(ob)?;
76        Ok(Self(out))
77    }
78}
79
80/// Filters for selecting EVM transactions by sender, recipient, sighash, status, etc.
81#[derive(Default, Debug, Clone)]
82#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
83pub struct TransactionRequest {
84    pub from_: Vec<Address>,
85    pub to: Vec<Address>,
86    pub sighash: Vec<Sighash>,
87    pub status: Vec<u8>,
88    pub type_: Vec<u8>,
89    pub contract_deployment_address: Vec<Address>,
90    pub hash: Vec<Hash>,
91    pub include_logs: bool,
92    pub include_traces: bool,
93    pub include_blocks: bool,
94}
95
96/// Filters for selecting EVM event logs by contract address and topics.
97#[derive(Default, Debug, Clone)]
98#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
99#[expect(clippy::struct_excessive_bools, reason = "field selection flags")]
100pub struct LogRequest {
101    pub address: Vec<Address>,
102    pub topic0: Vec<Topic>,
103    pub topic1: Vec<Topic>,
104    pub topic2: Vec<Topic>,
105    pub topic3: Vec<Topic>,
106    pub include_transactions: bool,
107    pub include_transaction_logs: bool,
108    pub include_transaction_traces: bool,
109    pub include_blocks: bool,
110}
111
112/// Filters for selecting EVM execution traces by address, call type, etc.
113#[derive(Default, Debug, Clone)]
114#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
115#[expect(clippy::struct_excessive_bools, reason = "field selection flags")]
116pub struct TraceRequest {
117    pub from_: Vec<Address>,
118    pub to: Vec<Address>,
119    pub address: Vec<Address>,
120    pub call_type: Vec<String>,
121    pub reward_type: Vec<String>,
122    pub type_: Vec<String>,
123    pub sighash: Vec<Sighash>,
124    pub author: Vec<Address>,
125    pub include_transactions: bool,
126    pub include_transaction_logs: bool,
127    pub include_transaction_traces: bool,
128    pub include_blocks: bool,
129}
130
131/// Controls which columns are included in the response for each table type.
132#[derive(Deserialize, Serialize, Default, Debug, Clone, Copy)]
133#[serde(default)]
134#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
135pub struct Fields {
136    pub block: BlockFields,
137    pub transaction: TransactionFields,
138    pub log: LogFields,
139    pub trace: TraceFields,
140}
141
142impl Fields {
143    pub fn all() -> Self {
144        Self {
145            block: BlockFields::all(),
146            transaction: TransactionFields::all(),
147            log: LogFields::all(),
148            trace: TraceFields::all(),
149        }
150    }
151}
152
153/// Field selector for EVM block data columns.
154#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)]
155#[serde(default)]
156#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
157#[expect(clippy::struct_excessive_bools, reason = "field selection flags")]
158pub struct BlockFields {
159    pub number: bool,
160    pub hash: bool,
161    pub parent_hash: bool,
162    pub nonce: bool,
163    pub sha3_uncles: bool,
164    pub logs_bloom: bool,
165    pub transactions_root: bool,
166    pub state_root: bool,
167    pub receipts_root: bool,
168    pub miner: bool,
169    pub difficulty: bool,
170    pub total_difficulty: bool,
171    pub extra_data: bool,
172    pub size: bool,
173    pub gas_limit: bool,
174    pub gas_used: bool,
175    pub timestamp: bool,
176    pub uncles: bool,
177    pub base_fee_per_gas: bool,
178    pub blob_gas_used: bool,
179    pub excess_blob_gas: bool,
180    pub parent_beacon_block_root: bool,
181    pub withdrawals_root: bool,
182    pub withdrawals: bool,
183    pub l1_block_number: bool,
184    pub send_count: bool,
185    pub send_root: bool,
186    pub mix_hash: bool,
187}
188
189impl BlockFields {
190    pub fn all() -> Self {
191        BlockFields {
192            number: true,
193            hash: true,
194            parent_hash: true,
195            nonce: true,
196            sha3_uncles: true,
197            logs_bloom: true,
198            transactions_root: true,
199            state_root: true,
200            receipts_root: true,
201            miner: true,
202            difficulty: true,
203            total_difficulty: true,
204            extra_data: true,
205            size: true,
206            gas_limit: true,
207            gas_used: true,
208            timestamp: true,
209            uncles: true,
210            base_fee_per_gas: true,
211            blob_gas_used: true,
212            excess_blob_gas: true,
213            parent_beacon_block_root: true,
214            withdrawals_root: true,
215            withdrawals: true,
216            l1_block_number: true,
217            send_count: true,
218            send_root: true,
219            mix_hash: true,
220        }
221    }
222}
223
224/// Field selector for EVM transaction data columns.
225#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)]
226#[serde(default)]
227#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
228#[expect(clippy::struct_excessive_bools, reason = "field selection flags")]
229pub struct TransactionFields {
230    pub block_hash: bool,
231    pub block_number: bool,
232    #[serde(rename = "from")]
233    pub from_: bool,
234    pub gas: bool,
235    pub gas_price: bool,
236    pub hash: bool,
237    pub input: bool,
238    pub nonce: bool,
239    pub to: bool,
240    pub transaction_index: bool,
241    pub value: bool,
242    pub v: bool,
243    pub r: bool,
244    pub s: bool,
245    pub max_priority_fee_per_gas: bool,
246    pub max_fee_per_gas: bool,
247    pub chain_id: bool,
248    pub cumulative_gas_used: bool,
249    pub effective_gas_price: bool,
250    pub gas_used: bool,
251    pub contract_address: bool,
252    pub logs_bloom: bool,
253    #[serde(rename = "type")]
254    pub type_: bool,
255    pub root: bool,
256    pub status: bool,
257    pub sighash: bool,
258    pub y_parity: bool,
259    pub access_list: bool,
260    pub l1_fee: bool,
261    pub l1_gas_price: bool,
262    pub l1_fee_scalar: bool,
263    pub gas_used_for_l1: bool,
264    pub max_fee_per_blob_gas: bool,
265    pub blob_versioned_hashes: bool,
266    pub deposit_nonce: bool,
267    pub blob_gas_price: bool,
268    pub deposit_receipt_version: bool,
269    pub blob_gas_used: bool,
270    pub l1_base_fee_scalar: bool,
271    pub l1_blob_base_fee: bool,
272    pub l1_blob_base_fee_scalar: bool,
273    pub l1_block_number: bool,
274    pub mint: bool,
275    pub source_hash: bool,
276}
277
278impl TransactionFields {
279    pub fn all() -> Self {
280        TransactionFields {
281            block_hash: true,
282            block_number: true,
283            from_: true,
284            gas: true,
285            gas_price: true,
286            hash: true,
287            input: true,
288            nonce: true,
289            to: true,
290            transaction_index: true,
291            value: true,
292            v: true,
293            r: true,
294            s: true,
295            max_priority_fee_per_gas: true,
296            max_fee_per_gas: true,
297            chain_id: true,
298            cumulative_gas_used: true,
299            effective_gas_price: true,
300            gas_used: true,
301            contract_address: true,
302            logs_bloom: true,
303            type_: true,
304            root: true,
305            status: true,
306            sighash: true,
307            y_parity: true,
308            access_list: true,
309            l1_fee: true,
310            l1_gas_price: true,
311            l1_fee_scalar: true,
312            gas_used_for_l1: true,
313            max_fee_per_blob_gas: true,
314            blob_versioned_hashes: true,
315            deposit_nonce: true,
316            blob_gas_price: true,
317            deposit_receipt_version: true,
318            blob_gas_used: true,
319            l1_base_fee_scalar: true,
320            l1_blob_base_fee: true,
321            l1_blob_base_fee_scalar: true,
322            l1_block_number: true,
323            mint: true,
324            source_hash: true,
325        }
326    }
327}
328
329/// Field selector for EVM log data columns.
330#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)]
331#[serde(default)]
332#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
333#[expect(clippy::struct_excessive_bools, reason = "field selection flags")]
334pub struct LogFields {
335    pub removed: bool,
336    pub log_index: bool,
337    pub transaction_index: bool,
338    pub transaction_hash: bool,
339    pub block_hash: bool,
340    pub block_number: bool,
341    pub address: bool,
342    pub data: bool,
343    pub topic0: bool,
344    pub topic1: bool,
345    pub topic2: bool,
346    pub topic3: bool,
347}
348
349impl LogFields {
350    pub fn all() -> Self {
351        LogFields {
352            removed: true,
353            log_index: true,
354            transaction_index: true,
355            transaction_hash: true,
356            block_hash: true,
357            block_number: true,
358            address: true,
359            data: true,
360            topic0: true,
361            topic1: true,
362            topic2: true,
363            topic3: true,
364        }
365    }
366}
367
368/// Field selector for EVM trace data columns.
369#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)]
370#[serde(default)]
371#[cfg_attr(feature = "pyo3", derive(pyo3::FromPyObject))]
372#[expect(clippy::struct_excessive_bools, reason = "field selection flags")]
373pub struct TraceFields {
374    #[serde(rename = "from")]
375    pub from_: bool,
376    pub to: bool,
377    pub call_type: bool,
378    pub gas: bool,
379    pub input: bool,
380    pub init: bool,
381    pub value: bool,
382    pub author: bool,
383    pub reward_type: bool,
384    pub block_hash: bool,
385    pub block_number: bool,
386    pub address: bool,
387    pub code: bool,
388    pub gas_used: bool,
389    pub output: bool,
390    pub subtraces: bool,
391    pub trace_address: bool,
392    pub transaction_hash: bool,
393    pub transaction_position: bool,
394    #[serde(rename = "type")]
395    pub type_: bool,
396    pub error: bool,
397    pub sighash: bool,
398    pub action_address: bool,
399    pub balance: bool,
400    pub refund_address: bool,
401}
402
403impl TraceFields {
404    pub fn all() -> Self {
405        TraceFields {
406            from_: true,
407            to: true,
408            call_type: true,
409            gas: true,
410            input: true,
411            init: true,
412            value: true,
413            author: true,
414            reward_type: true,
415            block_hash: true,
416            block_number: true,
417            address: true,
418            code: true,
419            gas_used: true,
420            output: true,
421            subtraces: true,
422            trace_address: true,
423            transaction_hash: true,
424            transaction_position: true,
425            type_: true,
426            error: true,
427            sighash: true,
428            action_address: true,
429            balance: true,
430            refund_address: true,
431        }
432    }
433}