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