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")]
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct LockTimeDecoderError(pub(super) encoding::UnexpectedEofError);
21
22#[cfg(feature = "encoding")]
23impl From<Infallible> for LockTimeDecoderError {
24 #[inline]
25 fn from(never: Infallible) -> Self { match never {} }
26}
27
28#[cfg(feature = "encoding")]
29impl fmt::Display for LockTimeDecoderError {
30 #[inline]
31 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
32 write_err!(f, "lock time decoder error"; self.0)
33 }
34}
35
36#[cfg(feature = "encoding")]
37#[cfg(feature = "std")]
38impl std::error::Error for LockTimeDecoderError {
39 #[inline]
40 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { Some(&self.0) }
41}
42
43#[derive(Debug, Clone, PartialEq, Eq)]
45pub struct IncompatibleHeightError {
46 pub(super) lock: MedianTimePast,
48 pub(super) incompatible: Height,
50}
51
52impl IncompatibleHeightError {
53 #[inline]
55 pub fn lock(&self) -> MedianTimePast { self.lock }
56
57 #[inline]
59 pub fn incompatible(&self) -> Height { self.incompatible }
60}
61
62impl From<Infallible> for IncompatibleHeightError {
63 #[inline]
64 fn from(never: Infallible) -> Self { match never {} }
65}
66
67impl fmt::Display for IncompatibleHeightError {
68 #[inline]
69 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
70 write!(
71 f,
72 "tried to satisfy a lock-by-time lock {} with height: {}",
73 self.lock, self.incompatible
74 )
75 }
76}
77
78#[cfg(feature = "std")]
79impl std::error::Error for IncompatibleHeightError {
80 #[inline]
81 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
82 let Self { lock: _, incompatible: _ } = self;
83 None
84 }
85}
86
87#[derive(Debug, Clone, PartialEq, Eq)]
89pub struct IncompatibleTimeError {
90 pub(super) lock: Height,
92 pub(super) incompatible: MedianTimePast,
94}
95
96impl IncompatibleTimeError {
97 #[inline]
99 pub fn lock(&self) -> Height { self.lock }
100
101 #[inline]
103 pub fn incompatible(&self) -> MedianTimePast { self.incompatible }
104}
105
106impl From<Infallible> for IncompatibleTimeError {
107 #[inline]
108 fn from(never: Infallible) -> Self { match never {} }
109}
110
111impl fmt::Display for IncompatibleTimeError {
112 #[inline]
113 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
114 write!(
115 f,
116 "tried to satisfy a lock-by-height lock {} with MTP: {}",
117 self.lock, self.incompatible
118 )
119 }
120}
121
122#[cfg(feature = "std")]
123impl std::error::Error for IncompatibleTimeError {
124 #[inline]
125 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
126 let Self { lock: _, incompatible: _ } = self;
127 None
128 }
129}
130
131#[derive(Debug, Clone, Eq, PartialEq)]
133pub struct ParseHeightError(ParseError);
134
135impl From<Infallible> for ParseHeightError {
136 #[inline]
137 fn from(never: Infallible) -> Self { match never {} }
138}
139
140impl fmt::Display for ParseHeightError {
141 #[inline]
142 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
143 self.0.display(f, "block height", 0, LOCK_TIME_THRESHOLD - 1)
144 }
145}
146
147#[cfg(feature = "std")]
148impl std::error::Error for ParseHeightError {
149 #[inline]
151 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { self.0.source() }
152}
153
154impl From<ParseError> for ParseHeightError {
155 #[inline]
156 fn from(value: ParseError) -> Self { Self(value) }
157}
158
159#[derive(Debug, Clone, Eq, PartialEq)]
161pub struct ParseTimeError(ParseError);
162
163impl From<Infallible> for ParseTimeError {
164 #[inline]
165 fn from(never: Infallible) -> Self { match never {} }
166}
167
168impl fmt::Display for ParseTimeError {
169 #[inline]
170 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
171 self.0.display(f, "block time", LOCK_TIME_THRESHOLD, u32::MAX)
172 }
173}
174
175#[cfg(feature = "std")]
176impl std::error::Error for ParseTimeError {
177 #[inline]
179 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { self.0.source() }
180}
181
182impl From<ParseError> for ParseTimeError {
183 #[inline]
184 fn from(value: ParseError) -> Self { Self(value) }
185}
186
187#[derive(Debug, Clone, Eq, PartialEq)]
189pub(super) enum ParseError {
190 PrefixedHex(PrefixedHexError),
192 UnprefixedHex(UnprefixedHexError),
194 ParseInt(ParseIntError),
196 Conversion(i64),
199}
200
201impl ParseError {
202 #[inline]
203 pub(super) fn invalid_int<S: Into<InputString>>(
204 s: S,
205 ) -> impl FnOnce(core::num::ParseIntError) -> Self {
206 move |source| {
207 Self::ParseInt(ParseIntError {
208 input: s.into(),
209 bits: 32,
210 is_signed: true,
211 truncated: false,
212 source,
213 })
214 }
215 }
216
217 pub(super) fn display(
218 &self,
219 f: &mut fmt::Formatter<'_>,
220 subject: &str,
221 lower_bound: u32,
222 upper_bound: u32,
223 ) -> fmt::Result {
224 use core::num::IntErrorKind;
225
226 match self {
227 Self::PrefixedHex(ref err) => fmt::Display::fmt(err, f),
228 Self::UnprefixedHex(ref err) => fmt::Display::fmt(err, f),
229 Self::ParseInt(ParseIntError {
230 input,
231 bits: _,
232 is_signed: _,
233 truncated: _,
234 source,
235 }) if *source.kind() == IntErrorKind::PosOverflow => {
236 write!(
238 f,
239 "{} ({} is above limit {})",
240 input.display_cannot_parse("absolute Height/MedianTimePast"),
241 subject,
242 upper_bound
243 )
244 }
245 Self::ParseInt(ParseIntError {
246 input,
247 bits: _,
248 is_signed: _,
249 truncated: _,
250 source,
251 }) if *source.kind() == IntErrorKind::NegOverflow => {
252 write!(
254 f,
255 "{} ({} is below limit {})",
256 input.display_cannot_parse("absolute Height/MedianTimePast"),
257 subject,
258 lower_bound
259 )
260 }
261 Self::ParseInt(ParseIntError {
262 input,
263 bits: _,
264 is_signed: _,
265 truncated: _,
266 source: _,
267 }) => {
268 write!(
269 f,
270 "{} ({})",
271 input.display_cannot_parse("absolute Height/MedianTimePast"),
272 subject
273 )
274 }
275 Self::Conversion(value) if *value < i64::from(lower_bound) => {
276 write!(f, "{} {} is below limit {}", subject, value, lower_bound)
277 }
278 Self::Conversion(value) => {
279 write!(f, "{} {} is above limit {}", subject, value, upper_bound)
280 }
281 }
282 }
283
284 #[inline]
286 #[cfg(feature = "std")]
287 pub(super) fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
288 use core::num::IntErrorKind;
289
290 match self {
291 Self::PrefixedHex(ref err) => Some(err),
292 Self::UnprefixedHex(ref err) => Some(err),
293 Self::ParseInt(ParseIntError { source, .. })
294 if *source.kind() == IntErrorKind::PosOverflow =>
295 None,
296 Self::ParseInt(ParseIntError { source, .. })
297 if *source.kind() == IntErrorKind::NegOverflow =>
298 None,
299 Self::ParseInt(ParseIntError { source, .. }) => Some(source),
300 Self::Conversion(_) => None,
301 }
302 }
303}
304
305impl From<Infallible> for ParseError {
306 #[inline]
307 fn from(never: Infallible) -> Self { match never {} }
308}
309
310impl From<ConversionError> for ParseError {
311 #[inline]
312 fn from(value: ConversionError) -> Self { Self::Conversion(value.input.into()) }
313}
314
315impl From<PrefixedHexError> for ParseError {
316 #[inline]
317 fn from(value: PrefixedHexError) -> Self { Self::PrefixedHex(value) }
318}
319
320impl From<UnprefixedHexError> for ParseError {
321 #[inline]
322 fn from(value: UnprefixedHexError) -> Self { Self::UnprefixedHex(value) }
323}
324
325#[derive(Debug, Clone, PartialEq, Eq)]
327#[non_exhaustive]
328pub struct ConversionError {
329 unit: LockTimeUnit,
331 input: u32,
333}
334
335impl ConversionError {
336 #[inline]
338 pub(super) const fn invalid_height(n: u32) -> Self {
339 Self { unit: LockTimeUnit::Blocks, input: n }
340 }
341
342 #[inline]
344 pub(super) const fn invalid_time(n: u32) -> Self {
345 Self { unit: LockTimeUnit::Seconds, input: n }
346 }
347}
348
349impl From<Infallible> for ConversionError {
350 #[inline]
351 fn from(never: Infallible) -> Self { match never {} }
352}
353
354impl fmt::Display for ConversionError {
355 #[inline]
356 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
357 write!(f, "invalid lock time value {}, {}", self.input, self.unit)
358 }
359}
360
361#[cfg(feature = "std")]
362impl std::error::Error for ConversionError {
363 #[inline]
364 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
365 let Self { unit: _, input: _ } = self;
366 None
367 }
368}
369
370#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
372enum LockTimeUnit {
373 Blocks,
375 Seconds,
377}
378
379impl fmt::Display for LockTimeUnit {
380 #[inline]
381 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
382 match *self {
383 Self::Blocks =>
384 write!(f, "expected lock-by-height (must be < {})", LOCK_TIME_THRESHOLD),
385 Self::Seconds =>
386 write!(f, "expected lock-by-time (must be >= {})", LOCK_TIME_THRESHOLD),
387 }
388 }
389}
390
391#[cfg(test)]
392mod tests {
393 #[cfg(feature = "alloc")]
394 use alloc::{format, string::ToString};
395 #[cfg(feature = "alloc")]
396 use core::str::FromStr;
397 #[cfg(feature = "std")]
398 use std::error::Error;
399
400 #[cfg(feature = "alloc")]
401 #[cfg(feature = "encoding")]
402 use encoding::{Decode as _, Decoder as _};
403
404 #[cfg(feature = "alloc")]
405 use super::LockTimeUnit;
406 #[cfg(feature = "alloc")]
407 use crate::{
408 locktime::absolute::{Height, LockTime, MedianTimePast},
409 BlockHeight,
410 };
411
412 #[test]
413 #[cfg(feature = "alloc")]
414 fn locktime_unit_display() {
415 let blocks = LockTimeUnit::Blocks;
416 let seconds = LockTimeUnit::Seconds;
417
418 assert_eq!(format!("{}", blocks), "expected lock-by-height (must be < 500000000)");
419 assert_eq!(format!("{}", seconds), "expected lock-by-time (must be >= 500000000)");
420 }
421
422 #[test]
423 #[cfg(feature = "alloc")]
424 fn error_display_is_non_empty() {
425 let too_big = BlockHeight::from_u32(u32::MAX);
427 let e = Height::try_from(too_big).unwrap_err();
428 assert!(!e.to_string().is_empty());
429 #[cfg(feature = "std")]
430 assert!(e.source().is_none());
431
432 let time_lock = LockTime::from_mtp(MedianTimePast::MIN.to_u32()).unwrap();
434 let e = time_lock.is_satisfied_by_height(Height::MIN).unwrap_err();
435 assert!(!e.to_string().is_empty());
436 #[cfg(feature = "std")]
437 assert!(e.source().is_none());
438
439 let height_lock = LockTime::from_height(Height::MIN.to_u32()).unwrap();
441 let e = height_lock.is_satisfied_by_time(MedianTimePast::MIN).unwrap_err();
442 assert!(!e.to_string().is_empty());
443 #[cfg(feature = "std")]
444 assert!(e.source().is_none());
445
446 let e = Height::from_str("invalid").unwrap_err();
448 assert!(!e.to_string().is_empty());
449 #[cfg(feature = "std")]
450 assert!(e.source().is_some());
451
452 let e = MedianTimePast::from_str("invalid").unwrap_err();
454 assert!(!e.to_string().is_empty());
455 #[cfg(feature = "std")]
456 assert!(e.source().is_some());
457
458 #[cfg(feature = "encoding")]
459 {
460 let mut decoder = LockTime::decoder();
462 let _ = decoder.push_bytes(&mut [0u8; 3].as_slice());
463 let e = decoder.end().unwrap_err();
464 assert!(!e.to_string().is_empty());
465 #[cfg(feature = "std")]
466 assert!(e.source().is_some());
467 }
468 }
469
470 #[test]
471 #[cfg(feature = "alloc")]
472 fn parse_error_display_escapes_control_characters() {
473 let height =
474 Height::from_str("1\nFORGED: privileged action succeeded").unwrap_err().to_string();
475 let time = MedianTimePast::from_str("\u{1b}[2JFORGED").unwrap_err().to_string();
476
477 for rendered in [height, time] {
478 assert!(!rendered.chars().any(char::is_control));
479 }
480 }
481}