1use crate::{
2 hypersync_net_types_capnp,
3 types::{AnyOf, Sighash},
4 CapnpBuilder, CapnpReader, Selection,
5};
6use anyhow::Context;
7use hypersync_format::{Address, FilterWrapper};
8use serde::{Deserialize, Serialize};
9
10pub type TraceSelection = Selection<TraceFilter>;
11
12impl From<TraceFilter> for AnyOf<TraceFilter> {
13 fn from(filter: TraceFilter) -> Self {
14 Self::new(filter)
15 }
16}
17
18#[derive(Default, Serialize, Deserialize, Clone, Debug, PartialEq)]
19#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
20pub struct TraceFilter {
21 #[serde(default, skip_serializing_if = "Vec::is_empty")]
22 pub from: Vec<Address>,
23 #[serde(default, skip_serializing_if = "Option::is_none")]
24 pub from_filter: Option<FilterWrapper>,
25 #[serde(default, skip_serializing_if = "Vec::is_empty")]
26 pub to: Vec<Address>,
27 #[serde(default, skip_serializing_if = "Option::is_none")]
28 pub to_filter: Option<FilterWrapper>,
29 #[serde(default, skip_serializing_if = "Vec::is_empty")]
30 pub address: Vec<Address>,
31 #[serde(default, skip_serializing_if = "Option::is_none")]
32 pub address_filter: Option<FilterWrapper>,
33 #[serde(default, skip_serializing_if = "Vec::is_empty")]
34 pub call_type: Vec<String>,
35 #[serde(default, skip_serializing_if = "Vec::is_empty")]
36 pub reward_type: Vec<String>,
37 #[serde(default, skip_serializing_if = "Vec::is_empty")]
38 #[serde(rename = "type")]
39 pub type_: Vec<String>,
40 #[serde(default, skip_serializing_if = "Vec::is_empty")]
41 pub sighash: Vec<Sighash>,
42}
43
44impl TraceFilter {
45 pub fn all() -> Self {
64 Default::default()
65 }
66
67 pub fn or(self, other: Self) -> AnyOf<Self> {
93 AnyOf::new(self).or(other)
94 }
95
96 pub fn and_from<I, A>(mut self, addresses: I) -> anyhow::Result<Self>
135 where
136 I: IntoIterator<Item = A>,
137 A: TryInto<Address>,
138 A::Error: std::error::Error + Send + Sync + 'static,
139 {
140 let mut converted_addresses: Vec<Address> = Vec::new();
141 for (idx, address) in addresses.into_iter().enumerate() {
142 converted_addresses.push(
143 address
144 .try_into()
145 .with_context(|| format!("invalid from address at position {idx}"))?,
146 );
147 }
148 self.from = converted_addresses;
149 Ok(self)
150 }
151
152 pub fn and_to<I, A>(mut self, addresses: I) -> anyhow::Result<Self>
188 where
189 I: IntoIterator<Item = A>,
190 A: TryInto<Address>,
191 A::Error: std::error::Error + Send + Sync + 'static,
192 {
193 let mut converted_addresses: Vec<Address> = Vec::new();
194 for (idx, address) in addresses.into_iter().enumerate() {
195 converted_addresses.push(
196 address
197 .try_into()
198 .with_context(|| format!("invalid to address at position {idx}"))?,
199 );
200 }
201 self.to = converted_addresses;
202 Ok(self)
203 }
204
205 pub fn and_address<I, A>(mut self, addresses: I) -> anyhow::Result<Self>
236 where
237 I: IntoIterator<Item = A>,
238 A: TryInto<Address>,
239 A::Error: std::error::Error + Send + Sync + 'static,
240 {
241 let mut converted_addresses: Vec<Address> = Vec::new();
242 for (idx, address) in addresses.into_iter().enumerate() {
243 converted_addresses.push(
244 address
245 .try_into()
246 .with_context(|| format!("invalid address at position {idx}"))?,
247 );
248 }
249 self.address = converted_addresses;
250 Ok(self)
251 }
252
253 pub fn and_call_type<I, S>(mut self, call_types: I) -> Self
281 where
282 I: IntoIterator<Item = S>,
283 S: Into<String>,
284 {
285 self.call_type = call_types.into_iter().map(Into::into).collect();
286 self
287 }
288
289 pub fn and_reward_type<I, S>(mut self, reward_types: I) -> Self
311 where
312 I: IntoIterator<Item = S>,
313 S: Into<String>,
314 {
315 self.reward_type = reward_types.into_iter().map(Into::into).collect();
316 self
317 }
318
319 pub fn and_type<I, S>(mut self, types: I) -> Self
341 where
342 I: IntoIterator<Item = S>,
343 S: Into<String>,
344 {
345 self.type_ = types.into_iter().map(Into::into).collect();
346 self
347 }
348
349 pub fn and_sighash<I, S>(mut self, sighashes: I) -> anyhow::Result<Self>
386 where
387 I: IntoIterator<Item = S>,
388 S: TryInto<Sighash>,
389 S::Error: std::error::Error + Send + Sync + 'static,
390 {
391 let mut converted_sighashes: Vec<Sighash> = Vec::new();
392 for (idx, sighash) in sighashes.into_iter().enumerate() {
393 converted_sighashes.push(
394 sighash
395 .try_into()
396 .with_context(|| format!("invalid sighash at position {idx}"))?,
397 );
398 }
399 self.sighash = converted_sighashes;
400 Ok(self)
401 }
402}
403
404impl CapnpBuilder<hypersync_net_types_capnp::trace_filter::Owned> for TraceFilter {
405 fn populate_builder(
406 &self,
407 builder: &mut hypersync_net_types_capnp::trace_filter::Builder,
408 ) -> Result<(), capnp::Error> {
409 if !self.from.is_empty() {
411 let mut from_list = builder.reborrow().init_from(self.from.len() as u32);
412 for (i, addr) in self.from.iter().enumerate() {
413 from_list.set(i as u32, addr.as_slice());
414 }
415 }
416
417 if let Some(filter) = &self.from_filter {
419 builder.reborrow().set_from_filter(filter.0.as_bytes());
420 }
421
422 if !self.to.is_empty() {
424 let mut to_list = builder.reborrow().init_to(self.to.len() as u32);
425 for (i, addr) in self.to.iter().enumerate() {
426 to_list.set(i as u32, addr.as_slice());
427 }
428 }
429
430 if let Some(filter) = &self.to_filter {
432 builder.reborrow().set_to_filter(filter.0.as_bytes());
433 }
434
435 if !self.address.is_empty() {
437 let mut addr_list = builder.reborrow().init_address(self.address.len() as u32);
438 for (i, addr) in self.address.iter().enumerate() {
439 addr_list.set(i as u32, addr.as_slice());
440 }
441 }
442
443 if let Some(filter) = &self.address_filter {
445 builder.reborrow().set_address_filter(filter.0.as_bytes());
446 }
447
448 if !self.call_type.is_empty() {
450 let mut call_type_list = builder
451 .reborrow()
452 .init_call_type(self.call_type.len() as u32);
453 for (i, call_type) in self.call_type.iter().enumerate() {
454 call_type_list.set(i as u32, call_type);
455 }
456 }
457
458 if !self.reward_type.is_empty() {
460 let mut reward_type_list = builder
461 .reborrow()
462 .init_reward_type(self.reward_type.len() as u32);
463 for (i, reward_type) in self.reward_type.iter().enumerate() {
464 reward_type_list.set(i as u32, reward_type);
465 }
466 }
467
468 if !self.type_.is_empty() {
470 let mut type_list = builder.reborrow().init_type(self.type_.len() as u32);
471 for (i, type_) in self.type_.iter().enumerate() {
472 type_list.set(i as u32, type_);
473 }
474 }
475
476 if !self.sighash.is_empty() {
478 let mut sighash_list = builder.reborrow().init_sighash(self.sighash.len() as u32);
479 for (i, sighash) in self.sighash.iter().enumerate() {
480 sighash_list.set(i as u32, sighash.as_slice());
481 }
482 }
483
484 Ok(())
485 }
486}
487
488impl CapnpReader<hypersync_net_types_capnp::trace_filter::Owned> for TraceFilter {
489 fn from_reader(
491 reader: hypersync_net_types_capnp::trace_filter::Reader,
492 ) -> Result<Self, capnp::Error> {
493 let mut from = Vec::new();
494
495 if reader.has_from() {
497 let from_list = reader.get_from()?;
498 for i in 0..from_list.len() {
499 let addr_data = from_list.get(i)?;
500 if addr_data.len() == 20 {
501 let mut addr_bytes = [0u8; 20];
502 addr_bytes.copy_from_slice(addr_data);
503 from.push(Address::from(addr_bytes));
504 }
505 }
506 }
507
508 let mut from_filter = None;
509
510 if reader.has_from_filter() {
512 let filter_data = reader.get_from_filter()?;
513 let Ok(wrapper) = FilterWrapper::from_bytes(filter_data) else {
514 return Err(capnp::Error::failed("Invalid from filter".to_string()));
515 };
516 from_filter = Some(wrapper);
517 }
518
519 let mut to = Vec::new();
520
521 if reader.has_to() {
523 let to_list = reader.get_to()?;
524 for i in 0..to_list.len() {
525 let addr_data = to_list.get(i)?;
526 if addr_data.len() == 20 {
527 let mut addr_bytes = [0u8; 20];
528 addr_bytes.copy_from_slice(addr_data);
529 to.push(Address::from(addr_bytes));
530 }
531 }
532 }
533
534 let mut to_filter = None;
535
536 if reader.has_to_filter() {
538 let filter_data = reader.get_to_filter()?;
539 let Ok(wrapper) = FilterWrapper::from_bytes(filter_data) else {
540 return Err(capnp::Error::failed("Invalid to filter".to_string()));
541 };
542 to_filter = Some(wrapper);
543 }
544
545 let mut address = Vec::new();
546
547 if reader.has_address() {
549 let addr_list = reader.get_address()?;
550 for i in 0..addr_list.len() {
551 let addr_data = addr_list.get(i)?;
552 if addr_data.len() == 20 {
553 let mut addr_bytes = [0u8; 20];
554 addr_bytes.copy_from_slice(addr_data);
555 address.push(Address::from(addr_bytes));
556 }
557 }
558 }
559
560 let mut address_filter = None;
561
562 if reader.has_address_filter() {
564 let filter_data = reader.get_address_filter()?;
565 let Ok(wrapper) = FilterWrapper::from_bytes(filter_data) else {
566 return Err(capnp::Error::failed("Invalid address filter".to_string()));
567 };
568 address_filter = Some(wrapper);
569 }
570
571 let mut call_type = Vec::new();
572
573 if reader.has_call_type() {
575 let call_type_list = reader.get_call_type()?;
576 for i in 0..call_type_list.len() {
577 let call_type_val = call_type_list.get(i)?;
578 call_type.push(call_type_val.to_string()?);
579 }
580 }
581
582 let mut reward_type = Vec::new();
583 if reader.has_reward_type() {
585 let reward_type_list = reader.get_reward_type()?;
586 for i in 0..reward_type_list.len() {
587 let reward_type_val = reward_type_list.get(i)?;
588 reward_type.push(reward_type_val.to_string()?);
589 }
590 }
591
592 let mut type_ = Vec::new();
593
594 if reader.has_type() {
596 let type_list = reader.get_type()?;
597 for i in 0..type_list.len() {
598 let type_val = type_list.get(i)?;
599 type_.push(type_val.to_string()?);
600 }
601 }
602
603 let mut sighash = Vec::new();
604
605 if reader.has_sighash() {
607 let sighash_list = reader.get_sighash()?;
608 for i in 0..sighash_list.len() {
609 let sighash_data = sighash_list.get(i)?;
610 if sighash_data.len() == 4 {
611 let mut sighash_bytes = [0u8; 4];
612 sighash_bytes.copy_from_slice(sighash_data);
613 sighash.push(Sighash::from(sighash_bytes));
614 }
615 }
616 }
617
618 Ok(Self {
619 from,
620 from_filter,
621 to,
622 to_filter,
623 address,
624 address_filter,
625 call_type,
626 reward_type,
627 type_,
628 sighash,
629 })
630 }
631}
632
633#[derive(
634 Debug,
635 Clone,
636 Copy,
637 Serialize,
638 Deserialize,
639 PartialEq,
640 Eq,
641 schemars::JsonSchema,
642 strum_macros::EnumIter,
643 strum_macros::AsRefStr,
644 strum_macros::Display,
645 strum_macros::EnumString,
646)]
647#[serde(rename_all = "snake_case")]
648#[strum(serialize_all = "snake_case")]
649#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
650pub enum TraceField {
651 TransactionHash,
653 BlockHash,
654 BlockNumber,
655 TransactionPosition,
656 Type,
657 Error,
658
659 From,
661 To,
662 Author,
663
664 Gas,
666 GasUsed,
667
668 ActionAddress,
670 Address,
671 Balance,
672 CallType,
673 Code,
674 Init,
675 Input,
676 Output,
677 RefundAddress,
678 RewardType,
679 Sighash,
680 Subtraces,
681 TraceAddress,
682 Value,
683}
684
685impl Ord for TraceField {
686 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
687 self.as_ref().cmp(other.as_ref())
688 }
689}
690
691impl PartialOrd for TraceField {
692 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
693 Some(self.cmp(other))
694 }
695}
696
697impl TraceField {
698 pub fn all() -> std::collections::BTreeSet<Self> {
699 use strum::IntoEnumIterator;
700 Self::iter().collect()
701 }
702
703 pub fn to_capnp(&self) -> crate::hypersync_net_types_capnp::TraceField {
705 match self {
706 TraceField::TransactionHash => {
707 crate::hypersync_net_types_capnp::TraceField::TransactionHash
708 }
709 TraceField::BlockHash => crate::hypersync_net_types_capnp::TraceField::BlockHash,
710 TraceField::BlockNumber => crate::hypersync_net_types_capnp::TraceField::BlockNumber,
711 TraceField::TransactionPosition => {
712 crate::hypersync_net_types_capnp::TraceField::TransactionPosition
713 }
714 TraceField::Type => crate::hypersync_net_types_capnp::TraceField::Type,
715 TraceField::Error => crate::hypersync_net_types_capnp::TraceField::Error,
716 TraceField::From => crate::hypersync_net_types_capnp::TraceField::From,
717 TraceField::To => crate::hypersync_net_types_capnp::TraceField::To,
718 TraceField::Author => crate::hypersync_net_types_capnp::TraceField::Author,
719 TraceField::Gas => crate::hypersync_net_types_capnp::TraceField::Gas,
720 TraceField::GasUsed => crate::hypersync_net_types_capnp::TraceField::GasUsed,
721 TraceField::ActionAddress => {
722 crate::hypersync_net_types_capnp::TraceField::ActionAddress
723 }
724 TraceField::Address => crate::hypersync_net_types_capnp::TraceField::Address,
725 TraceField::Balance => crate::hypersync_net_types_capnp::TraceField::Balance,
726 TraceField::CallType => crate::hypersync_net_types_capnp::TraceField::CallType,
727 TraceField::Code => crate::hypersync_net_types_capnp::TraceField::Code,
728 TraceField::Init => crate::hypersync_net_types_capnp::TraceField::Init,
729 TraceField::Input => crate::hypersync_net_types_capnp::TraceField::Input,
730 TraceField::Output => crate::hypersync_net_types_capnp::TraceField::Output,
731 TraceField::RefundAddress => {
732 crate::hypersync_net_types_capnp::TraceField::RefundAddress
733 }
734 TraceField::RewardType => crate::hypersync_net_types_capnp::TraceField::RewardType,
735 TraceField::Sighash => crate::hypersync_net_types_capnp::TraceField::Sighash,
736 TraceField::Subtraces => crate::hypersync_net_types_capnp::TraceField::Subtraces,
737 TraceField::TraceAddress => crate::hypersync_net_types_capnp::TraceField::TraceAddress,
738 TraceField::Value => crate::hypersync_net_types_capnp::TraceField::Value,
739 }
740 }
741
742 pub fn from_capnp(field: crate::hypersync_net_types_capnp::TraceField) -> Self {
744 match field {
745 crate::hypersync_net_types_capnp::TraceField::TransactionHash => {
746 TraceField::TransactionHash
747 }
748 crate::hypersync_net_types_capnp::TraceField::BlockHash => TraceField::BlockHash,
749 crate::hypersync_net_types_capnp::TraceField::BlockNumber => TraceField::BlockNumber,
750 crate::hypersync_net_types_capnp::TraceField::TransactionPosition => {
751 TraceField::TransactionPosition
752 }
753 crate::hypersync_net_types_capnp::TraceField::Type => TraceField::Type,
754 crate::hypersync_net_types_capnp::TraceField::Error => TraceField::Error,
755 crate::hypersync_net_types_capnp::TraceField::From => TraceField::From,
756 crate::hypersync_net_types_capnp::TraceField::To => TraceField::To,
757 crate::hypersync_net_types_capnp::TraceField::Author => TraceField::Author,
758 crate::hypersync_net_types_capnp::TraceField::Gas => TraceField::Gas,
759 crate::hypersync_net_types_capnp::TraceField::GasUsed => TraceField::GasUsed,
760 crate::hypersync_net_types_capnp::TraceField::ActionAddress => {
761 TraceField::ActionAddress
762 }
763 crate::hypersync_net_types_capnp::TraceField::Address => TraceField::Address,
764 crate::hypersync_net_types_capnp::TraceField::Balance => TraceField::Balance,
765 crate::hypersync_net_types_capnp::TraceField::CallType => TraceField::CallType,
766 crate::hypersync_net_types_capnp::TraceField::Code => TraceField::Code,
767 crate::hypersync_net_types_capnp::TraceField::Init => TraceField::Init,
768 crate::hypersync_net_types_capnp::TraceField::Input => TraceField::Input,
769 crate::hypersync_net_types_capnp::TraceField::Output => TraceField::Output,
770 crate::hypersync_net_types_capnp::TraceField::RefundAddress => {
771 TraceField::RefundAddress
772 }
773 crate::hypersync_net_types_capnp::TraceField::RewardType => TraceField::RewardType,
774 crate::hypersync_net_types_capnp::TraceField::Sighash => TraceField::Sighash,
775 crate::hypersync_net_types_capnp::TraceField::Subtraces => TraceField::Subtraces,
776 crate::hypersync_net_types_capnp::TraceField::TraceAddress => TraceField::TraceAddress,
777 crate::hypersync_net_types_capnp::TraceField::Value => TraceField::Value,
778 }
779 }
780}
781
782#[cfg(test)]
783mod tests {
784 use hypersync_format::Hex;
785
786 use super::*;
787 use crate::{query::tests::test_query_serde, Query};
788
789 #[test]
790 fn test_all_fields_in_schema() {
791 let schema = hypersync_schema::trace();
792 let schema_fields = schema
793 .fields
794 .iter()
795 .map(|f| f.name.clone())
796 .collect::<std::collections::BTreeSet<_>>();
797 let all_fields = TraceField::all()
798 .into_iter()
799 .map(|f| f.as_ref().to_string())
800 .collect::<std::collections::BTreeSet<_>>();
801 assert_eq!(schema_fields, all_fields);
802 }
803
804 #[test]
805 fn test_serde_matches_strum() {
806 for field in TraceField::all() {
807 let serialized = serde_json::to_string(&field).unwrap();
808 let strum = serde_json::to_string(&field.as_ref()).unwrap();
809 assert_eq!(serialized, strum, "strum value should be the same as serde");
810 }
811 }
812
813 #[test]
814 fn test_trace_filter_serde_with_defaults() {
815 let trace_filter = TraceSelection::default();
816 let query = Query::new()
817 .where_traces(trace_filter)
818 .select_trace_fields(TraceField::all());
819
820 test_query_serde(query, "trace selection with defaults");
821 }
822
823 #[test]
824 fn test_trace_filter_serde_with_full_values() {
825 let trace_filter = TraceFilter {
826 from: vec![Address::decode_hex("0xdadB0d80178819F2319190D340ce9A924f783711").unwrap()],
827 from_filter: Some(FilterWrapper::new(16, 1)),
828 to: vec![Address::decode_hex("0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6").unwrap()],
829 to_filter: Some(FilterWrapper::new(16, 1)),
830 address: vec![
831 Address::decode_hex("0x1234567890123456789012345678901234567890").unwrap(),
832 ],
833 address_filter: Some(FilterWrapper::new(16, 1)),
834 call_type: vec!["call".to_string(), "create".to_string()],
835 reward_type: vec!["block".to_string(), "uncle".to_string()],
836 type_: vec!["call".to_string(), "create".to_string()],
837 sighash: vec![Sighash::from([0x12, 0x34, 0x56, 0x78])],
838 };
839 let query = Query::new()
840 .where_traces(trace_filter)
841 .select_trace_fields(TraceField::all());
842
843 test_query_serde(query, "trace selection with full values");
844 }
845}