From 363541c2c0847d4ce2657568bff6dcbe1a4e280e Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 28 Aug 2025 10:09:31 +1000 Subject: [PATCH 1/8] Add newtype wrappers --- src/fixed_vector_u8.rs | 357 ++++++++++++++++++++++++++++++++++ src/lib.rs | 4 + src/variable_list_u8.rs | 410 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 771 insertions(+) create mode 100644 src/fixed_vector_u8.rs create mode 100644 src/variable_list_u8.rs diff --git a/src/fixed_vector_u8.rs b/src/fixed_vector_u8.rs new file mode 100644 index 0000000..1d5b83d --- /dev/null +++ b/src/fixed_vector_u8.rs @@ -0,0 +1,357 @@ +use crate::tree_hash::vec_tree_hash_root; +use crate::{Error, FixedVector}; +use serde::Deserialize; +use serde_derive::Serialize; +use std::ops::{Deref, DerefMut, Index, IndexMut}; +use std::slice::SliceIndex; +use tree_hash::Hash256; +use typenum::Unsigned; + +pub use typenum; + +#[derive(Debug, Clone, Serialize)] +#[serde(transparent)] +pub struct FixedVectorU8 { + inner: FixedVector, +} + +impl PartialEq for FixedVectorU8 { + fn eq(&self, other: &Self) -> bool { + self.inner == other.inner + } +} +impl Eq for FixedVectorU8 {} +impl std::hash::Hash for FixedVectorU8 { + fn hash(&self, state: &mut H) { + self.inner.hash(state); + } +} + +impl FixedVectorU8 { + pub fn new(vec: Vec) -> Result { + Ok(Self { + inner: FixedVector::new(vec)?, + }) + } + + pub fn from_elem(elem: u8) -> Self { + Self { + inner: FixedVector::from_elem(elem), + } + } + + pub fn len(&self) -> usize { + self.inner.len() + } + + pub fn is_empty(&self) -> bool { + self.inner.is_empty() + } + + pub fn capacity() -> usize { + FixedVector::::capacity() + } +} + +impl TryFrom> for FixedVectorU8 { + type Error = Error; + + fn try_from(vec: Vec) -> Result { + Self::new(vec) + } +} + +impl From> for Vec { + fn from(vector: FixedVectorU8) -> Vec { + vector.inner.into() + } +} + +impl Default for FixedVectorU8 { + fn default() -> Self { + Self { + inner: FixedVector::default(), + } + } +} + +impl> Index for FixedVectorU8 { + type Output = I::Output; + + #[inline] + fn index(&self, index: I) -> &Self::Output { + Index::index(&self.inner, index) + } +} + +impl> IndexMut for FixedVectorU8 { + #[inline] + fn index_mut(&mut self, index: I) -> &mut Self::Output { + IndexMut::index_mut(&mut self.inner, index) + } +} + +impl Deref for FixedVectorU8 { + type Target = [u8]; + + fn deref(&self) -> &[u8] { + &self.inner + } +} + +impl DerefMut for FixedVectorU8 { + fn deref_mut(&mut self) -> &mut [u8] { + &mut self.inner + } +} + +impl<'a, N: Unsigned> IntoIterator for &'a FixedVectorU8 { + type Item = &'a u8; + type IntoIter = std::slice::Iter<'a, u8>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl IntoIterator for FixedVectorU8 { + type Item = u8; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.inner.into_iter() + } +} + +impl tree_hash::TreeHash for FixedVectorU8 { + fn tree_hash_type() -> tree_hash::TreeHashType { + tree_hash::TreeHashType::Vector + } + + fn tree_hash_packed_encoding(&self) -> tree_hash::PackedEncoding { + unreachable!("Vector should never be packed.") + } + + fn tree_hash_packing_factor() -> usize { + unreachable!("Vector should never be packed.") + } + + fn tree_hash_root(&self) -> Hash256 { + vec_tree_hash_root::(&self.inner, N::to_usize()) + } +} + +impl ssz::Encode for FixedVectorU8 { + fn is_ssz_fixed_len() -> bool { + u8::is_ssz_fixed_len() + } + + fn ssz_fixed_len() -> usize { + if ::is_ssz_fixed_len() { + u8::ssz_fixed_len() * N::to_usize() + } else { + ssz::BYTES_PER_LENGTH_OFFSET + } + } + + fn ssz_bytes_len(&self) -> usize { + self.inner.ssz_bytes_len() + } + + fn ssz_append(&self, buf: &mut Vec) { + self.inner.ssz_append(buf) + } +} + +impl ssz::TryFromIter for FixedVectorU8 { + type Error = Error; + + fn try_from_iter(value: I) -> Result + where + I: IntoIterator, + { + let inner = FixedVector::try_from_iter(value)?; + Ok(Self { inner }) + } +} + +impl ssz::Decode for FixedVectorU8 { + fn is_ssz_fixed_len() -> bool { + u8::is_ssz_fixed_len() + } + + fn ssz_fixed_len() -> usize { + if ::is_ssz_fixed_len() { + u8::ssz_fixed_len() * N::to_usize() + } else { + ssz::BYTES_PER_LENGTH_OFFSET + } + } + + fn from_ssz_bytes(bytes: &[u8]) -> Result { + let inner = FixedVector::from_ssz_bytes(bytes)?; + Ok(Self { inner }) + } +} + +impl<'de, N> Deserialize<'de> for FixedVectorU8 +where + N: Unsigned, +{ + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let inner = FixedVector::::deserialize(deserializer)?; + Ok(Self { inner }) + } +} + +#[cfg(feature = "arbitrary")] +impl<'a, N: 'static + Unsigned> arbitrary::Arbitrary<'a> for FixedVectorU8 { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + let inner = FixedVector::::arbitrary(u)?; + Ok(Self { inner }) + } +} + +#[cfg(test)] +mod test { + use super::*; + use ssz::*; + use std::collections::HashSet; + use tree_hash::{merkle_root, TreeHash}; + use typenum::*; + + #[test] + fn new() { + let vec = vec![42; 5]; + let fixed: Result, _> = FixedVectorU8::new(vec); + assert!(fixed.is_err()); + + let vec = vec![42; 3]; + let fixed: Result, _> = FixedVectorU8::new(vec); + assert!(fixed.is_err()); + + let vec = vec![42; 4]; + let fixed: Result, _> = FixedVectorU8::new(vec); + assert!(fixed.is_ok()); + } + + #[test] + fn indexing() { + let mut vec = vec![1, 2]; + vec.resize_with(8192, u8::default); + + let mut fixed: FixedVectorU8 = vec.clone().try_into().unwrap(); + + assert_eq!(fixed[0], 1); + assert_eq!(&fixed[0..1], &vec[0..1]); + assert_eq!((fixed[..]).len(), 8192); + + fixed[1] = 3; + assert_eq!(fixed[1], 3); + } + + #[test] + fn wrong_length() { + let vec = vec![42; 5]; + let err = FixedVectorU8::::try_from(vec.clone()).unwrap_err(); + assert_eq!(err, Error::OutOfBounds { i: 5, len: 4 }); + + let vec = vec![42; 3]; + let err = FixedVectorU8::::try_from(vec.clone()).unwrap_err(); + assert_eq!(err, Error::OutOfBounds { i: 3, len: 4 }); + + let vec = vec![]; + let err = FixedVectorU8::::try_from(vec).unwrap_err(); + assert_eq!(err, Error::OutOfBounds { i: 0, len: 4 }); + } + + #[test] + fn deref() { + let vec = vec![0, 2, 4, 6]; + let fixed: FixedVectorU8 = FixedVectorU8::try_from(vec).unwrap(); + + assert_eq!(fixed.first(), Some(&0)); + assert_eq!(fixed.get(3), Some(&6)); + assert_eq!(fixed.get(4), None); + } + + #[test] + fn iterator() { + let vec = vec![0, 2, 4, 6]; + let fixed: FixedVectorU8 = FixedVectorU8::try_from(vec).unwrap(); + + assert_eq!((&fixed).into_iter().sum::(), 12); + assert_eq!(fixed.into_iter().sum::(), 12); + } + + #[test] + fn ssz_encode() { + let vec: FixedVectorU8 = vec![0; 2].try_into().unwrap(); + assert_eq!(vec.as_ssz_bytes(), vec![0, 0]); + assert_eq!( as Encode>::ssz_fixed_len(), 2); + } + + fn ssz_round_trip(item: T) { + let encoded = &item.as_ssz_bytes(); + assert_eq!(item.ssz_bytes_len(), encoded.len()); + assert_eq!(T::from_ssz_bytes(encoded), Ok(item)); + } + + #[test] + fn ssz_round_trip_u8_len_8() { + ssz_round_trip::>(vec![42; 8].try_into().unwrap()); + ssz_round_trip::>(vec![0; 8].try_into().unwrap()); + } + + #[test] + fn tree_hash_u8() { + let fixed: FixedVectorU8 = FixedVectorU8::try_from(vec![]).unwrap(); + assert_eq!(fixed.tree_hash_root(), merkle_root(&[0; 8], 0)); + + let fixed: FixedVectorU8 = FixedVectorU8::try_from(vec![0; 1]).unwrap(); + assert_eq!(fixed.tree_hash_root(), merkle_root(&[0; 8], 0)); + + let fixed: FixedVectorU8 = FixedVectorU8::try_from(vec![0; 8]).unwrap(); + assert_eq!(fixed.tree_hash_root(), merkle_root(&[0; 8], 0)); + + let fixed: FixedVectorU8 = FixedVectorU8::try_from(vec![42; 16]).unwrap(); + assert_eq!(fixed.tree_hash_root(), merkle_root(&[42; 16], 0)); + + let source: Vec = (0..16).collect(); + let fixed: FixedVectorU8 = FixedVectorU8::try_from(source.clone()).unwrap(); + assert_eq!(fixed.tree_hash_root(), merkle_root(&source, 0)); + } + + #[test] + fn std_hash() { + let x: FixedVectorU8 = FixedVectorU8::try_from(vec![3; 16]).unwrap(); + let y: FixedVectorU8 = FixedVectorU8::try_from(vec![4; 16]).unwrap(); + let mut hashset = HashSet::new(); + + for value in [x.clone(), y.clone()] { + assert!(hashset.insert(value.clone())); + assert!(!hashset.insert(value.clone())); + assert!(hashset.contains(&value)); + } + assert_eq!(hashset.len(), 2); + } + + #[test] + fn serde_invalid_length() { + use typenum::U4; + let json = serde_json::json!([1, 2, 3, 4, 5]); + let result: Result, _> = serde_json::from_value(json); + assert!(result.is_err()); + + let json = serde_json::json!([1, 2, 3]); + let result: Result, _> = serde_json::from_value(json); + assert!(result.is_err()); + + let json = serde_json::json!([1, 2, 3, 4]); + let result: Result, _> = serde_json::from_value(json); + assert!(result.is_ok()); + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 66223cb..9aa0c59 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -39,14 +39,18 @@ #[macro_use] mod fixed_vector; +mod fixed_vector_u8; pub mod serde_utils; mod tree_hash; mod variable_list; +mod variable_list_u8; pub use fixed_vector::FixedVector; +pub use fixed_vector_u8::FixedVectorU8; pub use ssz::{BitList, BitVector, Bitfield}; pub use typenum; pub use variable_list::VariableList; +pub use variable_list_u8::VariableListU8; pub mod length { pub use ssz::{Fixed, Variable}; diff --git a/src/variable_list_u8.rs b/src/variable_list_u8.rs new file mode 100644 index 0000000..1d3e6f6 --- /dev/null +++ b/src/variable_list_u8.rs @@ -0,0 +1,410 @@ +use crate::tree_hash::vec_tree_hash_root; +use crate::{Error, VariableList}; +use serde::Deserialize; +use serde_derive::Serialize; +use std::ops::{Deref, DerefMut, Index, IndexMut}; +use std::slice::SliceIndex; +use tree_hash::Hash256; +use typenum::Unsigned; + +pub use typenum; + +#[derive(Debug, Clone, Serialize)] +#[serde(transparent)] +pub struct VariableListU8 { + inner: VariableList, +} + +impl PartialEq for VariableListU8 { + fn eq(&self, other: &Self) -> bool { + self.inner == other.inner + } +} +impl Eq for VariableListU8 {} +impl std::hash::Hash for VariableListU8 { + fn hash(&self, state: &mut H) { + self.inner.hash(state); + } +} + +impl VariableListU8 { + pub fn new(vec: Vec) -> Result { + Ok(Self { + inner: VariableList::new(vec)?, + }) + } + + pub fn empty() -> Self { + Self { + inner: VariableList::empty(), + } + } + + pub fn len(&self) -> usize { + self.inner.len() + } + + pub fn is_empty(&self) -> bool { + self.inner.is_empty() + } + + pub fn max_len() -> usize { + VariableList::::max_len() + } + + pub fn push(&mut self, value: u8) -> Result<(), Error> { + self.inner.push(value) + } +} + +impl TryFrom> for VariableListU8 { + type Error = Error; + + fn try_from(vec: Vec) -> Result { + Self::new(vec) + } +} + +impl From> for Vec { + fn from(list: VariableListU8) -> Vec { + list.inner.into() + } +} + +impl Default for VariableListU8 { + fn default() -> Self { + Self { + inner: VariableList::default(), + } + } +} + +impl> Index for VariableListU8 { + type Output = I::Output; + + #[inline] + fn index(&self, index: I) -> &Self::Output { + Index::index(&self.inner, index) + } +} + +impl> IndexMut for VariableListU8 { + #[inline] + fn index_mut(&mut self, index: I) -> &mut Self::Output { + IndexMut::index_mut(&mut self.inner, index) + } +} + +impl Deref for VariableListU8 { + type Target = [u8]; + + fn deref(&self) -> &[u8] { + &self.inner + } +} + +impl DerefMut for VariableListU8 { + fn deref_mut(&mut self) -> &mut [u8] { + &mut self.inner + } +} + +impl<'a, N: Unsigned> IntoIterator for &'a VariableListU8 { + type Item = &'a u8; + type IntoIter = std::slice::Iter<'a, u8>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl IntoIterator for VariableListU8 { + type Item = u8; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.inner.into_iter() + } +} + +impl tree_hash::TreeHash for VariableListU8 { + fn tree_hash_type() -> tree_hash::TreeHashType { + tree_hash::TreeHashType::List + } + + fn tree_hash_packed_encoding(&self) -> tree_hash::PackedEncoding { + unreachable!("List should never be packed.") + } + + fn tree_hash_packing_factor() -> usize { + unreachable!("List should never be packed.") + } + + fn tree_hash_root(&self) -> Hash256 { + let root = vec_tree_hash_root::(&self.inner, N::to_usize()); + tree_hash::mix_in_length(&root, self.len()) + } +} + +impl ssz::Encode for VariableListU8 { + fn is_ssz_fixed_len() -> bool { + >::is_ssz_fixed_len() + } + + fn ssz_fixed_len() -> usize { + >::ssz_fixed_len() + } + + fn ssz_bytes_len(&self) -> usize { + self.inner.ssz_bytes_len() + } + + fn ssz_append(&self, buf: &mut Vec) { + self.inner.ssz_append(buf) + } +} + +impl ssz::TryFromIter for VariableListU8 { + type Error = Error; + + fn try_from_iter(value: I) -> Result + where + I: IntoIterator, + { + let inner = VariableList::try_from_iter(value)?; + Ok(Self { inner }) + } +} + +impl ssz::Decode for VariableListU8 +where + N: Unsigned, +{ + fn is_ssz_fixed_len() -> bool { + false + } + + fn from_ssz_bytes(bytes: &[u8]) -> Result { + let inner = VariableList::from_ssz_bytes(bytes)?; + Ok(Self { inner }) + } +} + +impl<'de, N> Deserialize<'de> for VariableListU8 +where + N: Unsigned, +{ + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let inner = VariableList::::deserialize(deserializer)?; + Ok(Self { inner }) + } +} + +#[cfg(feature = "arbitrary")] +impl<'a, N: 'static + Unsigned> arbitrary::Arbitrary<'a> for VariableListU8 { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + let inner = VariableList::::arbitrary(u)?; + Ok(Self { inner }) + } +} + +#[cfg(test)] +mod test { + use super::*; + use ssz::*; + use std::collections::HashSet; + use tree_hash::{merkle_root, TreeHash}; + use typenum::*; + + #[test] + fn new() { + let vec = vec![42; 5]; + let fixed: Result, _> = VariableListU8::new(vec); + assert!(fixed.is_err()); + + let vec = vec![42; 3]; + let fixed: Result, _> = VariableListU8::new(vec); + assert!(fixed.is_ok()); + + let vec = vec![42; 4]; + let fixed: Result, _> = VariableListU8::new(vec); + assert!(fixed.is_ok()); + } + + #[test] + fn indexing() { + let vec = vec![1, 2]; + + let mut fixed: VariableListU8 = vec.clone().try_into().unwrap(); + + assert_eq!(fixed[0], 1); + assert_eq!(&fixed[0..1], &vec[0..1]); + assert_eq!((fixed[..]).len(), 2); + + fixed[1] = 3; + assert_eq!(fixed[1], 3); + } + + #[test] + fn length() { + let vec = vec![42; 5]; + let err = VariableListU8::::try_from(vec.clone()).unwrap_err(); + assert_eq!(err, Error::OutOfBounds { i: 5, len: 4 }); + + let vec = vec![42; 3]; + let fixed: VariableListU8 = VariableListU8::try_from(vec.clone()).unwrap(); + assert_eq!(&fixed[0..3], &vec[..]); + assert_eq!(&fixed[..], &vec![42, 42, 42][..]); + + let vec = vec![]; + let fixed: VariableListU8 = VariableListU8::try_from(vec).unwrap(); + assert_eq!(&fixed[..], &[] as &[u8]); + } + + #[test] + fn deref() { + let vec = vec![0, 2, 4, 6]; + let fixed: VariableListU8 = VariableListU8::try_from(vec).unwrap(); + + assert_eq!(fixed.first(), Some(&0)); + assert_eq!(fixed.get(3), Some(&6)); + assert_eq!(fixed.get(4), None); + } + + #[test] + fn encode() { + let vec: VariableListU8 = vec![0; 2].try_into().unwrap(); + assert_eq!(vec.as_ssz_bytes(), vec![0, 0]); + assert_eq!( as Encode>::ssz_fixed_len(), 4); + } + + fn round_trip(item: T) { + let encoded = &item.as_ssz_bytes(); + assert_eq!(item.ssz_bytes_len(), encoded.len()); + assert_eq!(T::from_ssz_bytes(encoded), Ok(item)); + } + + #[test] + fn u8_len_8() { + round_trip::>(vec![42; 8].try_into().unwrap()); + round_trip::>(vec![0; 8].try_into().unwrap()); + round_trip::>(vec![].try_into().unwrap()); + } + + #[test] + fn ssz_empty_list() { + let empty_list = VariableListU8::::default(); + let bytes = empty_list.as_ssz_bytes(); + assert!(bytes.is_empty()); + assert_eq!(VariableListU8::from_ssz_bytes(&[]).unwrap(), empty_list); + } + + fn root_with_length(bytes: &[u8], len: usize) -> Hash256 { + let root = merkle_root(bytes, 0); + tree_hash::mix_in_length(&root, len) + } + + #[test] + fn tree_hash_u8() { + let fixed: VariableListU8 = VariableListU8::try_from(vec![]).unwrap(); + assert_eq!(fixed.tree_hash_root(), root_with_length(&[0; 8], 0)); + + for i in 0..=1 { + let fixed: VariableListU8 = VariableListU8::try_from(vec![0; i]).unwrap(); + assert_eq!(fixed.tree_hash_root(), root_with_length(&vec![0; i], i)); + } + + for i in 0..=8 { + let fixed: VariableListU8 = VariableListU8::try_from(vec![0; i]).unwrap(); + assert_eq!(fixed.tree_hash_root(), root_with_length(&vec![0; i], i)); + } + + for i in 0..=13 { + let fixed: VariableListU8 = VariableListU8::try_from(vec![0; i]).unwrap(); + assert_eq!(fixed.tree_hash_root(), root_with_length(&vec![0; i], i)); + } + + for i in 0..=16 { + let fixed: VariableListU8 = VariableListU8::try_from(vec![0; i]).unwrap(); + assert_eq!(fixed.tree_hash_root(), root_with_length(&vec![0; i], i)); + } + + let source: Vec = (0..16).collect(); + let fixed: VariableListU8 = VariableListU8::try_from(source.clone()).unwrap(); + assert_eq!(fixed.tree_hash_root(), root_with_length(&source, 16)); + } + + + #[test] + fn large_list_pre_allocation() { + use std::iter; + use typenum::U1099511627776; + + struct WonkyIterator { + hint: usize, + iter: I, + } + + impl Iterator for WonkyIterator + where + I: Iterator, + { + type Item = I::Item; + + fn next(&mut self) -> Option { + self.iter.next() + } + + fn size_hint(&self) -> (usize, Option) { + (0, Some(self.hint)) + } + } + + type N = U1099511627776; + type List = VariableListU8; + + let iter = iter::repeat(1).take(5); + let wonky_iter = WonkyIterator { + hint: N::to_usize() / 2, + iter: iter.clone(), + }; + + assert_eq!( + List::try_from_iter(iter).unwrap(), + List::try_from_iter(wonky_iter).unwrap() + ); + } + + #[test] + fn std_hash() { + let x: VariableListU8 = VariableListU8::try_from(vec![3; 16]).unwrap(); + let y: VariableListU8 = VariableListU8::try_from(vec![4; 16]).unwrap(); + let mut hashset = HashSet::new(); + + for value in [x.clone(), y.clone()] { + assert!(hashset.insert(value.clone())); + assert!(!hashset.insert(value.clone())); + assert!(hashset.contains(&value)); + } + assert_eq!(hashset.len(), 2); + } + + #[test] + fn serde_invalid_length() { + use typenum::U4; + let json = serde_json::json!([1, 2, 3, 4, 5]); + let result: Result, _> = serde_json::from_value(json); + assert!(result.is_err()); + + let json = serde_json::json!([1, 2, 3]); + let result: Result, _> = serde_json::from_value(json); + assert!(result.is_ok()); + + let json = serde_json::json!([1, 2, 3, 4]); + let result: Result, _> = serde_json::from_value(json); + assert!(result.is_ok()); + } +} \ No newline at end of file From a7f2d72eb67590b498ab27e9877d266d04270c0e Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 28 Aug 2025 10:19:00 +1000 Subject: [PATCH 2/8] Optimise tree hashing and SSZ encode --- src/fixed_vector_u8.rs | 19 +++++++------------ src/variable_list_u8.rs | 16 +++++++--------- 2 files changed, 14 insertions(+), 21 deletions(-) diff --git a/src/fixed_vector_u8.rs b/src/fixed_vector_u8.rs index 1d5b83d..0cba3a3 100644 --- a/src/fixed_vector_u8.rs +++ b/src/fixed_vector_u8.rs @@ -1,10 +1,9 @@ -use crate::tree_hash::vec_tree_hash_root; use crate::{Error, FixedVector}; use serde::Deserialize; use serde_derive::Serialize; use std::ops::{Deref, DerefMut, Index, IndexMut}; use std::slice::SliceIndex; -use tree_hash::Hash256; +use tree_hash::{merkle_root, Hash256}; use typenum::Unsigned; pub use typenum; @@ -137,29 +136,25 @@ impl tree_hash::TreeHash for FixedVectorU8 { } fn tree_hash_root(&self) -> Hash256 { - vec_tree_hash_root::(&self.inner, N::to_usize()) + merkle_root(&self, 0) } } impl ssz::Encode for FixedVectorU8 { fn is_ssz_fixed_len() -> bool { - u8::is_ssz_fixed_len() + true } fn ssz_fixed_len() -> usize { - if ::is_ssz_fixed_len() { - u8::ssz_fixed_len() * N::to_usize() - } else { - ssz::BYTES_PER_LENGTH_OFFSET - } + N::to_usize() } fn ssz_bytes_len(&self) -> usize { - self.inner.ssz_bytes_len() + self.len() } fn ssz_append(&self, buf: &mut Vec) { - self.inner.ssz_append(buf) + buf.extend_from_slice(&self.inner); } } @@ -354,4 +349,4 @@ mod test { let result: Result, _> = serde_json::from_value(json); assert!(result.is_ok()); } -} \ No newline at end of file +} diff --git a/src/variable_list_u8.rs b/src/variable_list_u8.rs index 1d3e6f6..22fbe11 100644 --- a/src/variable_list_u8.rs +++ b/src/variable_list_u8.rs @@ -1,10 +1,9 @@ -use crate::tree_hash::vec_tree_hash_root; use crate::{Error, VariableList}; use serde::Deserialize; use serde_derive::Serialize; use std::ops::{Deref, DerefMut, Index, IndexMut}; use std::slice::SliceIndex; -use tree_hash::Hash256; +use tree_hash::{merkle_root, Hash256}; use typenum::Unsigned; pub use typenum; @@ -141,26 +140,26 @@ impl tree_hash::TreeHash for VariableListU8 { } fn tree_hash_root(&self) -> Hash256 { - let root = vec_tree_hash_root::(&self.inner, N::to_usize()); + let root = merkle_root(&self, 0); tree_hash::mix_in_length(&root, self.len()) } } impl ssz::Encode for VariableListU8 { fn is_ssz_fixed_len() -> bool { - >::is_ssz_fixed_len() + false } fn ssz_fixed_len() -> usize { - >::ssz_fixed_len() + unreachable!("VariableListU8 is not fixed length") } fn ssz_bytes_len(&self) -> usize { - self.inner.ssz_bytes_len() + self.len() } fn ssz_append(&self, buf: &mut Vec) { - self.inner.ssz_append(buf) + buf.extend_from_slice(&self.inner); } } @@ -337,7 +336,6 @@ mod test { assert_eq!(fixed.tree_hash_root(), root_with_length(&source, 16)); } - #[test] fn large_list_pre_allocation() { use std::iter; @@ -407,4 +405,4 @@ mod test { let result: Result, _> = serde_json::from_value(json); assert!(result.is_ok()); } -} \ No newline at end of file +} From 8a014e6b6f0f129ae1280d32f24a7035ca09bdba Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 28 Aug 2025 10:26:02 +1000 Subject: [PATCH 3/8] Optimise ssz Decode function --- src/fixed_vector_u8.rs | 13 +++++-------- src/variable_list_u8.rs | 7 ++++--- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/fixed_vector_u8.rs b/src/fixed_vector_u8.rs index 0cba3a3..7a88ccc 100644 --- a/src/fixed_vector_u8.rs +++ b/src/fixed_vector_u8.rs @@ -172,20 +172,17 @@ impl ssz::TryFromIter for FixedVectorU8 { impl ssz::Decode for FixedVectorU8 { fn is_ssz_fixed_len() -> bool { - u8::is_ssz_fixed_len() + true } fn ssz_fixed_len() -> usize { - if ::is_ssz_fixed_len() { - u8::ssz_fixed_len() * N::to_usize() - } else { - ssz::BYTES_PER_LENGTH_OFFSET - } + N::to_usize() } fn from_ssz_bytes(bytes: &[u8]) -> Result { - let inner = FixedVector::from_ssz_bytes(bytes)?; - Ok(Self { inner }) + FixedVector::new(bytes.to_vec()) + .map(|inner| Self { inner }) + .map_err(|e| ssz::DecodeError::BytesInvalid(format!("{e:?}"))) } } diff --git a/src/variable_list_u8.rs b/src/variable_list_u8.rs index 22fbe11..9cbefb3 100644 --- a/src/variable_list_u8.rs +++ b/src/variable_list_u8.rs @@ -151,7 +151,7 @@ impl ssz::Encode for VariableListU8 { } fn ssz_fixed_len() -> usize { - unreachable!("VariableListU8 is not fixed length") + ssz::BYTES_PER_LENGTH_OFFSET } fn ssz_bytes_len(&self) -> usize { @@ -184,8 +184,9 @@ where } fn from_ssz_bytes(bytes: &[u8]) -> Result { - let inner = VariableList::from_ssz_bytes(bytes)?; - Ok(Self { inner }) + VariableList::new(bytes.to_vec()) + .map(|inner| Self { inner }) + .map_err(|e| ssz::DecodeError::BytesInvalid(format!("{e:?}"))) } } From 2589865da3c00085707f07ce4594d2d3339d530b Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 28 Aug 2025 10:44:20 +1000 Subject: [PATCH 4/8] Add more test vectors --- src/fixed_vector_u8.rs | 335 +++++++++++++++++++------- src/variable_list_u8.rs | 520 +++++++++++++++++++++++++++++++--------- 2 files changed, 650 insertions(+), 205 deletions(-) diff --git a/src/fixed_vector_u8.rs b/src/fixed_vector_u8.rs index 7a88ccc..07af3ac 100644 --- a/src/fixed_vector_u8.rs +++ b/src/fixed_vector_u8.rs @@ -136,7 +136,7 @@ impl tree_hash::TreeHash for FixedVectorU8 { } fn tree_hash_root(&self) -> Hash256 { - merkle_root(&self, 0) + merkle_root(self, 0) } } @@ -212,138 +212,287 @@ mod test { use super::*; use ssz::*; use std::collections::HashSet; - use tree_hash::{merkle_root, TreeHash}; + use tree_hash::TreeHash; use typenum::*; - #[test] - fn new() { - let vec = vec![42; 5]; - let fixed: Result, _> = FixedVectorU8::new(vec); - assert!(fixed.is_err()); + fn test_equivalent_behavior(test_vectors: Vec>) { + for vec in test_vectors { + let original_result = FixedVector::::new(vec.clone()); + let u8_result = FixedVectorU8::::new(vec); + + match (original_result, u8_result) { + (Ok(original), Ok(u8_variant)) => { + // Test basic properties + assert_eq!(original.len(), u8_variant.len()); + assert_eq!(original.is_empty(), u8_variant.is_empty()); + assert_eq!(&original[..], &u8_variant[..]); + + // Test trait implementations + assert_eq!(original.tree_hash_root(), u8_variant.tree_hash_root()); + assert_eq!(original.as_ssz_bytes(), u8_variant.as_ssz_bytes()); + assert_eq!(original.ssz_bytes_len(), u8_variant.ssz_bytes_len()); + + // Test conversion back to Vec + let original_vec: Vec = original.into(); + let u8_vec: Vec = u8_variant.into(); + assert_eq!(original_vec, u8_vec); + } + (Err(original_err), Err(u8_err)) => { + assert_eq!(original_err, u8_err); + } + _ => panic!("Results should both succeed or both fail"), + } + } + } - let vec = vec![42; 3]; - let fixed: Result, _> = FixedVectorU8::new(vec); - assert!(fixed.is_err()); + #[test] + fn construction_and_basic_operations() { + test_equivalent_behavior::(vec![ + vec![42; 5], // too long + vec![42; 3], // too short + vec![42; 4], // correct length + vec![], // empty (too short) + vec![1, 2, 3, 4], // correct length with different values + ]); - let vec = vec![42; 4]; - let fixed: Result, _> = FixedVectorU8::new(vec); - assert!(fixed.is_ok()); + test_equivalent_behavior::(vec![ + vec![], // correct (empty) + vec![1], // too long + ]); } #[test] - fn indexing() { - let mut vec = vec![1, 2]; - vec.resize_with(8192, u8::default); - - let mut fixed: FixedVectorU8 = vec.clone().try_into().unwrap(); + fn indexing_and_deref() { + let test_data = vec![0, 2, 4, 6]; + let original = FixedVector::::try_from(test_data.clone()).unwrap(); + let u8_variant = FixedVectorU8::::try_from(test_data).unwrap(); - assert_eq!(fixed[0], 1); - assert_eq!(&fixed[0..1], &vec[0..1]); - assert_eq!((fixed[..]).len(), 8192); + // Test indexing + assert_eq!(original[0], u8_variant[0]); + assert_eq!(original[3], u8_variant[3]); + assert_eq!(&original[0..2], &u8_variant[0..2]); - fixed[1] = 3; - assert_eq!(fixed[1], 3); + // Test deref operations + assert_eq!(original.first(), u8_variant.first()); + assert_eq!(original.last(), u8_variant.last()); + assert_eq!(original.get(3), u8_variant.get(3)); + assert_eq!(original.get(4), u8_variant.get(4)); } #[test] - fn wrong_length() { - let vec = vec![42; 5]; - let err = FixedVectorU8::::try_from(vec.clone()).unwrap_err(); - assert_eq!(err, Error::OutOfBounds { i: 5, len: 4 }); + fn mutable_operations() { + let test_data = vec![1, 2, 3, 4]; + let mut original = FixedVector::::try_from(test_data.clone()).unwrap(); + let mut u8_variant = FixedVectorU8::::try_from(test_data).unwrap(); - let vec = vec![42; 3]; - let err = FixedVectorU8::::try_from(vec.clone()).unwrap_err(); - assert_eq!(err, Error::OutOfBounds { i: 3, len: 4 }); + original[1] = 99; + u8_variant[1] = 99; - let vec = vec![]; - let err = FixedVectorU8::::try_from(vec).unwrap_err(); - assert_eq!(err, Error::OutOfBounds { i: 0, len: 4 }); + assert_eq!(&original[..], &u8_variant[..]); + assert_eq!(original[1], u8_variant[1]); } #[test] - fn deref() { - let vec = vec![0, 2, 4, 6]; - let fixed: FixedVectorU8 = FixedVectorU8::try_from(vec).unwrap(); + fn iterator_behavior() { + let test_data = vec![1, 2, 3, 4]; + let original = FixedVector::::try_from(test_data.clone()).unwrap(); + let u8_variant = FixedVectorU8::::try_from(test_data).unwrap(); - assert_eq!(fixed.first(), Some(&0)); - assert_eq!(fixed.get(3), Some(&6)); - assert_eq!(fixed.get(4), None); + // Test borrowed iterator + let original_sum: u8 = (&original).into_iter().sum(); + let u8_sum: u8 = (&u8_variant).into_iter().sum(); + assert_eq!(original_sum, u8_sum); + + // Test owned iterator (consume clones) + let original_sum: u8 = original.clone().into_iter().sum(); + let u8_sum: u8 = u8_variant.clone().into_iter().sum(); + assert_eq!(original_sum, u8_sum); } #[test] - fn iterator() { - let vec = vec![0, 2, 4, 6]; - let fixed: FixedVectorU8 = FixedVectorU8::try_from(vec).unwrap(); - - assert_eq!((&fixed).into_iter().sum::(), 12); - assert_eq!(fixed.into_iter().sum::(), 12); + fn ssz_encoding() { + let test_vectors = vec![ + vec![0; 2], + vec![42; 8], + vec![255, 128, 64, 32], + (0..16).collect(), + ]; + + for test_data in test_vectors { + let len = test_data.len(); + match len { + 2 => { + let original = FixedVector::::try_from(test_data.clone()).unwrap(); + let u8_variant = FixedVectorU8::::try_from(test_data).unwrap(); + + assert_eq!(original.as_ssz_bytes(), u8_variant.as_ssz_bytes()); + assert_eq!(original.ssz_bytes_len(), u8_variant.ssz_bytes_len()); + assert_eq!( as ssz::Encode>::ssz_fixed_len(), as ssz::Encode>::ssz_fixed_len()); + } + 4 => { + let original = FixedVector::::try_from(test_data.clone()).unwrap(); + let u8_variant = FixedVectorU8::::try_from(test_data).unwrap(); + + assert_eq!(original.as_ssz_bytes(), u8_variant.as_ssz_bytes()); + assert_eq!(original.ssz_bytes_len(), u8_variant.ssz_bytes_len()); + } + 8 => { + let original = FixedVector::::try_from(test_data.clone()).unwrap(); + let u8_variant = FixedVectorU8::::try_from(test_data).unwrap(); + + assert_eq!(original.as_ssz_bytes(), u8_variant.as_ssz_bytes()); + assert_eq!(original.ssz_bytes_len(), u8_variant.ssz_bytes_len()); + } + 16 => { + let original = FixedVector::::try_from(test_data.clone()).unwrap(); + let u8_variant = FixedVectorU8::::try_from(test_data).unwrap(); + + assert_eq!(original.as_ssz_bytes(), u8_variant.as_ssz_bytes()); + assert_eq!(original.ssz_bytes_len(), u8_variant.ssz_bytes_len()); + } + _ => {} + } + } } #[test] - fn ssz_encode() { - let vec: FixedVectorU8 = vec![0; 2].try_into().unwrap(); - assert_eq!(vec.as_ssz_bytes(), vec![0, 0]); - assert_eq!( as Encode>::ssz_fixed_len(), 2); - } + fn ssz_round_trip() { + let test_data = vec![42; 8]; + let original = FixedVector::::try_from(test_data.clone()).unwrap(); + let u8_variant = FixedVectorU8::::try_from(test_data).unwrap(); - fn ssz_round_trip(item: T) { - let encoded = &item.as_ssz_bytes(); - assert_eq!(item.ssz_bytes_len(), encoded.len()); - assert_eq!(T::from_ssz_bytes(encoded), Ok(item)); + let original_encoded = original.as_ssz_bytes(); + let u8_encoded = u8_variant.as_ssz_bytes(); + assert_eq!(original_encoded, u8_encoded); + + let original_decoded = FixedVector::::from_ssz_bytes(&original_encoded).unwrap(); + let u8_decoded = FixedVectorU8::::from_ssz_bytes(&u8_encoded).unwrap(); + + assert_eq!(&original_decoded[..], &u8_decoded[..]); + assert_eq!(original, original_decoded); + assert_eq!(u8_variant, u8_decoded); } #[test] - fn ssz_round_trip_u8_len_8() { - ssz_round_trip::>(vec![42; 8].try_into().unwrap()); - ssz_round_trip::>(vec![0; 8].try_into().unwrap()); + fn tree_hash_consistency() { + let test_vectors = vec![ + vec![], + vec![0], + vec![0; 8], + vec![42; 16], + (0..16).collect(), + ]; + + for test_data in test_vectors { + let len = test_data.len(); + match len { + 0 => { + let original = FixedVector::::try_from(test_data.clone()).unwrap(); + let u8_variant = FixedVectorU8::::try_from(test_data).unwrap(); + assert_eq!(original.tree_hash_root(), u8_variant.tree_hash_root()); + } + 1 => { + let original = FixedVector::::try_from(test_data.clone()).unwrap(); + let u8_variant = FixedVectorU8::::try_from(test_data).unwrap(); + assert_eq!(original.tree_hash_root(), u8_variant.tree_hash_root()); + } + 8 => { + let original = FixedVector::::try_from(test_data.clone()).unwrap(); + let u8_variant = FixedVectorU8::::try_from(test_data).unwrap(); + assert_eq!(original.tree_hash_root(), u8_variant.tree_hash_root()); + } + 16 => { + let original = FixedVector::::try_from(test_data.clone()).unwrap(); + let u8_variant = FixedVectorU8::::try_from(test_data).unwrap(); + assert_eq!(original.tree_hash_root(), u8_variant.tree_hash_root()); + } + _ => {} + } + } } #[test] - fn tree_hash_u8() { - let fixed: FixedVectorU8 = FixedVectorU8::try_from(vec![]).unwrap(); - assert_eq!(fixed.tree_hash_root(), merkle_root(&[0; 8], 0)); + fn hash_and_equality() { + let test_data1 = vec![3; 16]; + let test_data2 = vec![4; 16]; + + let original1 = FixedVector::::try_from(test_data1.clone()).unwrap(); + let original2 = FixedVector::::try_from(test_data2.clone()).unwrap(); + let u8_variant1 = FixedVectorU8::::try_from(test_data1).unwrap(); + let u8_variant2 = FixedVectorU8::::try_from(test_data2).unwrap(); - let fixed: FixedVectorU8 = FixedVectorU8::try_from(vec![0; 1]).unwrap(); - assert_eq!(fixed.tree_hash_root(), merkle_root(&[0; 8], 0)); + // Test equality + assert_eq!(original1 == original2, u8_variant1 == u8_variant2); + assert_ne!(original1, original2); + assert_ne!(u8_variant1, u8_variant2); - let fixed: FixedVectorU8 = FixedVectorU8::try_from(vec![0; 8]).unwrap(); - assert_eq!(fixed.tree_hash_root(), merkle_root(&[0; 8], 0)); + // Test hash behavior + let mut original_set = HashSet::new(); + let mut u8_set = HashSet::new(); - let fixed: FixedVectorU8 = FixedVectorU8::try_from(vec![42; 16]).unwrap(); - assert_eq!(fixed.tree_hash_root(), merkle_root(&[42; 16], 0)); + assert!(original_set.insert(original1.clone())); + assert!(u8_set.insert(u8_variant1.clone())); + assert!(!original_set.insert(original1.clone())); + assert!(!u8_set.insert(u8_variant1.clone())); - let source: Vec = (0..16).collect(); - let fixed: FixedVectorU8 = FixedVectorU8::try_from(source.clone()).unwrap(); - assert_eq!(fixed.tree_hash_root(), merkle_root(&source, 0)); + assert!(original_set.contains(&original1)); + assert!(u8_set.contains(&u8_variant1)); + + assert_eq!(original_set.len(), u8_set.len()); } #[test] - fn std_hash() { - let x: FixedVectorU8 = FixedVectorU8::try_from(vec![3; 16]).unwrap(); - let y: FixedVectorU8 = FixedVectorU8::try_from(vec![4; 16]).unwrap(); - let mut hashset = HashSet::new(); - - for value in [x.clone(), y.clone()] { - assert!(hashset.insert(value.clone())); - assert!(!hashset.insert(value.clone())); - assert!(hashset.contains(&value)); - } - assert_eq!(hashset.len(), 2); + fn serde_behavior() { + // Test successful deserialization + let json_valid = serde_json::json!([1, 2, 3, 4]); + let original_result: Result, _> = serde_json::from_value(json_valid.clone()); + let u8_result: Result, _> = serde_json::from_value(json_valid); + + assert!(original_result.is_ok()); + assert!(u8_result.is_ok()); + assert_eq!(&original_result.unwrap()[..], &u8_result.unwrap()[..]); + + // Test failed deserialization - too long + let json_too_long = serde_json::json!([1, 2, 3, 4, 5]); + let original_result: Result, _> = serde_json::from_value(json_too_long.clone()); + let u8_result: Result, _> = serde_json::from_value(json_too_long); + + assert!(original_result.is_err()); + assert!(u8_result.is_err()); + + // Test failed deserialization - too short + let json_too_short = serde_json::json!([1, 2, 3]); + let original_result: Result, _> = serde_json::from_value(json_too_short.clone()); + let u8_result: Result, _> = serde_json::from_value(json_too_short); + + assert!(original_result.is_err()); + assert!(u8_result.is_err()); } #[test] - fn serde_invalid_length() { - use typenum::U4; - let json = serde_json::json!([1, 2, 3, 4, 5]); - let result: Result, _> = serde_json::from_value(json); - assert!(result.is_err()); - - let json = serde_json::json!([1, 2, 3]); - let result: Result, _> = serde_json::from_value(json); - assert!(result.is_err()); - - let json = serde_json::json!([1, 2, 3, 4]); - let result: Result, _> = serde_json::from_value(json); - assert!(result.is_ok()); + fn from_elem_constructor() { + let original = FixedVector::::from_elem(42); + let u8_variant = FixedVectorU8::::from_elem(42); + + assert_eq!(&original[..], &u8_variant[..]); + assert_eq!(original.len(), u8_variant.len()); + assert_eq!(&original[..], &[42, 42, 42, 42]); + } + + #[test] + fn try_from_iter() { + let data = vec![1, 2, 3, 4]; + let original_result = FixedVector::::try_from_iter(data.clone()); + let u8_result = FixedVectorU8::::try_from_iter(data); + + match (original_result, u8_result) { + (Ok(original), Ok(u8_variant)) => { + assert_eq!(&original[..], &u8_variant[..]); + } + (Err(original_err), Err(u8_err)) => { + assert_eq!(original_err, u8_err); + } + _ => panic!("Both should succeed or both should fail"), + } } } diff --git a/src/variable_list_u8.rs b/src/variable_list_u8.rs index 9cbefb3..de769e7 100644 --- a/src/variable_list_u8.rs +++ b/src/variable_list_u8.rs @@ -140,7 +140,7 @@ impl tree_hash::TreeHash for VariableListU8 { } fn tree_hash_root(&self) -> Hash256 { - let root = merkle_root(&self, 0); + let root = merkle_root(self, 0); tree_hash::mix_in_length(&root, self.len()) } } @@ -216,125 +216,380 @@ mod test { use super::*; use ssz::*; use std::collections::HashSet; - use tree_hash::{merkle_root, TreeHash}; + use tree_hash::TreeHash; use typenum::*; - #[test] - fn new() { - let vec = vec![42; 5]; - let fixed: Result, _> = VariableListU8::new(vec); - assert!(fixed.is_err()); - - let vec = vec![42; 3]; - let fixed: Result, _> = VariableListU8::new(vec); - assert!(fixed.is_ok()); - - let vec = vec![42; 4]; - let fixed: Result, _> = VariableListU8::new(vec); - assert!(fixed.is_ok()); + fn test_equivalent_behavior(test_vectors: Vec>) { + for vec in test_vectors { + let original_result = VariableList::::new(vec.clone()); + let u8_result = VariableListU8::::new(vec); + + match (original_result, u8_result) { + (Ok(original), Ok(u8_variant)) => { + // Test basic properties + assert_eq!(original.len(), u8_variant.len()); + assert_eq!(original.is_empty(), u8_variant.is_empty()); + assert_eq!(&original[..], &u8_variant[..]); + + // Test trait implementations + assert_eq!(original.tree_hash_root(), u8_variant.tree_hash_root()); + assert_eq!(original.as_ssz_bytes(), u8_variant.as_ssz_bytes()); + assert_eq!(original.ssz_bytes_len(), u8_variant.ssz_bytes_len()); + + // Test conversion back to Vec + let original_vec: Vec = original.into(); + let u8_vec: Vec = u8_variant.into(); + assert_eq!(original_vec, u8_vec); + } + (Err(original_err), Err(u8_err)) => { + assert_eq!(original_err, u8_err); + } + _ => panic!("Results should both succeed or both fail"), + } + } } #[test] - fn indexing() { - let vec = vec![1, 2]; + fn construction_and_basic_operations() { + test_equivalent_behavior::(vec![ + vec![42; 5], // too long + vec![42; 3], // within limits + vec![42; 4], // at max length + vec![], // empty + vec![1, 2, 3, 4], // at max length with different values + ]); + + test_equivalent_behavior::(vec![ + vec![], // correct (empty) + vec![1], // too long + ]); + + // Test various lengths - comprehensive non-full testing + test_equivalent_behavior::(vec![ + vec![], // empty + vec![1], // length 1 + vec![1, 2], // length 2 + vec![1, 2, 3], // length 3 + vec![1, 2, 3, 4], // length 4 + vec![1, 2, 3, 4, 5], // length 5 + vec![1, 2, 3, 4, 5, 6], // length 6 + vec![1, 2, 3, 4, 5, 6, 7], // length 7 + vec![1, 2, 3, 4, 5, 6, 7, 8], // at max length + vec![1, 2, 3, 4, 5, 6, 7, 8, 9], // too long + ]); + + // Test non-full lists with different patterns + test_equivalent_behavior::(vec![ + vec![], + vec![255], // single byte, max value + vec![0, 255], // min/max pair + vec![128; 3], // repeated middle value + vec![1, 2, 3, 4, 5], // ascending sequence + vec![5, 4, 3, 2, 1], // descending sequence + (0..10).collect(), // length 10 (non-full) + (0..15).collect(), // length 15 (almost full) + (0..16).collect(), // length 16 (at max) + ]); + } - let mut fixed: VariableListU8 = vec.clone().try_into().unwrap(); + #[test] + fn indexing_and_deref() { + let test_data = vec![0, 2, 4, 6]; + let original = VariableList::::try_from(test_data.clone()).unwrap(); + let u8_variant = VariableListU8::::try_from(test_data).unwrap(); - assert_eq!(fixed[0], 1); - assert_eq!(&fixed[0..1], &vec[0..1]); - assert_eq!((fixed[..]).len(), 2); + // Test indexing + assert_eq!(original[0], u8_variant[0]); + assert_eq!(original[3], u8_variant[3]); + assert_eq!(&original[0..2], &u8_variant[0..2]); - fixed[1] = 3; - assert_eq!(fixed[1], 3); + // Test deref operations + assert_eq!(original.first(), u8_variant.first()); + assert_eq!(original.last(), u8_variant.last()); + assert_eq!(original.get(3), u8_variant.get(3)); + assert_eq!(original.get(4), u8_variant.get(4)); } #[test] - fn length() { - let vec = vec![42; 5]; - let err = VariableListU8::::try_from(vec.clone()).unwrap_err(); - assert_eq!(err, Error::OutOfBounds { i: 5, len: 4 }); + fn mutable_operations() { + let test_data = vec![1, 2, 3]; + let mut original = VariableList::::try_from(test_data.clone()).unwrap(); + let mut u8_variant = VariableListU8::::try_from(test_data).unwrap(); + + // Test mutable indexing + original[1] = 99; + u8_variant[1] = 99; + assert_eq!(&original[..], &u8_variant[..]); + + // Test push operation + let push_original = original.push(88); + let push_u8 = u8_variant.push(88); + + match (push_original, push_u8) { + (Ok(()), Ok(())) => { + assert_eq!(&original[..], &u8_variant[..]); + assert_eq!(original.len(), u8_variant.len()); + } + (Err(original_err), Err(u8_err)) => { + assert_eq!(original_err, u8_err); + } + _ => panic!("Push results should match"), + } + } - let vec = vec![42; 3]; - let fixed: VariableListU8 = VariableListU8::try_from(vec.clone()).unwrap(); - assert_eq!(&fixed[0..3], &vec[..]); - assert_eq!(&fixed[..], &vec![42, 42, 42][..]); + #[test] + fn push_behavior() { + let mut original = VariableList::::empty(); + let mut u8_variant = VariableListU8::::empty(); + + // Push until full + for i in 0..3 { + let push_original = original.push(i); + let push_u8 = u8_variant.push(i); + assert_eq!(push_original, push_u8); + assert!(push_original.is_ok()); + assert_eq!(&original[..], &u8_variant[..]); + } - let vec = vec![]; - let fixed: VariableListU8 = VariableListU8::try_from(vec).unwrap(); - assert_eq!(&fixed[..], &[] as &[u8]); + // Try to push past capacity + let push_original = original.push(99); + let push_u8 = u8_variant.push(99); + assert_eq!(push_original, push_u8); + assert!(push_original.is_err()); } #[test] - fn deref() { - let vec = vec![0, 2, 4, 6]; - let fixed: VariableListU8 = VariableListU8::try_from(vec).unwrap(); + fn iterator_behavior() { + let test_data = vec![1, 2, 3, 4]; + let original = VariableList::::try_from(test_data.clone()).unwrap(); + let u8_variant = VariableListU8::::try_from(test_data).unwrap(); - assert_eq!(fixed.first(), Some(&0)); - assert_eq!(fixed.get(3), Some(&6)); - assert_eq!(fixed.get(4), None); + // Test borrowed iterator + let original_sum: u8 = (&original).into_iter().sum(); + let u8_sum: u8 = (&u8_variant).into_iter().sum(); + assert_eq!(original_sum, u8_sum); + + // Test owned iterator (consume clones) + let original_sum: u8 = original.clone().into_iter().sum(); + let u8_sum: u8 = u8_variant.clone().into_iter().sum(); + assert_eq!(original_sum, u8_sum); } #[test] - fn encode() { - let vec: VariableListU8 = vec![0; 2].try_into().unwrap(); - assert_eq!(vec.as_ssz_bytes(), vec![0, 0]); - assert_eq!( as Encode>::ssz_fixed_len(), 4); + fn ssz_encoding() { + let test_vectors = vec![ + vec![], // empty + vec![0], // length 1 + vec![0; 2], // length 2 + vec![255], // single max value + vec![1, 2, 3], // length 3 + vec![42; 5], // length 5 + vec![42; 8], // length 8 + vec![255, 128, 64, 32], // length 4 with varied values + vec![0, 1, 2, 3, 4, 5, 6], // length 7 + (0..10).collect(), // length 10 + (0..12).collect(), // length 12 + (0..16).collect(), // length 16 (full) + ]; + + for test_data in test_vectors { + let len = test_data.len(); + if len <= 16 { + let original = VariableList::::try_from(test_data.clone()).unwrap(); + let u8_variant = VariableListU8::::try_from(test_data).unwrap(); + + assert_eq!(original.as_ssz_bytes(), u8_variant.as_ssz_bytes()); + assert_eq!(original.ssz_bytes_len(), u8_variant.ssz_bytes_len()); + assert_eq!( as ssz::Encode>::is_ssz_fixed_len(), as ssz::Encode>::is_ssz_fixed_len()); + } + } } - fn round_trip(item: T) { - let encoded = &item.as_ssz_bytes(); - assert_eq!(item.ssz_bytes_len(), encoded.len()); - assert_eq!(T::from_ssz_bytes(encoded), Ok(item)); + #[test] + fn ssz_round_trip() { + let test_vectors = vec![ + vec![], // empty + vec![1], // length 1 + vec![1, 2, 3], // length 3 (non-full) + vec![1, 2, 3, 4], // length 4 (non-full) + vec![255, 0, 128], // length 3 with edge values + vec![42; 5], // length 5 (non-full) + vec![42; 8], // length 8 (full) + ]; + + for test_data in test_vectors { + let original = VariableList::::try_from(test_data.clone()).unwrap(); + let u8_variant = VariableListU8::::try_from(test_data).unwrap(); + + let original_encoded = original.as_ssz_bytes(); + let u8_encoded = u8_variant.as_ssz_bytes(); + assert_eq!(original_encoded, u8_encoded); + + let original_decoded = VariableList::::from_ssz_bytes(&original_encoded).unwrap(); + let u8_decoded = VariableListU8::::from_ssz_bytes(&u8_encoded).unwrap(); + + assert_eq!(&original_decoded[..], &u8_decoded[..]); + assert_eq!(original, original_decoded); + assert_eq!(u8_variant, u8_decoded); + } } #[test] - fn u8_len_8() { - round_trip::>(vec![42; 8].try_into().unwrap()); - round_trip::>(vec![0; 8].try_into().unwrap()); - round_trip::>(vec![].try_into().unwrap()); + fn empty_list_handling() { + let original_empty = VariableList::::default(); + let u8_empty = VariableListU8::::default(); + + assert_eq!(original_empty.len(), u8_empty.len()); + assert_eq!(original_empty.is_empty(), u8_empty.is_empty()); + assert_eq!(&original_empty[..], &u8_empty[..]); + + // Test SSZ encoding of empty lists + let original_bytes = original_empty.as_ssz_bytes(); + let u8_bytes = u8_empty.as_ssz_bytes(); + assert_eq!(original_bytes, u8_bytes); + assert!(original_bytes.is_empty()); + + // Test decoding empty lists + let original_decoded = VariableList::::from_ssz_bytes(&[]).unwrap(); + let u8_decoded = VariableListU8::::from_ssz_bytes(&[]).unwrap(); + assert_eq!(original_decoded, original_empty); + assert_eq!(u8_decoded, u8_empty); } #[test] - fn ssz_empty_list() { - let empty_list = VariableListU8::::default(); - let bytes = empty_list.as_ssz_bytes(); - assert!(bytes.is_empty()); - assert_eq!(VariableListU8::from_ssz_bytes(&[]).unwrap(), empty_list); + fn tree_hash_consistency() { + let test_vectors = vec![ + (vec![], 0), + (vec![0], 1), + (vec![0; 3], 8), // non-full length 3 + (vec![42; 5], 8), // non-full length 5 + (vec![0; 8], 8), // full length 8 + (vec![1, 2, 3], 8), // non-full with varied data + (vec![255; 7], 8), // non-full with max values + (vec![42; 10], 16), // non-full length 10 + (vec![42; 16], 16), // full length 16 + ((0..5).collect(), 16), // non-full ascending sequence + ((0..12).collect(), 16), // non-full longer sequence + ((0..16).collect(), 16), // full ascending sequence + ]; + + for (test_data, max_len) in test_vectors { + match max_len { + 0 => { + let original = VariableList::::try_from(test_data.clone()).unwrap(); + let u8_variant = VariableListU8::::try_from(test_data).unwrap(); + assert_eq!(original.tree_hash_root(), u8_variant.tree_hash_root()); + } + 1 => { + let original = VariableList::::try_from(test_data.clone()).unwrap(); + let u8_variant = VariableListU8::::try_from(test_data).unwrap(); + assert_eq!(original.tree_hash_root(), u8_variant.tree_hash_root()); + } + 8 => { + let original = VariableList::::try_from(test_data.clone()).unwrap(); + let u8_variant = VariableListU8::::try_from(test_data).unwrap(); + assert_eq!(original.tree_hash_root(), u8_variant.tree_hash_root()); + } + 16 => { + let original = VariableList::::try_from(test_data.clone()).unwrap(); + let u8_variant = VariableListU8::::try_from(test_data).unwrap(); + assert_eq!(original.tree_hash_root(), u8_variant.tree_hash_root()); + } + _ => {} + } + } } - fn root_with_length(bytes: &[u8], len: usize) -> Hash256 { - let root = merkle_root(bytes, 0); - tree_hash::mix_in_length(&root, len) + #[test] + fn hash_and_equality() { + let test_data1 = vec![3; 8]; + let test_data2 = vec![4; 8]; + + let original1 = VariableList::::try_from(test_data1.clone()).unwrap(); + let original2 = VariableList::::try_from(test_data2.clone()).unwrap(); + let u8_variant1 = VariableListU8::::try_from(test_data1).unwrap(); + let u8_variant2 = VariableListU8::::try_from(test_data2).unwrap(); + + // Test equality + assert_eq!(original1 == original2, u8_variant1 == u8_variant2); + assert_ne!(original1, original2); + assert_ne!(u8_variant1, u8_variant2); + + // Test equal cases + let test_data3 = vec![3; 8]; + let original3 = VariableList::::try_from(test_data3.clone()).unwrap(); + let u8_variant3 = VariableListU8::::try_from(test_data3).unwrap(); + assert_eq!(original1, original3); + assert_eq!(u8_variant1, u8_variant3); + + // Test hash behavior + let mut original_set = HashSet::new(); + let mut u8_set = HashSet::new(); + + assert!(original_set.insert(original1.clone())); + assert!(u8_set.insert(u8_variant1.clone())); + assert!(!original_set.insert(original1.clone())); + assert!(!u8_set.insert(u8_variant1.clone())); + + assert!(original_set.contains(&original1)); + assert!(u8_set.contains(&u8_variant1)); + + assert_eq!(original_set.len(), u8_set.len()); } #[test] - fn tree_hash_u8() { - let fixed: VariableListU8 = VariableListU8::try_from(vec![]).unwrap(); - assert_eq!(fixed.tree_hash_root(), root_with_length(&[0; 8], 0)); - - for i in 0..=1 { - let fixed: VariableListU8 = VariableListU8::try_from(vec![0; i]).unwrap(); - assert_eq!(fixed.tree_hash_root(), root_with_length(&vec![0; i], i)); - } - - for i in 0..=8 { - let fixed: VariableListU8 = VariableListU8::try_from(vec![0; i]).unwrap(); - assert_eq!(fixed.tree_hash_root(), root_with_length(&vec![0; i], i)); + fn serde_behavior() { + // Test successful deserialization with various lengths + for json_data in [ + serde_json::json!([]), + serde_json::json!([1]), + serde_json::json!([1, 2, 3]), + serde_json::json!([1, 2, 3, 4]), + ] { + let original_result: Result, _> = serde_json::from_value(json_data.clone()); + let u8_result: Result, _> = serde_json::from_value(json_data); + + match (original_result, u8_result) { + (Ok(original), Ok(u8_variant)) => { + assert_eq!(&original[..], &u8_variant[..]); + } + (Err(_), Err(_)) => {} // Both should fail in same cases + _ => panic!("Serde results should match"), + } } - for i in 0..=13 { - let fixed: VariableListU8 = VariableListU8::try_from(vec![0; i]).unwrap(); - assert_eq!(fixed.tree_hash_root(), root_with_length(&vec![0; i], i)); - } + // Test failed deserialization - too long + let json_too_long = serde_json::json!([1, 2, 3, 4, 5]); + let original_result: Result, _> = serde_json::from_value(json_too_long.clone()); + let u8_result: Result, _> = serde_json::from_value(json_too_long); + + assert!(original_result.is_err()); + assert!(u8_result.is_err()); + } - for i in 0..=16 { - let fixed: VariableListU8 = VariableListU8::try_from(vec![0; i]).unwrap(); - assert_eq!(fixed.tree_hash_root(), root_with_length(&vec![0; i], i)); + #[test] + fn try_from_iter() { + let test_vectors = vec![ + vec![], + vec![1, 2, 3], + vec![1, 2, 3, 4], + vec![1, 2, 3, 4, 5], // Should fail for max_len 4 + ]; + + for data in test_vectors { + let original_result = VariableList::::try_from_iter(data.clone()); + let u8_result = VariableListU8::::try_from_iter(data); + + match (original_result, u8_result) { + (Ok(original), Ok(u8_variant)) => { + assert_eq!(&original[..], &u8_variant[..]); + } + (Err(original_err), Err(u8_err)) => { + assert_eq!(original_err, u8_err); + } + _ => panic!("Both should succeed or both should fail"), + } } - - let source: Vec = (0..16).collect(); - let fixed: VariableListU8 = VariableListU8::try_from(source.clone()).unwrap(); - assert_eq!(fixed.tree_hash_root(), root_with_length(&source, 16)); } #[test] @@ -363,7 +618,6 @@ mod test { } type N = U1099511627776; - type List = VariableListU8; let iter = iter::repeat(1).take(5); let wonky_iter = WonkyIterator { @@ -371,39 +625,81 @@ mod test { iter: iter.clone(), }; - assert_eq!( - List::try_from_iter(iter).unwrap(), - List::try_from_iter(wonky_iter).unwrap() - ); + let original_result = VariableList::::try_from_iter(iter); + let u8_result = VariableListU8::::try_from_iter(wonky_iter); + + assert!(original_result.is_ok()); + assert!(u8_result.is_ok()); + assert_eq!(&original_result.unwrap()[..], &u8_result.unwrap()[..]); } #[test] - fn std_hash() { - let x: VariableListU8 = VariableListU8::try_from(vec![3; 16]).unwrap(); - let y: VariableListU8 = VariableListU8::try_from(vec![4; 16]).unwrap(); - let mut hashset = HashSet::new(); - - for value in [x.clone(), y.clone()] { - assert!(hashset.insert(value.clone())); - assert!(!hashset.insert(value.clone())); - assert!(hashset.contains(&value)); - } - assert_eq!(hashset.len(), 2); + fn max_len_property() { + assert_eq!(VariableList::::max_len(), VariableListU8::::max_len()); + assert_eq!(VariableList::::max_len(), VariableListU8::::max_len()); + assert_eq!(VariableList::::max_len(), VariableListU8::::max_len()); } #[test] - fn serde_invalid_length() { - use typenum::U4; - let json = serde_json::json!([1, 2, 3, 4, 5]); - let result: Result, _> = serde_json::from_value(json); - assert!(result.is_err()); - - let json = serde_json::json!([1, 2, 3]); - let result: Result, _> = serde_json::from_value(json); - assert!(result.is_ok()); - - let json = serde_json::json!([1, 2, 3, 4]); - let result: Result, _> = serde_json::from_value(json); - assert!(result.is_ok()); + fn non_full_lists_comprehensive() { + // Test a variety of non-full lists with different capacities and sizes + let test_cases = vec![ + // (data, max_capacity) + (vec![1], 4), + (vec![1, 2], 4), + (vec![1, 2, 3], 4), + (vec![255], 8), + (vec![0, 128, 255], 8), + (vec![42; 5], 8), + (vec![1, 2, 3, 4, 5, 6, 7], 8), + (vec![100; 3], 16), + ((0..7).collect(), 16), + ((10..20).collect(), 16), + (vec![0, 255, 0, 255, 0], 16), + ]; + + for (test_data, max_cap) in test_cases { + match max_cap { + 4 => { + let original = VariableList::::try_from(test_data.clone()).unwrap(); + let u8_variant = VariableListU8::::try_from(test_data.clone()).unwrap(); + + // Verify the list is not at max capacity + assert!(original.len() < VariableList::::max_len(), + "Test data should be non-full: len={}, max={}", + original.len(), VariableList::::max_len()); + + // Test all behaviors are equivalent + assert_eq!(&original[..], &u8_variant[..]); + assert_eq!(original.len(), u8_variant.len()); + assert_eq!(original.is_empty(), u8_variant.is_empty()); + assert_eq!(original.tree_hash_root(), u8_variant.tree_hash_root()); + assert_eq!(original.as_ssz_bytes(), u8_variant.as_ssz_bytes()); + } + 8 => { + let original = VariableList::::try_from(test_data.clone()).unwrap(); + let u8_variant = VariableListU8::::try_from(test_data.clone()).unwrap(); + + assert!(original.len() < VariableList::::max_len()); + assert_eq!(&original[..], &u8_variant[..]); + assert_eq!(original.len(), u8_variant.len()); + assert_eq!(original.is_empty(), u8_variant.is_empty()); + assert_eq!(original.tree_hash_root(), u8_variant.tree_hash_root()); + assert_eq!(original.as_ssz_bytes(), u8_variant.as_ssz_bytes()); + } + 16 => { + let original = VariableList::::try_from(test_data.clone()).unwrap(); + let u8_variant = VariableListU8::::try_from(test_data.clone()).unwrap(); + + assert!(original.len() < VariableList::::max_len()); + assert_eq!(&original[..], &u8_variant[..]); + assert_eq!(original.len(), u8_variant.len()); + assert_eq!(original.is_empty(), u8_variant.is_empty()); + assert_eq!(original.tree_hash_root(), u8_variant.tree_hash_root()); + assert_eq!(original.as_ssz_bytes(), u8_variant.as_ssz_bytes()); + } + _ => {} + } + } } } From fdba148bf9c719d9cbff6ecfd8d68a53e2625256 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 28 Aug 2025 15:32:09 +1000 Subject: [PATCH 5/8] Trim down tests --- src/fixed_vector_u8.rs | 155 +++------------ src/variable_list_u8.rs | 407 +++++++--------------------------------- 2 files changed, 92 insertions(+), 470 deletions(-) diff --git a/src/fixed_vector_u8.rs b/src/fixed_vector_u8.rs index 07af3ac..2e9a848 100644 --- a/src/fixed_vector_u8.rs +++ b/src/fixed_vector_u8.rs @@ -248,67 +248,19 @@ mod test { #[test] fn construction_and_basic_operations() { test_equivalent_behavior::(vec![ - vec![42; 5], // too long - vec![42; 3], // too short - vec![42; 4], // correct length - vec![], // empty (too short) + vec![42; 5], // too long + vec![42; 3], // too short + vec![42; 4], // correct length + vec![], // empty (too short) vec![1, 2, 3, 4], // correct length with different values ]); test_equivalent_behavior::(vec![ - vec![], // correct (empty) + vec![], // correct (empty) vec![1], // too long ]); } - #[test] - fn indexing_and_deref() { - let test_data = vec![0, 2, 4, 6]; - let original = FixedVector::::try_from(test_data.clone()).unwrap(); - let u8_variant = FixedVectorU8::::try_from(test_data).unwrap(); - - // Test indexing - assert_eq!(original[0], u8_variant[0]); - assert_eq!(original[3], u8_variant[3]); - assert_eq!(&original[0..2], &u8_variant[0..2]); - - // Test deref operations - assert_eq!(original.first(), u8_variant.first()); - assert_eq!(original.last(), u8_variant.last()); - assert_eq!(original.get(3), u8_variant.get(3)); - assert_eq!(original.get(4), u8_variant.get(4)); - } - - #[test] - fn mutable_operations() { - let test_data = vec![1, 2, 3, 4]; - let mut original = FixedVector::::try_from(test_data.clone()).unwrap(); - let mut u8_variant = FixedVectorU8::::try_from(test_data).unwrap(); - - original[1] = 99; - u8_variant[1] = 99; - - assert_eq!(&original[..], &u8_variant[..]); - assert_eq!(original[1], u8_variant[1]); - } - - #[test] - fn iterator_behavior() { - let test_data = vec![1, 2, 3, 4]; - let original = FixedVector::::try_from(test_data.clone()).unwrap(); - let u8_variant = FixedVectorU8::::try_from(test_data).unwrap(); - - // Test borrowed iterator - let original_sum: u8 = (&original).into_iter().sum(); - let u8_sum: u8 = (&u8_variant).into_iter().sum(); - assert_eq!(original_sum, u8_sum); - - // Test owned iterator (consume clones) - let original_sum: u8 = original.clone().into_iter().sum(); - let u8_sum: u8 = u8_variant.clone().into_iter().sum(); - assert_eq!(original_sum, u8_sum); - } - #[test] fn ssz_encoding() { let test_vectors = vec![ @@ -324,29 +276,32 @@ mod test { 2 => { let original = FixedVector::::try_from(test_data.clone()).unwrap(); let u8_variant = FixedVectorU8::::try_from(test_data).unwrap(); - + assert_eq!(original.as_ssz_bytes(), u8_variant.as_ssz_bytes()); assert_eq!(original.ssz_bytes_len(), u8_variant.ssz_bytes_len()); - assert_eq!( as ssz::Encode>::ssz_fixed_len(), as ssz::Encode>::ssz_fixed_len()); + assert_eq!( + as ssz::Encode>::ssz_fixed_len(), + as ssz::Encode>::ssz_fixed_len() + ); } 4 => { let original = FixedVector::::try_from(test_data.clone()).unwrap(); let u8_variant = FixedVectorU8::::try_from(test_data).unwrap(); - + assert_eq!(original.as_ssz_bytes(), u8_variant.as_ssz_bytes()); assert_eq!(original.ssz_bytes_len(), u8_variant.ssz_bytes_len()); } 8 => { let original = FixedVector::::try_from(test_data.clone()).unwrap(); let u8_variant = FixedVectorU8::::try_from(test_data).unwrap(); - + assert_eq!(original.as_ssz_bytes(), u8_variant.as_ssz_bytes()); assert_eq!(original.ssz_bytes_len(), u8_variant.ssz_bytes_len()); } 16 => { let original = FixedVector::::try_from(test_data.clone()).unwrap(); let u8_variant = FixedVectorU8::::try_from(test_data).unwrap(); - + assert_eq!(original.as_ssz_bytes(), u8_variant.as_ssz_bytes()); assert_eq!(original.ssz_bytes_len(), u8_variant.ssz_bytes_len()); } @@ -367,7 +322,7 @@ mod test { let original_decoded = FixedVector::::from_ssz_bytes(&original_encoded).unwrap(); let u8_decoded = FixedVectorU8::::from_ssz_bytes(&u8_encoded).unwrap(); - + assert_eq!(&original_decoded[..], &u8_decoded[..]); assert_eq!(original, original_decoded); assert_eq!(u8_variant, u8_decoded); @@ -375,13 +330,7 @@ mod test { #[test] fn tree_hash_consistency() { - let test_vectors = vec![ - vec![], - vec![0], - vec![0; 8], - vec![42; 16], - (0..16).collect(), - ]; + let test_vectors = vec![vec![], vec![0], vec![0; 8], vec![42; 16], (0..16).collect()]; for test_data in test_vectors { let len = test_data.len(); @@ -411,88 +360,34 @@ mod test { } } - #[test] - fn hash_and_equality() { - let test_data1 = vec![3; 16]; - let test_data2 = vec![4; 16]; - - let original1 = FixedVector::::try_from(test_data1.clone()).unwrap(); - let original2 = FixedVector::::try_from(test_data2.clone()).unwrap(); - let u8_variant1 = FixedVectorU8::::try_from(test_data1).unwrap(); - let u8_variant2 = FixedVectorU8::::try_from(test_data2).unwrap(); - - // Test equality - assert_eq!(original1 == original2, u8_variant1 == u8_variant2); - assert_ne!(original1, original2); - assert_ne!(u8_variant1, u8_variant2); - - // Test hash behavior - let mut original_set = HashSet::new(); - let mut u8_set = HashSet::new(); - - assert!(original_set.insert(original1.clone())); - assert!(u8_set.insert(u8_variant1.clone())); - assert!(!original_set.insert(original1.clone())); - assert!(!u8_set.insert(u8_variant1.clone())); - - assert!(original_set.contains(&original1)); - assert!(u8_set.contains(&u8_variant1)); - - assert_eq!(original_set.len(), u8_set.len()); - } - #[test] fn serde_behavior() { // Test successful deserialization let json_valid = serde_json::json!([1, 2, 3, 4]); - let original_result: Result, _> = serde_json::from_value(json_valid.clone()); + let original_result: Result, _> = + serde_json::from_value(json_valid.clone()); let u8_result: Result, _> = serde_json::from_value(json_valid); - + assert!(original_result.is_ok()); assert!(u8_result.is_ok()); assert_eq!(&original_result.unwrap()[..], &u8_result.unwrap()[..]); // Test failed deserialization - too long let json_too_long = serde_json::json!([1, 2, 3, 4, 5]); - let original_result: Result, _> = serde_json::from_value(json_too_long.clone()); + let original_result: Result, _> = + serde_json::from_value(json_too_long.clone()); let u8_result: Result, _> = serde_json::from_value(json_too_long); - + assert!(original_result.is_err()); assert!(u8_result.is_err()); - // Test failed deserialization - too short + // Test failed deserialization - too short let json_too_short = serde_json::json!([1, 2, 3]); - let original_result: Result, _> = serde_json::from_value(json_too_short.clone()); + let original_result: Result, _> = + serde_json::from_value(json_too_short.clone()); let u8_result: Result, _> = serde_json::from_value(json_too_short); - + assert!(original_result.is_err()); assert!(u8_result.is_err()); } - - #[test] - fn from_elem_constructor() { - let original = FixedVector::::from_elem(42); - let u8_variant = FixedVectorU8::::from_elem(42); - - assert_eq!(&original[..], &u8_variant[..]); - assert_eq!(original.len(), u8_variant.len()); - assert_eq!(&original[..], &[42, 42, 42, 42]); - } - - #[test] - fn try_from_iter() { - let data = vec![1, 2, 3, 4]; - let original_result = FixedVector::::try_from_iter(data.clone()); - let u8_result = FixedVectorU8::::try_from_iter(data); - - match (original_result, u8_result) { - (Ok(original), Ok(u8_variant)) => { - assert_eq!(&original[..], &u8_variant[..]); - } - (Err(original_err), Err(u8_err)) => { - assert_eq!(original_err, u8_err); - } - _ => panic!("Both should succeed or both should fail"), - } - } } diff --git a/src/variable_list_u8.rs b/src/variable_list_u8.rs index de769e7..73b3c52 100644 --- a/src/variable_list_u8.rs +++ b/src/variable_list_u8.rs @@ -215,7 +215,6 @@ impl<'a, N: 'static + Unsigned> arbitrary::Arbitrary<'a> for VariableListU8 { mod test { use super::*; use ssz::*; - use std::collections::HashSet; use tree_hash::TreeHash; use typenum::*; @@ -252,144 +251,61 @@ mod test { #[test] fn construction_and_basic_operations() { test_equivalent_behavior::(vec![ - vec![42; 5], // too long - vec![42; 3], // within limits - vec![42; 4], // at max length - vec![], // empty + vec![42; 5], // too long + vec![42; 3], // within limits + vec![42; 4], // at max length + vec![], // empty vec![1, 2, 3, 4], // at max length with different values ]); test_equivalent_behavior::(vec![ - vec![], // correct (empty) + vec![], // correct (empty) vec![1], // too long ]); // Test various lengths - comprehensive non-full testing test_equivalent_behavior::(vec![ - vec![], // empty - vec![1], // length 1 - vec![1, 2], // length 2 - vec![1, 2, 3], // length 3 - vec![1, 2, 3, 4], // length 4 - vec![1, 2, 3, 4, 5], // length 5 - vec![1, 2, 3, 4, 5, 6], // length 6 - vec![1, 2, 3, 4, 5, 6, 7], // length 7 - vec![1, 2, 3, 4, 5, 6, 7, 8], // at max length - vec![1, 2, 3, 4, 5, 6, 7, 8, 9], // too long + vec![], // empty + vec![1], // length 1 + vec![1, 2], // length 2 + vec![1, 2, 3], // length 3 + vec![1, 2, 3, 4], // length 4 + vec![1, 2, 3, 4, 5], // length 5 + vec![1, 2, 3, 4, 5, 6], // length 6 + vec![1, 2, 3, 4, 5, 6, 7], // length 7 + vec![1, 2, 3, 4, 5, 6, 7, 8], // at max length + vec![1, 2, 3, 4, 5, 6, 7, 8, 9], // too long ]); - + // Test non-full lists with different patterns test_equivalent_behavior::(vec![ vec![], - vec![255], // single byte, max value - vec![0, 255], // min/max pair - vec![128; 3], // repeated middle value - vec![1, 2, 3, 4, 5], // ascending sequence - vec![5, 4, 3, 2, 1], // descending sequence - (0..10).collect(), // length 10 (non-full) - (0..15).collect(), // length 15 (almost full) - (0..16).collect(), // length 16 (at max) + vec![255], // single byte, max value + vec![0, 255], // min/max pair + vec![128; 3], // repeated middle value + vec![1, 2, 3, 4, 5], // ascending sequence + vec![5, 4, 3, 2, 1], // descending sequence + (0..10).collect(), // length 10 (non-full) + (0..15).collect(), // length 15 (almost full) + (0..16).collect(), // length 16 (at max) ]); } - #[test] - fn indexing_and_deref() { - let test_data = vec![0, 2, 4, 6]; - let original = VariableList::::try_from(test_data.clone()).unwrap(); - let u8_variant = VariableListU8::::try_from(test_data).unwrap(); - - // Test indexing - assert_eq!(original[0], u8_variant[0]); - assert_eq!(original[3], u8_variant[3]); - assert_eq!(&original[0..2], &u8_variant[0..2]); - - // Test deref operations - assert_eq!(original.first(), u8_variant.first()); - assert_eq!(original.last(), u8_variant.last()); - assert_eq!(original.get(3), u8_variant.get(3)); - assert_eq!(original.get(4), u8_variant.get(4)); - } - - #[test] - fn mutable_operations() { - let test_data = vec![1, 2, 3]; - let mut original = VariableList::::try_from(test_data.clone()).unwrap(); - let mut u8_variant = VariableListU8::::try_from(test_data).unwrap(); - - // Test mutable indexing - original[1] = 99; - u8_variant[1] = 99; - assert_eq!(&original[..], &u8_variant[..]); - - // Test push operation - let push_original = original.push(88); - let push_u8 = u8_variant.push(88); - - match (push_original, push_u8) { - (Ok(()), Ok(())) => { - assert_eq!(&original[..], &u8_variant[..]); - assert_eq!(original.len(), u8_variant.len()); - } - (Err(original_err), Err(u8_err)) => { - assert_eq!(original_err, u8_err); - } - _ => panic!("Push results should match"), - } - } - - #[test] - fn push_behavior() { - let mut original = VariableList::::empty(); - let mut u8_variant = VariableListU8::::empty(); - - // Push until full - for i in 0..3 { - let push_original = original.push(i); - let push_u8 = u8_variant.push(i); - assert_eq!(push_original, push_u8); - assert!(push_original.is_ok()); - assert_eq!(&original[..], &u8_variant[..]); - } - - // Try to push past capacity - let push_original = original.push(99); - let push_u8 = u8_variant.push(99); - assert_eq!(push_original, push_u8); - assert!(push_original.is_err()); - } - - #[test] - fn iterator_behavior() { - let test_data = vec![1, 2, 3, 4]; - let original = VariableList::::try_from(test_data.clone()).unwrap(); - let u8_variant = VariableListU8::::try_from(test_data).unwrap(); - - // Test borrowed iterator - let original_sum: u8 = (&original).into_iter().sum(); - let u8_sum: u8 = (&u8_variant).into_iter().sum(); - assert_eq!(original_sum, u8_sum); - - // Test owned iterator (consume clones) - let original_sum: u8 = original.clone().into_iter().sum(); - let u8_sum: u8 = u8_variant.clone().into_iter().sum(); - assert_eq!(original_sum, u8_sum); - } - #[test] fn ssz_encoding() { let test_vectors = vec![ - vec![], // empty - vec![0], // length 1 - vec![0; 2], // length 2 - vec![255], // single max value - vec![1, 2, 3], // length 3 - vec![42; 5], // length 5 - vec![42; 8], // length 8 - vec![255, 128, 64, 32], // length 4 with varied values - vec![0, 1, 2, 3, 4, 5, 6], // length 7 - (0..10).collect(), // length 10 - (0..12).collect(), // length 12 - (0..16).collect(), // length 16 (full) + vec![], // empty + vec![0], // length 1 + vec![0; 2], // length 2 + vec![255], // single max value + vec![1, 2, 3], // length 3 + vec![42; 5], // length 5 + vec![42; 8], // length 8 + vec![255, 128, 64, 32], // length 4 with varied values + vec![0, 1, 2, 3, 4, 5, 6], // length 7 + (0..10).collect(), // length 10 + (0..12).collect(), // length 12 + (0..16).collect(), // length 16 (full) ]; for test_data in test_vectors { @@ -397,10 +313,13 @@ mod test { if len <= 16 { let original = VariableList::::try_from(test_data.clone()).unwrap(); let u8_variant = VariableListU8::::try_from(test_data).unwrap(); - + assert_eq!(original.as_ssz_bytes(), u8_variant.as_ssz_bytes()); assert_eq!(original.ssz_bytes_len(), u8_variant.ssz_bytes_len()); - assert_eq!( as ssz::Encode>::is_ssz_fixed_len(), as ssz::Encode>::is_ssz_fixed_len()); + assert_eq!( + as ssz::Encode>::is_ssz_fixed_len(), + as ssz::Encode>::is_ssz_fixed_len() + ); } } } @@ -408,13 +327,13 @@ mod test { #[test] fn ssz_round_trip() { let test_vectors = vec![ - vec![], // empty - vec![1], // length 1 - vec![1, 2, 3], // length 3 (non-full) - vec![1, 2, 3, 4], // length 4 (non-full) - vec![255, 0, 128], // length 3 with edge values - vec![42; 5], // length 5 (non-full) - vec![42; 8], // length 8 (full) + vec![], // empty + vec![1], // length 1 + vec![1, 2, 3], // length 3 (non-full) + vec![1, 2, 3, 4], // length 4 (non-full) + vec![255, 0, 128], // length 3 with edge values + vec![42; 5], // length 5 (non-full) + vec![42; 8], // length 8 (full) ]; for test_data in test_vectors { @@ -425,52 +344,31 @@ mod test { let u8_encoded = u8_variant.as_ssz_bytes(); assert_eq!(original_encoded, u8_encoded); - let original_decoded = VariableList::::from_ssz_bytes(&original_encoded).unwrap(); + let original_decoded = + VariableList::::from_ssz_bytes(&original_encoded).unwrap(); let u8_decoded = VariableListU8::::from_ssz_bytes(&u8_encoded).unwrap(); - + assert_eq!(&original_decoded[..], &u8_decoded[..]); assert_eq!(original, original_decoded); assert_eq!(u8_variant, u8_decoded); } } - #[test] - fn empty_list_handling() { - let original_empty = VariableList::::default(); - let u8_empty = VariableListU8::::default(); - - assert_eq!(original_empty.len(), u8_empty.len()); - assert_eq!(original_empty.is_empty(), u8_empty.is_empty()); - assert_eq!(&original_empty[..], &u8_empty[..]); - - // Test SSZ encoding of empty lists - let original_bytes = original_empty.as_ssz_bytes(); - let u8_bytes = u8_empty.as_ssz_bytes(); - assert_eq!(original_bytes, u8_bytes); - assert!(original_bytes.is_empty()); - - // Test decoding empty lists - let original_decoded = VariableList::::from_ssz_bytes(&[]).unwrap(); - let u8_decoded = VariableListU8::::from_ssz_bytes(&[]).unwrap(); - assert_eq!(original_decoded, original_empty); - assert_eq!(u8_decoded, u8_empty); - } - #[test] fn tree_hash_consistency() { let test_vectors = vec![ (vec![], 0), (vec![0], 1), - (vec![0; 3], 8), // non-full length 3 - (vec![42; 5], 8), // non-full length 5 - (vec![0; 8], 8), // full length 8 - (vec![1, 2, 3], 8), // non-full with varied data - (vec![255; 7], 8), // non-full with max values - (vec![42; 10], 16), // non-full length 10 - (vec![42; 16], 16), // full length 16 - ((0..5).collect(), 16), // non-full ascending sequence - ((0..12).collect(), 16), // non-full longer sequence - ((0..16).collect(), 16), // full ascending sequence + (vec![0; 3], 8), // non-full length 3 + (vec![42; 5], 8), // non-full length 5 + (vec![0; 8], 8), // full length 8 + (vec![1, 2, 3], 8), // non-full with varied data + (vec![255; 7], 8), // non-full with max values + (vec![42; 10], 16), // non-full length 10 + (vec![42; 16], 16), // full length 16 + ((0..5).collect(), 16), // non-full ascending sequence + ((0..12).collect(), 16), // non-full longer sequence + ((0..16).collect(), 16), // full ascending sequence ]; for (test_data, max_len) in test_vectors { @@ -500,43 +398,6 @@ mod test { } } - #[test] - fn hash_and_equality() { - let test_data1 = vec![3; 8]; - let test_data2 = vec![4; 8]; - - let original1 = VariableList::::try_from(test_data1.clone()).unwrap(); - let original2 = VariableList::::try_from(test_data2.clone()).unwrap(); - let u8_variant1 = VariableListU8::::try_from(test_data1).unwrap(); - let u8_variant2 = VariableListU8::::try_from(test_data2).unwrap(); - - // Test equality - assert_eq!(original1 == original2, u8_variant1 == u8_variant2); - assert_ne!(original1, original2); - assert_ne!(u8_variant1, u8_variant2); - - // Test equal cases - let test_data3 = vec![3; 8]; - let original3 = VariableList::::try_from(test_data3.clone()).unwrap(); - let u8_variant3 = VariableListU8::::try_from(test_data3).unwrap(); - assert_eq!(original1, original3); - assert_eq!(u8_variant1, u8_variant3); - - // Test hash behavior - let mut original_set = HashSet::new(); - let mut u8_set = HashSet::new(); - - assert!(original_set.insert(original1.clone())); - assert!(u8_set.insert(u8_variant1.clone())); - assert!(!original_set.insert(original1.clone())); - assert!(!u8_set.insert(u8_variant1.clone())); - - assert!(original_set.contains(&original1)); - assert!(u8_set.contains(&u8_variant1)); - - assert_eq!(original_set.len(), u8_set.len()); - } - #[test] fn serde_behavior() { // Test successful deserialization with various lengths @@ -546,9 +407,10 @@ mod test { serde_json::json!([1, 2, 3]), serde_json::json!([1, 2, 3, 4]), ] { - let original_result: Result, _> = serde_json::from_value(json_data.clone()); + let original_result: Result, _> = + serde_json::from_value(json_data.clone()); let u8_result: Result, _> = serde_json::from_value(json_data); - + match (original_result, u8_result) { (Ok(original), Ok(u8_variant)) => { assert_eq!(&original[..], &u8_variant[..]); @@ -560,146 +422,11 @@ mod test { // Test failed deserialization - too long let json_too_long = serde_json::json!([1, 2, 3, 4, 5]); - let original_result: Result, _> = serde_json::from_value(json_too_long.clone()); + let original_result: Result, _> = + serde_json::from_value(json_too_long.clone()); let u8_result: Result, _> = serde_json::from_value(json_too_long); - + assert!(original_result.is_err()); assert!(u8_result.is_err()); } - - #[test] - fn try_from_iter() { - let test_vectors = vec![ - vec![], - vec![1, 2, 3], - vec![1, 2, 3, 4], - vec![1, 2, 3, 4, 5], // Should fail for max_len 4 - ]; - - for data in test_vectors { - let original_result = VariableList::::try_from_iter(data.clone()); - let u8_result = VariableListU8::::try_from_iter(data); - - match (original_result, u8_result) { - (Ok(original), Ok(u8_variant)) => { - assert_eq!(&original[..], &u8_variant[..]); - } - (Err(original_err), Err(u8_err)) => { - assert_eq!(original_err, u8_err); - } - _ => panic!("Both should succeed or both should fail"), - } - } - } - - #[test] - fn large_list_pre_allocation() { - use std::iter; - use typenum::U1099511627776; - - struct WonkyIterator { - hint: usize, - iter: I, - } - - impl Iterator for WonkyIterator - where - I: Iterator, - { - type Item = I::Item; - - fn next(&mut self) -> Option { - self.iter.next() - } - - fn size_hint(&self) -> (usize, Option) { - (0, Some(self.hint)) - } - } - - type N = U1099511627776; - - let iter = iter::repeat(1).take(5); - let wonky_iter = WonkyIterator { - hint: N::to_usize() / 2, - iter: iter.clone(), - }; - - let original_result = VariableList::::try_from_iter(iter); - let u8_result = VariableListU8::::try_from_iter(wonky_iter); - - assert!(original_result.is_ok()); - assert!(u8_result.is_ok()); - assert_eq!(&original_result.unwrap()[..], &u8_result.unwrap()[..]); - } - - #[test] - fn max_len_property() { - assert_eq!(VariableList::::max_len(), VariableListU8::::max_len()); - assert_eq!(VariableList::::max_len(), VariableListU8::::max_len()); - assert_eq!(VariableList::::max_len(), VariableListU8::::max_len()); - } - - #[test] - fn non_full_lists_comprehensive() { - // Test a variety of non-full lists with different capacities and sizes - let test_cases = vec![ - // (data, max_capacity) - (vec![1], 4), - (vec![1, 2], 4), - (vec![1, 2, 3], 4), - (vec![255], 8), - (vec![0, 128, 255], 8), - (vec![42; 5], 8), - (vec![1, 2, 3, 4, 5, 6, 7], 8), - (vec![100; 3], 16), - ((0..7).collect(), 16), - ((10..20).collect(), 16), - (vec![0, 255, 0, 255, 0], 16), - ]; - - for (test_data, max_cap) in test_cases { - match max_cap { - 4 => { - let original = VariableList::::try_from(test_data.clone()).unwrap(); - let u8_variant = VariableListU8::::try_from(test_data.clone()).unwrap(); - - // Verify the list is not at max capacity - assert!(original.len() < VariableList::::max_len(), - "Test data should be non-full: len={}, max={}", - original.len(), VariableList::::max_len()); - - // Test all behaviors are equivalent - assert_eq!(&original[..], &u8_variant[..]); - assert_eq!(original.len(), u8_variant.len()); - assert_eq!(original.is_empty(), u8_variant.is_empty()); - assert_eq!(original.tree_hash_root(), u8_variant.tree_hash_root()); - assert_eq!(original.as_ssz_bytes(), u8_variant.as_ssz_bytes()); - } - 8 => { - let original = VariableList::::try_from(test_data.clone()).unwrap(); - let u8_variant = VariableListU8::::try_from(test_data.clone()).unwrap(); - - assert!(original.len() < VariableList::::max_len()); - assert_eq!(&original[..], &u8_variant[..]); - assert_eq!(original.len(), u8_variant.len()); - assert_eq!(original.is_empty(), u8_variant.is_empty()); - assert_eq!(original.tree_hash_root(), u8_variant.tree_hash_root()); - assert_eq!(original.as_ssz_bytes(), u8_variant.as_ssz_bytes()); - } - 16 => { - let original = VariableList::::try_from(test_data.clone()).unwrap(); - let u8_variant = VariableListU8::::try_from(test_data.clone()).unwrap(); - - assert!(original.len() < VariableList::::max_len()); - assert_eq!(&original[..], &u8_variant[..]); - assert_eq!(original.len(), u8_variant.len()); - assert_eq!(original.is_empty(), u8_variant.is_empty()); - assert_eq!(original.tree_hash_root(), u8_variant.tree_hash_root()); - assert_eq!(original.as_ssz_bytes(), u8_variant.as_ssz_bytes()); - } - _ => {} - } - } - } } From a3f237d429c43d8c6137cf5a341334b52909a233 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 28 Aug 2025 15:37:11 +1000 Subject: [PATCH 6/8] Add more tree hash tests, use `vec_tree_hash_root` --- src/fixed_vector_u8.rs | 73 +++++++++++++++++++++++++-- src/variable_list_u8.rs | 107 +++++++++++++++++++++++++++++++--------- 2 files changed, 153 insertions(+), 27 deletions(-) diff --git a/src/fixed_vector_u8.rs b/src/fixed_vector_u8.rs index 2e9a848..14569a5 100644 --- a/src/fixed_vector_u8.rs +++ b/src/fixed_vector_u8.rs @@ -1,9 +1,10 @@ +use crate::tree_hash::vec_tree_hash_root; use crate::{Error, FixedVector}; use serde::Deserialize; use serde_derive::Serialize; use std::ops::{Deref, DerefMut, Index, IndexMut}; use std::slice::SliceIndex; -use tree_hash::{merkle_root, Hash256}; +use tree_hash::Hash256; use typenum::Unsigned; pub use typenum; @@ -136,7 +137,7 @@ impl tree_hash::TreeHash for FixedVectorU8 { } fn tree_hash_root(&self) -> Hash256 { - merkle_root(self, 0) + vec_tree_hash_root::(&self.inner, N::to_usize()) } } @@ -330,7 +331,23 @@ mod test { #[test] fn tree_hash_consistency() { - let test_vectors = vec![vec![], vec![0], vec![0; 8], vec![42; 16], (0..16).collect()]; + // Tree hashing uses 32-byte leaves, so test around these boundaries + let test_vectors = vec![ + vec![], // 0 bytes + vec![0], // 1 byte + vec![0; 8], // 8 bytes + vec![0; 16], // 16 bytes + vec![0; 31], // 31 bytes (just under 32) + vec![0; 32], // 32 bytes (exactly one leaf) + vec![0; 33], // 33 bytes (just over one leaf) + vec![42; 63], // 63 bytes (just under 2 leaves) + vec![42; 64], // 64 bytes (exactly 2 leaves) + vec![42; 65], // 65 bytes (just over 2 leaves) + vec![255; 95], // 95 bytes (just under 3 leaves) + vec![255; 96], // 96 bytes (exactly 3 leaves) + vec![128; 128], // 128 bytes (exactly 4 leaves) + (0..160).map(|i| (i % 256) as u8).collect(), // 160 bytes (5 leaves) + ]; for test_data in test_vectors { let len = test_data.len(); @@ -355,6 +372,56 @@ mod test { let u8_variant = FixedVectorU8::::try_from(test_data).unwrap(); assert_eq!(original.tree_hash_root(), u8_variant.tree_hash_root()); } + 31 => { + let original = FixedVector::::try_from(test_data.clone()).unwrap(); + let u8_variant = FixedVectorU8::::try_from(test_data).unwrap(); + assert_eq!(original.tree_hash_root(), u8_variant.tree_hash_root()); + } + 32 => { + let original = FixedVector::::try_from(test_data.clone()).unwrap(); + let u8_variant = FixedVectorU8::::try_from(test_data).unwrap(); + assert_eq!(original.tree_hash_root(), u8_variant.tree_hash_root()); + } + 33 => { + let original = FixedVector::::try_from(test_data.clone()).unwrap(); + let u8_variant = FixedVectorU8::::try_from(test_data).unwrap(); + assert_eq!(original.tree_hash_root(), u8_variant.tree_hash_root()); + } + 63 => { + let original = FixedVector::::try_from(test_data.clone()).unwrap(); + let u8_variant = FixedVectorU8::::try_from(test_data).unwrap(); + assert_eq!(original.tree_hash_root(), u8_variant.tree_hash_root()); + } + 64 => { + let original = FixedVector::::try_from(test_data.clone()).unwrap(); + let u8_variant = FixedVectorU8::::try_from(test_data).unwrap(); + assert_eq!(original.tree_hash_root(), u8_variant.tree_hash_root()); + } + 65 => { + let original = FixedVector::::try_from(test_data.clone()).unwrap(); + let u8_variant = FixedVectorU8::::try_from(test_data).unwrap(); + assert_eq!(original.tree_hash_root(), u8_variant.tree_hash_root()); + } + 95 => { + let original = FixedVector::::try_from(test_data.clone()).unwrap(); + let u8_variant = FixedVectorU8::::try_from(test_data).unwrap(); + assert_eq!(original.tree_hash_root(), u8_variant.tree_hash_root()); + } + 96 => { + let original = FixedVector::::try_from(test_data.clone()).unwrap(); + let u8_variant = FixedVectorU8::::try_from(test_data).unwrap(); + assert_eq!(original.tree_hash_root(), u8_variant.tree_hash_root()); + } + 128 => { + let original = FixedVector::::try_from(test_data.clone()).unwrap(); + let u8_variant = FixedVectorU8::::try_from(test_data).unwrap(); + assert_eq!(original.tree_hash_root(), u8_variant.tree_hash_root()); + } + 160 => { + let original = FixedVector::::try_from(test_data.clone()).unwrap(); + let u8_variant = FixedVectorU8::::try_from(test_data).unwrap(); + assert_eq!(original.tree_hash_root(), u8_variant.tree_hash_root()); + } _ => {} } } diff --git a/src/variable_list_u8.rs b/src/variable_list_u8.rs index 73b3c52..df4003b 100644 --- a/src/variable_list_u8.rs +++ b/src/variable_list_u8.rs @@ -1,9 +1,10 @@ +use crate::tree_hash::vec_tree_hash_root; use crate::{Error, VariableList}; use serde::Deserialize; use serde_derive::Serialize; use std::ops::{Deref, DerefMut, Index, IndexMut}; use std::slice::SliceIndex; -use tree_hash::{merkle_root, Hash256}; +use tree_hash::Hash256; use typenum::Unsigned; pub use typenum; @@ -140,7 +141,7 @@ impl tree_hash::TreeHash for VariableListU8 { } fn tree_hash_root(&self) -> Hash256 { - let root = merkle_root(self, 0); + let root = vec_tree_hash_root::(&self.inner, N::to_usize()); tree_hash::mix_in_length(&root, self.len()) } } @@ -356,36 +357,64 @@ mod test { #[test] fn tree_hash_consistency() { + // Tree hashing uses 32-byte leaves, so test around these boundaries + // Format: (test_data, max_capacity) let test_vectors = vec![ - (vec![], 0), - (vec![0], 1), - (vec![0; 3], 8), // non-full length 3 - (vec![42; 5], 8), // non-full length 5 - (vec![0; 8], 8), // full length 8 - (vec![1, 2, 3], 8), // non-full with varied data - (vec![255; 7], 8), // non-full with max values - (vec![42; 10], 16), // non-full length 10 - (vec![42; 16], 16), // full length 16 - ((0..5).collect(), 16), // non-full ascending sequence - ((0..12).collect(), 16), // non-full longer sequence - ((0..16).collect(), 16), // full ascending sequence + // Small sizes + (vec![], 1), // 0 bytes, max 1 + (vec![0], 2), // 1 byte, max 2 + (vec![0; 8], 16), // 8 bytes, max 16 + (vec![0; 16], 32), // 16 bytes, max 32 + + // Around 32-byte boundary (1 leaf) + (vec![42; 30], 64), // 30 bytes (under 1 leaf) + (vec![42; 31], 64), // 31 bytes (just under 1 leaf) + (vec![42; 32], 64), // 32 bytes (exactly 1 leaf) + (vec![255; 33], 64), // 33 bytes (just over 1 leaf) + (vec![128; 35], 64), // 35 bytes (over 1 leaf) + + // Around 64-byte boundary (2 leaves) + (vec![1; 62], 96), // 62 bytes (under 2 leaves) + (vec![2; 63], 96), // 63 bytes (just under 2 leaves) + (vec![3; 64], 96), // 64 bytes (exactly 2 leaves) + (vec![4; 65], 96), // 65 bytes (just over 2 leaves) + (vec![5; 67], 96), // 67 bytes (over 2 leaves) + + // Around 96-byte boundary (3 leaves) + ((0..94).map(|i| (i % 256) as u8).collect(), 128), // 94 bytes (under 3 leaves) + ((0..95).map(|i| (i % 256) as u8).collect(), 128), // 95 bytes (just under 3 leaves) + ((0..96).map(|i| (i % 256) as u8).collect(), 128), // 96 bytes (exactly 3 leaves) + ((0..97).map(|i| (i % 256) as u8).collect(), 128), // 97 bytes (just over 3 leaves) + ((0..99).map(|i| (i % 256) as u8).collect(), 128), // 99 bytes (over 3 leaves) + + // Around 128-byte boundary (4 leaves) + (vec![200; 126], 160), // 126 bytes (under 4 leaves) + (vec![201; 127], 160), // 127 bytes (just under 4 leaves) + (vec![202; 128], 160), // 128 bytes (exactly 4 leaves) + (vec![203; 129], 160), // 129 bytes (just over 4 leaves) + ((100..230).map(|i| (i % 256) as u8).collect(), 160), // 130 bytes (over 4 leaves) + + // Larger boundaries - 160 bytes (5 leaves) + ((0..158).map(|i| (i % 256) as u8).collect(), 192), // 158 bytes (under 5 leaves) + ((0..160).map(|i| (i % 256) as u8).collect(), 192), // 160 bytes (exactly 5 leaves) + ((0..162).map(|i| (i % 256) as u8).collect(), 192), // 162 bytes (over 5 leaves) + + // Test non-full at various capacities + (vec![42; 3], 64), // small non-full + (vec![99; 50], 128), // mid-size non-full + (vec![77; 100], 192), // larger non-full ]; - for (test_data, max_len) in test_vectors { - match max_len { - 0 => { - let original = VariableList::::try_from(test_data.clone()).unwrap(); - let u8_variant = VariableListU8::::try_from(test_data).unwrap(); - assert_eq!(original.tree_hash_root(), u8_variant.tree_hash_root()); - } + for (test_data, max_cap) in test_vectors { + match max_cap { 1 => { let original = VariableList::::try_from(test_data.clone()).unwrap(); let u8_variant = VariableListU8::::try_from(test_data).unwrap(); assert_eq!(original.tree_hash_root(), u8_variant.tree_hash_root()); } - 8 => { - let original = VariableList::::try_from(test_data.clone()).unwrap(); - let u8_variant = VariableListU8::::try_from(test_data).unwrap(); + 2 => { + let original = VariableList::::try_from(test_data.clone()).unwrap(); + let u8_variant = VariableListU8::::try_from(test_data).unwrap(); assert_eq!(original.tree_hash_root(), u8_variant.tree_hash_root()); } 16 => { @@ -393,6 +422,36 @@ mod test { let u8_variant = VariableListU8::::try_from(test_data).unwrap(); assert_eq!(original.tree_hash_root(), u8_variant.tree_hash_root()); } + 32 => { + let original = VariableList::::try_from(test_data.clone()).unwrap(); + let u8_variant = VariableListU8::::try_from(test_data).unwrap(); + assert_eq!(original.tree_hash_root(), u8_variant.tree_hash_root()); + } + 64 => { + let original = VariableList::::try_from(test_data.clone()).unwrap(); + let u8_variant = VariableListU8::::try_from(test_data).unwrap(); + assert_eq!(original.tree_hash_root(), u8_variant.tree_hash_root()); + } + 96 => { + let original = VariableList::::try_from(test_data.clone()).unwrap(); + let u8_variant = VariableListU8::::try_from(test_data).unwrap(); + assert_eq!(original.tree_hash_root(), u8_variant.tree_hash_root()); + } + 128 => { + let original = VariableList::::try_from(test_data.clone()).unwrap(); + let u8_variant = VariableListU8::::try_from(test_data).unwrap(); + assert_eq!(original.tree_hash_root(), u8_variant.tree_hash_root()); + } + 160 => { + let original = VariableList::::try_from(test_data.clone()).unwrap(); + let u8_variant = VariableListU8::::try_from(test_data).unwrap(); + assert_eq!(original.tree_hash_root(), u8_variant.tree_hash_root()); + } + 192 => { + let original = VariableList::::try_from(test_data.clone()).unwrap(); + let u8_variant = VariableListU8::::try_from(test_data).unwrap(); + assert_eq!(original.tree_hash_root(), u8_variant.tree_hash_root()); + } _ => {} } } From 1effb0f75c6e77eb112558ce108b3e133fa52bed Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 28 Aug 2025 15:42:42 +1000 Subject: [PATCH 7/8] Remove unused dep in test --- src/fixed_vector_u8.rs | 27 +++++++++++++-------------- src/variable_list_u8.rs | 22 ++++++++-------------- 2 files changed, 21 insertions(+), 28 deletions(-) diff --git a/src/fixed_vector_u8.rs b/src/fixed_vector_u8.rs index 14569a5..bd0c1f8 100644 --- a/src/fixed_vector_u8.rs +++ b/src/fixed_vector_u8.rs @@ -212,7 +212,6 @@ impl<'a, N: 'static + Unsigned> arbitrary::Arbitrary<'a> for FixedVectorU8 { mod test { use super::*; use ssz::*; - use std::collections::HashSet; use tree_hash::TreeHash; use typenum::*; @@ -333,19 +332,19 @@ mod test { fn tree_hash_consistency() { // Tree hashing uses 32-byte leaves, so test around these boundaries let test_vectors = vec![ - vec![], // 0 bytes - vec![0], // 1 byte - vec![0; 8], // 8 bytes - vec![0; 16], // 16 bytes - vec![0; 31], // 31 bytes (just under 32) - vec![0; 32], // 32 bytes (exactly one leaf) - vec![0; 33], // 33 bytes (just over one leaf) - vec![42; 63], // 63 bytes (just under 2 leaves) - vec![42; 64], // 64 bytes (exactly 2 leaves) - vec![42; 65], // 65 bytes (just over 2 leaves) - vec![255; 95], // 95 bytes (just under 3 leaves) - vec![255; 96], // 96 bytes (exactly 3 leaves) - vec![128; 128], // 128 bytes (exactly 4 leaves) + vec![], // 0 bytes + vec![0], // 1 byte + vec![0; 8], // 8 bytes + vec![0; 16], // 16 bytes + vec![0; 31], // 31 bytes (just under 32) + vec![0; 32], // 32 bytes (exactly one leaf) + vec![0; 33], // 33 bytes (just over one leaf) + vec![42; 63], // 63 bytes (just under 2 leaves) + vec![42; 64], // 64 bytes (exactly 2 leaves) + vec![42; 65], // 65 bytes (just over 2 leaves) + vec![255; 95], // 95 bytes (just under 3 leaves) + vec![255; 96], // 96 bytes (exactly 3 leaves) + vec![128; 128], // 128 bytes (exactly 4 leaves) (0..160).map(|i| (i % 256) as u8).collect(), // 160 bytes (5 leaves) ]; diff --git a/src/variable_list_u8.rs b/src/variable_list_u8.rs index df4003b..7845b73 100644 --- a/src/variable_list_u8.rs +++ b/src/variable_list_u8.rs @@ -361,47 +361,41 @@ mod test { // Format: (test_data, max_capacity) let test_vectors = vec![ // Small sizes - (vec![], 1), // 0 bytes, max 1 - (vec![0], 2), // 1 byte, max 2 - (vec![0; 8], 16), // 8 bytes, max 16 + (vec![], 1), // 0 bytes, max 1 + (vec![0], 2), // 1 byte, max 2 + (vec![0; 8], 16), // 8 bytes, max 16 (vec![0; 16], 32), // 16 bytes, max 32 - // Around 32-byte boundary (1 leaf) - (vec![42; 30], 64), // 30 bytes (under 1 leaf) - (vec![42; 31], 64), // 31 bytes (just under 1 leaf) - (vec![42; 32], 64), // 32 bytes (exactly 1 leaf) + (vec![42; 30], 64), // 30 bytes (under 1 leaf) + (vec![42; 31], 64), // 31 bytes (just under 1 leaf) + (vec![42; 32], 64), // 32 bytes (exactly 1 leaf) (vec![255; 33], 64), // 33 bytes (just over 1 leaf) (vec![128; 35], 64), // 35 bytes (over 1 leaf) - // Around 64-byte boundary (2 leaves) (vec![1; 62], 96), // 62 bytes (under 2 leaves) (vec![2; 63], 96), // 63 bytes (just under 2 leaves) (vec![3; 64], 96), // 64 bytes (exactly 2 leaves) (vec![4; 65], 96), // 65 bytes (just over 2 leaves) (vec![5; 67], 96), // 67 bytes (over 2 leaves) - // Around 96-byte boundary (3 leaves) ((0..94).map(|i| (i % 256) as u8).collect(), 128), // 94 bytes (under 3 leaves) ((0..95).map(|i| (i % 256) as u8).collect(), 128), // 95 bytes (just under 3 leaves) ((0..96).map(|i| (i % 256) as u8).collect(), 128), // 96 bytes (exactly 3 leaves) ((0..97).map(|i| (i % 256) as u8).collect(), 128), // 97 bytes (just over 3 leaves) ((0..99).map(|i| (i % 256) as u8).collect(), 128), // 99 bytes (over 3 leaves) - // Around 128-byte boundary (4 leaves) (vec![200; 126], 160), // 126 bytes (under 4 leaves) (vec![201; 127], 160), // 127 bytes (just under 4 leaves) (vec![202; 128], 160), // 128 bytes (exactly 4 leaves) (vec![203; 129], 160), // 129 bytes (just over 4 leaves) ((100..230).map(|i| (i % 256) as u8).collect(), 160), // 130 bytes (over 4 leaves) - // Larger boundaries - 160 bytes (5 leaves) ((0..158).map(|i| (i % 256) as u8).collect(), 192), // 158 bytes (under 5 leaves) ((0..160).map(|i| (i % 256) as u8).collect(), 192), // 160 bytes (exactly 5 leaves) ((0..162).map(|i| (i % 256) as u8).collect(), 192), // 162 bytes (over 5 leaves) - // Test non-full at various capacities - (vec![42; 3], 64), // small non-full - (vec![99; 50], 128), // mid-size non-full + (vec![42; 3], 64), // small non-full + (vec![99; 50], 128), // mid-size non-full (vec![77; 100], 192), // larger non-full ]; From 5ab74947724ba9b401b5e119a84600530fd4193d Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 28 Aug 2025 15:48:38 +1000 Subject: [PATCH 8/8] Use `merkle_root` directly --- src/fixed_vector_u8.rs | 5 ++--- src/variable_list_u8.rs | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/fixed_vector_u8.rs b/src/fixed_vector_u8.rs index bd0c1f8..dd77fd8 100644 --- a/src/fixed_vector_u8.rs +++ b/src/fixed_vector_u8.rs @@ -1,10 +1,9 @@ -use crate::tree_hash::vec_tree_hash_root; use crate::{Error, FixedVector}; use serde::Deserialize; use serde_derive::Serialize; use std::ops::{Deref, DerefMut, Index, IndexMut}; use std::slice::SliceIndex; -use tree_hash::Hash256; +use tree_hash::{merkle_root, Hash256, HASHSIZE}; use typenum::Unsigned; pub use typenum; @@ -137,7 +136,7 @@ impl tree_hash::TreeHash for FixedVectorU8 { } fn tree_hash_root(&self) -> Hash256 { - vec_tree_hash_root::(&self.inner, N::to_usize()) + merkle_root(self, N::to_usize().div_ceil(HASHSIZE)) } } diff --git a/src/variable_list_u8.rs b/src/variable_list_u8.rs index 7845b73..e3530d2 100644 --- a/src/variable_list_u8.rs +++ b/src/variable_list_u8.rs @@ -1,10 +1,9 @@ -use crate::tree_hash::vec_tree_hash_root; use crate::{Error, VariableList}; use serde::Deserialize; use serde_derive::Serialize; use std::ops::{Deref, DerefMut, Index, IndexMut}; use std::slice::SliceIndex; -use tree_hash::Hash256; +use tree_hash::{merkle_root, Hash256, HASHSIZE}; use typenum::Unsigned; pub use typenum; @@ -141,7 +140,7 @@ impl tree_hash::TreeHash for VariableListU8 { } fn tree_hash_root(&self) -> Hash256 { - let root = vec_tree_hash_root::(&self.inner, N::to_usize()); + let root = merkle_root(self, N::to_usize().div_ceil(HASHSIZE)); tree_hash::mix_in_length(&root, self.len()) } }