web3_etz/types/
sync_state.rs

1use crate::types::U256;
2use serde::de::{Deserializer, Error};
3use serde::ser::Serializer;
4use serde::{Deserialize, Serialize};
5
6/// Information about current blockchain syncing operations.
7#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
8#[serde(rename_all = "camelCase")]
9pub struct SyncInfo {
10    /// The block at which import began.
11    pub starting_block: U256,
12
13    /// The highest currently synced block.
14    pub current_block: U256,
15
16    /// The estimated highest block.
17    pub highest_block: U256,
18}
19
20/// The current state of blockchain syncing operations.
21#[derive(Debug, Clone, PartialEq)]
22pub enum SyncState {
23    /// Blockchain is syncing.
24    Syncing(SyncInfo),
25
26    /// Blockchain is not syncing.
27    NotSyncing,
28}
29
30// Sync info from subscription has a different key format
31#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
32#[serde(rename_all = "PascalCase")]
33struct SubscriptionSyncInfo {
34    /// The block at which import began.
35    pub starting_block: U256,
36
37    /// The highest currently synced block.
38    pub current_block: U256,
39
40    /// The estimated highest block.
41    pub highest_block: U256,
42}
43
44impl From<SubscriptionSyncInfo> for SyncInfo {
45    fn from(s: SubscriptionSyncInfo) -> Self {
46        Self {
47            starting_block: s.starting_block,
48            current_block: s.current_block,
49            highest_block: s.highest_block,
50        }
51    }
52}
53
54#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
55struct SubscriptionSyncState {
56    pub syncing: bool,
57    pub status: Option<SubscriptionSyncInfo>,
58}
59
60#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
61#[serde(untagged)]
62enum SyncStateVariants {
63    Rpc(SyncInfo),
64    Subscription(SubscriptionSyncState),
65    Boolean(bool),
66}
67
68// The `eth_syncing` method returns either `false` or an instance of the sync info object.
69// This doesn't play particularly well with the features exposed by `serde_derive`,
70// so we use the custom impls below to ensure proper behavior.
71impl<'de> Deserialize<'de> for SyncState {
72    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
73    where
74        D: Deserializer<'de>,
75    {
76        let v: SyncStateVariants = Deserialize::deserialize(deserializer)?;
77        match v {
78            SyncStateVariants::Rpc(info) => Ok(SyncState::Syncing(info)),
79            SyncStateVariants::Subscription(state) => match state.status {
80                None if !state.syncing => Ok(SyncState::NotSyncing),
81                Some(ref info) if state.syncing => Ok(SyncState::Syncing(info.clone().into())),
82                _ => Err(D::Error::custom(
83                    "expected object or `syncing = false`, got `syncing = true`",
84                )),
85            },
86            SyncStateVariants::Boolean(boolean) => {
87                if !boolean {
88                    Ok(SyncState::NotSyncing)
89                } else {
90                    Err(D::Error::custom("expected object or `false`, got `true`"))
91                }
92            }
93        }
94    }
95}
96
97impl Serialize for SyncState {
98    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
99    where
100        S: Serializer,
101    {
102        match *self {
103            SyncState::Syncing(ref info) => info.serialize(serializer),
104            SyncState::NotSyncing => false.serialize(serializer),
105        }
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use super::{SyncInfo, SyncState};
112
113    use serde_json;
114
115    #[test]
116    fn should_deserialize_rpc_sync_info() {
117        let sync_state = r#"{
118          "currentBlock": "0x42",
119          "highestBlock": "0x9001",
120          "knownStates": "0x1337",
121          "pulledStates": "0x13",
122          "startingBlock": "0x0"
123        }"#;
124
125        let value: SyncState = serde_json::from_str(sync_state).unwrap();
126
127        assert_eq!(
128            value,
129            SyncState::Syncing(SyncInfo {
130                starting_block: 0x0.into(),
131                current_block: 0x42.into(),
132                highest_block: 0x9001.into()
133            })
134        );
135    }
136
137    #[test]
138    fn should_deserialize_subscription_sync_info() {
139        let sync_state = r#"{
140          "syncing": true,
141          "status": {
142            "CurrentBlock": "0x42",
143            "HighestBlock": "0x9001",
144            "KnownStates": "0x1337",
145            "PulledStates": "0x13",
146            "StartingBlock": "0x0"
147          }
148        }"#;
149
150        let value: SyncState = serde_json::from_str(sync_state).unwrap();
151
152        assert_eq!(
153            value,
154            SyncState::Syncing(SyncInfo {
155                starting_block: 0x0.into(),
156                current_block: 0x42.into(),
157                highest_block: 0x9001.into()
158            })
159        );
160    }
161
162    #[test]
163    fn should_deserialize_boolean_not_syncing() {
164        let sync_state = r#"false"#;
165        let value: SyncState = serde_json::from_str(sync_state).unwrap();
166
167        assert_eq!(value, SyncState::NotSyncing);
168    }
169
170    #[test]
171    fn should_deserialize_subscription_not_syncing() {
172        let sync_state = r#"{
173          "syncing": false
174        }"#;
175
176        let value: SyncState = serde_json::from_str(sync_state).unwrap();
177
178        assert_eq!(value, SyncState::NotSyncing);
179    }
180
181    #[test]
182    fn should_not_deserialize_invalid_boolean_syncing() {
183        let sync_state = r#"true"#;
184        let res: Result<SyncState, _> = serde_json::from_str(sync_state);
185        assert!(res.is_err());
186    }
187
188    #[test]
189    fn should_not_deserialize_invalid_subscription_syncing() {
190        let sync_state = r#"{
191          "syncing": true
192        }"#;
193
194        let res: Result<SyncState, _> = serde_json::from_str(sync_state);
195        assert!(res.is_err());
196    }
197
198    #[test]
199    fn should_not_deserialize_invalid_subscription_not_syncing() {
200        let sync_state = r#"{
201          "syncing": false,
202          "status": {
203            "CurrentBlock": "0x42",
204            "HighestBlock": "0x9001",
205            "KnownStates": "0x1337",
206            "PulledStates": "0x13",
207            "StartingBlock": "0x0"
208          }
209        }"#;
210
211        let res: Result<SyncState, _> = serde_json::from_str(sync_state);
212        assert!(res.is_err());
213    }
214}