Skip to main content

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