primitives: Replace decoder definitions with decoder_newtype macro
What changed, and why it matters
This commit is a routine code cleanup in the rust-bitcoin library. It replaces several manually-written decoder helper structs with a new macro that generates the same code automatically. There is no change to how data is parsed, no new behavior, and no security fix or vulnerability introduced.
No security action required. Treat as a normal refactoring review; verify the macro generates equivalent code if reviewing for correctness.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch refactors consensus-encoding decoder definitions in the primitives crate by introducing a decoder_newtype! macro (via include!) and replacing hand-written Decoder trait implementations for BlockDecoder, HeaderDecoder, VersionDecoder, BlockHashDecoder, TxMerkleNodeDecoder, WitnessMerkleNodeDecoder, TxInDecoder, TxOutDecoder, and OutPointDecoder. The generated code preserves the same inner decoder, new() constructor, Default, push_bytes, end, and read_limit behavior. The only functional differences are structural: some error-mapping closures are now passed to the macro, and HeaderDecoder keeps its from_inner helper outside the macro. No parsing logic, bounds, or validation semantics changed.
Changed components
primitives/src/block.rsprimitives/src/hash_types/block_hash.rsprimitives/src/hash_types/transaction_merkle_node.rsprimitives/src/hash_types/witness_merkle_node.rsprimitives/src/lib.rsprimitives/src/transaction.rsInspect captured patch +84 / −266
diff --git a/primitives/src/block.rs b/primitives/src/block.rs
index 4f119ebd..9a444364 100644
--- a/primitives/src/block.rs
+++ b/primitives/src/block.rs
@@ -356,42 +356,21 @@ where
#[cfg(feature = "alloc")]
type BlockInnerDecoder = Decoder2<HeaderDecoder, VecDecoder<Transaction>>;
-/// The decoder for the [`Block`] type.
-///
-/// This decoder can only produce a `Block<Unchecked>`.
#[cfg(feature = "alloc")]
-#[derive(Debug, Clone)]
-pub struct BlockDecoder(BlockInnerDecoder);
+crate::decoder_newtype! {
+ /// The decoder for the [`Block`] type.
+ ///
+ /// This decoder can only produce a `Block<Unchecked>`.
+ #[derive(Debug, Clone)]
+ pub struct BlockDecoder(BlockInnerDecoder);
-#[cfg(feature = "alloc")]
-impl BlockDecoder {
/// Constructs a new [`Block`] decoder.
pub const fn new() -> Self { Self(Decoder2::new(HeaderDecoder::new(), VecDecoder::new())) }
-}
-
-#[cfg(feature = "alloc")]
-impl Default for BlockDecoder {
- fn default() -> Self { Self::new() }
-}
-
-#[cfg(feature = "alloc")]
-impl encoding::Decoder for BlockDecoder {
- type Output = Block;
- type Error = BlockDecoderError;
-
- #[inline]
- fn push_bytes(&mut self, bytes: &mut &[u8]) -> Result<bool, Self::Error> {
- self.0.push_bytes(bytes).map_err(BlockDecoderError)
- }
- #[inline]
- fn end(self) -> Result<Self::Output, Self::Error> {
- let (header, transactions) = self.0.end().map_err(BlockDecoderError)?;
+ fn end(result: Result<(Header, Vec<Transaction>), <BlockInnerDecoder as encoding::Decoder>::Error>) -> Result<Block, BlockDecoderError> {
+ let (header, transactions) = result.map_err(BlockDecoderError)?;
Ok(Self::Output::new_unchecked(header, transactions))
}
-
- #[inline]
- fn read_limit(&self) -> usize { self.0.read_limit() }
}
#[cfg(feature = "alloc")]
@@ -584,11 +563,11 @@ type HeaderInnerDecoder = Decoder6<
encoding::ArrayDecoder<4>, // Nonce
>;
-/// The decoder for the [`Header`] type.
-#[derive(Debug, Clone)]
-pub struct HeaderDecoder(HeaderInnerDecoder);
+crate::decoder_newtype! {
+ /// The decoder for the [`Header`] type.
+ #[derive(Debug, Clone)]
+ pub struct HeaderDecoder(HeaderInnerDecoder);
-impl HeaderDecoder {
/// Constructs a new [`Header`] decoder.
pub const fn new() -> Self {
Self(Decoder6::new(
@@ -601,6 +580,20 @@ impl HeaderDecoder {
))
}
+ fn map_push_bytes_err(err: <HeaderInnerDecoder as encoding::Decoder>::Error) -> HeaderDecoderError {
+ Self::from_inner(err)
+ }
+
+ fn end(
+ result: Result<<HeaderInnerDecoder as encoding::Decoder>::Output, <HeaderInnerDecoder as encoding::Decoder>::Error>
+ ) -> Result<Header, HeaderDecoderError> {
+ let (version, prev_blockhash, merkle_root, time, bits, nonce) = result.map_err(Self::from_inner)?;
+ let nonce = u32::from_le_bytes(nonce);
+ Ok(Header { version, prev_blockhash, merkle_root, time, bits, nonce })
+ }
+}
+
+impl HeaderDecoder {
fn from_inner(e: <HeaderInnerDecoder as encoding::Decoder>::Error) -> HeaderDecoderError {
match e {
encoding::Decoder6Error::First(e) => HeaderDecoderError::Version(e),
@@ -613,31 +606,6 @@ impl HeaderDecoder {
}
}
-impl Default for HeaderDecoder {
- fn default() -> Self { Self::new() }
-}
-
-impl encoding::Decoder for HeaderDecoder {
- type Output = Header;
- type Error = HeaderDecoderError;
-
- #[inline]
- fn push_bytes(&mut self, bytes: &mut &[u8]) -> Result<bool, Self::Error> {
- self.0.push_bytes(bytes).map_err(Self::from_inner)
- }
-
- #[inline]
- fn end(self) -> Result<Self::Output, Self::Error> {
- let (version, prev_blockhash, merkle_root, time, bits, nonce) =
- self.0.end().map_err(Self::from_inner)?;
- let nonce = u32::from_le_bytes(nonce);
- Ok(Header { version, prev_blockhash, merkle_root, time, bits, nonce })
- }
-
- #[inline]
- fn read_limit(&self) -> usize { self.0.read_limit() }
-}
-
impl encoding::Decodable for Header {
type Decoder = HeaderDecoder;
fn decoder() -> Self::Decoder {
@@ -774,36 +742,19 @@ impl encoding::Encodable for Version {
}
}
-/// The decoder for the [`Version`] type.
-#[derive(Debug, Clone)]
-pub struct VersionDecoder(encoding::ArrayDecoder<4>);
+crate::decoder_newtype! {
+ /// The decoder for the [`Version`] type.
+ #[derive(Debug, Clone)]
+ pub struct VersionDecoder(encoding::ArrayDecoder<4>);
-impl VersionDecoder {
/// Constructs a new [`Version`] decoder.
pub const fn new() -> Self { Self(encoding::ArrayDecoder::new()) }
-}
-
-impl Default for VersionDecoder {
- fn default() -> Self { Self::new() }
-}
-
-impl encoding::Decoder for VersionDecoder {
- type Output = Version;
- type Error = VersionDecoderError;
- #[inline]
- fn push_bytes(&mut self, bytes: &mut &[u8]) -> Result<bool, Self::Error> {
- self.0.push_bytes(bytes).map_err(VersionDecoderError)
- }
-
- #[inline]
- fn end(self) -> Result<Self::Output, Self::Error> {
- let n = i32::from_le_bytes(self.0.end().map_err(VersionDecoderError)?);
+ fn end(result: Result<[u8; 4], encoding::UnexpectedEofError>) -> Result<Version, VersionDecoderError> {
+ let value = result.map_err(VersionDecoderError)?;
+ let n = i32::from_le_bytes(value);
Ok(Version::from_consensus(n))
}
-
- #[inline]
- fn read_limit(&self) -> usize { self.0.read_limit() }
}
impl encoding::Decodable for Version {
diff --git a/primitives/src/hash_types/block_hash.rs b/primitives/src/hash_types/block_hash.rs
index 5cb0a4dc..a8cc5259 100644
--- a/primitives/src/hash_types/block_hash.rs
+++ b/primitives/src/hash_types/block_hash.rs
@@ -52,38 +52,18 @@ encoding::encoder_newtype_exact! {
pub struct BlockHashEncoder<'e>(encoding::ArrayRefEncoder<'e, 32>);
}
-/// The decoder for the [`BlockHash`] type.
-#[derive(Debug, Clone)]
-pub struct BlockHashDecoder(encoding::ArrayDecoder<32>);
+crate::decoder_newtype! {
+ /// The decoder for the [`BlockHash`] type.
+ #[derive(Debug, Clone)]
+ pub struct BlockHashDecoder(encoding::ArrayDecoder<32>);
-impl BlockHashDecoder {
/// Constructs a new [`BlockHash`] decoder.
- #[inline]
pub const fn new() -> Self { Self(encoding::ArrayDecoder::new()) }
-}
-
-impl Default for BlockHashDecoder {
- #[inline]
- fn default() -> Self { Self::new() }
-}
-
-impl encoding::Decoder for BlockHashDecoder {
- type Output = BlockHash;
- type Error = BlockHashDecoderError;
- #[inline]
- fn push_bytes(&mut self, bytes: &mut &[u8]) -> Result<bool, Self::Error> {
- self.0.push_bytes(bytes).map_err(BlockHashDecoderError)
- }
-
- #[inline]
- fn end(self) -> Result<Self::Output, Self::Error> {
- let a = self.0.end().map_err(BlockHashDecoderError)?;
- Ok(BlockHash::from_byte_array(a))
+ fn end(result: Result<[u8; 32], encoding::UnexpectedEofError>) -> Result<BlockHash, BlockHashDecoderError> {
+ let bytes = result.map_err(BlockHashDecoderError)?;
+ Ok(BlockHash::from_byte_array(bytes))
}
-
- #[inline]
- fn read_limit(&self) -> usize { self.0.read_limit() }
}
/// An error consensus decoding an `BlockHash`.
diff --git a/primitives/src/hash_types/transaction_merkle_node.rs b/primitives/src/hash_types/transaction_merkle_node.rs
index 8a1e3df1..857d11f0 100644
--- a/primitives/src/hash_types/transaction_merkle_node.rs
+++ b/primitives/src/hash_types/transaction_merkle_node.rs
@@ -72,38 +72,18 @@ encoding::encoder_newtype_exact! {
pub struct TxMerkleNodeEncoder<'e>(encoding::ArrayRefEncoder<'e, 32>);
}
-/// The decoder for the [`TxMerkleNode`] type.
-#[derive(Debug, Clone)]
-pub struct TxMerkleNodeDecoder(encoding::ArrayDecoder<32>);
+crate::decoder_newtype! {
+ /// The decoder for the [`TxMerkleNode`] type.
+ #[derive(Debug, Clone)]
+ pub struct TxMerkleNodeDecoder(encoding::ArrayDecoder<32>);
-impl TxMerkleNodeDecoder {
/// Constructs a new [`TxMerkleNode`] decoder.
- #[inline]
pub const fn new() -> Self { Self(encoding::ArrayDecoder::new()) }
-}
-
-impl Default for TxMerkleNodeDecoder {
- #[inline]
- fn default() -> Self { Self::new() }
-}
-
-impl encoding::Decoder for TxMerkleNodeDecoder {
- type Output = TxMerkleNode;
- type Error = TxMerkleNodeDecoderError;
- #[inline]
- fn push_bytes(&mut self, bytes: &mut &[u8]) -> Result<bool, Self::Error> {
- self.0.push_bytes(bytes).map_err(TxMerkleNodeDecoderError)
- }
-
- #[inline]
- fn end(self) -> Result<Self::Output, Self::Error> {
- let a = self.0.end().map_err(TxMerkleNodeDecoderError)?;
- Ok(TxMerkleNode::from_byte_array(a))
+ fn end(result: Result<[u8; 32], encoding::UnexpectedEofError>) -> Result<TxMerkleNode, TxMerkleNodeDecoderError> {
+ let bytes = result.map_err(TxMerkleNodeDecoderError)?;
+ Ok(TxMerkleNode::from_byte_array(bytes))
}
-
- #[inline]
- fn read_limit(&self) -> usize { self.0.read_limit() }
}
/// An error consensus decoding an `TxMerkleNode`.
diff --git a/primitives/src/hash_types/witness_merkle_node.rs b/primitives/src/hash_types/witness_merkle_node.rs
index be1d3d05..00d1a181 100644
--- a/primitives/src/hash_types/witness_merkle_node.rs
+++ b/primitives/src/hash_types/witness_merkle_node.rs
@@ -72,38 +72,18 @@ encoding::encoder_newtype_exact! {
pub struct WitnessMerkleNodeEncoder<'e>(encoding::ArrayRefEncoder<'e, 32>);
}
-/// The decoder for the [`WitnessMerkleNode`] type.
-#[derive(Debug, Clone)]
-pub struct WitnessMerkleNodeDecoder(encoding::ArrayDecoder<32>);
+crate::decoder_newtype! {
+ /// The decoder for the [`WitnessMerkleNode`] type.
+ #[derive(Debug, Clone)]
+ pub struct WitnessMerkleNodeDecoder(encoding::ArrayDecoder<32>);
-impl WitnessMerkleNodeDecoder {
/// Constructs a new [`WitnessMerkleNode`] decoder.
- #[inline]
pub const fn new() -> Self { Self(encoding::ArrayDecoder::new()) }
-}
-
-impl Default for WitnessMerkleNodeDecoder {
- #[inline]
- fn default() -> Self { Self::new() }
-}
-
-impl encoding::Decoder for WitnessMerkleNodeDecoder {
- type Output = WitnessMerkleNode;
- type Error = WitnessMerkleNodeDecoderError;
- #[inline]
- fn push_bytes(&mut self, bytes: &mut &[u8]) -> Result<bool, Self::Error> {
- self.0.push_bytes(bytes).map_err(WitnessMerkleNodeDecoderError)
- }
-
- #[inline]
- fn end(self) -> Result<Self::Output, Self::Error> {
- let a = self.0.end().map_err(WitnessMerkleNodeDecoderError)?;
- Ok(WitnessMerkleNode::from_byte_array(a))
+ fn end(result: Result<[u8; 32], encoding::UnexpectedEofError>) -> Result<WitnessMerkleNode, WitnessMerkleNodeDecoderError> {
+ let bytes = result.map_err(WitnessMerkleNodeDecoderError)?;
+ Ok(WitnessMerkleNode::from_byte_array(bytes))
}
-
- #[inline]
- fn read_limit(&self) -> usize { self.0.read_limit() }
}
/// An error consensus decoding an `WitnessMerkleNode`.
diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs
index 723ea924..b6741e32 100644
--- a/primitives/src/lib.rs
+++ b/primitives/src/lib.rs
@@ -130,3 +130,4 @@ pub(crate) fn compact_size_encode(value: usize) -> ArrayVec<u8, 9> {
#[cfg(feature = "alloc")]
include!("../../include/newtype.rs"); // Explained in `REPO_DIR/docs/README.md`.
+include!("../../include/decoder_newtype.rs"); // decoder_newtype! macro
diff --git a/primitives/src/transaction.rs b/primitives/src/transaction.rs
index e9effb9b..77d4d5ad 100644
--- a/primitives/src/transaction.rs
+++ b/primitives/src/transaction.rs
@@ -831,13 +831,12 @@ impl encoding::Encoder for WitnessesEncoder<'_> {
#[cfg(feature = "alloc")]
type TxInInnerDecoder = Decoder3<OutPointDecoder, ScriptSigBufDecoder, SequenceDecoder>;
-/// The decoder for the [`TxIn`] type.
#[cfg(feature = "alloc")]
-#[derive(Debug, Clone)]
-pub struct TxInDecoder(TxInInnerDecoder);
+crate::decoder_newtype! {
+ /// The decoder for the [`TxIn`] type.
+ #[derive(Debug, Clone)]
+ pub struct TxInDecoder(TxInInnerDecoder);
-#[cfg(feature = "alloc")]
-impl TxInDecoder {
/// Constructs a new [`TxIn`] decoder.
pub const fn new() -> Self {
Self(Decoder3::new(
@@ -846,31 +845,13 @@ impl TxInDecoder {
SequenceDecoder::new(),
))
}
-}
-#[cfg(feature = "alloc")]
-impl Default for TxInDecoder {
- fn default() -> Self { Self::new() }
-}
-
-#[cfg(feature = "alloc")]
-impl encoding::Decoder for TxInDecoder {
- type Output = TxIn;
- type Error = TxInDecoderError;
-
- #[inline]
- fn push_bytes(&mut self, bytes: &mut &[u8]) -> Result<bool, Self::Error> {
- self.0.push_bytes(bytes).map_err(TxInDecoderError)
- }
-
- #[inline]
- fn end(self) -> Result<Self::Output, Self::Error> {
- let (previous_output, script_sig, sequence) = self.0.end().map_err(TxInDecoderError)?;
+ fn end(
+ result: Result<<TxInInnerDecoder as encoding::Decoder>::Output, <TxInInnerDecoder as encoding::Decoder>::Error>
+ ) -> Result<TxIn, TxInDecoderError> {
+ let (previous_output, script_sig, sequence) = result.map_err(TxInDecoderError)?;
Ok(TxIn { previous_output, script_sig, sequence, witness: Witness::default() })
}
-
- #[inline]
- fn read_limit(&self) -> usize { self.0.read_limit() }
}
#[cfg(feature = "alloc")]
@@ -927,42 +908,23 @@ impl encoding::Encodable for TxOut {
#[cfg(feature = "alloc")]
type TxOutInnerDecoder = Decoder2<AmountDecoder, ScriptPubKeyBufDecoder>;
-/// The decoder for the [`TxOut`] type.
#[cfg(feature = "alloc")]
-#[derive(Debug, Clone)]
-pub struct TxOutDecoder(TxOutInnerDecoder);
+crate::decoder_newtype! {
+ /// The decoder for the [`TxOut`] type.
+ #[derive(Debug, Clone)]
+ pub struct TxOutDecoder(TxOutInnerDecoder);
-#[cfg(feature = "alloc")]
-impl TxOutDecoder {
/// Constructs a new [`TxOut`] decoder.
pub const fn new() -> Self {
Self(Decoder2::new(AmountDecoder::new(), ScriptPubKeyBufDecoder::new()))
}
-}
-
-#[cfg(feature = "alloc")]
-impl Default for TxOutDecoder {
- fn default() -> Self { Self::new() }
-}
-
-#[cfg(feature = "alloc")]
-impl encoding::Decoder for TxOutDecoder {
- type Output = TxOut;
- type Error = TxOutDecoderError;
-
- #[inline]
- fn push_bytes(&mut self, bytes: &mut &[u8]) -> Result<bool, Self::Error> {
- self.0.push_bytes(bytes).map_err(TxOutDecoderError)
- }
- #[inline]
- fn end(self) -> Result<Self::Output, Self::Error> {
- let (amount, script_pubkey) = self.0.end().map_err(TxOutDecoderError)?;
+ fn end(
+ result: Result<<TxOutInnerDecoder as encoding::Decoder>::Output, <TxOutInnerDecoder as encoding::Decoder>::Error>
+ ) -> Result<TxOut, TxOutDecoderError> {
+ let (amount, script_pubkey) = result.map_err(TxOutDecoderError)?;
Ok(TxOut { amount, script_pubkey })
}
-
- #[inline]
- fn read_limit(&self) -> usize { self.0.read_limit() }
}
#[cfg(feature = "alloc")]
@@ -1065,32 +1027,17 @@ fn parse_vout(s: &str) -> Result<u32, ParseOutPointError> {
parse_int::int_from_str(s).map_err(ParseOutPointError::Vout)
}
-/// The decoder for the [`OutPoint`] type.
-// 32 for the txid + 4 for the vout
-#[derive(Debug, Clone)]
-pub struct OutPointDecoder(encoding::ArrayDecoder<36>);
+crate::decoder_newtype! {
+ /// The decoder for the [`OutPoint`] type.
+ // 32 for the txid + 4 for the vout
+ #[derive(Debug, Clone)]
+ pub struct OutPointDecoder(encoding::ArrayDecoder<36>);
-impl OutPointDecoder {
/// Constructs a new [`OutPoint`] decoder.
pub const fn new() -> Self { Self(encoding::ArrayDecoder::new()) }
-}
-
-impl Default for OutPointDecoder {
- fn default() -> Self { Self::new() }
-}
-impl encoding::Decoder for OutPointDecoder {
- type Output = OutPoint;
- type Error = OutPointDecoderError;
-
- #[inline]
- fn push_bytes(&mut self, bytes: &mut &[u8]) -> Result<bool, Self::Error> {
- self.0.push_bytes(bytes).map_err(OutPointDecoderError)
- }
-
- #[inline]
- fn end(self) -> Result<Self::Output, Self::Error> {
- let encoded = self.0.end().map_err(OutPointDecoderError)?;
+ fn end(result: Result<[u8; 36], encoding::UnexpectedEofError>) -> Result<OutPoint, OutPointDecoderError> {
+ let encoded = result.map_err(OutPointDecoderError)?;
let (txid_buf, vout_buf) = encoded.split_array::<32, 4>();
let txid = Txid::from_byte_array(*txid_buf);
@@ -1098,9 +1045,6 @@ impl encoding::Decoder for OutPointDecoder {
Ok(OutPoint { txid, vout })
}
-
- #[inline]
- fn read_limit(&self) -> usize { self.0.read_limit() }
}
impl encoding::Decodable for OutPoint {
@@ -1313,37 +1257,19 @@ impl encoding::Encodable for Version {
}
}
-/// The decoder for the [`Version`] type.
-#[derive(Debug, Clone)]
-pub struct VersionDecoder(encoding::ArrayDecoder<4>);
+crate::decoder_newtype! {
+ /// The decoder for the [`Version`] type.
+ #[derive(Debug, Clone)]
+ pub struct VersionDecoder(encoding::ArrayDecoder<4>);
-impl VersionDecoder {
/// Constructs a new [`Version`] decoder.
pub const fn new() -> Self { Self(encoding::ArrayDecoder::new()) }
-}
-
-impl Default for VersionDecoder {
- fn default() -> Self { Self::new() }
-}
-
-impl encoding::Decoder for VersionDecoder {
- type Output = Version;
- type Error = VersionDecoderError;
- #[inline]
- fn push_bytes(&mut self, bytes: &mut &[u8]) -> Result<bool, Self::Error> {
- self.0.push_bytes(bytes).map_err(VersionDecoderError)
- }
-
- #[inline]
- fn end(self) -> Result<Self::Output, Self::Error> {
- let bytes = self.0.end().map_err(VersionDecoderError)?;
+ fn end(result: Result<[u8; 4], encoding::UnexpectedEofError>) -> Result<Version, VersionDecoderError> {
+ let bytes = result.map_err(VersionDecoderError)?;
let n = u32::from_le_bytes(bytes);
Ok(Version::maybe_non_standard(n))
}
-
- #[inline]
- fn read_limit(&self) -> usize { self.0.read_limit() }
}
impl encoding::Decodable for Version {
Why this scored 15/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.