cfx_rpc_cfx_types/
epoch_number.rs

1// Copyright 2019 Conflux Foundation. All rights reserved.
2// Conflux is free software and distributed under GNU General Public License.
3// See http://www.gnu.org/licenses/
4
5use cfx_types::{H256, U64};
6use primitives::{
7    BlockHashOrEpochNumber as PrimitiveBlockHashOrEpochNumber,
8    EpochNumber as PrimitiveEpochNumber,
9};
10use serde::{
11    de::{Error, MapAccess, Visitor},
12    Deserialize, Deserializer, Serialize, Serializer,
13};
14use std::{fmt, str::FromStr};
15
16/// Represents rpc api epoch number param.
17#[derive(Debug, PartialEq, Clone, Hash, Eq)]
18pub enum EpochNumber {
19    /// Number
20    Num(U64),
21    /// Earliest epoch (true genesis)
22    Earliest,
23    /// The latest checkpoint (cur_era_genesis)
24    LatestCheckpoint,
25    ///
26    LatestFinalized,
27    /// The latest confirmed (with the estimation of the confirmation meter)
28    LatestConfirmed,
29    /// Latest block with state.
30    LatestState,
31    /// Latest mined block.
32    LatestMined,
33}
34
35//impl Default for EpochNumber {
36//    fn default() -> Self { EpochNumber::Latest }
37//}
38
39impl<'a> Deserialize<'a> for EpochNumber {
40    fn deserialize<D>(deserializer: D) -> Result<EpochNumber, D::Error>
41    where D: Deserializer<'a> {
42        deserializer.deserialize_any(EpochNumberVisitor)
43    }
44}
45
46impl Serialize for EpochNumber {
47    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
48    where S: Serializer {
49        match *self {
50            EpochNumber::Num(ref x) => {
51                serializer.serialize_str(&format!("0x{:x}", x))
52            }
53            EpochNumber::LatestMined => {
54                serializer.serialize_str("latest_mined")
55            }
56            EpochNumber::LatestFinalized => {
57                serializer.serialize_str("latest_finalized")
58            }
59            EpochNumber::LatestState => {
60                serializer.serialize_str("latest_state")
61            }
62            EpochNumber::Earliest => serializer.serialize_str("earliest"),
63            EpochNumber::LatestCheckpoint => {
64                serializer.serialize_str("latest_checkpoint")
65            }
66            EpochNumber::LatestConfirmed => {
67                serializer.serialize_str("latest_confirmed")
68            }
69        }
70    }
71}
72
73impl EpochNumber {
74    pub fn into_primitive(self) -> PrimitiveEpochNumber {
75        match self {
76            EpochNumber::Earliest => PrimitiveEpochNumber::Earliest,
77            EpochNumber::LatestMined => PrimitiveEpochNumber::LatestMined,
78            EpochNumber::LatestState => PrimitiveEpochNumber::LatestState,
79            EpochNumber::LatestFinalized => {
80                PrimitiveEpochNumber::LatestFinalized
81            }
82            EpochNumber::Num(num) => PrimitiveEpochNumber::Number(num.as_u64()),
83            EpochNumber::LatestCheckpoint => {
84                PrimitiveEpochNumber::LatestCheckpoint
85            }
86            EpochNumber::LatestConfirmed => {
87                PrimitiveEpochNumber::LatestConfirmed
88            }
89        }
90    }
91}
92
93impl FromStr for EpochNumber {
94    type Err = String;
95
96    fn from_str(s: &str) -> Result<Self, Self::Err> {
97        match s {
98            "latest_mined" => Ok(EpochNumber::LatestMined),
99            "latest_state" => Ok(EpochNumber::LatestState),
100            "latest_finalized" => Ok(EpochNumber::LatestFinalized),
101            "latest_confirmed" => Ok(EpochNumber::LatestConfirmed),
102            "earliest" => Ok(EpochNumber::Earliest),
103            "latest_checkpoint" => Ok(EpochNumber::LatestCheckpoint),
104            _ if s.starts_with("0x") => u64::from_str_radix(&s[2..], 16)
105                .map(U64::from)
106                .map(EpochNumber::Num)
107                .map_err(|e| format!("Invalid epoch number: {}", e)),
108            _ => Err("Invalid epoch number: missing 0x prefix".to_string()),
109        }
110    }
111}
112
113impl Into<PrimitiveEpochNumber> for EpochNumber {
114    fn into(self) -> PrimitiveEpochNumber { self.into_primitive() }
115}
116
117impl Into<EpochNumber> for u64 {
118    fn into(self) -> EpochNumber { EpochNumber::Num(U64::from(self)) }
119}
120
121struct EpochNumberVisitor;
122
123impl<'a> Visitor<'a> for EpochNumberVisitor {
124    type Value = EpochNumber;
125
126    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
127        write!(
128            formatter,
129            "an epoch number or 'latest_mined', 'latest_state', 'latest_checkpoint', 'latest_finalized', 'latest_confirmed' or 'earliest'"
130        )
131    }
132
133    fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
134    where E: Error {
135        value.parse().map_err(Error::custom)
136    }
137
138    fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
139    where E: Error {
140        self.visit_str(value.as_ref())
141    }
142}
143
144#[derive(Debug, PartialEq, Clone, Hash, Eq)]
145pub enum BlockHashOrEpochNumber {
146    BlockHashWithOption {
147        hash: H256,
148        /// Refer to BlockHashOrEpochNumberVisitor
149        /// for implementation detail
150        require_pivot: Option<bool>,
151    },
152    EpochNumber(EpochNumber),
153}
154
155impl BlockHashOrEpochNumber {
156    pub fn into_primitive(self) -> PrimitiveBlockHashOrEpochNumber {
157        match self {
158            BlockHashOrEpochNumber::BlockHashWithOption {
159                hash,
160                require_pivot,
161            } => PrimitiveBlockHashOrEpochNumber::BlockHashWithOption {
162                hash,
163                require_pivot,
164            },
165            BlockHashOrEpochNumber::EpochNumber(epoch_number) => {
166                PrimitiveBlockHashOrEpochNumber::EpochNumber(
167                    epoch_number.into(),
168                )
169            }
170        }
171    }
172}
173
174impl Into<PrimitiveBlockHashOrEpochNumber> for BlockHashOrEpochNumber {
175    fn into(self) -> PrimitiveBlockHashOrEpochNumber { self.into_primitive() }
176}
177
178impl<'a> Deserialize<'a> for BlockHashOrEpochNumber {
179    fn deserialize<D>(
180        deserializer: D,
181    ) -> Result<BlockHashOrEpochNumber, D::Error>
182    where D: Deserializer<'a> {
183        deserializer.deserialize_any(BlockHashOrEpochNumberVisitor)
184    }
185}
186
187impl Serialize for BlockHashOrEpochNumber {
188    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
189    where S: Serializer {
190        match self {
191            BlockHashOrEpochNumber::EpochNumber(epoch_number) => {
192                epoch_number.serialize(serializer)
193            }
194            BlockHashOrEpochNumber::BlockHashWithOption {
195                hash,
196                require_pivot,
197            } => {
198                // If require_pivot is None,
199                // serialize to the format of "hash:0x..."
200                if let Some(require_pivot) = require_pivot {
201                    serializer.serialize_str(&format!(
202                        "{{ 'hash': '{}', 'requirePivot': '{}'  }}",
203                        hash, require_pivot
204                    ))
205                } else {
206                    serializer.serialize_str(&format!("hash:{:#x}", hash))
207                }
208            }
209        }
210    }
211}
212
213struct BlockHashOrEpochNumberVisitor;
214
215// In order to keep compatibility with legacy "hash:0x..." format parameter
216// the `require_pivot` field is designed to be Option<bool>
217// if input is "hash:0x..." then `require_pivot` will be None
218// else if input is a object { blockHash: 0x... }
219// the `require_pivot` will be Some and default to true
220impl<'a> Visitor<'a> for BlockHashOrEpochNumberVisitor {
221    type Value = BlockHashOrEpochNumber;
222
223    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
224        write!(
225            formatter,
226            "an epoch number or 'latest_mined', 'latest_state', 'latest_checkpoint', 'latest_finalized', \
227             'latest_confirmed', or 'earliest', or 'hash:<BLOCK_HASH>'"
228        )
229    }
230
231    fn visit_map<V>(self, mut visitor: V) -> Result<Self::Value, V::Error>
232    where V: MapAccess<'a> {
233        // require_pivot defaults to true if input is a map
234        let (mut require_pivot, mut epoch_number, mut block_hash) =
235            (true, None::<u64>, None::<H256>);
236
237        // following the implementation in rpc/types/eth/block_number.rs
238        loop {
239            let key_str: Option<String> = visitor.next_key()?;
240
241            match key_str {
242                Some(key) => match key.as_str() {
243                    "epochNumber" => {
244                        let value: String = visitor.next_value()?;
245                        if value.starts_with("0x") {
246                            let number = u64::from_str_radix(&value[2..], 16)
247                                .map_err(|e| {
248                                Error::custom(format!(
249                                    "Invalid epoch number: {}",
250                                    e
251                                ))
252                            })?;
253
254                            epoch_number = Some(number.into());
255                            break;
256                        } else {
257                            return Err(Error::custom(
258                                "Invalid block number: missing 0x prefix"
259                                    .to_string(),
260                            ));
261                        }
262                    }
263                    "blockHash" => {
264                        block_hash = Some(visitor.next_value()?);
265                    }
266                    "requirePivot" => {
267                        require_pivot = visitor.next_value()?;
268                    }
269                    key => {
270                        return Err(Error::custom(format!(
271                            "Unknown key: {}",
272                            key
273                        )))
274                    }
275                },
276                None => break,
277            };
278        }
279
280        if let Some(number) = epoch_number {
281            return Ok(BlockHashOrEpochNumber::EpochNumber(EpochNumber::Num(
282                number.into(),
283            )));
284        }
285
286        if let Some(hash) = block_hash {
287            return Ok(BlockHashOrEpochNumber::BlockHashWithOption {
288                hash,
289                require_pivot: Some(require_pivot),
290            });
291        }
292
293        return Err(Error::custom("Invalid input"));
294    }
295
296    fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
297    where E: Error {
298        if value.starts_with("hash:0x") {
299            Ok(BlockHashOrEpochNumber::BlockHashWithOption {
300                hash: value[7..].parse().map_err(Error::custom)?,
301                require_pivot: None,
302            })
303        } else {
304            value.parse().map_err(Error::custom).map(
305                |epoch_number: EpochNumber| {
306                    BlockHashOrEpochNumber::EpochNumber(epoch_number)
307                },
308            )
309        }
310    }
311
312    fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
313    where E: Error {
314        self.visit_str(value.as_ref())
315    }
316}
317
318#[cfg(test)]
319mod tests {
320    use super::*;
321    use serde_json;
322    use std::str::FromStr;
323
324    #[test]
325    fn block_hash_or_epoch_number_deserialization() {
326        let s = r#"[
327			"0xa",
328			"latest_state",
329			"earliest",
330            "latest_mined",
331            "latest_checkpoint",
332            "latest_confirmed",
333            "latest_finalized",
334            "hash:0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
335            {"epochNumber": "0xa"},
336			{"blockHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"},
337			{"blockHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", "requirePivot": false}
338		]"#;
339        let deserialized: Vec<BlockHashOrEpochNumber> =
340            serde_json::from_str(s).unwrap();
341
342        assert_eq!(
343            deserialized,
344            vec![
345                BlockHashOrEpochNumber::EpochNumber(EpochNumber::Num((10).into())),
346                BlockHashOrEpochNumber::EpochNumber(EpochNumber::LatestState),
347                BlockHashOrEpochNumber::EpochNumber(EpochNumber::Earliest),
348                BlockHashOrEpochNumber::EpochNumber(EpochNumber::LatestMined),
349                BlockHashOrEpochNumber::EpochNumber(EpochNumber::LatestCheckpoint),
350                BlockHashOrEpochNumber::EpochNumber(EpochNumber::LatestConfirmed),
351                BlockHashOrEpochNumber::EpochNumber(EpochNumber::LatestFinalized),
352                BlockHashOrEpochNumber::BlockHashWithOption {
353                    hash: H256::from_str(
354                        "1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"
355                    )
356                    .unwrap(),
357                    // the "hash:0x..." will return an object with 
358                    // require_pivot = None
359                    require_pivot: None
360                },
361                BlockHashOrEpochNumber::EpochNumber(EpochNumber::Num((10).into())),
362                BlockHashOrEpochNumber::BlockHashWithOption {
363                    hash: H256::from_str(
364                        "1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"
365                    )
366                    .unwrap(),
367                    require_pivot: Some(true)
368                },
369                BlockHashOrEpochNumber::BlockHashWithOption {
370                    hash: H256::from_str(
371                        "1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"
372                    )
373                    .unwrap(),
374                    require_pivot: Some(false)
375                }
376            ]
377        )
378    }
379
380    #[test]
381    fn should_not_deserialize() {
382        let s = r#"[{}, "10"]"#;
383        assert!(serde_json::from_str::<Vec<BlockHashOrEpochNumber>>(s).is_err());
384    }
385}