stratus/eth/primitives/
log_filter_input.rs

1use std::ops::Deref;
2use std::sync::Arc;
3
4use display_json::DebugAsJson;
5use serde_with::DefaultOnNull;
6use serde_with::OneOrMany;
7use serde_with::PickFirst;
8use serde_with::formats::PreferMany;
9use serde_with::serde_as;
10
11use crate::eth::primitives::Address;
12use crate::eth::primitives::BlockFilter;
13use crate::eth::primitives::Hash;
14use crate::eth::primitives::LogFilter;
15use crate::eth::primitives::LogTopic;
16use crate::eth::primitives::PointInTime;
17use crate::eth::storage::StratusStorage;
18
19/// JSON-RPC input used in methods like `eth_getLogs` and `eth_subscribe`.
20#[serde_as]
21#[derive(DebugAsJson, Clone, Default, serde::Deserialize, serde::Serialize, PartialEq, Eq, Hash)]
22#[cfg_attr(test, derive(fake::Dummy))]
23pub struct LogFilterInput {
24    #[serde(rename = "fromBlock", default)]
25    pub from_block: Option<BlockFilter>,
26
27    #[serde(rename = "toBlock", default)]
28    pub to_block: Option<BlockFilter>,
29
30    #[serde(rename = "blockHash", default)]
31    pub block_hash: Option<Hash>,
32
33    #[serde(rename = "address", default)]
34    #[serde_as(deserialize_as = "PickFirst<(DefaultOnNull, OneOrMany<_, PreferMany>)>")]
35    pub address: Vec<Address>,
36
37    // NOTE: we are not checking if this is of size 4, which is the limit in the spec
38    #[serde(rename = "topics", default)]
39    #[serde_as(deserialize_as = "DefaultOnNull")]
40    pub topics: Vec<LogFilterInputTopic>,
41}
42
43impl LogFilterInput {
44    /// Parses itself into a filter that can be applied in produced log events or to query the storage.
45    pub fn parse(self, storage: &Arc<StratusStorage>) -> anyhow::Result<LogFilter> {
46        let original_input = self.clone();
47
48        // parse point-in-time
49        let (from, to) = match self.block_hash {
50            Some(hash) => {
51                let from_to = storage.translate_to_point_in_time(BlockFilter::Hash(hash))?;
52                (from_to, from_to)
53            }
54            None => {
55                let from = storage.translate_to_point_in_time(self.from_block.unwrap_or(BlockFilter::Latest))?;
56                let to = storage.translate_to_point_in_time(self.to_block.unwrap_or(BlockFilter::Latest))?;
57                (from, to)
58            }
59        };
60
61        // translate point-in-time to block according to context
62        let from = match from {
63            PointInTime::Pending => storage.read_pending_block_header().0.number,
64            PointInTime::Mined => storage.read_mined_block_number(),
65            PointInTime::MinedPast(number) => number,
66        };
67        let to = match to {
68            PointInTime::Pending => None,
69            PointInTime::Mined => None,
70            PointInTime::MinedPast(number) => Some(number),
71        };
72
73        Ok(LogFilter {
74            from_block: from,
75            to_block: to,
76            addresses: self.address,
77            original_input,
78        })
79    }
80}
81
82#[serde_as]
83#[derive(DebugAsJson, Clone, Default, serde::Deserialize, serde::Serialize, PartialEq, Eq, Hash)]
84#[cfg_attr(test, derive(fake::Dummy))]
85// This nested type is necessary to fine-tune how we want serde to deserialize the topics field
86pub struct LogFilterInputTopic(#[serde_as(deserialize_as = "OneOrMany<_, PreferMany>")] pub Vec<Option<LogTopic>>);
87
88impl Deref for LogFilterInputTopic {
89    type Target = Vec<Option<LogTopic>>;
90    fn deref(&self) -> &Self::Target {
91        &self.0
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[test]
100    fn deserialize_log_filter_input_with_topics() {
101        use serde_plain::from_str as deser;
102
103        let input = r#"{
104            "fromBlock": "0x3F5DBCA",
105            "toBlock": "latest",
106            "address": [
107              "0xea86da4a617b32b081a4af12e6c13ae7edf8dfc9"
108            ],
109            "topics": [
110              [
111                "0x712a5b346bb553ab14a2a2b44106991a5b94e4d44890d9aaa0f8e6b3268c502c",
112                "0xc9de12e35626948d49833bbe7ac6ebe7e7d96e2d2a2e01e1eaca07830c0bf03d"
113              ],
114              null,
115              "0x000000000000000000000000c23f832f3d9dd9492df35197f3ec0caa1cb23ce1",
116              "0x453138313839353437323032343036323031373434307a495331324d4b446f36"
117            ]
118        }"#;
119
120        let result: LogFilterInput = serde_json::from_str(input).unwrap();
121
122        let expected = LogFilterInput {
123            from_block: Some(deser("0x3F5DBCA").unwrap()),
124            to_block: Some(deser("latest").unwrap()),
125            block_hash: None,
126            address: vec![deser("0xea86da4a617b32b081a4af12e6c13ae7edf8dfc9").unwrap()],
127            topics: vec![
128                LogFilterInputTopic(vec![
129                    Some(deser("0x712a5b346bb553ab14a2a2b44106991a5b94e4d44890d9aaa0f8e6b3268c502c").unwrap()),
130                    Some(deser("0xc9de12e35626948d49833bbe7ac6ebe7e7d96e2d2a2e01e1eaca07830c0bf03d").unwrap()),
131                ]),
132                LogFilterInputTopic(vec![None]),
133                LogFilterInputTopic(vec![Some(deser("0x000000000000000000000000c23f832f3d9dd9492df35197f3ec0caa1cb23ce1").unwrap())]),
134                LogFilterInputTopic(vec![Some(deser("0x453138313839353437323032343036323031373434307a495331324d4b446f36").unwrap())]),
135            ],
136        };
137
138        assert_eq!(result, expected);
139    }
140
141    #[test]
142    fn deserialize_log_filter_input_empty() {
143        let input = "{}";
144        let result: LogFilterInput = serde_json::from_str(input).unwrap();
145        let expected = LogFilterInput::default();
146        assert_eq!(result, expected);
147    }
148}