From cbed2c2fa68bf7cba54cac22925e1e70e896b9ad Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 13 Jan 2023 15:00:46 +1000 Subject: [PATCH] Format commitments and nonces as hex --- zebra-chain/src/block.rs | 2 +- zebra-chain/src/block/arbitrary.rs | 20 +++++----- zebra-chain/src/block/commitment.rs | 48 +++++++++++++++++++++++- zebra-chain/src/block/header.rs | 5 ++- zebra-chain/src/block/serialize.rs | 7 ++-- zebra-chain/src/block/tests/prop.rs | 2 +- zebra-chain/src/fmt.rs | 35 +++++++++++++++++ zebra-chain/src/transaction/serialize.rs | 16 +++++++- zebra-chain/src/work/equihash.rs | 2 +- zebra-chain/src/work/tests/prop.rs | 4 +- zebra-consensus/src/block/tests.rs | 2 +- zebra-state/src/tests.rs | 2 +- 12 files changed, 120 insertions(+), 25 deletions(-) diff --git a/zebra-chain/src/block.rs b/zebra-chain/src/block.rs index 53bf21f5318..6e3ef56c451 100644 --- a/zebra-chain/src/block.rs +++ b/zebra-chain/src/block.rs @@ -98,7 +98,7 @@ impl Block { None => Err(CommitmentError::MissingBlockHeight { block_hash: self.hash(), }), - Some(height) => Commitment::from_bytes(self.header.commitment_bytes, network, height), + Some(height) => Commitment::from_bytes(*self.header.commitment_bytes, network, height), } } diff --git a/zebra-chain/src/block/arbitrary.rs b/zebra-chain/src/block/arbitrary.rs index be078394936..c8c37411e66 100644 --- a/zebra-chain/src/block/arbitrary.rs +++ b/zebra-chain/src/block/arbitrary.rs @@ -1,16 +1,16 @@ //! Randomised property testing for [`Block`]s. +use std::{collections::HashMap, sync::Arc}; + use proptest::{ arbitrary::{any, Arbitrary}, prelude::*, }; -use std::{collections::HashMap, sync::Arc}; - use crate::{ amount::NonNegative, block, - fmt::SummaryDebug, + fmt::{HexDebug, SummaryDebug}, history_tree::HistoryTree, parameters::{ Network, @@ -482,13 +482,13 @@ impl Block { // needs to be well-formed, i.e. smaller than 𝑞_J, so we // arbitrarily set it to 1. let block_header = Arc::make_mut(&mut block.header); - block_header.commitment_bytes = [0u8; 32]; + block_header.commitment_bytes = [0u8; 32].into(); block_header.commitment_bytes[0] = 1; } std::cmp::Ordering::Equal => { // The Heartwood activation block has a hardcoded all-zeroes commitment. let block_header = Arc::make_mut(&mut block.header); - block_header.commitment_bytes = [0u8; 32]; + block_header.commitment_bytes = [0u8; 32].into(); } std::cmp::Ordering::Greater => { // Set the correct commitment bytes according to the network upgrade. @@ -505,10 +505,12 @@ impl Block { &auth_data_root, ); let block_header = Arc::make_mut(&mut block.header); - block_header.commitment_bytes = hash_block_commitments.into(); + block_header.commitment_bytes = + hash_block_commitments.bytes_in_serialized_order().into(); } else { let block_header = Arc::make_mut(&mut block.header); - block_header.commitment_bytes = history_tree_root.into(); + block_header.commitment_bytes = + history_tree_root.bytes_in_serialized_order().into(); } } } @@ -723,10 +725,10 @@ impl Arbitrary for Header { (4u32..(i32::MAX as u32)), any::(), any::(), - any::<[u8; 32]>(), + any::>(), serialization::arbitrary::datetime_u32(), any::(), - any::<[u8; 32]>(), + any::>(), any::(), ) .prop_map( diff --git a/zebra-chain/src/block/commitment.rs b/zebra-chain/src/block/commitment.rs index df760cc6ace..3d2591d4563 100644 --- a/zebra-chain/src/block/commitment.rs +++ b/zebra-chain/src/block/commitment.rs @@ -1,5 +1,7 @@ //! The Commitment enum, used for the corresponding block header field. +use std::fmt; + use hex::{FromHex, ToHex}; use thiserror::Error; @@ -97,6 +99,8 @@ pub(crate) const CHAIN_HISTORY_ACTIVATION_RESERVED: [u8; 32] = [0; 32]; impl Commitment { /// Returns `bytes` as the Commitment variant for `network` and `height`. + // + // TODO: rename as from_bytes_in_serialized_order() pub(super) fn from_bytes( bytes: [u8; 32], network: Network, @@ -126,6 +130,8 @@ impl Commitment { } /// Returns the serialized bytes for this Commitment. + // + // TODO: refactor as bytes_in_serialized_order(&self) #[cfg(test)] pub(super) fn to_bytes(self) -> [u8; 32] { use Commitment::*; @@ -145,9 +151,23 @@ impl Commitment { // - add methods for maintaining the MMR peaks, and calculating the root // hash from the current set of peaks // - move to a separate file -#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Copy, Eq, PartialEq, Serialize, Deserialize)] pub struct ChainHistoryMmrRootHash([u8; 32]); +impl fmt::Display for ChainHistoryMmrRootHash { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(&self.encode_hex::()) + } +} + +impl fmt::Debug for ChainHistoryMmrRootHash { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_tuple("ChainHistoryMmrRootHash") + .field(&self.encode_hex::()) + .finish() + } +} + impl From<[u8; 32]> for ChainHistoryMmrRootHash { fn from(hash: [u8; 32]) -> Self { ChainHistoryMmrRootHash(hash) @@ -183,6 +203,11 @@ impl ChainHistoryMmrRootHash { ChainHistoryMmrRootHash(internal_byte_order) } + + /// Returns the serialized bytes for this Commitment. + pub fn bytes_in_serialized_order(&self) -> [u8; 32] { + self.0 + } } impl ToHex for &ChainHistoryMmrRootHash { @@ -222,9 +247,23 @@ impl FromHex for ChainHistoryMmrRootHash { /// - the transaction authorising data in this block. /// /// Introduced in NU5. -#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Copy, Eq, PartialEq, Serialize, Deserialize)] pub struct ChainHistoryBlockTxAuthCommitmentHash([u8; 32]); +impl fmt::Display for ChainHistoryBlockTxAuthCommitmentHash { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(&self.encode_hex::()) + } +} + +impl fmt::Debug for ChainHistoryBlockTxAuthCommitmentHash { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_tuple("ChainHistoryBlockTxAuthCommitmentHash") + .field(&self.encode_hex::()) + .finish() + } +} + impl From<[u8; 32]> for ChainHistoryBlockTxAuthCommitmentHash { fn from(hash: [u8; 32]) -> Self { ChainHistoryBlockTxAuthCommitmentHash(hash) @@ -292,6 +331,11 @@ impl ChainHistoryBlockTxAuthCommitmentHash { ChainHistoryBlockTxAuthCommitmentHash(internal_byte_order) } + + /// Returns the serialized bytes for this Commitment. + pub fn bytes_in_serialized_order(&self) -> [u8; 32] { + self.0 + } } impl ToHex for &ChainHistoryBlockTxAuthCommitmentHash { diff --git a/zebra-chain/src/block/header.rs b/zebra-chain/src/block/header.rs index a1492b566ba..c59d3972ff5 100644 --- a/zebra-chain/src/block/header.rs +++ b/zebra-chain/src/block/header.rs @@ -6,6 +6,7 @@ use chrono::{DateTime, Duration, Utc}; use thiserror::Error; use crate::{ + fmt::HexDebug, serialization::{TrustedPreallocate, MAX_PROTOCOL_MESSAGE_LEN}, work::{difficulty::CompactDifficulty, equihash::Solution}, }; @@ -58,7 +59,7 @@ pub struct Header { /// this field cannot be parsed without the network and height. Use /// [`Block::commitment`](super::Block::commitment) to get the parsed /// [`Commitment`](super::Commitment). - pub commitment_bytes: [u8; 32], + pub commitment_bytes: HexDebug<[u8; 32]>, /// The block timestamp is a Unix epoch time (UTC) when the miner /// started hashing the header (according to the miner). @@ -77,7 +78,7 @@ pub struct Header { /// An arbitrary field that miners can change to modify the header /// hash in order to produce a hash less than or equal to the /// target threshold. - pub nonce: [u8; 32], + pub nonce: HexDebug<[u8; 32]>, /// The Equihash solution. pub solution: Solution, diff --git a/zebra-chain/src/block/serialize.rs b/zebra-chain/src/block/serialize.rs index a5742a80faa..4082df023e3 100644 --- a/zebra-chain/src/block/serialize.rs +++ b/zebra-chain/src/block/serialize.rs @@ -6,6 +6,7 @@ use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use chrono::{TimeZone, Utc}; use crate::{ + block::{header::ZCASH_BLOCK_VERSION, merkle, Block, CountedHeader, Hash, Header}, serialization::{ CompactSizeMessage, ReadZcashExt, SerializationError, ZcashDeserialize, ZcashDeserializeInto, ZcashSerialize, @@ -13,8 +14,6 @@ use crate::{ work::{difficulty::CompactDifficulty, equihash}, }; -use super::{header::ZCASH_BLOCK_VERSION, merkle, Block, CountedHeader, Hash, Header}; - /// The maximum size of a Zcash block, in bytes. /// /// Post-Sapling, this is also the maximum size of a transaction @@ -85,7 +84,7 @@ impl ZcashDeserialize for Header { version, previous_block_hash: Hash::zcash_deserialize(&mut reader)?, merkle_root: merkle::Root(reader.read_32_bytes()?), - commitment_bytes: reader.read_32_bytes()?, + commitment_bytes: reader.read_32_bytes()?.into(), // This can't panic, because all u32 values are valid `Utc.timestamp`s time: Utc .timestamp_opt(reader.read_u32::()?.into(), 0) @@ -94,7 +93,7 @@ impl ZcashDeserialize for Header { "out-of-range number of seconds and/or invalid nanosecond", ))?, difficulty_threshold: CompactDifficulty(reader.read_u32::()?), - nonce: reader.read_32_bytes()?, + nonce: reader.read_32_bytes()?.into(), solution: equihash::Solution::zcash_deserialize(reader)?, }) } diff --git a/zebra-chain/src/block/tests/prop.rs b/zebra-chain/src/block/tests/prop.rs index 8d75312d0c1..b4446c0ee88 100644 --- a/zebra-chain/src/block/tests/prop.rs +++ b/zebra-chain/src/block/tests/prop.rs @@ -107,7 +107,7 @@ proptest! { let commitment = block.commitment(network); if let Ok(commitment) = commitment { let commitment_bytes = commitment.to_bytes(); - prop_assert_eq![block.header.commitment_bytes, commitment_bytes]; + prop_assert_eq![block.header.commitment_bytes.0, commitment_bytes]; } // Check the block size limit diff --git a/zebra-chain/src/fmt.rs b/zebra-chain/src/fmt.rs index aaacf9737b7..800663147b6 100644 --- a/zebra-chain/src/fmt.rs +++ b/zebra-chain/src/fmt.rs @@ -160,3 +160,38 @@ where type Strategy = BoxedStrategy; } + +/// Wrapper to override `Debug`, redirecting it to hex-encode the type. +/// The type must be hex-encodable. +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))] +#[serde(transparent)] +pub struct HexDebug>(pub T); + +impl> fmt::Debug for HexDebug { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple(std::any::type_name::()) + .field(&hex::encode(self.as_ref())) + .finish() + } +} + +impl> ops::Deref for HexDebug { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl> ops::DerefMut for HexDebug { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl> From for HexDebug { + fn from(t: T) -> Self { + Self(t) + } +} diff --git a/zebra-chain/src/transaction/serialize.rs b/zebra-chain/src/transaction/serialize.rs index b432b0e222f..e35908eb1fe 100644 --- a/zebra-chain/src/transaction/serialize.rs +++ b/zebra-chain/src/transaction/serialize.rs @@ -992,11 +992,25 @@ impl TrustedPreallocate for transparent::Output { /// Stores bytes that are guaranteed to be deserializable into a [`Transaction`]. /// /// Sorts in lexicographic order of the transaction's serialized data. -#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct SerializedTransaction { bytes: Vec, } +impl fmt::Display for SerializedTransaction { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(&hex::encode(&self.bytes)) + } +} + +impl fmt::Debug for SerializedTransaction { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_tuple("SerializedTransaction") + .field(&hex::encode(&self.bytes)) + .finish() + } +} + /// Build a [`SerializedTransaction`] by serializing a block. impl> From for SerializedTransaction { fn from(tx: B) -> Self { diff --git a/zebra-chain/src/work/equihash.rs b/zebra-chain/src/work/equihash.rs index 230ba6d5a94..621ea646dc5 100644 --- a/zebra-chain/src/work/equihash.rs +++ b/zebra-chain/src/work/equihash.rs @@ -55,7 +55,7 @@ impl Solution { let input = &input[0..Solution::INPUT_LENGTH]; - equihash::is_valid_solution(n, k, input, nonce, solution)?; + equihash::is_valid_solution(n, k, input, nonce.as_ref(), solution)?; Ok(()) } diff --git a/zebra-chain/src/work/tests/prop.rs b/zebra-chain/src/work/tests/prop.rs index 069eb1e2f49..6c3b4ae8ae8 100644 --- a/zebra-chain/src/work/tests/prop.rs +++ b/zebra-chain/src/work/tests/prop.rs @@ -73,12 +73,12 @@ prop_compose! { fn randomized_nonce(real_header: block::Header) (fake_nonce in proptest::array::uniform32(any::()) .prop_filter("nonce must not be the actual nonce", move |fake_nonce| { - fake_nonce != &real_header.nonce + fake_nonce != &real_header.nonce.0 }) ) -> Arc { let mut fake_header = real_header; - fake_header.nonce = fake_nonce; + fake_header.nonce = fake_nonce.into(); Arc::new(fake_header) } diff --git a/zebra-consensus/src/block/tests.rs b/zebra-consensus/src/block/tests.rs index 1901984541b..f934d63b8d7 100644 --- a/zebra-consensus/src/block/tests.rs +++ b/zebra-consensus/src/block/tests.rs @@ -65,7 +65,7 @@ static INVALID_HEADER_SOLUTION_TRANSCRIPT: Lazy< Block::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_GENESIS_BYTES[..]).unwrap(); // Change nonce to something invalid - Arc::make_mut(&mut block.header).nonce = [0; 32]; + Arc::make_mut(&mut block.header).nonce = [0; 32].into(); vec![(Arc::new(block), Err(ExpectedTranscriptError::Any))] }); diff --git a/zebra-state/src/tests.rs b/zebra-state/src/tests.rs index 725f659ae5c..1133542df1a 100644 --- a/zebra-state/src/tests.rs +++ b/zebra-state/src/tests.rs @@ -58,7 +58,7 @@ impl FakeChainHelper for Arc { fn set_block_commitment(mut self, block_commitment: [u8; 32]) -> Arc { let block = Arc::make_mut(&mut self); - Arc::make_mut(&mut block.header).commitment_bytes = block_commitment; + Arc::make_mut(&mut block.header).commitment_bytes = block_commitment.into(); self } }