bitcoin_units/locktime/absolute/
error.rs1use core::convert::Infallible;
6use core::fmt;
7
8use internals::error::InputString;
9#[cfg(feature = "encoding")]
10use internals::write_err;
11
12use super::{Height, MedianTimePast, LOCK_TIME_THRESHOLD};
13use crate::parse_int::{ParseIntError, PrefixedHexError, UnprefixedHexError};
14
15#[cfg(feature = "encoding")]
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct LockTimeDecoderError(pub(super) encoding::UnexpectedEofError);
19
20#[cfg(feature = "encoding")]
21impl From<Infallible> for LockTimeDecoderError {
22 fn from(never: Infallible) -> Self { match never {} }
23}
24
25#[cfg(feature = "encoding")]
26impl fmt::Display for LockTimeDecoderError {
27 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
28 write_err!(f, "lock time decoder error"; self.0)
29 }
30}
31
32#[cfg(feature = "encoding")]
33#[cfg(feature = "std")]
34impl std::error::Error for LockTimeDecoderError {
35 #[inline]
36 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { Some(&self.0) }
37}
38
39#[derive(Debug, Clone, PartialEq, Eq)]
41pub struct IncompatibleHeightError {
42 pub(super) lock: MedianTimePast,
44 pub(super) incompatible: Height,
46}
47
48impl IncompatibleHeightError {
49 #[inline]
51 pub fn lock(&self) -> MedianTimePast { self.lock }
52
53 #[inline]
55 pub fn incompatible(&self) -> Height { self.incompatible }
56}
57
58impl From<Infallible> for IncompatibleHeightError {
59 fn from(never: Infallible) -> Self { match never {} }
60}
61
62impl fmt::Display for IncompatibleHeightError {
63 #[inline]
64 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
65 write!(
66 f,
67 "tried to satisfy a lock-by-time lock {} with height: {}",
68 self.lock, self.incompatible
69 )
70 }
71}
72
73#[cfg(feature = "std")]
74impl std::error::Error for IncompatibleHeightError {
75 #[inline]
76 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
77 let Self { lock: _, incompatible: _ } = self;
78 None
79 }
80}
81
82#[derive(Debug, Clone, PartialEq, Eq)]
84pub struct IncompatibleTimeError {
85 pub(super) lock: Height,
87 pub(super) incompatible: MedianTimePast,
89}
90
91impl IncompatibleTimeError {
92 #[inline]
94 pub fn lock(&self) -> Height { self.lock }
95
96 #[inline]
98 pub fn incompatible(&self) -> MedianTimePast { self.incompatible }
99}
100
101impl From<Infallible> for IncompatibleTimeError {
102 fn from(never: Infallible) -> Self { match never {} }
103}
104
105impl fmt::Display for IncompatibleTimeError {
106 #[inline]
107 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
108 write!(
109 f,
110 "tried to satisfy a lock-by-height lock {} with MTP: {}",
111 self.lock, self.incompatible
112 )
113 }
114}
115
116#[cfg(feature = "std")]
117impl std::error::Error for IncompatibleTimeError {
118 #[inline]
119 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
120 let Self { lock: _, incompatible: _ } = self;
121 None
122 }
123}
124
125#[derive(Debug, Clone, Eq, PartialEq)]
127pub struct ParseHeightError(ParseError);
128
129impl From<Infallible> for ParseHeightError {
130 fn from(never: Infallible) -> Self { match never {} }
131}
132
133impl fmt::Display for ParseHeightError {
134 #[inline]
135 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
136 self.0.display(f, "block height", 0, LOCK_TIME_THRESHOLD - 1)
137 }
138}
139
140#[cfg(feature = "std")]
141impl std::error::Error for ParseHeightError {
142 #[inline]
144 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { self.0.source() }
145}
146
147impl From<ParseError> for ParseHeightError {
148 #[inline]
149 fn from(value: ParseError) -> Self { Self(value) }
150}
151
152#[derive(Debug, Clone, Eq, PartialEq)]
154pub struct ParseTimeError(ParseError);
155
156impl From<Infallible> for ParseTimeError {
157 fn from(never: Infallible) -> Self { match never {} }
158}
159
160impl fmt::Display for ParseTimeError {
161 #[inline]
162 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
163 self.0.display(f, "block time", LOCK_TIME_THRESHOLD, u32::MAX)
164 }
165}
166
167#[cfg(feature = "std")]
168impl std::error::Error for ParseTimeError {
169 #[inline]
171 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { self.0.source() }
172}
173
174impl From<ParseError> for ParseTimeError {
175 #[inline]
176 fn from(value: ParseError) -> Self { Self(value) }
177}
178
179#[derive(Debug, Clone, Eq, PartialEq)]
181pub(super) enum ParseError {
182 PrefixedHex(PrefixedHexError),
184 UnprefixedHex(UnprefixedHexError),
186 ParseInt(ParseIntError),
188 Conversion(i64),
191}
192
193impl ParseError {
194 #[inline]
195 pub(super) fn invalid_int<S: Into<InputString>>(
196 s: S,
197 ) -> impl FnOnce(core::num::ParseIntError) -> Self {
198 move |source| {
199 Self::ParseInt(ParseIntError { input: s.into(), bits: 32, is_signed: true, source })
200 }
201 }
202
203 pub(super) fn display(
204 &self,
205 f: &mut fmt::Formatter<'_>,
206 subject: &str,
207 lower_bound: u32,
208 upper_bound: u32,
209 ) -> fmt::Result {
210 use core::num::IntErrorKind;
211
212 match self {
213 Self::PrefixedHex(ref err) => fmt::Display::fmt(err, f),
214 Self::UnprefixedHex(ref err) => fmt::Display::fmt(err, f),
215 Self::ParseInt(ParseIntError { input, bits: _, is_signed: _, source })
216 if *source.kind() == IntErrorKind::PosOverflow =>
217 {
218 write!(
220 f,
221 "{} ({} is above limit {})",
222 input.display_cannot_parse("absolute Height/MedianTimePast"),
223 subject,
224 upper_bound
225 )
226 }
227 Self::ParseInt(ParseIntError { input, bits: _, is_signed: _, source })
228 if *source.kind() == IntErrorKind::NegOverflow =>
229 {
230 write!(
232 f,
233 "{} ({} is below limit {})",
234 input.display_cannot_parse("absolute Height/MedianTimePast"),
235 subject,
236 lower_bound
237 )
238 }
239 Self::ParseInt(ParseIntError { input, bits: _, is_signed: _, source: _ }) => {
240 write!(
241 f,
242 "{} ({})",
243 input.display_cannot_parse("absolute Height/MedianTimePast"),
244 subject
245 )
246 }
247 Self::Conversion(value) if *value < i64::from(lower_bound) => {
248 write!(f, "{} {} is below limit {}", subject, value, lower_bound)
249 }
250 Self::Conversion(value) => {
251 write!(f, "{} {} is above limit {}", subject, value, upper_bound)
252 }
253 }
254 }
255
256 #[inline]
258 #[cfg(feature = "std")]
259 pub(super) fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
260 use core::num::IntErrorKind;
261
262 match self {
263 Self::PrefixedHex(ref err) => Some(err),
264 Self::UnprefixedHex(ref err) => Some(err),
265 Self::ParseInt(ParseIntError { source, .. })
266 if *source.kind() == IntErrorKind::PosOverflow =>
267 None,
268 Self::ParseInt(ParseIntError { source, .. })
269 if *source.kind() == IntErrorKind::NegOverflow =>
270 None,
271 Self::ParseInt(ParseIntError { source, .. }) => Some(source),
272 Self::Conversion(_) => None,
273 }
274 }
275}
276
277impl From<Infallible> for ParseError {
278 fn from(never: Infallible) -> Self { match never {} }
279}
280
281impl From<ConversionError> for ParseError {
282 #[inline]
283 fn from(value: ConversionError) -> Self { Self::Conversion(value.input.into()) }
284}
285
286impl From<PrefixedHexError> for ParseError {
287 #[inline]
288 fn from(value: PrefixedHexError) -> Self { Self::PrefixedHex(value) }
289}
290
291impl From<UnprefixedHexError> for ParseError {
292 #[inline]
293 fn from(value: UnprefixedHexError) -> Self { Self::UnprefixedHex(value) }
294}
295
296#[derive(Debug, Clone, PartialEq, Eq)]
298#[non_exhaustive]
299pub struct ConversionError {
300 unit: LockTimeUnit,
302 input: u32,
304}
305
306impl ConversionError {
307 #[inline]
309 pub(super) const fn invalid_height(n: u32) -> Self {
310 Self { unit: LockTimeUnit::Blocks, input: n }
311 }
312
313 #[inline]
315 pub(super) const fn invalid_time(n: u32) -> Self {
316 Self { unit: LockTimeUnit::Seconds, input: n }
317 }
318}
319
320impl From<Infallible> for ConversionError {
321 fn from(never: Infallible) -> Self { match never {} }
322}
323
324impl fmt::Display for ConversionError {
325 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
326 write!(f, "invalid lock time value {}, {}", self.input, self.unit)
327 }
328}
329
330#[cfg(feature = "std")]
331impl std::error::Error for ConversionError {
332 #[inline]
333 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
334 let Self { unit: _, input: _ } = self;
335 None
336 }
337}
338
339#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
341enum LockTimeUnit {
342 Blocks,
344 Seconds,
346}
347
348impl fmt::Display for LockTimeUnit {
349 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
350 match *self {
351 Self::Blocks =>
352 write!(f, "expected lock-by-height (must be < {})", LOCK_TIME_THRESHOLD),
353 Self::Seconds =>
354 write!(f, "expected lock-by-time (must be >= {})", LOCK_TIME_THRESHOLD),
355 }
356 }
357}
358
359#[cfg(test)]
360mod tests {
361 #[cfg(feature = "alloc")]
362 use alloc::{format, string::ToString};
363 #[cfg(feature = "alloc")]
364 use core::str::FromStr;
365 #[cfg(feature = "std")]
366 use std::error::Error;
367
368 #[cfg(feature = "alloc")]
369 #[cfg(feature = "encoding")]
370 use encoding::{Decode as _, Decoder as _};
371
372 #[cfg(feature = "alloc")]
373 use super::LockTimeUnit;
374 #[cfg(feature = "alloc")]
375 use crate::{
376 locktime::absolute::{Height, LockTime, MedianTimePast},
377 BlockHeight,
378 };
379
380 #[test]
381 #[cfg(feature = "alloc")]
382 fn locktime_unit_display() {
383 let blocks = LockTimeUnit::Blocks;
384 let seconds = LockTimeUnit::Seconds;
385
386 assert_eq!(format!("{}", blocks), "expected lock-by-height (must be < 500000000)");
387 assert_eq!(format!("{}", seconds), "expected lock-by-time (must be >= 500000000)");
388 }
389
390 #[test]
391 #[cfg(feature = "alloc")]
392 fn error_display_is_non_empty() {
393 let too_big = BlockHeight::from_u32(u32::MAX);
395 let e = Height::try_from(too_big).unwrap_err();
396 assert!(!e.to_string().is_empty());
397 #[cfg(feature = "std")]
398 assert!(e.source().is_none());
399
400 let time_lock = LockTime::from_mtp(MedianTimePast::MIN.to_u32()).unwrap();
402 let e = time_lock.is_satisfied_by_height(Height::MIN).unwrap_err();
403 assert!(!e.to_string().is_empty());
404 #[cfg(feature = "std")]
405 assert!(e.source().is_none());
406
407 let height_lock = LockTime::from_height(Height::MIN.to_u32()).unwrap();
409 let e = height_lock.is_satisfied_by_time(MedianTimePast::MIN).unwrap_err();
410 assert!(!e.to_string().is_empty());
411 #[cfg(feature = "std")]
412 assert!(e.source().is_none());
413
414 let e = Height::from_str("invalid").unwrap_err();
416 assert!(!e.to_string().is_empty());
417 #[cfg(feature = "std")]
418 assert!(e.source().is_some());
419
420 let e = MedianTimePast::from_str("invalid").unwrap_err();
422 assert!(!e.to_string().is_empty());
423 #[cfg(feature = "std")]
424 assert!(e.source().is_some());
425
426 #[cfg(feature = "encoding")]
427 {
428 let mut decoder = LockTime::decoder();
430 let _ = decoder.push_bytes(&mut [0u8; 3].as_slice());
431 let e = decoder.end().unwrap_err();
432 assert!(!e.to_string().is_empty());
433 #[cfg(feature = "std")]
434 assert!(e.source().is_some());
435 }
436 }
437}