dubp_common/
blockstamp.rs

1//! Wrapper for blockstamp
2
3use crate::*;
4
5#[derive(Clone, Copy, Debug, Error, PartialEq)]
6/// Error when converting bytes to Blockstamp
7pub enum BlockstampFromBytesError {
8    /// Given bytes have invalid length
9    #[error("Given bytes have invalid length")]
10    InvalidLen,
11}
12
13/// Type of errors for [`Blockstamp`] parsing.
14///
15/// [`Blockstamp`]: struct.Blockstamp.html
16#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
17pub enum BlockstampParseError {
18    /// Given bytes have invalid length
19    #[error("Given bytes have invalid length")]
20    InvalidLen,
21    /// Given string have invalid format
22    #[error("Given string have invalid format")]
23    InvalidFormat,
24    /// [`BlockNumber`](struct.BlockHash.html) part is not a valid number.
25    #[error("BlockNumber part is not a valid number.")]
26    InvalidBlockNumber,
27    /// [`BlockHash`](struct.BlockHash.html) part is not a valid hex number.
28    #[error("BlockHash part is not a valid hex number.")]
29    InvalidBlockHash(BaseConversionError),
30}
31
32/// A blockstamp (Unique ID).
33///
34/// It's composed of the [`BlockNumber`] and
35/// the [`BlockHash`] of the block.
36///
37/// Thanks to blockchain immutability and frequent block production, it can
38/// be used to date information.
39///
40/// [`BlockNumber`]: struct.BlockNumber.html
41/// [`BlockHash`]: struct.BlockHash.html
42
43#[derive(Copy, Clone, Default, Deserialize, PartialEq, Eq, Hash, Serialize)]
44pub struct Blockstamp {
45    /// Block Id.
46    pub number: BlockNumber,
47    /// Block hash.
48    pub hash: BlockHash,
49}
50
51/// Previous blockstamp (BlockNumber-1, previous_hash)
52pub type PreviousBlockstamp = Blockstamp;
53
54impl Blockstamp {
55    /// Blockstamp size (in bytes).
56    pub const SIZE_IN_BYTES: usize = 36;
57}
58
59impl From<Blockstamp> for [u8; Blockstamp::SIZE_IN_BYTES] {
60    fn from(blockstamp: Blockstamp) -> Self {
61        let mut bytes = [0u8; Blockstamp::SIZE_IN_BYTES];
62
63        bytes[..4].copy_from_slice(&blockstamp.number.0.to_be_bytes());
64
65        unsafe {
66            std::ptr::copy_nonoverlapping(
67                (blockstamp.hash.0).0.as_ptr(),
68                bytes[4..].as_mut_ptr(),
69                Hash::SIZE_IN_BYTES,
70            );
71        }
72
73        bytes
74    }
75}
76
77impl Display for Blockstamp {
78    fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
79        write!(f, "{}-{}", self.number, self.hash)
80    }
81}
82
83impl Debug for Blockstamp {
84    fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
85        write!(f, "Blockstamp({})", self)
86    }
87}
88
89impl PartialOrd for Blockstamp {
90    fn partial_cmp(&self, other: &Blockstamp) -> Option<Ordering> {
91        Some(self.cmp(other))
92    }
93}
94
95impl Ord for Blockstamp {
96    fn cmp(&self, other: &Blockstamp) -> Ordering {
97        if self.number == other.number {
98            self.hash.cmp(&other.hash)
99        } else {
100            self.number.cmp(&other.number)
101        }
102    }
103}
104
105impl crate::bytes_traits::FromBytes for Blockstamp {
106    type Err = BlockstampFromBytesError;
107
108    /// Create a `Blockstamp` from bytes.
109    fn from_bytes(src: &[u8]) -> Result<Blockstamp, BlockstampFromBytesError> {
110        if src.len() != Blockstamp::SIZE_IN_BYTES {
111            Err(BlockstampFromBytesError::InvalidLen)
112        } else {
113            let mut id_bytes = [0u8; 4];
114            id_bytes.copy_from_slice(&src[..4]);
115            let mut hash_bytes = [0u8; 32];
116            unsafe {
117                std::ptr::copy_nonoverlapping(
118                    src[4..].as_ptr(),
119                    hash_bytes.as_mut_ptr(),
120                    Hash::SIZE_IN_BYTES,
121                );
122            }
123            Ok(Blockstamp {
124                number: BlockNumber(u32::from_be_bytes(id_bytes)),
125                hash: BlockHash(Hash(hash_bytes)),
126            })
127        }
128    }
129}
130
131impl FromStr for Blockstamp {
132    type Err = BlockstampParseError;
133
134    fn from_str(src: &str) -> Result<Blockstamp, BlockstampParseError> {
135        let mut split = src.split('-');
136
137        match (split.next(), split.next(), split.next()) {
138            (Some(id), Some(hash), None) => {
139                let hash = Hash::from_hex(hash).map_err(BlockstampParseError::InvalidBlockHash)?;
140
141                if let Ok(id) = id.parse::<u32>() {
142                    Ok(Blockstamp {
143                        number: BlockNumber(id),
144                        hash: BlockHash(hash),
145                    })
146                } else {
147                    Err(BlockstampParseError::InvalidBlockNumber)
148                }
149            }
150            _ => Err(BlockstampParseError::InvalidFormat),
151        }
152    }
153}
154
155#[cfg(test)]
156mod tests {
157
158    use super::*;
159    use crate::bytes_traits::FromBytes;
160    use unwrap::unwrap;
161
162    #[test]
163    fn blockstamp_default() {
164        assert_eq!(
165            Blockstamp::default(),
166            Blockstamp {
167                number: BlockNumber(0),
168                hash: BlockHash(Hash([0u8; 32])),
169            }
170        )
171    }
172
173    #[test]
174    fn blockstamp_ord() {
175        let b1 = unwrap!(Blockstamp::from_str(
176            "123-000003176306959F8674C25757BCE1CD27768E29A9B2F6DD2A4AACEAFF8C9413"
177        ));
178        let b2 = unwrap!(Blockstamp::from_str(
179            "124-000003176306959F8674C25757BCE1CD27768E29A9B2F6DD2A4AACEAFF8C9413"
180        ));
181        let b3 = unwrap!(Blockstamp::from_str(
182            "124-000003176306959F8674C25757BCE1CD27768E29A9B2F6DD2A4AACEAFF8C9415"
183        ));
184
185        assert!(b1 < b2);
186        assert!(b2 < b3);
187    }
188
189    #[test]
190    fn blockstamp_from_str_errors() {
191        assert_eq!(
192            Err(BlockstampParseError::InvalidFormat),
193            Blockstamp::from_str("invalid_format")
194        );
195        assert_eq!(
196            Err(BlockstampParseError::InvalidBlockNumber),
197            Blockstamp::from_str(
198                "not_a_number-000003176306959F8674C25757BCE1CD27768E29A9B2F6DD2A4AACEAFF8C9413"
199            )
200        );
201        assert_eq!(
202            Err(BlockstampParseError::InvalidBlockHash(
203                BaseConversionError::InvalidLength {
204                    expected: 64,
205                    found: 3,
206                }
207            )),
208            Blockstamp::from_str("123-ZZZ")
209        );
210    }
211
212    #[test]
213    fn blockstamp_from_bytes() -> Result<(), BlockstampFromBytesError> {
214        assert_eq!(
215            Blockstamp::from_bytes(&[]),
216            Err(BlockstampFromBytesError::InvalidLen)
217        );
218
219        assert_eq!(
220            Blockstamp::default(),
221            Blockstamp::from_bytes(&[
222                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
223                0, 0, 0, 0, 0, 0, 0, 0
224            ])?
225        );
226
227        assert_eq!(
228            Blockstamp {
229                number: BlockNumber(3),
230                hash: BlockHash(Hash([2u8; 32])),
231            },
232            Blockstamp::from_bytes(&[
233                0, 0, 0, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
234                2, 2, 2, 2, 2, 2, 2, 2,
235            ])?
236        );
237
238        Ok(())
239    }
240
241    #[test]
242    fn blockstamp_into_bytes() {
243        let bytes: [u8; Blockstamp::SIZE_IN_BYTES] = Blockstamp::default().into();
244        assert_eq!(&bytes[..4], &[0, 0, 0, 0,]);
245        assert_eq!(
246            &bytes[4..],
247            &[
248                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
249                0, 0, 0, 0,
250            ]
251        );
252
253        let bytes: [u8; Blockstamp::SIZE_IN_BYTES] = Blockstamp {
254            number: BlockNumber(3),
255            hash: BlockHash(Hash([2u8; 32])),
256        }
257        .into();
258        assert_eq!(&bytes[..4], &[0, 0, 0, 3,]);
259        assert_eq!(
260            &bytes[4..],
261            &[
262                2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
263                2, 2, 2, 2,
264            ]
265        );
266    }
267}