What changed, and why it matters
This commit adds ordinary string formatting and hex parsing support for the Block type in a Rust Bitcoin library. It is a routine feature addition with no security relevance visible in the code or commit message.
No security action required. Review as normal code-quality/feature addition.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch implements core::str::FromStr, fmt::Display, fmt::LowerHex, and fmt::UpperHex for Block, gated behind the ‘hex’ and ‘alloc’ features. It also generalizes the existing Encodable implementation from Block to Block
Changed components
primitives/src/block.rsbitcoin/src/blockdata/block.rsInspect captured patch +106 / −2
diff --git a/bitcoin/src/blockdata/block.rs b/bitcoin/src/blockdata/block.rs
index cf7f0bbe..afcf91a5 100644
--- a/bitcoin/src/blockdata/block.rs
+++ b/bitcoin/src/blockdata/block.rs
@@ -29,7 +29,7 @@ pub use primitives::block::{
WitnessCommitment, compute_merkle_root, compute_witness_root,
};
#[doc(no_inline)]
-pub use primitives::block::{InvalidBlockError, ParseHeaderError};
+pub use primitives::block::{InvalidBlockError, ParseBlockError, ParseHeaderError};
#[doc(inline)]
pub use units::block::{BlockHeight, BlockHeightInterval, BlockMtp, BlockMtpInterval};
#[doc(no_inline)]
diff --git a/primitives/src/block.rs b/primitives/src/block.rs
index 3e326106..539d1fbd 100644
--- a/primitives/src/block.rs
+++ b/primitives/src/block.rs
@@ -274,6 +274,65 @@ mod sealed {
impl Validation for super::Unchecked {}
}
+#[cfg(all(feature = "hex", feature = "alloc"))]
+impl core::str::FromStr for Block<Unchecked>
+where
+ Self: Decodable
+{
+ type Err = ParseBlockError;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ HexPrimitive::from_str(s).map_err(ParseBlockError)
+ }
+}
+
+#[cfg(all(feature = "hex", feature = "alloc"))]
+impl<V: Validation> fmt::Display for Block<V>
+where
+ Self: Encodable
+{
+ #[allow(clippy::use_self)]
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ fmt::Display::fmt(&HexPrimitive(self), f)
+ }
+}
+
+#[cfg(all(feature = "hex", feature = "alloc"))]
+impl<V: Validation> fmt::LowerHex for Block<V> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(&HexPrimitive(self), f) }
+}
+
+#[cfg(all(feature = "hex", feature = "alloc"))]
+impl<V: Validation> fmt::UpperHex for Block<V> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::UpperHex::fmt(&HexPrimitive(self), f) }
+}
+
+/// An error that occurs during parsing of a [`Block`] from a hex string.
+#[cfg(all(feature = "hex", feature = "alloc"))]
+pub struct ParseBlockError(ParsePrimitiveError<Block>);
+
+#[cfg(all(feature = "hex", feature = "alloc"))]
+impl From<Infallible> for ParseBlockError {
+ fn from(never: Infallible) -> Self { match never {} }
+}
+
+#[cfg(all(feature = "hex", feature = "alloc"))]
+impl fmt::Debug for ParseBlockError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(&self.0, f) }
+}
+
+#[cfg(all(feature = "hex", feature = "alloc"))]
+impl fmt::Display for ParseBlockError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write_err!(f, "parse block error"; self.0)
+ }
+}
+
+#[cfg(all(feature = "hex", feature = "alloc", feature = "std"))]
+impl std::error::Error for ParseBlockError {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { Some(&self.0) }
+}
+
#[cfg(feature = "alloc")]
encoding::encoder_newtype! {
/// The encoder for the [`Block`] type.
@@ -283,7 +342,10 @@ encoding::encoder_newtype! {
}
#[cfg(feature = "alloc")]
-impl Encodable for Block {
+impl<V> Encodable for Block<V>
+where
+ V: Validation,
+{
type Encoder<'e>
= Encoder2<HeaderEncoder<'e>, Encoder2<CompactSizeEncoder, SliceEncoder<'e, Transaction>>>
where
@@ -1369,6 +1431,48 @@ mod tests {
Block::new_unchecked(header, transactions)
}
+ #[test]
+ #[cfg(feature = "hex")]
+ #[cfg(feature = "alloc")]
+ fn block_hex() {
+ let header = dummy_header();
+ let transactions = vec![Transaction {
+ version: crate::transaction::Version::ONE,
+ lock_time: crate::locktime::absolute::LockTime::ZERO,
+ inputs: vec![],
+ outputs: vec![],
+ }];
+ let block = Block::new_unchecked(header, transactions);
+
+ // Transaction with no inputs uses segwit serialization:
+ // version (4) + marker (1) + flag (1) + input_count (1) + output_count (1) + lock_time (4)
+ let want = "010000009999999999999999999999999999999999999999999999999999999999999999777777777777777777777777777777777777777777777777777777777777777702000000030000000400000001010000000001000000000000";
+
+ assert_eq!(format!("{}", block), want);
+ assert_eq!(format!("{:x}", block), want);
+
+ // Note this is pointless because the hex does not have letters in it, only numbers.
+ let want =
+ want.chars().map(|chr| chr.to_ascii_uppercase()).collect::<alloc::string::String>();
+ assert_eq!(want, format!("{:X}", block));
+ }
+
+ #[test]
+ #[cfg(feature = "hex")]
+ #[cfg(feature = "alloc")]
+ fn block_from_hex_str_round_trip() {
+ let block = dummy_block();
+
+ let lower_hex_block = format!("{:x}", block);
+ let upper_hex_block = format!("{:X}", block);
+
+ let parsed_lower = Block::from_str(&lower_hex_block).unwrap();
+ let parsed_upper = Block::from_str(&upper_hex_block).unwrap();
+
+ assert_eq!(parsed_lower, block);
+ assert_eq!(parsed_upper, block);
+ }
+
#[test]
#[cfg(feature = "alloc")]
fn block_decode() {
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.