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 const fn is_nullable(&self) -> bool {
704 match self {
705 TraceField::From
706 | TraceField::To
707 | TraceField::CallType
708 | TraceField::Gas
709 | TraceField::Input
710 | TraceField::Init
711 | TraceField::Value
712 | TraceField::Author
713 | TraceField::RewardType
714 | TraceField::Address
715 | TraceField::Code
716 | TraceField::GasUsed
717 | TraceField::Output
718 | TraceField::Subtraces
719 | TraceField::TraceAddress
720 | TraceField::TransactionHash
721 | TraceField::TransactionPosition
722 | TraceField::Type
723 | TraceField::Error
724 | TraceField::Sighash
725 | TraceField::ActionAddress
726 | TraceField::Balance
727 | TraceField::RefundAddress => true,
728 TraceField::BlockHash | TraceField::BlockNumber => false,
729 }
730 }
731
732 pub fn to_capnp(&self) -> crate::hypersync_net_types_capnp::TraceField {
734 match self {
735 TraceField::TransactionHash => {
736 crate::hypersync_net_types_capnp::TraceField::TransactionHash
737 }
738 TraceField::BlockHash => crate::hypersync_net_types_capnp::TraceField::BlockHash,
739 TraceField::BlockNumber => crate::hypersync_net_types_capnp::TraceField::BlockNumber,
740 TraceField::TransactionPosition => {
741 crate::hypersync_net_types_capnp::TraceField::TransactionPosition
742 }
743 TraceField::Type => crate::hypersync_net_types_capnp::TraceField::Type,
744 TraceField::Error => crate::hypersync_net_types_capnp::TraceField::Error,
745 TraceField::From => crate::hypersync_net_types_capnp::TraceField::From,
746 TraceField::To => crate::hypersync_net_types_capnp::TraceField::To,
747 TraceField::Author => crate::hypersync_net_types_capnp::TraceField::Author,
748 TraceField::Gas => crate::hypersync_net_types_capnp::TraceField::Gas,
749 TraceField::GasUsed => crate::hypersync_net_types_capnp::TraceField::GasUsed,
750 TraceField::ActionAddress => {
751 crate::hypersync_net_types_capnp::TraceField::ActionAddress
752 }
753 TraceField::Address => crate::hypersync_net_types_capnp::TraceField::Address,
754 TraceField::Balance => crate::hypersync_net_types_capnp::TraceField::Balance,
755 TraceField::CallType => crate::hypersync_net_types_capnp::TraceField::CallType,
756 TraceField::Code => crate::hypersync_net_types_capnp::TraceField::Code,
757 TraceField::Init => crate::hypersync_net_types_capnp::TraceField::Init,
758 TraceField::Input => crate::hypersync_net_types_capnp::TraceField::Input,
759 TraceField::Output => crate::hypersync_net_types_capnp::TraceField::Output,
760 TraceField::RefundAddress => {
761 crate::hypersync_net_types_capnp::TraceField::RefundAddress
762 }
763 TraceField::RewardType => crate::hypersync_net_types_capnp::TraceField::RewardType,
764 TraceField::Sighash => crate::hypersync_net_types_capnp::TraceField::Sighash,
765 TraceField::Subtraces => crate::hypersync_net_types_capnp::TraceField::Subtraces,
766 TraceField::TraceAddress => crate::hypersync_net_types_capnp::TraceField::TraceAddress,
767 TraceField::Value => crate::hypersync_net_types_capnp::TraceField::Value,
768 }
769 }
770
771 pub fn from_capnp(field: crate::hypersync_net_types_capnp::TraceField) -> Self {
773 match field {
774 crate::hypersync_net_types_capnp::TraceField::TransactionHash => {
775 TraceField::TransactionHash
776 }
777 crate::hypersync_net_types_capnp::TraceField::BlockHash => TraceField::BlockHash,
778 crate::hypersync_net_types_capnp::TraceField::BlockNumber => TraceField::BlockNumber,
779 crate::hypersync_net_types_capnp::TraceField::TransactionPosition => {
780 TraceField::TransactionPosition
781 }
782 crate::hypersync_net_types_capnp::TraceField::Type => TraceField::Type,
783 crate::hypersync_net_types_capnp::TraceField::Error => TraceField::Error,
784 crate::hypersync_net_types_capnp::TraceField::From => TraceField::From,
785 crate::hypersync_net_types_capnp::TraceField::To => TraceField::To,
786 crate::hypersync_net_types_capnp::TraceField::Author => TraceField::Author,
787 crate::hypersync_net_types_capnp::TraceField::Gas => TraceField::Gas,
788 crate::hypersync_net_types_capnp::TraceField::GasUsed => TraceField::GasUsed,
789 crate::hypersync_net_types_capnp::TraceField::ActionAddress => {
790 TraceField::ActionAddress
791 }
792 crate::hypersync_net_types_capnp::TraceField::Address => TraceField::Address,
793 crate::hypersync_net_types_capnp::TraceField::Balance => TraceField::Balance,
794 crate::hypersync_net_types_capnp::TraceField::CallType => TraceField::CallType,
795 crate::hypersync_net_types_capnp::TraceField::Code => TraceField::Code,
796 crate::hypersync_net_types_capnp::TraceField::Init => TraceField::Init,
797 crate::hypersync_net_types_capnp::TraceField::Input => TraceField::Input,
798 crate::hypersync_net_types_capnp::TraceField::Output => TraceField::Output,
799 crate::hypersync_net_types_capnp::TraceField::RefundAddress => {
800 TraceField::RefundAddress
801 }
802 crate::hypersync_net_types_capnp::TraceField::RewardType => TraceField::RewardType,
803 crate::hypersync_net_types_capnp::TraceField::Sighash => TraceField::Sighash,
804 crate::hypersync_net_types_capnp::TraceField::Subtraces => TraceField::Subtraces,
805 crate::hypersync_net_types_capnp::TraceField::TraceAddress => TraceField::TraceAddress,
806 crate::hypersync_net_types_capnp::TraceField::Value => TraceField::Value,
807 }
808 }
809}
810
811#[cfg(test)]
812mod tests {
813 use hypersync_format::Hex;
814
815 use super::*;
816 use crate::{query::tests::test_query_serde, Query};
817
818 #[test]
819 fn test_all_fields_in_schema() {
820 let schema = hypersync_schema::trace();
821 let schema_fields = schema
822 .fields
823 .iter()
824 .map(|f| f.name().clone())
825 .collect::<std::collections::BTreeSet<_>>();
826 let all_fields = TraceField::all()
827 .into_iter()
828 .map(|f| f.as_ref().to_string())
829 .collect::<std::collections::BTreeSet<_>>();
830 assert_eq!(schema_fields, all_fields);
831 }
832
833 #[test]
834 fn test_serde_matches_strum() {
835 for field in TraceField::all() {
836 let serialized = serde_json::to_string(&field).unwrap();
837 let strum = serde_json::to_string(&field.as_ref()).unwrap();
838 assert_eq!(serialized, strum, "strum value should be the same as serde");
839 }
840 }
841
842 #[test]
843 fn test_trace_filter_serde_with_defaults() {
844 let trace_filter = TraceSelection::default();
845 let query = Query::new()
846 .where_traces(trace_filter)
847 .select_trace_fields(TraceField::all());
848
849 test_query_serde(query, "trace selection with defaults");
850 }
851
852 #[test]
853 fn test_trace_filter_serde_with_full_values() {
854 let trace_filter = TraceFilter {
855 from: vec![Address::decode_hex("0xdadB0d80178819F2319190D340ce9A924f783711").unwrap()],
856 from_filter: Some(FilterWrapper::new(16, 1)),
857 to: vec![Address::decode_hex("0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6").unwrap()],
858 to_filter: Some(FilterWrapper::new(16, 1)),
859 address: vec![
860 Address::decode_hex("0x1234567890123456789012345678901234567890").unwrap(),
861 ],
862 address_filter: Some(FilterWrapper::new(16, 1)),
863 call_type: vec!["call".to_string(), "create".to_string()],
864 reward_type: vec!["block".to_string(), "uncle".to_string()],
865 type_: vec!["call".to_string(), "create".to_string()],
866 sighash: vec![Sighash::from([0x12, 0x34, 0x56, 0x78])],
867 };
868 let query = Query::new()
869 .where_traces(trace_filter)
870 .select_trace_fields(TraceField::all());
871
872 test_query_serde(query, "trace selection with full values");
873 }
874
875 #[test]
876 fn nullable_fields() {
877 use std::collections::HashMap;
878
879 let is_nullable_map: HashMap<_, _> = TraceField::all()
880 .iter()
881 .map(|f| (f.to_string(), f.is_nullable()))
882 .collect();
883 for field in hypersync_schema::trace().fields.iter() {
884 let should_be_nullable = is_nullable_map.get(field.name().as_str()).unwrap();
885 assert_eq!(
886 field.is_nullable(),
887 *should_be_nullable,
888 "field {} nullable mismatch",
889 field.name()
890 );
891 }
892 }
893}