mirror of
https://github.com/sigp/lighthouse.git
synced 2026-05-07 16:55:46 +00:00
Directory Restructure (#1163)
* Move tests -> testing * Directory restructure * Update Cargo.toml during restructure * Update Makefile during restructure * Fix arbitrary path
This commit is contained in:
21
consensus/ssz_types/Cargo.toml
Normal file
21
consensus/ssz_types/Cargo.toml
Normal file
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "eth2_ssz_types"
|
||||
version = "0.2.0"
|
||||
authors = ["Paul Hauner <paul@paulhauner.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
name = "ssz_types"
|
||||
|
||||
[dependencies]
|
||||
tree_hash = "0.1.0"
|
||||
serde = "1.0.110"
|
||||
serde_derive = "1.0.110"
|
||||
serde_hex = { path = "../serde_hex" }
|
||||
eth2_ssz = "0.1.2"
|
||||
typenum = "1.12.0"
|
||||
arbitrary = { version = "0.4.4", features = ["derive"], optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
serde_yaml = "0.8.11"
|
||||
tree_hash_derive = "0.2.0"
|
||||
1218
consensus/ssz_types/src/bitfield.rs
Normal file
1218
consensus/ssz_types/src/bitfield.rs
Normal file
File diff suppressed because it is too large
Load Diff
429
consensus/ssz_types/src/fixed_vector.rs
Normal file
429
consensus/ssz_types/src/fixed_vector.rs
Normal file
@@ -0,0 +1,429 @@
|
||||
use crate::tree_hash::vec_tree_hash_root;
|
||||
use crate::Error;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::{Deref, Index, IndexMut};
|
||||
use std::slice::SliceIndex;
|
||||
use tree_hash::Hash256;
|
||||
use typenum::Unsigned;
|
||||
|
||||
pub use typenum;
|
||||
|
||||
/// Emulates a SSZ `Vector` (distinct from a Rust `Vec`).
|
||||
///
|
||||
/// An ordered, heap-allocated, fixed-length, homogeneous collection of `T`, with `N` values.
|
||||
///
|
||||
/// This struct is backed by a Rust `Vec` but constrained such that it must be instantiated with a
|
||||
/// fixed number of elements and you may not add or remove elements, only modify.
|
||||
///
|
||||
/// The length of this struct is fixed at the type-level using
|
||||
/// [typenum](https://crates.io/crates/typenum).
|
||||
///
|
||||
/// ## Note
|
||||
///
|
||||
/// Whilst it is possible with this library, SSZ declares that a `FixedVector` with a length of `0`
|
||||
/// is illegal.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```
|
||||
/// use ssz_types::{FixedVector, typenum};
|
||||
///
|
||||
/// let base: Vec<u64> = vec![1, 2, 3, 4];
|
||||
///
|
||||
/// // Create a `FixedVector` from a `Vec` that has the expected length.
|
||||
/// let exact: FixedVector<_, typenum::U4> = FixedVector::from(base.clone());
|
||||
/// assert_eq!(&exact[..], &[1, 2, 3, 4]);
|
||||
///
|
||||
/// // Create a `FixedVector` from a `Vec` that is too long and the `Vec` is truncated.
|
||||
/// let short: FixedVector<_, typenum::U3> = FixedVector::from(base.clone());
|
||||
/// assert_eq!(&short[..], &[1, 2, 3]);
|
||||
///
|
||||
/// // Create a `FixedVector` from a `Vec` that is too short and the missing values are created
|
||||
/// // using `std::default::Default`.
|
||||
/// let long: FixedVector<_, typenum::U5> = FixedVector::from(base);
|
||||
/// assert_eq!(&long[..], &[1, 2, 3, 4, 0]);
|
||||
/// ```
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct FixedVector<T, N> {
|
||||
vec: Vec<T>,
|
||||
_phantom: PhantomData<N>,
|
||||
}
|
||||
|
||||
impl<T, N: Unsigned> FixedVector<T, N> {
|
||||
/// Returns `Ok` if the given `vec` equals the fixed length of `Self`. Otherwise returns
|
||||
/// `Err`.
|
||||
pub fn new(vec: Vec<T>) -> Result<Self, Error> {
|
||||
if vec.len() == Self::capacity() {
|
||||
Ok(Self {
|
||||
vec,
|
||||
_phantom: PhantomData,
|
||||
})
|
||||
} else {
|
||||
Err(Error::OutOfBounds {
|
||||
i: vec.len(),
|
||||
len: Self::capacity(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new vector filled with clones of `elem`.
|
||||
pub fn from_elem(elem: T) -> Self
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
Self {
|
||||
vec: vec![elem; N::to_usize()],
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Identical to `self.capacity`, returns the type-level constant length.
|
||||
///
|
||||
/// Exists for compatibility with `Vec`.
|
||||
pub fn len(&self) -> usize {
|
||||
self.vec.len()
|
||||
}
|
||||
|
||||
/// True if the type-level constant length of `self` is zero.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
/// Returns the type-level constant length.
|
||||
pub fn capacity() -> usize {
|
||||
N::to_usize()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Default, N: Unsigned> From<Vec<T>> for FixedVector<T, N> {
|
||||
fn from(mut vec: Vec<T>) -> Self {
|
||||
vec.resize_with(Self::capacity(), Default::default);
|
||||
|
||||
Self {
|
||||
vec,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, N: Unsigned> Into<Vec<T>> for FixedVector<T, N> {
|
||||
fn into(self) -> Vec<T> {
|
||||
self.vec
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, N: Unsigned> Default for FixedVector<T, N> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
vec: Vec::default(),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, N: Unsigned, I: SliceIndex<[T]>> Index<I> for FixedVector<T, N> {
|
||||
type Output = I::Output;
|
||||
|
||||
#[inline]
|
||||
fn index(&self, index: I) -> &Self::Output {
|
||||
Index::index(&self.vec, index)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, N: Unsigned, I: SliceIndex<[T]>> IndexMut<I> for FixedVector<T, N> {
|
||||
#[inline]
|
||||
fn index_mut(&mut self, index: I) -> &mut Self::Output {
|
||||
IndexMut::index_mut(&mut self.vec, index)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, N: Unsigned> Deref for FixedVector<T, N> {
|
||||
type Target = [T];
|
||||
|
||||
fn deref(&self) -> &[T] {
|
||||
&self.vec[..]
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, N: Unsigned> tree_hash::TreeHash for FixedVector<T, N>
|
||||
where
|
||||
T: tree_hash::TreeHash,
|
||||
{
|
||||
fn tree_hash_type() -> tree_hash::TreeHashType {
|
||||
tree_hash::TreeHashType::Vector
|
||||
}
|
||||
|
||||
fn tree_hash_packed_encoding(&self) -> Vec<u8> {
|
||||
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::<T, N>(&self.vec)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, N: Unsigned> ssz::Encode for FixedVector<T, N>
|
||||
where
|
||||
T: ssz::Encode,
|
||||
{
|
||||
fn is_ssz_fixed_len() -> bool {
|
||||
T::is_ssz_fixed_len()
|
||||
}
|
||||
|
||||
fn ssz_fixed_len() -> usize {
|
||||
if <Self as ssz::Encode>::is_ssz_fixed_len() {
|
||||
T::ssz_fixed_len() * N::to_usize()
|
||||
} else {
|
||||
ssz::BYTES_PER_LENGTH_OFFSET
|
||||
}
|
||||
}
|
||||
|
||||
fn ssz_bytes_len(&self) -> usize {
|
||||
self.vec.ssz_bytes_len()
|
||||
}
|
||||
|
||||
fn ssz_append(&self, buf: &mut Vec<u8>) {
|
||||
if T::is_ssz_fixed_len() {
|
||||
buf.reserve(T::ssz_fixed_len() * self.len());
|
||||
|
||||
for item in &self.vec {
|
||||
item.ssz_append(buf);
|
||||
}
|
||||
} else {
|
||||
let mut encoder = ssz::SszEncoder::list(buf, self.len() * ssz::BYTES_PER_LENGTH_OFFSET);
|
||||
|
||||
for item in &self.vec {
|
||||
encoder.append(item);
|
||||
}
|
||||
|
||||
encoder.finalize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, N: Unsigned> ssz::Decode for FixedVector<T, N>
|
||||
where
|
||||
T: ssz::Decode + Default,
|
||||
{
|
||||
fn is_ssz_fixed_len() -> bool {
|
||||
T::is_ssz_fixed_len()
|
||||
}
|
||||
|
||||
fn ssz_fixed_len() -> usize {
|
||||
if <Self as ssz::Decode>::is_ssz_fixed_len() {
|
||||
T::ssz_fixed_len() * N::to_usize()
|
||||
} else {
|
||||
ssz::BYTES_PER_LENGTH_OFFSET
|
||||
}
|
||||
}
|
||||
|
||||
fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
|
||||
let fixed_len = N::to_usize();
|
||||
|
||||
if bytes.is_empty() {
|
||||
Err(ssz::DecodeError::InvalidByteLength {
|
||||
len: 0,
|
||||
expected: 1,
|
||||
})
|
||||
} else if T::is_ssz_fixed_len() {
|
||||
let num_items = bytes
|
||||
.len()
|
||||
.checked_div(T::ssz_fixed_len())
|
||||
.ok_or_else(|| ssz::DecodeError::ZeroLengthItem)?;
|
||||
|
||||
if num_items != fixed_len {
|
||||
return Err(ssz::DecodeError::BytesInvalid(format!(
|
||||
"FixedVector of {} items has {} items",
|
||||
num_items, fixed_len
|
||||
)));
|
||||
}
|
||||
|
||||
bytes
|
||||
.chunks(T::ssz_fixed_len())
|
||||
.map(|chunk| T::from_ssz_bytes(chunk))
|
||||
.collect::<Result<Vec<T>, _>>()
|
||||
.and_then(|vec| {
|
||||
if vec.len() == fixed_len {
|
||||
Ok(vec.into())
|
||||
} else {
|
||||
Err(ssz::DecodeError::BytesInvalid(format!(
|
||||
"Wrong number of FixedVector elements, got: {}, expected: {}",
|
||||
vec.len(),
|
||||
N::to_usize()
|
||||
)))
|
||||
}
|
||||
})
|
||||
} else {
|
||||
ssz::decode_list_of_variable_length_items(bytes, Some(fixed_len))
|
||||
.and_then(|vec| Ok(vec.into()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "arbitrary")]
|
||||
impl<T: arbitrary::Arbitrary, N: 'static + Unsigned> arbitrary::Arbitrary for FixedVector<T, N> {
|
||||
fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
|
||||
let size = N::to_usize();
|
||||
let mut vec: Vec<T> = Vec::with_capacity(size);
|
||||
for _ in 0..size {
|
||||
vec.push(<T>::arbitrary(u)?);
|
||||
}
|
||||
Ok(Self::new(vec).map_err(|_| arbitrary::Error::IncorrectFormat)?)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use ssz::*;
|
||||
use tree_hash::{merkle_root, TreeHash};
|
||||
use tree_hash_derive::TreeHash;
|
||||
use typenum::*;
|
||||
|
||||
#[test]
|
||||
fn new() {
|
||||
let vec = vec![42; 5];
|
||||
let fixed: Result<FixedVector<u64, U4>, _> = FixedVector::new(vec);
|
||||
assert!(fixed.is_err());
|
||||
|
||||
let vec = vec![42; 3];
|
||||
let fixed: Result<FixedVector<u64, U4>, _> = FixedVector::new(vec);
|
||||
assert!(fixed.is_err());
|
||||
|
||||
let vec = vec![42; 4];
|
||||
let fixed: Result<FixedVector<u64, U4>, _> = FixedVector::new(vec);
|
||||
assert!(fixed.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn indexing() {
|
||||
let vec = vec![1, 2];
|
||||
|
||||
let mut fixed: FixedVector<u64, U8192> = vec.clone().into();
|
||||
|
||||
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 length() {
|
||||
let vec = vec![42; 5];
|
||||
let fixed: FixedVector<u64, U4> = FixedVector::from(vec.clone());
|
||||
assert_eq!(&fixed[..], &vec[0..4]);
|
||||
|
||||
let vec = vec![42; 3];
|
||||
let fixed: FixedVector<u64, U4> = FixedVector::from(vec.clone());
|
||||
assert_eq!(&fixed[0..3], &vec[..]);
|
||||
assert_eq!(&fixed[..], &vec![42, 42, 42, 0][..]);
|
||||
|
||||
let vec = vec![];
|
||||
let fixed: FixedVector<u64, U4> = FixedVector::from(vec);
|
||||
assert_eq!(&fixed[..], &vec![0, 0, 0, 0][..]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deref() {
|
||||
let vec = vec![0, 2, 4, 6];
|
||||
let fixed: FixedVector<u64, U4> = FixedVector::from(vec);
|
||||
|
||||
assert_eq!(fixed.get(0), Some(&0));
|
||||
assert_eq!(fixed.get(3), Some(&6));
|
||||
assert_eq!(fixed.get(4), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ssz_encode() {
|
||||
let vec: FixedVector<u16, U2> = vec![0; 2].into();
|
||||
assert_eq!(vec.as_ssz_bytes(), vec![0, 0, 0, 0]);
|
||||
assert_eq!(<FixedVector<u16, U2> as Encode>::ssz_fixed_len(), 4);
|
||||
}
|
||||
|
||||
fn ssz_round_trip<T: Encode + Decode + std::fmt::Debug + PartialEq>(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_u16_len_8() {
|
||||
ssz_round_trip::<FixedVector<u16, U8>>(vec![42; 8].into());
|
||||
ssz_round_trip::<FixedVector<u16, U8>>(vec![0; 8].into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tree_hash_u8() {
|
||||
let fixed: FixedVector<u8, U0> = FixedVector::from(vec![]);
|
||||
assert_eq!(fixed.tree_hash_root(), merkle_root(&[0; 8], 0));
|
||||
|
||||
let fixed: FixedVector<u8, U1> = FixedVector::from(vec![0; 1]);
|
||||
assert_eq!(fixed.tree_hash_root(), merkle_root(&[0; 8], 0));
|
||||
|
||||
let fixed: FixedVector<u8, U8> = FixedVector::from(vec![0; 8]);
|
||||
assert_eq!(fixed.tree_hash_root(), merkle_root(&[0; 8], 0));
|
||||
|
||||
let fixed: FixedVector<u8, U16> = FixedVector::from(vec![42; 16]);
|
||||
assert_eq!(fixed.tree_hash_root(), merkle_root(&[42; 16], 0));
|
||||
|
||||
let source: Vec<u8> = (0..16).collect();
|
||||
let fixed: FixedVector<u8, U16> = FixedVector::from(source.clone());
|
||||
assert_eq!(fixed.tree_hash_root(), merkle_root(&source, 0));
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, TreeHash, Default)]
|
||||
struct A {
|
||||
a: u32,
|
||||
b: u32,
|
||||
}
|
||||
|
||||
fn repeat(input: &[u8], n: usize) -> Vec<u8> {
|
||||
let mut output = vec![];
|
||||
|
||||
for _ in 0..n {
|
||||
output.append(&mut input.to_vec());
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tree_hash_composite() {
|
||||
let a = A { a: 0, b: 1 };
|
||||
|
||||
let fixed: FixedVector<A, U0> = FixedVector::from(vec![]);
|
||||
assert_eq!(fixed.tree_hash_root(), merkle_root(&[0; 32], 0));
|
||||
|
||||
let fixed: FixedVector<A, U1> = FixedVector::from(vec![a]);
|
||||
assert_eq!(
|
||||
fixed.tree_hash_root(),
|
||||
merkle_root(a.tree_hash_root().as_bytes(), 0)
|
||||
);
|
||||
|
||||
let fixed: FixedVector<A, U8> = FixedVector::from(vec![a; 8]);
|
||||
assert_eq!(
|
||||
fixed.tree_hash_root(),
|
||||
merkle_root(&repeat(a.tree_hash_root().as_bytes(), 8), 0)
|
||||
);
|
||||
|
||||
let fixed: FixedVector<A, U13> = FixedVector::from(vec![a; 13]);
|
||||
assert_eq!(
|
||||
fixed.tree_hash_root(),
|
||||
merkle_root(&repeat(a.tree_hash_root().as_bytes(), 13), 0)
|
||||
);
|
||||
|
||||
let fixed: FixedVector<A, U16> = FixedVector::from(vec![a; 16]);
|
||||
assert_eq!(
|
||||
fixed.tree_hash_root(),
|
||||
merkle_root(&repeat(a.tree_hash_root().as_bytes(), 16), 0)
|
||||
);
|
||||
}
|
||||
}
|
||||
71
consensus/ssz_types/src/lib.rs
Normal file
71
consensus/ssz_types/src/lib.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
//! Provides types with unique properties required for SSZ serialization and Merklization:
|
||||
//!
|
||||
//! - `FixedVector`: A heap-allocated list with a size that is fixed at compile time.
|
||||
//! - `VariableList`: A heap-allocated list that cannot grow past a type-level maximum length.
|
||||
//! - `BitList`: A heap-allocated bitfield that with a type-level _maximum_ length.
|
||||
//! - `BitVector`: A heap-allocated bitfield that with a type-level _fixed__ length.
|
||||
//!
|
||||
//! These structs are required as SSZ serialization and Merklization rely upon type-level lengths
|
||||
//! for padding and verification.
|
||||
//!
|
||||
//! Adheres to the Ethereum 2.0 [SSZ
|
||||
//! specification](https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/ssz/simple-serialize.md)
|
||||
//! at v0.11.1.
|
||||
//!
|
||||
//! ## Example
|
||||
//! ```
|
||||
//! use ssz_types::*;
|
||||
//!
|
||||
//! pub struct Example {
|
||||
//! bit_vector: BitVector<typenum::U8>,
|
||||
//! bit_list: BitList<typenum::U8>,
|
||||
//! variable_list: VariableList<u64, typenum::U8>,
|
||||
//! fixed_vector: FixedVector<u64, typenum::U8>,
|
||||
//! }
|
||||
//!
|
||||
//! let mut example = Example {
|
||||
//! bit_vector: Bitfield::new(),
|
||||
//! bit_list: Bitfield::with_capacity(4).unwrap(),
|
||||
//! variable_list: <_>::from(vec![0, 1]),
|
||||
//! fixed_vector: <_>::from(vec![2, 3]),
|
||||
//! };
|
||||
//!
|
||||
//! assert_eq!(example.bit_vector.len(), 8);
|
||||
//! assert_eq!(example.bit_list.len(), 4);
|
||||
//! assert_eq!(&example.variable_list[..], &[0, 1]);
|
||||
//! assert_eq!(&example.fixed_vector[..], &[2, 3, 0, 0, 0, 0, 0, 0]);
|
||||
//!
|
||||
//! ```
|
||||
|
||||
#[macro_use]
|
||||
mod bitfield;
|
||||
mod fixed_vector;
|
||||
mod tree_hash;
|
||||
mod variable_list;
|
||||
|
||||
pub use bitfield::{BitList, BitVector, Bitfield};
|
||||
pub use fixed_vector::FixedVector;
|
||||
pub use typenum;
|
||||
pub use variable_list::VariableList;
|
||||
|
||||
pub mod length {
|
||||
pub use crate::bitfield::{Fixed, Variable};
|
||||
}
|
||||
|
||||
/// Returned when an item encounters an error.
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub enum Error {
|
||||
OutOfBounds {
|
||||
i: usize,
|
||||
len: usize,
|
||||
},
|
||||
/// A `BitList` does not have a set bit, therefore it's length is unknowable.
|
||||
MissingLengthInformation,
|
||||
/// A `BitList` has excess bits set to true.
|
||||
ExcessBits,
|
||||
/// A `BitList` has an invalid number of bytes for a given bit length.
|
||||
InvalidByteCount {
|
||||
given: usize,
|
||||
expected: usize,
|
||||
},
|
||||
}
|
||||
58
consensus/ssz_types/src/tree_hash.rs
Normal file
58
consensus/ssz_types/src/tree_hash.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
use tree_hash::{Hash256, MerkleHasher, TreeHash, TreeHashType, BYTES_PER_CHUNK};
|
||||
use typenum::Unsigned;
|
||||
|
||||
/// A helper function providing common functionality between the `TreeHash` implementations for
|
||||
/// `FixedVector` and `VariableList`.
|
||||
pub fn vec_tree_hash_root<T, N>(vec: &[T]) -> Hash256
|
||||
where
|
||||
T: TreeHash,
|
||||
N: Unsigned,
|
||||
{
|
||||
match T::tree_hash_type() {
|
||||
TreeHashType::Basic => {
|
||||
let mut hasher = MerkleHasher::with_leaves(
|
||||
(N::to_usize() + T::tree_hash_packing_factor() - 1) / T::tree_hash_packing_factor(),
|
||||
);
|
||||
|
||||
for item in vec {
|
||||
hasher
|
||||
.write(&item.tree_hash_packed_encoding())
|
||||
.expect("ssz_types variable vec should not contain more elements than max");
|
||||
}
|
||||
|
||||
hasher
|
||||
.finish()
|
||||
.expect("ssz_types variable vec should not have a remaining buffer")
|
||||
}
|
||||
TreeHashType::Container | TreeHashType::List | TreeHashType::Vector => {
|
||||
let mut hasher = MerkleHasher::with_leaves(N::to_usize());
|
||||
|
||||
for item in vec {
|
||||
hasher
|
||||
.write(item.tree_hash_root().as_bytes())
|
||||
.expect("ssz_types vec should not contain more elements than max");
|
||||
}
|
||||
|
||||
hasher
|
||||
.finish()
|
||||
.expect("ssz_types vec should not have a remaining buffer")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A helper function providing common functionality for finding the Merkle root of some bytes that
|
||||
/// represent a bitfield.
|
||||
pub fn bitfield_bytes_tree_hash_root<N: Unsigned>(bytes: &[u8]) -> Hash256 {
|
||||
let byte_size = (N::to_usize() + 7) / 8;
|
||||
let leaf_count = (byte_size + BYTES_PER_CHUNK - 1) / BYTES_PER_CHUNK;
|
||||
|
||||
let mut hasher = MerkleHasher::with_leaves(leaf_count);
|
||||
|
||||
hasher
|
||||
.write(bytes)
|
||||
.expect("bitfield should not exceed tree hash leaf limit");
|
||||
|
||||
hasher
|
||||
.finish()
|
||||
.expect("bitfield tree hash buffer should not exceed leaf limit")
|
||||
}
|
||||
461
consensus/ssz_types/src/variable_list.rs
Normal file
461
consensus/ssz_types/src/variable_list.rs
Normal file
@@ -0,0 +1,461 @@
|
||||
use crate::tree_hash::vec_tree_hash_root;
|
||||
use crate::Error;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::{Deref, DerefMut, Index, IndexMut};
|
||||
use std::slice::SliceIndex;
|
||||
use tree_hash::Hash256;
|
||||
use typenum::Unsigned;
|
||||
|
||||
pub use typenum;
|
||||
|
||||
/// Emulates a SSZ `List`.
|
||||
///
|
||||
/// An ordered, heap-allocated, variable-length, homogeneous collection of `T`, with no more than
|
||||
/// `N` values.
|
||||
///
|
||||
/// This struct is backed by a Rust `Vec` but constrained such that it must be instantiated with a
|
||||
/// fixed number of elements and you may not add or remove elements, only modify.
|
||||
///
|
||||
/// The length of this struct is fixed at the type-level using
|
||||
/// [typenum](https://crates.io/crates/typenum).
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```
|
||||
/// use ssz_types::{VariableList, typenum};
|
||||
///
|
||||
/// let base: Vec<u64> = vec![1, 2, 3, 4];
|
||||
///
|
||||
/// // Create a `VariableList` from a `Vec` that has the expected length.
|
||||
/// let exact: VariableList<_, typenum::U4> = VariableList::from(base.clone());
|
||||
/// assert_eq!(&exact[..], &[1, 2, 3, 4]);
|
||||
///
|
||||
/// // Create a `VariableList` from a `Vec` that is too long and the `Vec` is truncated.
|
||||
/// let short: VariableList<_, typenum::U3> = VariableList::from(base.clone());
|
||||
/// assert_eq!(&short[..], &[1, 2, 3]);
|
||||
///
|
||||
/// // Create a `VariableList` from a `Vec` that is shorter than the maximum.
|
||||
/// let mut long: VariableList<_, typenum::U5> = VariableList::from(base);
|
||||
/// assert_eq!(&long[..], &[1, 2, 3, 4]);
|
||||
///
|
||||
/// // Push a value to if it does not exceed the maximum
|
||||
/// long.push(5).unwrap();
|
||||
/// assert_eq!(&long[..], &[1, 2, 3, 4, 5]);
|
||||
///
|
||||
/// // Push a value to if it _does_ exceed the maximum.
|
||||
/// assert!(long.push(6).is_err());
|
||||
/// ```
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct VariableList<T, N> {
|
||||
vec: Vec<T>,
|
||||
_phantom: PhantomData<N>,
|
||||
}
|
||||
|
||||
impl<T, N: Unsigned> VariableList<T, N> {
|
||||
/// Returns `Some` if the given `vec` equals the fixed length of `Self`. Otherwise returns
|
||||
/// `None`.
|
||||
pub fn new(vec: Vec<T>) -> Result<Self, Error> {
|
||||
if vec.len() <= N::to_usize() {
|
||||
Ok(Self {
|
||||
vec,
|
||||
_phantom: PhantomData,
|
||||
})
|
||||
} else {
|
||||
Err(Error::OutOfBounds {
|
||||
i: vec.len(),
|
||||
len: Self::max_len(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an empty list.
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
vec: vec![],
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the number of values presently in `self`.
|
||||
pub fn len(&self) -> usize {
|
||||
self.vec.len()
|
||||
}
|
||||
|
||||
/// True if `self` does not contain any values.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
/// Returns the type-level maximum length.
|
||||
pub fn max_len() -> usize {
|
||||
N::to_usize()
|
||||
}
|
||||
|
||||
/// Appends `value` to the back of `self`.
|
||||
///
|
||||
/// Returns `Err(())` when appending `value` would exceed the maximum length.
|
||||
pub fn push(&mut self, value: T) -> Result<(), Error> {
|
||||
if self.vec.len() < Self::max_len() {
|
||||
self.vec.push(value);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::OutOfBounds {
|
||||
i: self.vec.len() + 1,
|
||||
len: Self::max_len(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, N: Unsigned> From<Vec<T>> for VariableList<T, N> {
|
||||
fn from(mut vec: Vec<T>) -> Self {
|
||||
vec.truncate(N::to_usize());
|
||||
|
||||
Self {
|
||||
vec,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, N: Unsigned> Into<Vec<T>> for VariableList<T, N> {
|
||||
fn into(self) -> Vec<T> {
|
||||
self.vec
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, N: Unsigned> Default for VariableList<T, N> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
vec: Vec::default(),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, N: Unsigned, I: SliceIndex<[T]>> Index<I> for VariableList<T, N> {
|
||||
type Output = I::Output;
|
||||
|
||||
#[inline]
|
||||
fn index(&self, index: I) -> &Self::Output {
|
||||
Index::index(&self.vec, index)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, N: Unsigned, I: SliceIndex<[T]>> IndexMut<I> for VariableList<T, N> {
|
||||
#[inline]
|
||||
fn index_mut(&mut self, index: I) -> &mut Self::Output {
|
||||
IndexMut::index_mut(&mut self.vec, index)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, N: Unsigned> Deref for VariableList<T, N> {
|
||||
type Target = [T];
|
||||
|
||||
fn deref(&self) -> &[T] {
|
||||
&self.vec[..]
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, N: Unsigned> DerefMut for VariableList<T, N> {
|
||||
fn deref_mut(&mut self) -> &mut [T] {
|
||||
&mut self.vec[..]
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T, N: Unsigned> IntoIterator for &'a VariableList<T, N> {
|
||||
type Item = &'a T;
|
||||
type IntoIter = std::slice::Iter<'a, T>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, N: Unsigned> tree_hash::TreeHash for VariableList<T, N>
|
||||
where
|
||||
T: tree_hash::TreeHash,
|
||||
{
|
||||
fn tree_hash_type() -> tree_hash::TreeHashType {
|
||||
tree_hash::TreeHashType::List
|
||||
}
|
||||
|
||||
fn tree_hash_packed_encoding(&self) -> Vec<u8> {
|
||||
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::<T, N>(&self.vec);
|
||||
|
||||
tree_hash::mix_in_length(&root, self.len())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, N: Unsigned> ssz::Encode for VariableList<T, N>
|
||||
where
|
||||
T: ssz::Encode,
|
||||
{
|
||||
fn is_ssz_fixed_len() -> bool {
|
||||
<Vec<T>>::is_ssz_fixed_len()
|
||||
}
|
||||
|
||||
fn ssz_fixed_len() -> usize {
|
||||
<Vec<T>>::ssz_fixed_len()
|
||||
}
|
||||
|
||||
fn ssz_bytes_len(&self) -> usize {
|
||||
self.vec.ssz_bytes_len()
|
||||
}
|
||||
|
||||
fn ssz_append(&self, buf: &mut Vec<u8>) {
|
||||
self.vec.ssz_append(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, N> ssz::Decode for VariableList<T, N>
|
||||
where
|
||||
T: ssz::Decode,
|
||||
N: Unsigned,
|
||||
{
|
||||
fn is_ssz_fixed_len() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
|
||||
let max_len = N::to_usize();
|
||||
|
||||
if bytes.is_empty() {
|
||||
Ok(vec![].into())
|
||||
} else if T::is_ssz_fixed_len() {
|
||||
let num_items = bytes
|
||||
.len()
|
||||
.checked_div(T::ssz_fixed_len())
|
||||
.ok_or_else(|| ssz::DecodeError::ZeroLengthItem)?;
|
||||
|
||||
if num_items > max_len {
|
||||
return Err(ssz::DecodeError::BytesInvalid(format!(
|
||||
"VariableList of {} items exceeds maximum of {}",
|
||||
num_items, max_len
|
||||
)));
|
||||
}
|
||||
|
||||
bytes
|
||||
.chunks(T::ssz_fixed_len())
|
||||
.map(|chunk| T::from_ssz_bytes(chunk))
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map(Into::into)
|
||||
} else {
|
||||
ssz::decode_list_of_variable_length_items(bytes, Some(max_len)).map(|vec| vec.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "arbitrary")]
|
||||
impl<T: arbitrary::Arbitrary, N: 'static + Unsigned> arbitrary::Arbitrary for VariableList<T, N> {
|
||||
fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
|
||||
let max_size = N::to_usize();
|
||||
let rand = usize::arbitrary(u)?;
|
||||
let size = std::cmp::min(rand, max_size);
|
||||
let mut vec: Vec<T> = Vec::with_capacity(size);
|
||||
for _ in 0..size {
|
||||
vec.push(<T>::arbitrary(u)?);
|
||||
}
|
||||
Ok(Self::new(vec).map_err(|_| arbitrary::Error::IncorrectFormat)?)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use ssz::*;
|
||||
use tree_hash::{merkle_root, TreeHash};
|
||||
use tree_hash_derive::TreeHash;
|
||||
use typenum::*;
|
||||
|
||||
#[test]
|
||||
fn new() {
|
||||
let vec = vec![42; 5];
|
||||
let fixed: Result<VariableList<u64, U4>, _> = VariableList::new(vec);
|
||||
assert!(fixed.is_err());
|
||||
|
||||
let vec = vec![42; 3];
|
||||
let fixed: Result<VariableList<u64, U4>, _> = VariableList::new(vec);
|
||||
assert!(fixed.is_ok());
|
||||
|
||||
let vec = vec![42; 4];
|
||||
let fixed: Result<VariableList<u64, U4>, _> = VariableList::new(vec);
|
||||
assert!(fixed.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn indexing() {
|
||||
let vec = vec![1, 2];
|
||||
|
||||
let mut fixed: VariableList<u64, U8192> = vec.clone().into();
|
||||
|
||||
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 fixed: VariableList<u64, U4> = VariableList::from(vec.clone());
|
||||
assert_eq!(&fixed[..], &vec[0..4]);
|
||||
|
||||
let vec = vec![42; 3];
|
||||
let fixed: VariableList<u64, U4> = VariableList::from(vec.clone());
|
||||
assert_eq!(&fixed[0..3], &vec[..]);
|
||||
assert_eq!(&fixed[..], &vec![42, 42, 42][..]);
|
||||
|
||||
let vec = vec![];
|
||||
let fixed: VariableList<u64, U4> = VariableList::from(vec);
|
||||
assert_eq!(&fixed[..], &vec![][..]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deref() {
|
||||
let vec = vec![0, 2, 4, 6];
|
||||
let fixed: VariableList<u64, U4> = VariableList::from(vec);
|
||||
|
||||
assert_eq!(fixed.get(0), Some(&0));
|
||||
assert_eq!(fixed.get(3), Some(&6));
|
||||
assert_eq!(fixed.get(4), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode() {
|
||||
let vec: VariableList<u16, U2> = vec![0; 2].into();
|
||||
assert_eq!(vec.as_ssz_bytes(), vec![0, 0, 0, 0]);
|
||||
assert_eq!(<VariableList<u16, U2> as Encode>::ssz_fixed_len(), 4);
|
||||
}
|
||||
|
||||
fn round_trip<T: Encode + Decode + std::fmt::Debug + PartialEq>(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 u16_len_8() {
|
||||
round_trip::<VariableList<u16, U8>>(vec![42; 8].into());
|
||||
round_trip::<VariableList<u16, U8>>(vec![0; 8].into());
|
||||
}
|
||||
|
||||
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: VariableList<u8, U0> = VariableList::from(vec![]);
|
||||
assert_eq!(fixed.tree_hash_root(), root_with_length(&[0; 8], 0));
|
||||
|
||||
for i in 0..=1 {
|
||||
let fixed: VariableList<u8, U1> = VariableList::from(vec![0; i]);
|
||||
assert_eq!(fixed.tree_hash_root(), root_with_length(&vec![0; i], i));
|
||||
}
|
||||
|
||||
for i in 0..=8 {
|
||||
let fixed: VariableList<u8, U8> = VariableList::from(vec![0; i]);
|
||||
assert_eq!(fixed.tree_hash_root(), root_with_length(&vec![0; i], i));
|
||||
}
|
||||
|
||||
for i in 0..=13 {
|
||||
let fixed: VariableList<u8, U13> = VariableList::from(vec![0; i]);
|
||||
assert_eq!(fixed.tree_hash_root(), root_with_length(&vec![0; i], i));
|
||||
}
|
||||
|
||||
for i in 0..=16 {
|
||||
let fixed: VariableList<u8, U16> = VariableList::from(vec![0; i]);
|
||||
assert_eq!(fixed.tree_hash_root(), root_with_length(&vec![0; i], i));
|
||||
}
|
||||
|
||||
let source: Vec<u8> = (0..16).collect();
|
||||
let fixed: VariableList<u8, U16> = VariableList::from(source.clone());
|
||||
assert_eq!(fixed.tree_hash_root(), root_with_length(&source, 16));
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, TreeHash, Default)]
|
||||
struct A {
|
||||
a: u32,
|
||||
b: u32,
|
||||
}
|
||||
|
||||
fn repeat(input: &[u8], n: usize) -> Vec<u8> {
|
||||
let mut output = vec![];
|
||||
|
||||
for _ in 0..n {
|
||||
output.append(&mut input.to_vec());
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
fn padded_root_with_length(bytes: &[u8], len: usize, min_nodes: usize) -> Hash256 {
|
||||
let root = merkle_root(bytes, min_nodes);
|
||||
tree_hash::mix_in_length(&root, len)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tree_hash_composite() {
|
||||
let a = A { a: 0, b: 1 };
|
||||
|
||||
let fixed: VariableList<A, U0> = VariableList::from(vec![]);
|
||||
assert_eq!(
|
||||
fixed.tree_hash_root(),
|
||||
padded_root_with_length(&[0; 32], 0, 0),
|
||||
);
|
||||
|
||||
for i in 0..=1 {
|
||||
let fixed: VariableList<A, U1> = VariableList::from(vec![a; i]);
|
||||
assert_eq!(
|
||||
fixed.tree_hash_root(),
|
||||
padded_root_with_length(&repeat(a.tree_hash_root().as_bytes(), i), i, 1),
|
||||
"U1 {}",
|
||||
i
|
||||
);
|
||||
}
|
||||
|
||||
for i in 0..=8 {
|
||||
let fixed: VariableList<A, U8> = VariableList::from(vec![a; i]);
|
||||
assert_eq!(
|
||||
fixed.tree_hash_root(),
|
||||
padded_root_with_length(&repeat(a.tree_hash_root().as_bytes(), i), i, 8),
|
||||
"U8 {}",
|
||||
i
|
||||
);
|
||||
}
|
||||
|
||||
for i in 0..=13 {
|
||||
let fixed: VariableList<A, U13> = VariableList::from(vec![a; i]);
|
||||
assert_eq!(
|
||||
fixed.tree_hash_root(),
|
||||
padded_root_with_length(&repeat(a.tree_hash_root().as_bytes(), i), i, 13),
|
||||
"U13 {}",
|
||||
i
|
||||
);
|
||||
}
|
||||
|
||||
for i in 0..=16 {
|
||||
let fixed: VariableList<A, U16> = VariableList::from(vec![a; i]);
|
||||
assert_eq!(
|
||||
fixed.tree_hash_root(),
|
||||
padded_root_with_length(&repeat(a.tree_hash_root().as_bytes(), i), i, 16),
|
||||
"U16 {}",
|
||||
i
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user