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