mwc_web3/types/
sync_state.rs

1use crate::types::U256;
2use serde::{
3    de::{Deserializer, Error},
4    ser::Serializer,
5    Deserialize, Serialize,
6};
7
8/// Information about current blockchain syncing operations.
9#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
10#[serde(rename_all = "camelCase")]
11pub struct SyncInfo {
12    /// The block at which import began.
13    pub starting_block: U256,
14
15    /// The highest currently synced block.
16    pub current_block: U256,
17
18    /// The estimated highest block.
19    pub highest_block: U256,
20}
21
22/// The current state of blockchain syncing operations.
23#[derive(Debug, Clone, PartialEq)]
24pub enum SyncState {
25    /// Blockchain is syncing.
26    Syncing(SyncInfo),
27
28    /// Blockchain is not syncing.
29    NotSyncing,
30}
31
32// Sync info from subscription has a different key format
33#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
34#[serde(rename_all = "PascalCase")]
35struct SubscriptionSyncInfo {
36    /// The block at which import began.
37    pub starting_block: U256,
38
39    /// The highest currently synced block.
40    pub current_block: U256,
41
42    /// The estimated highest block.
43    pub highest_block: U256,
44}
45
46impl From<SubscriptionSyncInfo> for SyncInfo {
47    fn from(s: SubscriptionSyncInfo) -> Self {
48        Self {
49            starting_block: s.starting_block,
50            current_block: s.current_block,
51            highest_block: s.highest_block,
52        }
53    }
54}
55
56#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
57struct SubscriptionSyncState {
58    pub syncing: bool,
59    pub status: Option<SubscriptionSyncInfo>,
60}
61
62#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
63#[serde(untagged)]
64enum SyncStateVariants {
65    Rpc(SyncInfo),
66    Subscription(SubscriptionSyncState),
67    Boolean(bool),
68}
69
70// The `eth_syncing` method returns either `false` or an instance of the sync info object.
71// This doesn't play particularly well with the features exposed by `serde_derive`,
72// so we use the custom impls below to ensure proper behavior.
73impl<'de> Deserialize<'de> for SyncState {
74    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
75    where
76        D: Deserializer<'de>,
77    {
78        let v: SyncStateVariants = Deserialize::deserialize(deserializer)?;
79        match v {
80            SyncStateVariants::Rpc(info) => Ok(SyncState::Syncing(info)),
81            SyncStateVariants::Subscription(state) => match state.status {
82                None if !state.syncing => Ok(SyncState::NotSyncing),
83                Some(ref info) if state.syncing => Ok(SyncState::Syncing(info.clone().into())),
84                _ => Err(D::Error::custom(
85                    "expected object or `syncing = false`, got `syncing = true`",
86                )),
87            },
88            SyncStateVariants::Boolean(boolean) => {
89                if !boolean {
90                    Ok(SyncState::NotSyncing)
91                } else {
92                    Err(D::Error::custom("expected object or `false`, got `true`"))
93                }
94            }
95        }
96    }
97}
98
99impl Serialize for SyncState {
100    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
101    where
102        S: Serializer,
103    {
104        match *self {
105            SyncState::Syncing(ref info) => info.serialize(serializer),
106            SyncState::NotSyncing => false.serialize(serializer),
107        }
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use super::{SyncInfo, SyncState};
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}