What changed, and why it matters
This commit is a routine code-quality refactor in the rust-bitcoin library. It changes several decoder error types from a shared generic error into separate, type-specific wrapper errors so that different decoders can be combined more easily. There is no security vulnerability being fixed here; it is purely an API design improvement.
No security action required. Treat as a normal API refactor; review for downstream compatibility if your code matches on the previous `UnexpectedEofError` type directly.
Security signals we found
No memory-safety, cryptographic, or consensus-related changes
No input validation or parsing logic altered
No bug fix or vulnerability remediation described
Refactor motivated by API composability, not security
Evidence from the diff
The patch wraps encoding::UnexpectedEofError in new per-type error structs (BlockHeightDecoderError, BlockTimeDecoderError, SequenceDecoderError, LockTimeDecoderError) for the encoding::Decoder implementations in units. This lets distinct decoders be composed without their Error associated types conflicting. The change is additive and preserves the original error as the source, so behavior is unchanged. No bounds checks, parsing logic, or consensus rules are modified.
Changed components
units/src/block.rsunits/src/locktime/absolute/error.rsunits/src/locktime/absolute/mod.rsunits/src/sequence.rsunits/src/time.rsInspect captured patch +119 / −12
diff --git a/units/src/block.rs b/units/src/block.rs
index 6f16c102..a6030276 100644
--- a/units/src/block.rs
+++ b/units/src/block.rs
@@ -11,10 +11,14 @@
//! The difference between these types and the locktime types is that these types are thin wrappers
//! whereas the locktime types contain more complex locktime specific abstractions.
+#[cfg(feature = "encoding")]
+use core::convert::Infallible;
use core::{fmt, ops};
#[cfg(feature = "arbitrary")]
use arbitrary::{Arbitrary, Unstructured};
+#[cfg(feature = "encoding")]
+use internals::write_err;
#[cfg(feature = "serde")]
use serde::{Deserialize, Deserializer, Serialize, Serializer};
@@ -164,16 +168,16 @@ pub struct BlockHeightDecoder(encoding::ArrayDecoder<4>);
#[cfg(feature = "encoding")]
impl encoding::Decoder for BlockHeightDecoder {
type Output = BlockHeight;
- type Error = encoding::UnexpectedEofError;
+ type Error = BlockHeightDecoderError;
#[inline]
fn push_bytes(&mut self, bytes: &mut &[u8]) -> Result<bool, Self::Error> {
- self.0.push_bytes(bytes)
+ self.0.push_bytes(bytes).map_err(BlockHeightDecoderError)
}
#[inline]
fn end(self) -> Result<Self::Output, Self::Error> {
- let n = u32::from_le_bytes(self.0.end()?);
+ let n = u32::from_le_bytes(self.0.end().map_err(BlockHeightDecoderError)?);
Ok(BlockHeight::from_u32(n))
}
}
@@ -184,6 +188,28 @@ impl encoding::Decodable for BlockHeight {
fn decoder() -> Self::Decoder { BlockHeightDecoder(encoding::ArrayDecoder::<4>::new()) }
}
+/// An error consensus decoding an `BlockHeight`.
+#[cfg(feature = "encoding")]
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct BlockHeightDecoderError(encoding::UnexpectedEofError);
+
+#[cfg(feature = "encoding")]
+impl From<Infallible> for BlockHeightDecoderError {
+ fn from(never: Infallible) -> Self { match never {} }
+}
+
+#[cfg(feature = "encoding")]
+impl fmt::Display for BlockHeightDecoderError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write_err!(f, "block height decoder error"; self.0)
+ }
+}
+
+#[cfg(all(feature = "std", feature = "encoding"))]
+impl std::error::Error for BlockHeightDecoderError {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { Some(&self.0) }
+}
+
impl_u32_wrapper! {
/// An unsigned block interval.
///
diff --git a/units/src/locktime/absolute/error.rs b/units/src/locktime/absolute/error.rs
index 7474b8ac..5a9d6335 100644
--- a/units/src/locktime/absolute/error.rs
+++ b/units/src/locktime/absolute/error.rs
@@ -6,10 +6,34 @@ use core::convert::Infallible;
use core::fmt;
use internals::error::InputString;
+#[cfg(feature = "encoding")]
+use internals::write_err;
use super::{Height, MedianTimePast, LOCK_TIME_THRESHOLD};
use crate::parse_int::ParseIntError;
+/// An error consensus decoding an `LockTime`.
+#[cfg(feature = "encoding")]
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct LockTimeDecoderError(pub(super) encoding::UnexpectedEofError);
+
+#[cfg(feature = "encoding")]
+impl From<Infallible> for LockTimeDecoderError {
+ fn from(never: Infallible) -> Self { match never {} }
+}
+
+#[cfg(feature = "encoding")]
+impl fmt::Display for LockTimeDecoderError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write_err!(f, "lock time decoder error"; self.0)
+ }
+}
+
+#[cfg(all(feature = "std", feature = "encoding"))]
+impl std::error::Error for LockTimeDecoderError {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { Some(&self.0) }
+}
+
/// Tried to satisfy a lock-by-time lock using a height value.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IncompatibleHeightError {
diff --git a/units/src/locktime/absolute/mod.rs b/units/src/locktime/absolute/mod.rs
index b6be9235..a8708a95 100644
--- a/units/src/locktime/absolute/mod.rs
+++ b/units/src/locktime/absolute/mod.rs
@@ -24,6 +24,8 @@ use crate::parse_int::{self, PrefixedHexError, UnprefixedHexError};
pub use self::error::{
ConversionError, IncompatibleHeightError, IncompatibleTimeError, ParseHeightError, ParseTimeError,
};
+#[cfg(feature = "encoding")]
+pub use self::error::LockTimeDecoderError;
/// The Threshold for deciding whether a lock time value is a height or a time (see [Bitcoin Core]).
///
@@ -424,16 +426,16 @@ pub struct LockTimeDecoder(encoding::ArrayDecoder<4>);
#[cfg(feature = "encoding")]
impl encoding::Decoder for LockTimeDecoder {
type Output = LockTime;
- type Error = encoding::UnexpectedEofError;
+ type Error = LockTimeDecoderError;
#[inline]
fn push_bytes(&mut self, bytes: &mut &[u8]) -> Result<bool, Self::Error> {
- self.0.push_bytes(bytes)
+ Ok(self.0.push_bytes(bytes).map_err(LockTimeDecoderError)?)
}
#[inline]
fn end(self) -> Result<Self::Output, Self::Error> {
- let n = u32::from_le_bytes(self.0.end()?);
+ let n = u32::from_le_bytes(self.0.end().map_err(LockTimeDecoderError)?);
Ok(LockTime::from_consensus(n))
}
}
diff --git a/units/src/sequence.rs b/units/src/sequence.rs
index 814192b5..9085ad1b 100644
--- a/units/src/sequence.rs
+++ b/units/src/sequence.rs
@@ -14,10 +14,14 @@
//! [BIP-0068]: <https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki>
//! [BIP-0125]: <https://github.com/bitcoin/bips/blob/master/bip-0125.mediawiki>
+#[cfg(feature = "encoding")]
+use core::convert::Infallible;
use core::fmt;
#[cfg(feature = "arbitrary")]
use arbitrary::{Arbitrary, Unstructured};
+#[cfg(feature = "encoding")]
+use internals::write_err;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
@@ -287,16 +291,16 @@ pub struct SequenceDecoder(encoding::ArrayDecoder<4>);
#[cfg(feature = "encoding")]
impl encoding::Decoder for SequenceDecoder {
type Output = Sequence;
- type Error = encoding::UnexpectedEofError;
+ type Error = SequenceDecoderError;
#[inline]
fn push_bytes(&mut self, bytes: &mut &[u8]) -> Result<bool, Self::Error> {
- self.0.push_bytes(bytes)
+ self.0.push_bytes(bytes).map_err(SequenceDecoderError)
}
#[inline]
fn end(self) -> Result<Self::Output, Self::Error> {
- let n = u32::from_le_bytes(self.0.end()?);
+ let n = u32::from_le_bytes(self.0.end().map_err(SequenceDecoderError)?);
Ok(Sequence::from_consensus(n))
}
}
@@ -307,6 +311,28 @@ impl encoding::Decodable for Sequence {
fn decoder() -> Self::Decoder { SequenceDecoder(encoding::ArrayDecoder::<4>::new()) }
}
+/// An error consensus decoding an `Sequence`.
+#[cfg(feature = "encoding")]
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct SequenceDecoderError(encoding::UnexpectedEofError);
+
+#[cfg(feature = "encoding")]
+impl From<Infallible> for SequenceDecoderError {
+ fn from(never: Infallible) -> Self { match never {} }
+}
+
+#[cfg(feature = "encoding")]
+impl fmt::Display for SequenceDecoderError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write_err!(f, "sequence decoder error"; self.0)
+ }
+}
+
+#[cfg(all(feature = "std", feature = "encoding"))]
+impl std::error::Error for SequenceDecoderError {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { Some(&self.0) }
+}
+
#[cfg(feature = "arbitrary")]
#[cfg(feature = "alloc")]
impl<'a> Arbitrary<'a> for Sequence {
diff --git a/units/src/time.rs b/units/src/time.rs
index 1f5945fe..fb6921ce 100644
--- a/units/src/time.rs
+++ b/units/src/time.rs
@@ -7,8 +7,15 @@
//! This differs from other UNIX timestamps in that we only use non-negative values. The Epoch
//! pre-dates Bitcoin so timestamps before this are not useful for block timestamps.
+#[cfg(feature = "encoding")]
+use core::convert::Infallible;
+#[cfg(feature = "encoding")]
+use core::fmt;
+
#[cfg(feature = "arbitrary")]
use arbitrary::{Arbitrary, Unstructured};
+#[cfg(feature = "encoding")]
+use internals::write_err;
#[cfg(feature = "serde")]
use serde::{Deserialize, Deserializer, Serialize, Serializer};
@@ -94,16 +101,16 @@ pub struct BlockTimeDecoder(encoding::ArrayDecoder<4>);
#[cfg(feature = "encoding")]
impl encoding::Decoder for BlockTimeDecoder {
type Output = BlockTime;
- type Error = encoding::UnexpectedEofError;
+ type Error = BlockTimeDecoderError;
#[inline]
fn push_bytes(&mut self, bytes: &mut &[u8]) -> Result<bool, Self::Error> {
- self.0.push_bytes(bytes)
+ self.0.push_bytes(bytes).map_err(BlockTimeDecoderError)
}
#[inline]
fn end(self) -> Result<Self::Output, Self::Error> {
- let t = u32::from_le_bytes(self.0.end()?);
+ let t = u32::from_le_bytes(self.0.end().map_err(BlockTimeDecoderError)?);
Ok(BlockTime::from_u32(t))
}
}
@@ -114,6 +121,28 @@ impl encoding::Decodable for BlockTime {
fn decoder() -> Self::Decoder { BlockTimeDecoder(encoding::ArrayDecoder::<4>::new()) }
}
+/// An error consensus decoding an `BlockTime`.
+#[cfg(feature = "encoding")]
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct BlockTimeDecoderError(encoding::UnexpectedEofError);
+
+#[cfg(feature = "encoding")]
+impl From<Infallible> for BlockTimeDecoderError {
+ fn from(never: Infallible) -> Self { match never {} }
+}
+
+#[cfg(feature = "encoding")]
+impl fmt::Display for BlockTimeDecoderError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write_err!(f, "block time decoder error"; self.0)
+ }
+}
+
+#[cfg(all(feature = "std", feature = "encoding"))]
+impl std::error::Error for BlockTimeDecoderError {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { Some(&self.0) }
+}
+
#[cfg(feature = "arbitrary")]
impl<'a> Arbitrary<'a> for BlockTime {
#[inline]
Why this scored 18/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.