Skip to main content

bitcoin_units/locktime/relative/
error.rs

1// SPDX-License-Identifier: CC0-1.0
2
3//! Error types for the relative locktime module.
4
5use core::convert::Infallible;
6use core::fmt;
7
8use internals::write_err;
9
10use super::{NumberOf512Seconds, NumberOfBlocks};
11
12/// Error returned when a sequence number is parsed as a lock time, but its
13/// "disable" flag is set.
14#[derive(Debug, Clone, Eq, PartialEq)]
15pub struct DisabledLockTimeError(pub(super) u32);
16
17impl DisabledLockTimeError {
18    /// Accessor for the `u32` whose "disable" flag was set, preventing
19    /// it from being parsed as a relative locktime.
20    #[inline]
21    pub fn disabled_locktime_value(&self) -> u32 { self.0 }
22}
23
24impl From<Infallible> for DisabledLockTimeError {
25    fn from(never: Infallible) -> Self { match never {} }
26}
27
28impl fmt::Display for DisabledLockTimeError {
29    #[inline]
30    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
31        write!(f, "lock time 0x{:08x} has disable flag set", self.0)
32    }
33}
34
35#[cfg(feature = "std")]
36impl std::error::Error for DisabledLockTimeError {
37    #[inline]
38    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
39}
40
41/// Error returned when attempting to satisfy lock fails.
42#[derive(Debug, Clone, Eq, PartialEq)]
43pub enum IsSatisfiedByError {
44    /// Error when attempting to satisfy lock by height.
45    Blocks(InvalidHeightError),
46    /// Error when attempting to satisfy lock by time.
47    Time(InvalidTimeError),
48}
49
50impl From<Infallible> for IsSatisfiedByError {
51    fn from(never: Infallible) -> Self { match never {} }
52}
53
54impl fmt::Display for IsSatisfiedByError {
55    #[inline]
56    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
57        match *self {
58            Self::Blocks(ref e) => write_err!(f, "blocks"; e),
59            Self::Time(ref e) => write_err!(f, "time"; e),
60        }
61    }
62}
63
64#[cfg(feature = "std")]
65impl std::error::Error for IsSatisfiedByError {
66    #[inline]
67    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
68        match *self {
69            Self::Blocks(ref e) => Some(e),
70            Self::Time(ref e) => Some(e),
71        }
72    }
73}
74
75/// Error returned when `is_satisfied_by_height` fails.
76#[derive(Debug, Clone, PartialEq, Eq)]
77pub enum IsSatisfiedByHeightError {
78    /// Satisfaction of the lock height value failed.
79    Satisfaction(InvalidHeightError),
80    /// Tried to satisfy a lock-by-height locktime using seconds.
81    Incompatible(IncompatibleHeightError),
82}
83
84impl From<Infallible> for IsSatisfiedByHeightError {
85    fn from(never: Infallible) -> Self { match never {} }
86}
87
88impl fmt::Display for IsSatisfiedByHeightError {
89    #[inline]
90    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
91        match *self {
92            Self::Satisfaction(ref e) => write_err!(f, "satisfaction"; e),
93            Self::Incompatible(ref e) => write_err!(f, "incompatible"; e),
94        }
95    }
96}
97
98#[cfg(feature = "std")]
99impl std::error::Error for IsSatisfiedByHeightError {
100    #[inline]
101    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
102        match *self {
103            Self::Satisfaction(ref e) => Some(e),
104            Self::Incompatible(ref e) => Some(e),
105        }
106    }
107}
108
109/// Error returned when `is_satisfied_by_height` fails with a block time.
110#[derive(Debug, Clone, PartialEq, Eq)]
111pub struct IncompatibleHeightError(pub(crate) NumberOf512Seconds);
112
113impl From<Infallible> for IncompatibleHeightError {
114    fn from(never: Infallible) -> Self { match never {} }
115}
116
117impl fmt::Display for IncompatibleHeightError {
118    #[inline]
119    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
120        write!(f, "tried to satisfy a lock-by-height locktime using seconds {}", self.0)
121    }
122}
123
124#[cfg(feature = "std")]
125impl std::error::Error for IncompatibleHeightError {
126    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
127}
128
129/// Error returned when `is_satisfied_by_time` fails.
130#[derive(Debug, Clone, PartialEq, Eq)]
131pub enum IsSatisfiedByTimeError {
132    /// Satisfaction of the lock time value failed.
133    Satisfaction(InvalidTimeError),
134    /// Tried to satisfy a lock-by-time locktime using number of blocks.
135    Incompatible(IncompatibleTimeError),
136}
137
138impl From<Infallible> for IsSatisfiedByTimeError {
139    fn from(never: Infallible) -> Self { match never {} }
140}
141
142impl fmt::Display for IsSatisfiedByTimeError {
143    #[inline]
144    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
145        match *self {
146            Self::Satisfaction(ref e) => write_err!(f, "satisfaction"; e),
147            Self::Incompatible(ref e) => write_err!(f, "incompatible"; e),
148        }
149    }
150}
151
152#[cfg(feature = "std")]
153impl std::error::Error for IsSatisfiedByTimeError {
154    #[inline]
155    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
156        match *self {
157            Self::Satisfaction(ref e) => Some(e),
158            Self::Incompatible(ref e) => Some(e),
159        }
160    }
161}
162
163/// Error returned when `is_satisfied_by_time` fails with a block height.
164#[derive(Debug, Clone, PartialEq, Eq)]
165pub struct IncompatibleTimeError(pub(crate) NumberOfBlocks);
166
167impl From<Infallible> for IncompatibleTimeError {
168    fn from(never: Infallible) -> Self { match never {} }
169}
170
171impl fmt::Display for IncompatibleTimeError {
172    #[inline]
173    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
174        write!(f, "tried to satisfy a lock-by-time locktime using blocks {}", self.0)
175    }
176}
177
178#[cfg(feature = "std")]
179impl std::error::Error for IncompatibleTimeError {
180    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
181}
182
183/// Error returned when the input time in seconds was too large to be encoded to a 16 bit 512 second interval.
184#[derive(Debug, Clone, PartialEq, Eq)]
185pub struct TimeOverflowError {
186    /// Time interval value in seconds that overflowed.
187    // Private because we maintain an invariant that the `seconds` value does actually overflow.
188    pub(crate) seconds: u32,
189}
190
191impl From<Infallible> for TimeOverflowError {
192    fn from(never: Infallible) -> Self { match never {} }
193}
194
195impl fmt::Display for TimeOverflowError {
196    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
197        write!(
198            f,
199            "{} seconds is too large to be encoded to a 16 bit 512 second interval",
200            self.seconds
201        )
202    }
203}
204
205#[cfg(feature = "std")]
206impl std::error::Error for TimeOverflowError {
207    #[inline]
208    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
209}
210
211/// Error returned when `NumberOfBlocks::is_satisfied_by` is incorrectly called.
212#[derive(Debug, Clone, PartialEq, Eq)]
213pub struct InvalidHeightError {
214    /// The `chain_tip` argument.
215    pub(crate) chain_tip: crate::BlockHeight,
216    /// The `utxo_mined_at` argument.
217    pub(crate) utxo_mined_at: crate::BlockHeight,
218}
219
220impl From<Infallible> for InvalidHeightError {
221    fn from(never: Infallible) -> Self { match never {} }
222}
223
224impl fmt::Display for InvalidHeightError {
225    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
226        write!(f, "is_satisfied_by arguments invalid (probably the wrong way around) chain_tip: {} utxo_mined_at: {}", self.chain_tip, self.utxo_mined_at
227        )
228    }
229}
230
231#[cfg(feature = "std")]
232impl std::error::Error for InvalidHeightError {
233    #[inline]
234    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
235}
236
237/// Error returned when `NumberOf512Seconds::is_satisfied_by` is incorrectly called.
238#[derive(Debug, Clone, PartialEq, Eq)]
239pub struct InvalidTimeError {
240    /// The `chain_tip` argument.
241    pub(crate) chain_tip: crate::BlockMtp,
242    /// The `utxo_mined_at` argument.
243    pub(crate) utxo_mined_at: crate::BlockMtp,
244}
245
246impl From<Infallible> for InvalidTimeError {
247    fn from(never: Infallible) -> Self { match never {} }
248}
249
250impl fmt::Display for InvalidTimeError {
251    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
252        write!(f, "is_satisfied_by arguments invalid (probably the wrong way around) chain_tip: {} utxo_mined_at: {}", self.chain_tip, self.utxo_mined_at
253        )
254    }
255}
256
257#[cfg(feature = "std")]
258impl std::error::Error for InvalidTimeError {
259    #[inline]
260    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
261}
262
263#[cfg(test)]
264mod tests {
265    #[cfg(feature = "alloc")]
266    use alloc::string::ToString;
267    #[cfg(feature = "std")]
268    use std::error::Error;
269
270    #[cfg(feature = "alloc")]
271    use crate::{
272        locktime::relative::{LockTime, NumberOf512Seconds, NumberOfBlocks},
273        BlockHeight, BlockMtp, BlockMtpInterval, Sequence,
274    };
275
276    #[test]
277    #[cfg(feature = "alloc")]
278    fn error_display_is_non_empty() {
279        // DisabledLockTimeError - parse disabled lock time
280        let disabled = Sequence::MAX; // Sequence with disable flag set
281        let e = LockTime::from_sequence(disabled).unwrap_err();
282        assert!(!e.to_string().is_empty());
283        #[cfg(feature = "std")]
284        assert!(e.source().is_none());
285
286        // TimeOverflowError - time too large for relative locktime
287        let too_big = BlockMtpInterval::MAX;
288        let e = too_big.to_relative_mtp_interval_floor().unwrap_err();
289        assert!(!e.to_string().is_empty());
290        #[cfg(feature = "std")]
291        assert!(e.source().is_none());
292
293        // InvalidHeightError - is_satisfied_by with invalid args
294        let blocks = NumberOfBlocks::from(10u16);
295        let e = blocks
296            .is_satisfied_by(BlockHeight::from_u32(5), BlockHeight::from_u32(10))
297            .unwrap_err();
298        assert!(!e.to_string().is_empty());
299        #[cfg(feature = "std")]
300        assert!(e.source().is_none());
301
302        // InvalidTimeError - is_satisfied_by with invalid args
303        let time = NumberOf512Seconds::from_512_second_intervals(10);
304        let e = time.is_satisfied_by(BlockMtp::from_u32(5), BlockMtp::from_u32(10)).unwrap_err();
305        assert!(!e.to_string().is_empty());
306        #[cfg(feature = "std")]
307        assert!(e.source().is_none());
308
309        // IsSatisfiedBy*Error
310        let time_lock = LockTime::from_512_second_intervals(10);
311        let height_lock = LockTime::from_height(10);
312
313        // IsSatisfiedByError - wraps InvalidHeightError or InvalidTimeError
314        // Error when chain_tip < utxo_mined_at (args wrong way around)
315        // blocks type
316        let e = height_lock
317            .is_satisfied_by(
318                BlockHeight::from_u32(5),
319                BlockMtp::ZERO,
320                BlockHeight::from_u32(10),
321                BlockMtp::ZERO,
322            )
323            .unwrap_err();
324        assert!(!e.to_string().is_empty());
325        #[cfg(feature = "std")]
326        assert!(e.source().is_some());
327        // time type
328        let e = time_lock
329            .is_satisfied_by(
330                BlockHeight::ZERO,
331                BlockMtp::from_u32(5),
332                BlockHeight::ZERO,
333                BlockMtp::from_u32(10),
334            )
335            .unwrap_err();
336        assert!(!e.to_string().is_empty());
337        #[cfg(feature = "std")]
338        assert!(e.source().is_some());
339
340        // IsSatisfiedByHeightError
341        // Incompatible type
342        let e = time_lock
343            .is_satisfied_by_height(BlockHeight::from_u32(5), BlockHeight::from_u32(10))
344            .unwrap_err();
345        assert!(!e.to_string().is_empty());
346        #[cfg(feature = "std")]
347        assert!(e.source().is_some());
348        // Satisfaction type
349        let e = height_lock
350            .is_satisfied_by_height(BlockHeight::from_u32(5), BlockHeight::from_u32(10))
351            .unwrap_err();
352        assert!(!e.to_string().is_empty());
353        #[cfg(feature = "std")]
354        assert!(e.source().is_some());
355
356        // IsSatisfiedByTimeError
357        // Incompatible type
358        let e = height_lock
359            .is_satisfied_by_time(BlockMtp::from_u32(5), BlockMtp::from_u32(10))
360            .unwrap_err();
361        assert!(!e.to_string().is_empty());
362        #[cfg(feature = "std")]
363        assert!(e.source().is_some());
364        // Satisfaction type
365        let e = time_lock
366            .is_satisfied_by_time(BlockMtp::from_u32(5), BlockMtp::from_u32(10))
367            .unwrap_err();
368        assert!(!e.to_string().is_empty());
369        #[cfg(feature = "std")]
370        assert!(e.source().is_some());
371    }
372}