hypersync_net_types/
trace.rs

1use crate::{hypersync_net_types_capnp, types::Sighash, BuilderReader, Selection};
2use hypersync_format::{Address, FilterWrapper};
3use serde::{Deserialize, Serialize};
4
5pub type TraceSelection = Selection<TraceFilter>;
6
7#[derive(Default, Serialize, Deserialize, Clone, Debug, PartialEq)]
8pub struct TraceFilter {
9    #[serde(default, skip_serializing_if = "Vec::is_empty")]
10    pub from: Vec<Address>,
11    #[serde(default, skip_serializing_if = "Option::is_none")]
12    pub from_filter: Option<FilterWrapper>,
13    #[serde(default, skip_serializing_if = "Vec::is_empty")]
14    pub to: Vec<Address>,
15    #[serde(default, skip_serializing_if = "Option::is_none")]
16    pub to_filter: Option<FilterWrapper>,
17    #[serde(default, skip_serializing_if = "Vec::is_empty")]
18    pub address: Vec<Address>,
19    #[serde(default, skip_serializing_if = "Option::is_none")]
20    pub address_filter: Option<FilterWrapper>,
21    #[serde(default, skip_serializing_if = "Vec::is_empty")]
22    pub call_type: Vec<String>,
23    #[serde(default, skip_serializing_if = "Vec::is_empty")]
24    pub reward_type: Vec<String>,
25    #[serde(default, skip_serializing_if = "Vec::is_empty")]
26    #[serde(rename = "type")]
27    pub type_: Vec<String>,
28    #[serde(default, skip_serializing_if = "Vec::is_empty")]
29    pub sighash: Vec<Sighash>,
30}
31
32impl BuilderReader<hypersync_net_types_capnp::trace_filter::Owned> for TraceFilter {
33    fn populate_builder(
34        &self,
35        builder: &mut hypersync_net_types_capnp::trace_filter::Builder,
36    ) -> Result<(), capnp::Error> {
37        // Set from addresses
38        if !self.from.is_empty() {
39            let mut from_list = builder.reborrow().init_from(self.from.len() as u32);
40            for (i, addr) in self.from.iter().enumerate() {
41                from_list.set(i as u32, addr.as_slice());
42            }
43        }
44
45        // Set from filter
46        if let Some(filter) = &self.from_filter {
47            builder.reborrow().set_from_filter(filter.0.as_bytes());
48        }
49
50        // Set to addresses
51        if !self.to.is_empty() {
52            let mut to_list = builder.reborrow().init_to(self.to.len() as u32);
53            for (i, addr) in self.to.iter().enumerate() {
54                to_list.set(i as u32, addr.as_slice());
55            }
56        }
57
58        // Set to filter
59        if let Some(filter) = &self.to_filter {
60            builder.reborrow().set_to_filter(filter.0.as_bytes());
61        }
62
63        // Set addresses
64        if !self.address.is_empty() {
65            let mut addr_list = builder.reborrow().init_address(self.address.len() as u32);
66            for (i, addr) in self.address.iter().enumerate() {
67                addr_list.set(i as u32, addr.as_slice());
68            }
69        }
70
71        // Set address filter
72        if let Some(filter) = &self.address_filter {
73            builder.reborrow().set_address_filter(filter.0.as_bytes());
74        }
75
76        // Set call types
77        if !self.call_type.is_empty() {
78            let mut call_type_list = builder
79                .reborrow()
80                .init_call_type(self.call_type.len() as u32);
81            for (i, call_type) in self.call_type.iter().enumerate() {
82                call_type_list.set(i as u32, call_type);
83            }
84        }
85
86        // Set reward types
87        if !self.reward_type.is_empty() {
88            let mut reward_type_list = builder
89                .reborrow()
90                .init_reward_type(self.reward_type.len() as u32);
91            for (i, reward_type) in self.reward_type.iter().enumerate() {
92                reward_type_list.set(i as u32, reward_type);
93            }
94        }
95
96        // Set types
97        if !self.type_.is_empty() {
98            let mut type_list = builder.reborrow().init_type(self.type_.len() as u32);
99            for (i, type_) in self.type_.iter().enumerate() {
100                type_list.set(i as u32, type_);
101            }
102        }
103
104        // Set sighash
105        if !self.sighash.is_empty() {
106            let mut sighash_list = builder.reborrow().init_sighash(self.sighash.len() as u32);
107            for (i, sighash) in self.sighash.iter().enumerate() {
108                sighash_list.set(i as u32, sighash.as_slice());
109            }
110        }
111
112        Ok(())
113    }
114
115    /// Deserialize TraceSelection from Cap'n Proto reader
116    fn from_reader(
117        reader: hypersync_net_types_capnp::trace_filter::Reader,
118    ) -> Result<Self, capnp::Error> {
119        let mut from = Vec::new();
120
121        // Parse from addresses
122        if reader.has_from() {
123            let from_list = reader.get_from()?;
124            for i in 0..from_list.len() {
125                let addr_data = from_list.get(i)?;
126                if addr_data.len() == 20 {
127                    let mut addr_bytes = [0u8; 20];
128                    addr_bytes.copy_from_slice(addr_data);
129                    from.push(Address::from(addr_bytes));
130                }
131            }
132        }
133
134        let mut from_filter = None;
135
136        // Parse from filter
137        if reader.has_from_filter() {
138            let filter_data = reader.get_from_filter()?;
139            let Ok(wrapper) = FilterWrapper::from_bytes(filter_data) else {
140                return Err(capnp::Error::failed("Invalid from filter".to_string()));
141            };
142            from_filter = Some(wrapper);
143        }
144
145        let mut to = Vec::new();
146
147        // Parse to addresses
148        if reader.has_to() {
149            let to_list = reader.get_to()?;
150            for i in 0..to_list.len() {
151                let addr_data = to_list.get(i)?;
152                if addr_data.len() == 20 {
153                    let mut addr_bytes = [0u8; 20];
154                    addr_bytes.copy_from_slice(addr_data);
155                    to.push(Address::from(addr_bytes));
156                }
157            }
158        }
159
160        let mut to_filter = None;
161
162        // Parse to filter
163        if reader.has_to_filter() {
164            let filter_data = reader.get_to_filter()?;
165            let Ok(wrapper) = FilterWrapper::from_bytes(filter_data) else {
166                return Err(capnp::Error::failed("Invalid to filter".to_string()));
167            };
168            to_filter = Some(wrapper);
169        }
170
171        let mut address = Vec::new();
172
173        // Parse addresses
174        if reader.has_address() {
175            let addr_list = reader.get_address()?;
176            for i in 0..addr_list.len() {
177                let addr_data = addr_list.get(i)?;
178                if addr_data.len() == 20 {
179                    let mut addr_bytes = [0u8; 20];
180                    addr_bytes.copy_from_slice(addr_data);
181                    address.push(Address::from(addr_bytes));
182                }
183            }
184        }
185
186        let mut address_filter = None;
187
188        // Parse address filter
189        if reader.has_address_filter() {
190            let filter_data = reader.get_address_filter()?;
191            let Ok(wrapper) = FilterWrapper::from_bytes(filter_data) else {
192                return Err(capnp::Error::failed("Invalid address filter".to_string()));
193            };
194            address_filter = Some(wrapper);
195        }
196
197        let mut call_type = Vec::new();
198
199        // Parse call types
200        if reader.has_call_type() {
201            let call_type_list = reader.get_call_type()?;
202            for i in 0..call_type_list.len() {
203                let call_type_val = call_type_list.get(i)?;
204                call_type.push(call_type_val.to_string()?);
205            }
206        }
207
208        let mut reward_type = Vec::new();
209        // Parse reward types
210        if reader.has_reward_type() {
211            let reward_type_list = reader.get_reward_type()?;
212            for i in 0..reward_type_list.len() {
213                let reward_type_val = reward_type_list.get(i)?;
214                reward_type.push(reward_type_val.to_string()?);
215            }
216        }
217
218        let mut type_ = Vec::new();
219
220        // Parse types
221        if reader.has_type() {
222            let type_list = reader.get_type()?;
223            for i in 0..type_list.len() {
224                let type_val = type_list.get(i)?;
225                type_.push(type_val.to_string()?);
226            }
227        }
228
229        let mut sighash = Vec::new();
230
231        // Parse sighash
232        if reader.has_sighash() {
233            let sighash_list = reader.get_sighash()?;
234            for i in 0..sighash_list.len() {
235                let sighash_data = sighash_list.get(i)?;
236                if sighash_data.len() == 4 {
237                    let mut sighash_bytes = [0u8; 4];
238                    sighash_bytes.copy_from_slice(sighash_data);
239                    sighash.push(Sighash::from(sighash_bytes));
240                }
241            }
242        }
243
244        Ok(Self {
245            from,
246            from_filter,
247            to,
248            to_filter,
249            address,
250            address_filter,
251            call_type,
252            reward_type,
253            type_,
254            sighash,
255        })
256    }
257}
258
259#[derive(
260    Debug,
261    Clone,
262    Copy,
263    Serialize,
264    Deserialize,
265    PartialEq,
266    Eq,
267    schemars::JsonSchema,
268    strum_macros::EnumIter,
269    strum_macros::AsRefStr,
270    strum_macros::Display,
271    strum_macros::EnumString,
272)]
273#[serde(rename_all = "snake_case")]
274#[strum(serialize_all = "snake_case")]
275pub enum TraceField {
276    // Core trace fields
277    TransactionHash,
278    BlockHash,
279    BlockNumber,
280    TransactionPosition,
281    Type,
282    Error,
283
284    // Address fields
285    From,
286    To,
287    Author,
288
289    // Gas fields
290    Gas,
291    GasUsed,
292
293    // Additional trace fields from Arrow schema
294    ActionAddress,
295    Address,
296    Balance,
297    CallType,
298    Code,
299    Init,
300    Input,
301    Output,
302    RefundAddress,
303    RewardType,
304    Sighash,
305    Subtraces,
306    TraceAddress,
307    Value,
308}
309
310impl Ord for TraceField {
311    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
312        self.as_ref().cmp(other.as_ref())
313    }
314}
315
316impl PartialOrd for TraceField {
317    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
318        Some(self.cmp(other))
319    }
320}
321
322impl TraceField {
323    pub fn all() -> std::collections::BTreeSet<Self> {
324        use strum::IntoEnumIterator;
325        Self::iter().collect()
326    }
327
328    /// Convert TraceField to Cap'n Proto enum
329    pub fn to_capnp(&self) -> crate::hypersync_net_types_capnp::TraceField {
330        match self {
331            TraceField::TransactionHash => {
332                crate::hypersync_net_types_capnp::TraceField::TransactionHash
333            }
334            TraceField::BlockHash => crate::hypersync_net_types_capnp::TraceField::BlockHash,
335            TraceField::BlockNumber => crate::hypersync_net_types_capnp::TraceField::BlockNumber,
336            TraceField::TransactionPosition => {
337                crate::hypersync_net_types_capnp::TraceField::TransactionPosition
338            }
339            TraceField::Type => crate::hypersync_net_types_capnp::TraceField::Type,
340            TraceField::Error => crate::hypersync_net_types_capnp::TraceField::Error,
341            TraceField::From => crate::hypersync_net_types_capnp::TraceField::From,
342            TraceField::To => crate::hypersync_net_types_capnp::TraceField::To,
343            TraceField::Author => crate::hypersync_net_types_capnp::TraceField::Author,
344            TraceField::Gas => crate::hypersync_net_types_capnp::TraceField::Gas,
345            TraceField::GasUsed => crate::hypersync_net_types_capnp::TraceField::GasUsed,
346            TraceField::ActionAddress => {
347                crate::hypersync_net_types_capnp::TraceField::ActionAddress
348            }
349            TraceField::Address => crate::hypersync_net_types_capnp::TraceField::Address,
350            TraceField::Balance => crate::hypersync_net_types_capnp::TraceField::Balance,
351            TraceField::CallType => crate::hypersync_net_types_capnp::TraceField::CallType,
352            TraceField::Code => crate::hypersync_net_types_capnp::TraceField::Code,
353            TraceField::Init => crate::hypersync_net_types_capnp::TraceField::Init,
354            TraceField::Input => crate::hypersync_net_types_capnp::TraceField::Input,
355            TraceField::Output => crate::hypersync_net_types_capnp::TraceField::Output,
356            TraceField::RefundAddress => {
357                crate::hypersync_net_types_capnp::TraceField::RefundAddress
358            }
359            TraceField::RewardType => crate::hypersync_net_types_capnp::TraceField::RewardType,
360            TraceField::Sighash => crate::hypersync_net_types_capnp::TraceField::Sighash,
361            TraceField::Subtraces => crate::hypersync_net_types_capnp::TraceField::Subtraces,
362            TraceField::TraceAddress => crate::hypersync_net_types_capnp::TraceField::TraceAddress,
363            TraceField::Value => crate::hypersync_net_types_capnp::TraceField::Value,
364        }
365    }
366
367    /// Convert Cap'n Proto enum to TraceField
368    pub fn from_capnp(field: crate::hypersync_net_types_capnp::TraceField) -> Self {
369        match field {
370            crate::hypersync_net_types_capnp::TraceField::TransactionHash => {
371                TraceField::TransactionHash
372            }
373            crate::hypersync_net_types_capnp::TraceField::BlockHash => TraceField::BlockHash,
374            crate::hypersync_net_types_capnp::TraceField::BlockNumber => TraceField::BlockNumber,
375            crate::hypersync_net_types_capnp::TraceField::TransactionPosition => {
376                TraceField::TransactionPosition
377            }
378            crate::hypersync_net_types_capnp::TraceField::Type => TraceField::Type,
379            crate::hypersync_net_types_capnp::TraceField::Error => TraceField::Error,
380            crate::hypersync_net_types_capnp::TraceField::From => TraceField::From,
381            crate::hypersync_net_types_capnp::TraceField::To => TraceField::To,
382            crate::hypersync_net_types_capnp::TraceField::Author => TraceField::Author,
383            crate::hypersync_net_types_capnp::TraceField::Gas => TraceField::Gas,
384            crate::hypersync_net_types_capnp::TraceField::GasUsed => TraceField::GasUsed,
385            crate::hypersync_net_types_capnp::TraceField::ActionAddress => {
386                TraceField::ActionAddress
387            }
388            crate::hypersync_net_types_capnp::TraceField::Address => TraceField::Address,
389            crate::hypersync_net_types_capnp::TraceField::Balance => TraceField::Balance,
390            crate::hypersync_net_types_capnp::TraceField::CallType => TraceField::CallType,
391            crate::hypersync_net_types_capnp::TraceField::Code => TraceField::Code,
392            crate::hypersync_net_types_capnp::TraceField::Init => TraceField::Init,
393            crate::hypersync_net_types_capnp::TraceField::Input => TraceField::Input,
394            crate::hypersync_net_types_capnp::TraceField::Output => TraceField::Output,
395            crate::hypersync_net_types_capnp::TraceField::RefundAddress => {
396                TraceField::RefundAddress
397            }
398            crate::hypersync_net_types_capnp::TraceField::RewardType => TraceField::RewardType,
399            crate::hypersync_net_types_capnp::TraceField::Sighash => TraceField::Sighash,
400            crate::hypersync_net_types_capnp::TraceField::Subtraces => TraceField::Subtraces,
401            crate::hypersync_net_types_capnp::TraceField::TraceAddress => TraceField::TraceAddress,
402            crate::hypersync_net_types_capnp::TraceField::Value => TraceField::Value,
403        }
404    }
405}
406
407#[cfg(test)]
408mod tests {
409    use hypersync_format::Hex;
410
411    use super::*;
412    use crate::{query::tests::test_query_serde, FieldSelection, Query};
413
414    #[test]
415    fn test_all_fields_in_schema() {
416        let schema = hypersync_schema::trace();
417        let schema_fields = schema
418            .fields
419            .iter()
420            .map(|f| f.name.clone())
421            .collect::<std::collections::BTreeSet<_>>();
422        let all_fields = TraceField::all()
423            .into_iter()
424            .map(|f| f.as_ref().to_string())
425            .collect::<std::collections::BTreeSet<_>>();
426        assert_eq!(schema_fields, all_fields);
427    }
428
429    #[test]
430    fn test_serde_matches_strum() {
431        for field in TraceField::all() {
432            let serialized = serde_json::to_string(&field).unwrap();
433            let strum = serde_json::to_string(&field.as_ref()).unwrap();
434            assert_eq!(serialized, strum, "strum value should be the same as serde");
435        }
436    }
437
438    #[test]
439    fn test_trace_filter_serde_with_defaults() {
440        let trace_filter = TraceSelection::default();
441        let field_selection = FieldSelection {
442            trace: TraceField::all(),
443            ..Default::default()
444        };
445        let query = Query {
446            traces: vec![trace_filter],
447            field_selection,
448            ..Default::default()
449        };
450
451        test_query_serde(query, "trace selection with defaults");
452    }
453
454    #[test]
455    fn test_trace_filter_serde_with_full_values() {
456        let trace_filter = TraceFilter {
457            from: vec![Address::decode_hex("0xdadB0d80178819F2319190D340ce9A924f783711").unwrap()],
458            from_filter: Some(FilterWrapper::new(16, 1)),
459            to: vec![Address::decode_hex("0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6").unwrap()],
460            to_filter: Some(FilterWrapper::new(16, 1)),
461            address: vec![
462                Address::decode_hex("0x1234567890123456789012345678901234567890").unwrap(),
463            ],
464            address_filter: Some(FilterWrapper::new(16, 1)),
465            call_type: vec!["call".to_string(), "create".to_string()],
466            reward_type: vec!["block".to_string(), "uncle".to_string()],
467            type_: vec!["call".to_string(), "create".to_string()],
468            sighash: vec![Sighash::from([0x12, 0x34, 0x56, 0x78])],
469        };
470        let field_selection = FieldSelection {
471            trace: TraceField::all(),
472            ..Default::default()
473        };
474        let query = Query {
475            traces: vec![trace_filter.into()],
476            field_selection,
477            ..Default::default()
478        };
479
480        test_query_serde(query, "trace selection with full values");
481    }
482}