casper_types/block/
block_identifier.rs

1use alloc::vec::Vec;
2use core::num::ParseIntError;
3
4#[cfg(any(feature = "testing", test))]
5use rand::Rng;
6#[cfg(feature = "json-schema")]
7use schemars::JsonSchema;
8use serde::{Deserialize, Serialize};
9
10#[cfg(any(feature = "testing", test))]
11use crate::testing::TestRng;
12use crate::{
13    bytesrepr::{self, FromBytes, ToBytes, U8_SERIALIZED_LENGTH},
14    BlockHash, Digest, DigestError,
15};
16
17const HASH_TAG: u8 = 0;
18const HEIGHT_TAG: u8 = 1;
19
20/// Identifier for possible ways to retrieve a block.
21#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
22#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
23#[serde(deny_unknown_fields)]
24pub enum BlockIdentifier {
25    /// Identify and retrieve the block with its hash.
26    Hash(BlockHash),
27    /// Identify and retrieve the block with its height.
28    Height(u64),
29}
30
31impl BlockIdentifier {
32    /// Random.
33    #[cfg(any(feature = "testing", test))]
34    pub fn random(rng: &mut TestRng) -> Self {
35        match rng.gen_range(0..1) {
36            0 => Self::Hash(BlockHash::random(rng)),
37            1 => Self::Height(rng.gen()),
38            _ => panic!(),
39        }
40    }
41}
42
43impl FromBytes for BlockIdentifier {
44    fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
45        match bytes.split_first() {
46            Some((&HASH_TAG, rem)) => {
47                let (hash, rem) = FromBytes::from_bytes(rem)?;
48                Ok((BlockIdentifier::Hash(hash), rem))
49            }
50            Some((&HEIGHT_TAG, rem)) => {
51                let (height, rem) = FromBytes::from_bytes(rem)?;
52                Ok((BlockIdentifier::Height(height), rem))
53            }
54            Some(_) | None => Err(bytesrepr::Error::Formatting),
55        }
56    }
57}
58
59impl ToBytes for BlockIdentifier {
60    fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
61        let mut buffer = bytesrepr::allocate_buffer(self)?;
62        self.write_bytes(&mut buffer)?;
63        Ok(buffer)
64    }
65
66    fn write_bytes(&self, writer: &mut Vec<u8>) -> Result<(), bytesrepr::Error> {
67        match self {
68            BlockIdentifier::Hash(hash) => {
69                writer.push(HASH_TAG);
70                hash.write_bytes(writer)?;
71            }
72            BlockIdentifier::Height(height) => {
73                writer.push(HEIGHT_TAG);
74                height.write_bytes(writer)?;
75            }
76        }
77        Ok(())
78    }
79
80    fn serialized_length(&self) -> usize {
81        U8_SERIALIZED_LENGTH
82            + match self {
83                BlockIdentifier::Hash(hash) => hash.serialized_length(),
84                BlockIdentifier::Height(height) => height.serialized_length(),
85            }
86    }
87}
88
89impl core::str::FromStr for BlockIdentifier {
90    type Err = ParseBlockIdentifierError;
91
92    fn from_str(maybe_block_identifier: &str) -> Result<Self, Self::Err> {
93        if maybe_block_identifier.is_empty() {
94            return Err(ParseBlockIdentifierError::EmptyString);
95        }
96
97        if maybe_block_identifier.len() == (Digest::LENGTH * 2) {
98            let hash = Digest::from_hex(maybe_block_identifier)
99                .map_err(ParseBlockIdentifierError::FromHexError)?;
100            Ok(BlockIdentifier::Hash(BlockHash::new(hash)))
101        } else {
102            let height = maybe_block_identifier
103                .parse()
104                .map_err(ParseBlockIdentifierError::ParseIntError)?;
105            Ok(BlockIdentifier::Height(height))
106        }
107    }
108}
109
110/// Represents errors that can arise when parsing a [`BlockIdentifier`].
111#[derive(Debug)]
112#[cfg_attr(feature = "std", derive(thiserror::Error))]
113pub enum ParseBlockIdentifierError {
114    /// String was empty.
115    #[cfg_attr(
116        feature = "std",
117        error("Empty string is not a valid block identifier.")
118    )]
119    EmptyString,
120    /// Couldn't parse a height value.
121    #[cfg_attr(feature = "std", error("Unable to parse height from string. {0}"))]
122    ParseIntError(ParseIntError),
123    /// Couldn't parse a blake2bhash.
124    #[cfg_attr(feature = "std", error("Unable to parse digest from string. {0}"))]
125    FromHexError(DigestError),
126}
127
128#[cfg(test)]
129mod tests {
130    use super::*;
131    use crate::testing::TestRng;
132
133    #[test]
134    fn bytesrepr_roundtrip() {
135        let rng = &mut TestRng::new();
136
137        let val = BlockIdentifier::random(rng);
138        bytesrepr::test_serialization_roundtrip(&val);
139    }
140}