1use core::convert::Infallible;
6use core::fmt;
7
8#[cfg(feature = "arbitrary")]
9use arbitrary::{Arbitrary, Unstructured};
10
11use crate::internal_macros::write_err;
12use crate::parse::{self, ParseIntError};
13#[cfg(feature = "alloc")]
14use crate::prelude::*;
15
16pub const LOCK_TIME_THRESHOLD: u32 = 500_000_000;
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
31pub struct Height(u32);
32
33impl Height {
34 pub const ZERO: Self = Height(0);
36
37 pub const MIN: Self = Self::ZERO;
39
40 pub const MAX: Self = Height(LOCK_TIME_THRESHOLD - 1);
42
43 pub fn from_hex(s: &str) -> Result<Self, ParseHeightError> {
47 parse_hex(s, Self::from_consensus)
48 }
49
50 #[inline]
65 pub fn from_consensus(n: u32) -> Result<Height, ConversionError> {
66 if is_block_height(n) {
67 Ok(Self(n))
68 } else {
69 Err(ConversionError::invalid_height(n))
70 }
71 }
72
73 #[inline]
75 pub fn to_consensus_u32(self) -> u32 { self.0 }
76}
77
78impl fmt::Display for Height {
79 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) }
80}
81
82crate::impl_parse_str!(Height, ParseHeightError, parser(Height::from_consensus));
83
84#[derive(Debug, Clone, Eq, PartialEq)]
86pub struct ParseHeightError(ParseError);
87
88impl fmt::Display for ParseHeightError {
89 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
90 self.0.display(f, "block height", 0, LOCK_TIME_THRESHOLD - 1)
91 }
92}
93
94#[cfg(feature = "std")]
95impl std::error::Error for ParseHeightError {
96 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { self.0.source() }
98}
99
100impl From<ParseError> for ParseHeightError {
101 fn from(value: ParseError) -> Self { Self(value) }
102}
103
104#[cfg(feature = "serde")]
105impl<'de> serde::Deserialize<'de> for Height {
106 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
107 where
108 D: serde::Deserializer<'de>,
109 {
110 let u = u32::deserialize(deserializer)?;
111 Ok(Height::from_consensus(u).map_err(serde::de::Error::custom)?)
112 }
113}
114
115#[cfg(feature = "serde")]
116impl serde::Serialize for Height {
117 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
118 where
119 S: serde::Serializer,
120 {
121 self.to_consensus_u32().serialize(serializer)
122 }
123}
124
125#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
131pub struct Time(u32);
132
133impl Time {
134 pub const MIN: Self = Time(LOCK_TIME_THRESHOLD);
136
137 pub const MAX: Self = Time(u32::MAX);
139
140 pub fn from_hex(s: &str) -> Result<Self, ParseTimeError> { parse_hex(s, Self::from_consensus) }
144
145 #[inline]
160 pub fn from_consensus(n: u32) -> Result<Time, ConversionError> {
161 if is_block_time(n) {
162 Ok(Self(n))
163 } else {
164 Err(ConversionError::invalid_time(n))
165 }
166 }
167
168 #[inline]
170 pub fn to_consensus_u32(self) -> u32 { self.0 }
171}
172
173impl fmt::Display for Time {
174 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) }
175}
176
177crate::impl_parse_str!(Time, ParseTimeError, parser(Time::from_consensus));
178
179#[cfg(feature = "serde")]
180impl<'de> serde::Deserialize<'de> for Time {
181 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
182 where
183 D: serde::Deserializer<'de>,
184 {
185 let u = u32::deserialize(deserializer)?;
186 Ok(Time::from_consensus(u).map_err(serde::de::Error::custom)?)
187 }
188}
189
190#[cfg(feature = "serde")]
191impl serde::Serialize for Time {
192 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
193 where
194 S: serde::Serializer,
195 {
196 self.to_consensus_u32().serialize(serializer)
197 }
198}
199
200#[derive(Debug, Clone, Eq, PartialEq)]
202pub struct ParseTimeError(ParseError);
203
204impl fmt::Display for ParseTimeError {
205 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
206 self.0.display(f, "block height", LOCK_TIME_THRESHOLD, u32::MAX)
207 }
208}
209
210#[cfg(feature = "std")]
211impl std::error::Error for ParseTimeError {
212 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { self.0.source() }
214}
215
216impl From<ParseError> for ParseTimeError {
217 fn from(value: ParseError) -> Self { Self(value) }
218}
219
220fn parser<T, E, S, F>(f: F) -> impl FnOnce(S) -> Result<T, E>
221where
222 E: From<ParseError>,
223 S: AsRef<str> + Into<String>,
224 F: FnOnce(u32) -> Result<T, ConversionError>,
225{
226 move |s| {
227 let n = s.as_ref().parse::<i64>().map_err(ParseError::invalid_int(s))?;
228 let n = u32::try_from(n).map_err(|_| ParseError::Conversion(n))?;
229 f(n).map_err(ParseError::from).map_err(Into::into)
230 }
231}
232
233fn parse_hex<T, E, S, F>(s: S, f: F) -> Result<T, E>
234where
235 E: From<ParseError>,
236 S: AsRef<str> + Into<String>,
237 F: FnOnce(u32) -> Result<T, ConversionError>,
238{
239 let n = i64::from_str_radix(parse::strip_hex_prefix(s.as_ref()), 16)
240 .map_err(ParseError::invalid_int(s))?;
241 let n = u32::try_from(n).map_err(|_| ParseError::Conversion(n))?;
242 f(n).map_err(ParseError::from).map_err(Into::into)
243}
244
245pub fn is_block_height(n: u32) -> bool { n < LOCK_TIME_THRESHOLD }
247
248pub fn is_block_time(n: u32) -> bool { n >= LOCK_TIME_THRESHOLD }
250
251#[derive(Debug, Clone, PartialEq, Eq)]
253#[non_exhaustive]
254pub struct ConversionError {
255 unit: LockTimeUnit,
257 input: u32,
259}
260
261impl ConversionError {
262 fn invalid_height(n: u32) -> Self { Self { unit: LockTimeUnit::Blocks, input: n } }
264
265 fn invalid_time(n: u32) -> Self { Self { unit: LockTimeUnit::Seconds, input: n } }
267}
268
269impl fmt::Display for ConversionError {
270 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
271 write!(f, "invalid lock time value {}, {}", self.input, self.unit)
272 }
273}
274
275#[cfg(feature = "std")]
276impl std::error::Error for ConversionError {
277 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
278}
279
280#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
282enum LockTimeUnit {
283 Blocks,
285 Seconds,
287}
288
289impl fmt::Display for LockTimeUnit {
290 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
291 use LockTimeUnit::*;
292
293 match *self {
294 Blocks => write!(f, "expected lock-by-blockheight (must be < {})", LOCK_TIME_THRESHOLD),
295 Seconds => write!(f, "expected lock-by-blocktime (must be >= {})", LOCK_TIME_THRESHOLD),
296 }
297 }
298}
299
300#[derive(Debug, Clone, Eq, PartialEq)]
302enum ParseError {
303 InvalidInteger { source: core::num::ParseIntError, input: String },
304 Conversion(i64),
307}
308
309impl From<Infallible> for ParseError {
310 fn from(never: Infallible) -> Self { match never {} }
311}
312
313impl ParseError {
314 fn invalid_int<S: Into<String>>(s: S) -> impl FnOnce(core::num::ParseIntError) -> Self {
315 move |source| Self::InvalidInteger { source, input: s.into() }
316 }
317
318 fn display(
319 &self,
320 f: &mut fmt::Formatter<'_>,
321 subject: &str,
322 lower_bound: u32,
323 upper_bound: u32,
324 ) -> fmt::Result {
325 use core::num::IntErrorKind;
326
327 use ParseError::*;
328
329 match self {
330 InvalidInteger { source, input } if *source.kind() == IntErrorKind::PosOverflow => {
331 write!(f, "{} {} is above limit {}", subject, input, upper_bound)
332 }
333 InvalidInteger { source, input } if *source.kind() == IntErrorKind::NegOverflow => {
334 write!(f, "{} {} is below limit {}", subject, input, lower_bound)
335 }
336 InvalidInteger { source, input } => {
337 write_err!(f, "failed to parse {} as {}", input, subject; source)
338 }
339 Conversion(value) if *value < i64::from(lower_bound) => {
340 write!(f, "{} {} is below limit {}", subject, value, lower_bound)
341 }
342 Conversion(value) => {
343 write!(f, "{} {} is above limit {}", subject, value, upper_bound)
344 }
345 }
346 }
347
348 #[cfg(feature = "std")]
350 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
351 use core::num::IntErrorKind;
352
353 use ParseError::*;
354
355 match self {
356 InvalidInteger { source, .. } if *source.kind() == IntErrorKind::PosOverflow => None,
357 InvalidInteger { source, .. } if *source.kind() == IntErrorKind::NegOverflow => None,
358 InvalidInteger { source, .. } => Some(source),
359 Conversion(_) => None,
360 }
361 }
362}
363
364impl From<ParseIntError> for ParseError {
365 fn from(value: ParseIntError) -> Self {
366 Self::InvalidInteger { source: value.source, input: value.input }
367 }
368}
369
370impl From<ConversionError> for ParseError {
371 fn from(value: ConversionError) -> Self { Self::Conversion(value.input.into()) }
372}
373
374#[cfg(feature = "arbitrary")]
375impl<'a> Arbitrary<'a> for Height {
376 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
377 let choice = u.int_in_range(0..=2)?;
378 match choice {
379 0 => Ok(Height::MIN),
380 1 => Ok(Height::MAX),
381 _ => {
382 let min = Height::MIN.to_consensus_u32();
383 let max = Height::MAX.to_consensus_u32();
384 let h = u.int_in_range(min..=max)?;
385 Ok(Height::from_consensus(h).unwrap())
386 }
387 }
388 }
389}
390
391#[cfg(feature = "arbitrary")]
392impl<'a> Arbitrary<'a> for Time {
393 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
394 let choice = u.int_in_range(0..=2)?;
395 match choice {
396 0 => Ok(Time::MIN),
397 1 => Ok(Time::MAX),
398 _ => {
399 let min = Time::MIN.to_consensus_u32();
400 let max = Time::MAX.to_consensus_u32();
401 let t = u.int_in_range(min..=max)?;
402 Ok(Time::from_consensus(t).unwrap())
403 }
404 }
405 }
406}
407
408#[cfg(test)]
409mod tests {
410 use super::*;
411
412 #[test]
413 fn time_from_str_hex_happy_path() {
414 let actual = Time::from_hex("0x6289C350").unwrap();
415 let expected = Time::from_consensus(0x6289C350).unwrap();
416 assert_eq!(actual, expected);
417 }
418
419 #[test]
420 fn time_from_str_hex_no_prefix_happy_path() {
421 let time = Time::from_hex("6289C350").unwrap();
422 assert_eq!(time, Time(0x6289C350));
423 }
424
425 #[test]
426 fn time_from_str_hex_invalid_hex_should_err() {
427 let hex = "0xzb93";
428 let result = Time::from_hex(hex);
429 assert!(result.is_err());
430 }
431
432 #[test]
433 fn height_from_str_hex_happy_path() {
434 let actual = Height::from_hex("0xBA70D").unwrap();
435 let expected = Height(0xBA70D);
436 assert_eq!(actual, expected);
437 }
438
439 #[test]
440 fn height_from_str_hex_no_prefix_happy_path() {
441 let height = Height::from_hex("BA70D").unwrap();
442 assert_eq!(height, Height(0xBA70D));
443 }
444
445 #[test]
446 fn height_from_str_hex_invalid_hex_should_err() {
447 let hex = "0xzb93";
448 let result = Height::from_hex(hex);
449 assert!(result.is_err());
450 }
451
452 #[test]
453 #[cfg(feature = "serde")]
454 pub fn encode_decode_height() {
455 serde_round_trip!(Height::ZERO);
456 serde_round_trip!(Height::MIN);
457 serde_round_trip!(Height::MAX);
458 }
459
460 #[test]
461 #[cfg(feature = "serde")]
462 pub fn encode_decode_time() {
463 serde_round_trip!(Time::MIN);
464 serde_round_trip!(Time::MAX);
465 }
466}