diff --git a/Cargo-minimal.lock b/Cargo-minimal.lock index afb80f59b..eabf863d5 100644 --- a/Cargo-minimal.lock +++ b/Cargo-minimal.lock @@ -111,6 +111,7 @@ name = "bitcoin-primitives" version = "0.101.0" dependencies = [ "arbitrary", + "arrayvec", "bincode", "bitcoin-internals", "bitcoin-units", diff --git a/Cargo-recent.lock b/Cargo-recent.lock index f33ae82ee..002fdd04b 100644 --- a/Cargo-recent.lock +++ b/Cargo-recent.lock @@ -110,6 +110,7 @@ name = "bitcoin-primitives" version = "0.101.0" dependencies = [ "arbitrary", + "arrayvec", "bincode", "bitcoin-internals", "bitcoin-units", diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index d2ed52686..7af43f83b 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -16,7 +16,7 @@ exclude = ["tests", "contrib"] [features] default = ["std", "hex"] -std = ["alloc", "hashes/std", "hex?/std", "internals/std", "units/std"] +std = ["alloc", "hashes/std", "hex?/std", "internals/std", "units/std", "arrayvec/std"] alloc = ["hashes/alloc", "hex?/alloc", "internals/alloc", "units/alloc"] serde = ["dep:serde", "hashes/serde", "hex?/serde", "internals/serde", "units/serde", "alloc", "hex"] arbitrary = ["dep:arbitrary", "units/arbitrary"] @@ -26,6 +26,7 @@ hex = ["dep:hex", "hashes/hex", "internals/hex"] hashes = { package = "bitcoin_hashes", path = "../hashes", default-features = false } internals = { package = "bitcoin-internals", path = "../internals" } units = { package = "bitcoin-units", path = "../units", default-features = false } +arrayvec = { version = "0.7", default-features = false } arbitrary = { version = "1.4", optional = true } hex = { package = "hex-conservative", version = "0.3.0", default-features = false, optional = true } diff --git a/primitives/src/block.rs b/primitives/src/block.rs index 003e2d90e..f746f4dbc 100644 --- a/primitives/src/block.rs +++ b/primitives/src/block.rs @@ -207,6 +207,27 @@ impl Header { } } +#[cfg(feature = "hex")] +impl fmt::Display for Header { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use fmt::Write as _; + use hex::DisplayHex as _; + + let mut buf = arrayvec::ArrayString::<160>::new(); + write!( + &mut buf, + "{}{}{}{}{}{}", + self.version.to_consensus().to_le_bytes().as_hex(), + self.prev_blockhash.as_byte_array().as_hex(), + self.merkle_root.as_byte_array().as_hex(), + self.time.to_u32().to_le_bytes().as_hex(), + self.bits.to_consensus().to_le_bytes().as_hex(), + self.nonce.to_le_bytes().as_hex(), + )?; + fmt::Display::fmt(&buf, f) + } +} + impl fmt::Debug for Header { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Header") @@ -535,4 +556,35 @@ mod tests { ); assert_eq!(format!("{:?}", header), expected); } + + #[test] + #[cfg(feature = "hex")] + fn header_display() { + let seconds: u32 = 1_653_195_600; // Arbitrary timestamp: May 22nd, 5am UTC. + + let header = Header { + version: Version::TWO, + prev_blockhash: BlockHash::from_byte_array([0xab; 32]), + merkle_root: TxMerkleNode::from_byte_array([0xcd; 32]), + time: BlockTime::from(seconds), + bits: CompactTarget::from_consensus(0xbeef), + nonce: 0xcafe, + }; + + let want = concat!( + "02000000", // version + "abababababababababababababababababababababababababababababababab", // prev_blockhash + "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", // merkle_root + "50c38962", // time + "efbe0000", // bits + "feca0000", // nonce + ); + assert_eq!(want.len(), 160); + assert_eq!(format!("{}", header), want); + + // Check how formatting options are handled. + let want = format!("{:.20}", want); + let got = format!("{:.20}", header); + assert_eq!(got, want); + } }