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:
Paul Hauner
2020-05-18 21:24:23 +10:00
committed by GitHub
parent c571afb8d8
commit 4331834003
358 changed files with 217 additions and 229 deletions

20
consensus/ssz/Cargo.toml Normal file
View File

@@ -0,0 +1,20 @@
[package]
name = "eth2_ssz"
version = "0.1.2"
authors = ["Paul Hauner <paul@sigmaprime.io>"]
edition = "2018"
description = "SimpleSerialize (SSZ) as used in Ethereum 2.0"
license = "Apache-2.0"
[lib]
name = "ssz"
[dev-dependencies]
eth2_ssz_derive = "0.1.0"
[dependencies]
ethereum-types = "0.9.1"
smallvec = "1.4.0"
[features]
arbitrary = ["ethereum-types/arbitrary"]

3
consensus/ssz/README.md Normal file
View File

@@ -0,0 +1,3 @@
# simpleserialize (ssz)
[<img src="https://img.shields.io/crates/v/eth2_ssz">](https://crates.io/crates/eth2_ssz)

View File

@@ -0,0 +1,15 @@
//! Encode and decode a list many times.
//!
//! Useful for `cargo flamegraph`.
use ssz::{Decode, Encode};
fn main() {
let vec: Vec<u64> = vec![4242; 8196];
let output: Vec<Vec<u64>> = (0..40_000)
.map(|_| Vec::from_ssz_bytes(&vec.as_ssz_bytes()).unwrap())
.collect();
println!("{}", output.len());
}

View File

@@ -0,0 +1,31 @@
//! Encode and decode a list many times.
//!
//! Useful for `cargo flamegraph`.
use ssz::{Decode, Encode};
use ssz_derive::{Decode, Encode};
#[derive(Clone, Copy, Encode, Decode)]
pub struct FixedLen {
a: u64,
b: u64,
c: u64,
d: u64,
}
fn main() {
let fixed_len = FixedLen {
a: 42,
b: 42,
c: 42,
d: 42,
};
let vec: Vec<FixedLen> = vec![fixed_len; 8196];
let output: Vec<Vec<u64>> = (0..40_000)
.map(|_| Vec::from_ssz_bytes(&vec.as_ssz_bytes()).unwrap())
.collect();
println!("{}", output.len());
}

View File

@@ -0,0 +1,73 @@
use ssz::{Decode, DecodeError, Encode, SszDecoderBuilder, SszEncoder};
#[derive(Debug, PartialEq)]
pub struct Foo {
a: u16,
b: Vec<u8>,
c: u16,
}
impl Encode for Foo {
fn is_ssz_fixed_len() -> bool {
<u16 as Encode>::is_ssz_fixed_len() && <Vec<u16> as Encode>::is_ssz_fixed_len()
}
fn ssz_bytes_len(&self) -> usize {
<u16 as Encode>::ssz_fixed_len()
+ ssz::BYTES_PER_LENGTH_OFFSET
+ <u16 as Encode>::ssz_fixed_len()
+ self.b.ssz_bytes_len()
}
fn ssz_append(&self, buf: &mut Vec<u8>) {
let offset = <u16 as Encode>::ssz_fixed_len()
+ <Vec<u16> as Encode>::ssz_fixed_len()
+ <u16 as Encode>::ssz_fixed_len();
let mut encoder = SszEncoder::container(buf, offset);
encoder.append(&self.a);
encoder.append(&self.b);
encoder.append(&self.c);
encoder.finalize();
}
}
impl Decode for Foo {
fn is_ssz_fixed_len() -> bool {
<u16 as Decode>::is_ssz_fixed_len() && <Vec<u16> as Decode>::is_ssz_fixed_len()
}
fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, DecodeError> {
let mut builder = SszDecoderBuilder::new(bytes);
builder.register_type::<u16>()?;
builder.register_type::<Vec<u8>>()?;
builder.register_type::<u16>()?;
let mut decoder = builder.build()?;
Ok(Self {
a: decoder.decode_next()?,
b: decoder.decode_next()?,
c: decoder.decode_next()?,
})
}
}
fn main() {
let my_foo = Foo {
a: 42,
b: vec![0, 1, 2, 3],
c: 11,
};
let bytes = vec![42, 0, 8, 0, 0, 0, 11, 0, 0, 1, 2, 3];
assert_eq!(my_foo.as_ssz_bytes(), bytes);
let decoded_foo = Foo::from_ssz_bytes(&bytes).unwrap();
assert_eq!(my_foo, decoded_foo);
}

310
consensus/ssz/src/decode.rs Normal file
View File

@@ -0,0 +1,310 @@
use super::*;
use smallvec::{smallvec, SmallVec};
type SmallVec8<T> = SmallVec<[T; 8]>;
pub mod impls;
/// Returned when SSZ decoding fails.
#[derive(Debug, PartialEq, Clone)]
pub enum DecodeError {
/// The bytes supplied were too short to be decoded into the specified type.
InvalidByteLength { len: usize, expected: usize },
/// The given bytes were too short to be read as a length prefix.
InvalidLengthPrefix { len: usize, expected: usize },
/// A length offset pointed to a byte that was out-of-bounds (OOB).
///
/// A bytes may be OOB for the following reasons:
///
/// - It is `>= bytes.len()`.
/// - When decoding variable length items, the 1st offset points "backwards" into the fixed
/// length items (i.e., `length[0] < BYTES_PER_LENGTH_OFFSET`).
/// - When decoding variable-length items, the `n`'th offset was less than the `n-1`'th offset.
OutOfBoundsByte { i: usize },
/// An offset points “backwards” into the fixed-bytes portion of the message, essentially
/// double-decoding bytes that will also be decoded as fixed-length.
///
/// https://notes.ethereum.org/ruKvDXl6QOW3gnqVYb8ezA?view#1-Offset-into-fixed-portion
OffsetIntoFixedPortion(usize),
/// The first offset does not point to the byte that follows the fixed byte portion,
/// essentially skipping a variable-length byte.
///
/// https://notes.ethereum.org/ruKvDXl6QOW3gnqVYb8ezA?view#2-Skip-first-variable-byte
OffsetSkipsVariableBytes(usize),
/// An offset points to bytes prior to the previous offset. Depending on how you look at it,
/// this either double-decodes bytes or makes the first offset a negative-length.
///
/// https://notes.ethereum.org/ruKvDXl6QOW3gnqVYb8ezA?view#3-Offsets-are-decreasing
OffsetsAreDecreasing(usize),
/// An offset references byte indices that do not exist in the source bytes.
///
/// https://notes.ethereum.org/ruKvDXl6QOW3gnqVYb8ezA?view#4-Offsets-are-out-of-bounds
OffsetOutOfBounds(usize),
/// A variable-length list does not have a fixed portion that is cleanly divisible by
/// `BYTES_PER_LENGTH_OFFSET`.
InvalidListFixedBytesLen(usize),
/// Some item has a `ssz_fixed_len` of zero. This is illegal.
ZeroLengthItem,
/// The given bytes were invalid for some application-level reason.
BytesInvalid(String),
}
/// Performs checks on the `offset` based upon the other parameters provided.
///
/// ## Detail
///
/// - `offset`: the offset bytes (e.g., result of `read_offset(..)`).
/// - `previous_offset`: unless this is the first offset in the SSZ object, the value of the
/// previously-read offset. Used to ensure offsets are not decreasing.
/// - `num_bytes`: the total number of bytes in the SSZ object. Used to ensure the offset is not
/// out of bounds.
/// - `num_fixed_bytes`: the number of fixed-bytes in the struct, if it is known. Used to ensure
/// that the first offset doesn't skip any variable bytes.
///
/// ## References
///
/// The checks here are derived from this document:
///
/// https://notes.ethereum.org/ruKvDXl6QOW3gnqVYb8ezA?view
pub fn sanitize_offset(
offset: usize,
previous_offset: Option<usize>,
num_bytes: usize,
num_fixed_bytes: Option<usize>,
) -> Result<usize, DecodeError> {
if num_fixed_bytes.map_or(false, |fixed_bytes| offset < fixed_bytes) {
Err(DecodeError::OffsetIntoFixedPortion(offset))
} else if previous_offset.is_none()
&& num_fixed_bytes.map_or(false, |fixed_bytes| offset != fixed_bytes)
{
Err(DecodeError::OffsetSkipsVariableBytes(offset))
} else if offset > num_bytes {
Err(DecodeError::OffsetOutOfBounds(offset))
} else if previous_offset.map_or(false, |prev| prev > offset) {
Err(DecodeError::OffsetsAreDecreasing(offset))
} else {
Ok(offset)
}
}
/// Provides SSZ decoding (de-serialization) via the `from_ssz_bytes(&bytes)` method.
///
/// See `examples/` for manual implementations or the crate root for implementations using
/// `#[derive(Decode)]`.
pub trait Decode: Sized {
/// Returns `true` if this object has a fixed-length.
///
/// I.e., there are no variable length items in this object or any of it's contained objects.
fn is_ssz_fixed_len() -> bool;
/// The number of bytes this object occupies in the fixed-length portion of the SSZ bytes.
///
/// By default, this is set to `BYTES_PER_LENGTH_OFFSET` which is suitable for variable length
/// objects, but not fixed-length objects. Fixed-length objects _must_ return a value which
/// represents their length.
fn ssz_fixed_len() -> usize {
BYTES_PER_LENGTH_OFFSET
}
/// Attempts to decode `Self` from `bytes`, returning a `DecodeError` on failure.
///
/// The supplied bytes must be the exact length required to decode `Self`, excess bytes will
/// result in an error.
fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, DecodeError>;
}
#[derive(Copy, Clone, Debug)]
pub struct Offset {
position: usize,
offset: usize,
}
/// Builds an `SszDecoder`.
///
/// The purpose of this struct is to split some SSZ bytes into individual slices. The builder is
/// then converted into a `SszDecoder` which decodes those values into object instances.
///
/// See [`SszDecoder`](struct.SszDecoder.html) for usage examples.
pub struct SszDecoderBuilder<'a> {
bytes: &'a [u8],
items: SmallVec8<&'a [u8]>,
offsets: SmallVec8<Offset>,
items_index: usize,
}
impl<'a> SszDecoderBuilder<'a> {
/// Instantiate a new builder that should build a `SszDecoder` over the given `bytes` which
/// are assumed to be the SSZ encoding of some object.
pub fn new(bytes: &'a [u8]) -> Self {
Self {
bytes,
items: smallvec![],
offsets: smallvec![],
items_index: 0,
}
}
/// Declares that some type `T` is the next item in `bytes`.
pub fn register_type<T: Decode>(&mut self) -> Result<(), DecodeError> {
if T::is_ssz_fixed_len() {
let start = self.items_index;
self.items_index += T::ssz_fixed_len();
let slice = self.bytes.get(start..self.items_index).ok_or_else(|| {
DecodeError::InvalidByteLength {
len: self.bytes.len(),
expected: self.items_index,
}
})?;
self.items.push(slice);
} else {
self.offsets.push(Offset {
position: self.items.len(),
offset: sanitize_offset(
read_offset(&self.bytes[self.items_index..])?,
self.offsets.last().map(|o| o.offset),
self.bytes.len(),
None,
)?,
});
// Push an empty slice into items; it will be replaced later.
self.items.push(&[]);
self.items_index += BYTES_PER_LENGTH_OFFSET;
}
Ok(())
}
fn finalize(&mut self) -> Result<(), DecodeError> {
if let Some(first_offset) = self.offsets.first().map(|o| o.offset) {
// Check to ensure the first offset points to the byte immediately following the
// fixed-length bytes.
if first_offset < self.items_index {
return Err(DecodeError::OffsetIntoFixedPortion(first_offset));
} else if first_offset > self.items_index {
return Err(DecodeError::OffsetSkipsVariableBytes(first_offset));
}
// Iterate through each pair of offsets, grabbing the slice between each of the offsets.
for pair in self.offsets.windows(2) {
let a = pair[0];
let b = pair[1];
self.items[a.position] = &self.bytes[a.offset..b.offset];
}
// Handle the last offset, pushing a slice from it's start through to the end of
// `self.bytes`.
if let Some(last) = self.offsets.last() {
self.items[last.position] = &self.bytes[last.offset..]
}
} else {
// If the container is fixed-length, ensure there are no excess bytes.
if self.items_index != self.bytes.len() {
return Err(DecodeError::InvalidByteLength {
len: self.bytes.len(),
expected: self.items_index,
});
}
}
Ok(())
}
/// Finalizes the builder, returning a `SszDecoder` that may be used to instantiate objects.
pub fn build(mut self) -> Result<SszDecoder<'a>, DecodeError> {
self.finalize()?;
Ok(SszDecoder { items: self.items })
}
}
/// Decodes some slices of SSZ into object instances. Should be instantiated using
/// [`SszDecoderBuilder`](struct.SszDecoderBuilder.html).
///
/// ## Example
///
/// ```rust
/// use ssz_derive::{Encode, Decode};
/// use ssz::{Decode, Encode, SszDecoder, SszDecoderBuilder};
///
/// #[derive(PartialEq, Debug, Encode, Decode)]
/// struct Foo {
/// a: u64,
/// b: Vec<u16>,
/// }
///
/// fn ssz_decoding_example() {
/// let foo = Foo {
/// a: 42,
/// b: vec![1, 3, 3, 7]
/// };
///
/// let bytes = foo.as_ssz_bytes();
///
/// let mut builder = SszDecoderBuilder::new(&bytes);
///
/// builder.register_type::<u64>().unwrap();
/// builder.register_type::<Vec<u16>>().unwrap();
///
/// let mut decoder = builder.build().unwrap();
///
/// let decoded_foo = Foo {
/// a: decoder.decode_next().unwrap(),
/// b: decoder.decode_next().unwrap(),
/// };
///
/// assert_eq!(foo, decoded_foo);
/// }
///
/// ```
pub struct SszDecoder<'a> {
items: SmallVec8<&'a [u8]>,
}
impl<'a> SszDecoder<'a> {
/// Decodes the next item.
///
/// # Panics
///
/// Panics when attempting to decode more items than actually exist.
pub fn decode_next<T: Decode>(&mut self) -> Result<T, DecodeError> {
T::from_ssz_bytes(self.items.remove(0))
}
}
/// Reads a `BYTES_PER_LENGTH_OFFSET`-byte union index from `bytes`, where `bytes.len() >=
/// BYTES_PER_LENGTH_OFFSET`.
pub fn read_union_index(bytes: &[u8]) -> Result<usize, DecodeError> {
read_offset(bytes)
}
/// Reads a `BYTES_PER_LENGTH_OFFSET`-byte length from `bytes`, where `bytes.len() >=
/// BYTES_PER_LENGTH_OFFSET`.
fn read_offset(bytes: &[u8]) -> Result<usize, DecodeError> {
decode_offset(bytes.get(0..BYTES_PER_LENGTH_OFFSET).ok_or_else(|| {
DecodeError::InvalidLengthPrefix {
len: bytes.len(),
expected: BYTES_PER_LENGTH_OFFSET,
}
})?)
}
/// Decode bytes as a little-endian usize, returning an `Err` if `bytes.len() !=
/// BYTES_PER_LENGTH_OFFSET`.
fn decode_offset(bytes: &[u8]) -> Result<usize, DecodeError> {
let len = bytes.len();
let expected = BYTES_PER_LENGTH_OFFSET;
if len != expected {
Err(DecodeError::InvalidLengthPrefix { len, expected })
} else {
let mut array: [u8; BYTES_PER_LENGTH_OFFSET] = std::default::Default::default();
array.clone_from_slice(bytes);
Ok(u32::from_le_bytes(array) as usize)
}
}

View File

@@ -0,0 +1,712 @@
use super::*;
use core::num::NonZeroUsize;
use ethereum_types::{H256, U128, U256};
use smallvec::SmallVec;
macro_rules! impl_decodable_for_uint {
($type: ident, $bit_size: expr) => {
impl Decode for $type {
fn is_ssz_fixed_len() -> bool {
true
}
fn ssz_fixed_len() -> usize {
$bit_size / 8
}
fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, DecodeError> {
let len = bytes.len();
let expected = <Self as Decode>::ssz_fixed_len();
if len != expected {
Err(DecodeError::InvalidByteLength { len, expected })
} else {
let mut array: [u8; $bit_size / 8] = std::default::Default::default();
array.clone_from_slice(bytes);
Ok(Self::from_le_bytes(array))
}
}
}
};
}
impl_decodable_for_uint!(u8, 8);
impl_decodable_for_uint!(u16, 16);
impl_decodable_for_uint!(u32, 32);
impl_decodable_for_uint!(u64, 64);
#[cfg(target_pointer_width = "32")]
impl_decodable_for_uint!(usize, 32);
#[cfg(target_pointer_width = "64")]
impl_decodable_for_uint!(usize, 64);
macro_rules! impl_decode_for_tuples {
($(
$Tuple:ident {
$(($idx:tt) -> $T:ident)+
}
)+) => {
$(
impl<$($T: Decode),+> Decode for ($($T,)+) {
fn is_ssz_fixed_len() -> bool {
$(
<$T as Decode>::is_ssz_fixed_len() &&
)*
true
}
fn ssz_fixed_len() -> usize {
if <Self as Decode>::is_ssz_fixed_len() {
$(
<$T as Decode>::ssz_fixed_len() +
)*
0
} else {
BYTES_PER_LENGTH_OFFSET
}
}
fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, DecodeError> {
let mut builder = SszDecoderBuilder::new(bytes);
$(
builder.register_type::<$T>()?;
)*
let mut decoder = builder.build()?;
Ok(($(
decoder.decode_next::<$T>()?,
)*
))
}
}
)+
}
}
impl_decode_for_tuples! {
Tuple2 {
(0) -> A
(1) -> B
}
Tuple3 {
(0) -> A
(1) -> B
(2) -> C
}
Tuple4 {
(0) -> A
(1) -> B
(2) -> C
(3) -> D
}
Tuple5 {
(0) -> A
(1) -> B
(2) -> C
(3) -> D
(4) -> E
}
Tuple6 {
(0) -> A
(1) -> B
(2) -> C
(3) -> D
(4) -> E
(5) -> F
}
Tuple7 {
(0) -> A
(1) -> B
(2) -> C
(3) -> D
(4) -> E
(5) -> F
(6) -> G
}
Tuple8 {
(0) -> A
(1) -> B
(2) -> C
(3) -> D
(4) -> E
(5) -> F
(6) -> G
(7) -> H
}
Tuple9 {
(0) -> A
(1) -> B
(2) -> C
(3) -> D
(4) -> E
(5) -> F
(6) -> G
(7) -> H
(8) -> I
}
Tuple10 {
(0) -> A
(1) -> B
(2) -> C
(3) -> D
(4) -> E
(5) -> F
(6) -> G
(7) -> H
(8) -> I
(9) -> J
}
Tuple11 {
(0) -> A
(1) -> B
(2) -> C
(3) -> D
(4) -> E
(5) -> F
(6) -> G
(7) -> H
(8) -> I
(9) -> J
(10) -> K
}
Tuple12 {
(0) -> A
(1) -> B
(2) -> C
(3) -> D
(4) -> E
(5) -> F
(6) -> G
(7) -> H
(8) -> I
(9) -> J
(10) -> K
(11) -> L
}
}
impl Decode for bool {
fn is_ssz_fixed_len() -> bool {
true
}
fn ssz_fixed_len() -> usize {
1
}
fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, DecodeError> {
let len = bytes.len();
let expected = <Self as Decode>::ssz_fixed_len();
if len != expected {
Err(DecodeError::InvalidByteLength { len, expected })
} else {
match bytes[0] {
0b0000_0000 => Ok(false),
0b0000_0001 => Ok(true),
_ => Err(DecodeError::BytesInvalid(format!(
"Out-of-range for boolean: {}",
bytes[0]
))),
}
}
}
}
impl Decode for NonZeroUsize {
fn is_ssz_fixed_len() -> bool {
<usize as Decode>::is_ssz_fixed_len()
}
fn ssz_fixed_len() -> usize {
<usize as Decode>::ssz_fixed_len()
}
fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, DecodeError> {
let x = usize::from_ssz_bytes(bytes)?;
if x == 0 {
Err(DecodeError::BytesInvalid(
"NonZeroUsize cannot be zero.".to_string(),
))
} else {
// `unwrap` is safe here as `NonZeroUsize::new()` succeeds if `x > 0` and this path
// never executes when `x == 0`.
Ok(NonZeroUsize::new(x).unwrap())
}
}
}
/// The SSZ union type.
impl<T: Decode> Decode for Option<T> {
fn is_ssz_fixed_len() -> bool {
false
}
fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, DecodeError> {
if bytes.len() < BYTES_PER_LENGTH_OFFSET {
return Err(DecodeError::InvalidByteLength {
len: bytes.len(),
expected: BYTES_PER_LENGTH_OFFSET,
});
}
let (index_bytes, value_bytes) = bytes.split_at(BYTES_PER_LENGTH_OFFSET);
let index = read_union_index(index_bytes)?;
if index == 0 {
Ok(None)
} else if index == 1 {
Ok(Some(T::from_ssz_bytes(value_bytes)?))
} else {
Err(DecodeError::BytesInvalid(format!(
"{} is not a valid union index for Option<T>",
index
)))
}
}
}
impl Decode for H256 {
fn is_ssz_fixed_len() -> bool {
true
}
fn ssz_fixed_len() -> usize {
32
}
fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, DecodeError> {
let len = bytes.len();
let expected = <Self as Decode>::ssz_fixed_len();
if len != expected {
Err(DecodeError::InvalidByteLength { len, expected })
} else {
Ok(H256::from_slice(bytes))
}
}
}
impl Decode for U256 {
fn is_ssz_fixed_len() -> bool {
true
}
fn ssz_fixed_len() -> usize {
32
}
fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, DecodeError> {
let len = bytes.len();
let expected = <Self as Decode>::ssz_fixed_len();
if len != expected {
Err(DecodeError::InvalidByteLength { len, expected })
} else {
Ok(U256::from_little_endian(bytes))
}
}
}
impl Decode for U128 {
fn is_ssz_fixed_len() -> bool {
true
}
fn ssz_fixed_len() -> usize {
16
}
fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, DecodeError> {
let len = bytes.len();
let expected = <Self as Decode>::ssz_fixed_len();
if len != expected {
Err(DecodeError::InvalidByteLength { len, expected })
} else {
Ok(U128::from_little_endian(bytes))
}
}
}
macro_rules! impl_decodable_for_u8_array {
($len: expr) => {
impl Decode for [u8; $len] {
fn is_ssz_fixed_len() -> bool {
true
}
fn ssz_fixed_len() -> usize {
$len
}
fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, DecodeError> {
let len = bytes.len();
let expected = <Self as Decode>::ssz_fixed_len();
if len != expected {
Err(DecodeError::InvalidByteLength { len, expected })
} else {
let mut array: [u8; $len] = [0; $len];
array.copy_from_slice(&bytes[..]);
Ok(array)
}
}
}
};
}
impl_decodable_for_u8_array!(4);
impl_decodable_for_u8_array!(32);
macro_rules! impl_for_vec {
($type: ty, $max_len: expr) => {
impl<T: Decode> Decode for $type {
fn is_ssz_fixed_len() -> bool {
false
}
fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, DecodeError> {
if bytes.is_empty() {
Ok(vec![].into())
} else if T::is_ssz_fixed_len() {
bytes
.chunks(T::ssz_fixed_len())
.map(|chunk| T::from_ssz_bytes(chunk))
.collect()
} else {
decode_list_of_variable_length_items(bytes, $max_len).map(|vec| vec.into())
}
}
}
};
}
impl_for_vec!(Vec<T>, None);
impl_for_vec!(SmallVec<[T; 1]>, Some(1));
impl_for_vec!(SmallVec<[T; 2]>, Some(2));
impl_for_vec!(SmallVec<[T; 3]>, Some(3));
impl_for_vec!(SmallVec<[T; 4]>, Some(4));
impl_for_vec!(SmallVec<[T; 5]>, Some(5));
impl_for_vec!(SmallVec<[T; 6]>, Some(6));
impl_for_vec!(SmallVec<[T; 7]>, Some(7));
impl_for_vec!(SmallVec<[T; 8]>, Some(8));
/// Decodes `bytes` as if it were a list of variable-length items.
///
/// The `ssz::SszDecoder` can also perform this functionality, however it it significantly faster
/// as it is optimized to read same-typed items whilst `ssz::SszDecoder` supports reading items of
/// differing types.
pub fn decode_list_of_variable_length_items<T: Decode>(
bytes: &[u8],
max_len: Option<usize>,
) -> Result<Vec<T>, DecodeError> {
if bytes.is_empty() {
return Ok(vec![]);
}
let first_offset = read_offset(bytes)?;
sanitize_offset(first_offset, None, bytes.len(), Some(first_offset))?;
if first_offset % BYTES_PER_LENGTH_OFFSET != 0 || first_offset < BYTES_PER_LENGTH_OFFSET {
return Err(DecodeError::InvalidListFixedBytesLen(first_offset));
}
let num_items = first_offset / BYTES_PER_LENGTH_OFFSET;
if max_len.map_or(false, |max| num_items > max) {
return Err(DecodeError::BytesInvalid(format!(
"Variable length list of {} items exceeds maximum of {:?}",
num_items, max_len
)));
}
// Only initialize the vec with a capacity if a maximum length is provided.
//
// We assume that if a max length is provided then the application is able to handle an
// allocation of this size.
let mut values = if max_len.is_some() {
Vec::with_capacity(num_items)
} else {
vec![]
};
let mut offset = first_offset;
for i in 1..=num_items {
let slice_option = if i == num_items {
bytes.get(offset..)
} else {
let start = offset;
let next_offset = read_offset(&bytes[(i * BYTES_PER_LENGTH_OFFSET)..])?;
offset = sanitize_offset(next_offset, Some(offset), bytes.len(), Some(first_offset))?;
bytes.get(start..offset)
};
let slice = slice_option.ok_or_else(|| DecodeError::OutOfBoundsByte { i: offset })?;
values.push(T::from_ssz_bytes(slice)?);
}
Ok(values)
}
#[cfg(test)]
mod tests {
use super::*;
// Note: decoding of valid bytes is generally tested "indirectly" in the `/tests` dir, by
// encoding then decoding the element.
#[test]
fn invalid_u8_array_4() {
assert_eq!(
<[u8; 4]>::from_ssz_bytes(&[0; 3]),
Err(DecodeError::InvalidByteLength {
len: 3,
expected: 4
})
);
assert_eq!(
<[u8; 4]>::from_ssz_bytes(&[0; 5]),
Err(DecodeError::InvalidByteLength {
len: 5,
expected: 4
})
);
}
#[test]
fn invalid_bool() {
assert_eq!(
bool::from_ssz_bytes(&[0; 2]),
Err(DecodeError::InvalidByteLength {
len: 2,
expected: 1
})
);
assert_eq!(
bool::from_ssz_bytes(&[]),
Err(DecodeError::InvalidByteLength {
len: 0,
expected: 1
})
);
if let Err(DecodeError::BytesInvalid(_)) = bool::from_ssz_bytes(&[2]) {
// Success.
} else {
panic!("Did not return error on invalid bool val")
}
}
#[test]
fn invalid_h256() {
assert_eq!(
H256::from_ssz_bytes(&[0; 33]),
Err(DecodeError::InvalidByteLength {
len: 33,
expected: 32
})
);
assert_eq!(
H256::from_ssz_bytes(&[0; 31]),
Err(DecodeError::InvalidByteLength {
len: 31,
expected: 32
})
);
}
#[test]
fn empty_list() {
let vec: Vec<Vec<u16>> = vec![];
let bytes = vec.as_ssz_bytes();
assert!(bytes.is_empty());
assert_eq!(Vec::from_ssz_bytes(&bytes), Ok(vec),);
}
#[test]
fn first_length_points_backwards() {
assert_eq!(
<Vec<Vec<u16>>>::from_ssz_bytes(&[0, 0, 0, 0]),
Err(DecodeError::InvalidListFixedBytesLen(0))
);
assert_eq!(
<Vec<Vec<u16>>>::from_ssz_bytes(&[1, 0, 0, 0]),
Err(DecodeError::InvalidListFixedBytesLen(1))
);
assert_eq!(
<Vec<Vec<u16>>>::from_ssz_bytes(&[2, 0, 0, 0]),
Err(DecodeError::InvalidListFixedBytesLen(2))
);
assert_eq!(
<Vec<Vec<u16>>>::from_ssz_bytes(&[3, 0, 0, 0]),
Err(DecodeError::InvalidListFixedBytesLen(3))
);
}
#[test]
fn lengths_are_decreasing() {
assert_eq!(
<Vec<Vec<u16>>>::from_ssz_bytes(&[12, 0, 0, 0, 14, 0, 0, 0, 12, 0, 0, 0, 1, 0, 1, 0]),
Err(DecodeError::OffsetsAreDecreasing(12))
);
}
#[test]
fn awkward_fixed_length_portion() {
assert_eq!(
<Vec<Vec<u16>>>::from_ssz_bytes(&[10, 0, 0, 0, 10, 0, 0, 0, 0, 0]),
Err(DecodeError::InvalidListFixedBytesLen(10))
);
}
#[test]
fn length_out_of_bounds() {
assert_eq!(
<Vec<Vec<u16>>>::from_ssz_bytes(&[5, 0, 0, 0]),
Err(DecodeError::OffsetOutOfBounds(5))
);
assert_eq!(
<Vec<Vec<u16>>>::from_ssz_bytes(&[8, 0, 0, 0, 9, 0, 0, 0]),
Err(DecodeError::OffsetOutOfBounds(9))
);
assert_eq!(
<Vec<Vec<u16>>>::from_ssz_bytes(&[8, 0, 0, 0, 16, 0, 0, 0]),
Err(DecodeError::OffsetOutOfBounds(16))
);
}
#[test]
fn vec_of_vec_of_u16() {
assert_eq!(
<Vec<Vec<u16>>>::from_ssz_bytes(&[4, 0, 0, 0]),
Ok(vec![vec![]])
);
assert_eq!(
<Vec<u16>>::from_ssz_bytes(&[0, 0, 1, 0, 2, 0, 3, 0]),
Ok(vec![0, 1, 2, 3])
);
assert_eq!(<u16>::from_ssz_bytes(&[16, 0]), Ok(16));
assert_eq!(<u16>::from_ssz_bytes(&[0, 1]), Ok(256));
assert_eq!(<u16>::from_ssz_bytes(&[255, 255]), Ok(65535));
assert_eq!(
<u16>::from_ssz_bytes(&[255]),
Err(DecodeError::InvalidByteLength {
len: 1,
expected: 2
})
);
assert_eq!(
<u16>::from_ssz_bytes(&[]),
Err(DecodeError::InvalidByteLength {
len: 0,
expected: 2
})
);
assert_eq!(
<u16>::from_ssz_bytes(&[0, 1, 2]),
Err(DecodeError::InvalidByteLength {
len: 3,
expected: 2
})
);
}
#[test]
fn vec_of_u16() {
assert_eq!(<Vec<u16>>::from_ssz_bytes(&[0, 0, 0, 0]), Ok(vec![0, 0]));
assert_eq!(
<Vec<u16>>::from_ssz_bytes(&[0, 0, 1, 0, 2, 0, 3, 0]),
Ok(vec![0, 1, 2, 3])
);
assert_eq!(<u16>::from_ssz_bytes(&[16, 0]), Ok(16));
assert_eq!(<u16>::from_ssz_bytes(&[0, 1]), Ok(256));
assert_eq!(<u16>::from_ssz_bytes(&[255, 255]), Ok(65535));
assert_eq!(
<u16>::from_ssz_bytes(&[255]),
Err(DecodeError::InvalidByteLength {
len: 1,
expected: 2
})
);
assert_eq!(
<u16>::from_ssz_bytes(&[]),
Err(DecodeError::InvalidByteLength {
len: 0,
expected: 2
})
);
assert_eq!(
<u16>::from_ssz_bytes(&[0, 1, 2]),
Err(DecodeError::InvalidByteLength {
len: 3,
expected: 2
})
);
}
#[test]
fn u16() {
assert_eq!(<u16>::from_ssz_bytes(&[0, 0]), Ok(0));
assert_eq!(<u16>::from_ssz_bytes(&[16, 0]), Ok(16));
assert_eq!(<u16>::from_ssz_bytes(&[0, 1]), Ok(256));
assert_eq!(<u16>::from_ssz_bytes(&[255, 255]), Ok(65535));
assert_eq!(
<u16>::from_ssz_bytes(&[255]),
Err(DecodeError::InvalidByteLength {
len: 1,
expected: 2
})
);
assert_eq!(
<u16>::from_ssz_bytes(&[]),
Err(DecodeError::InvalidByteLength {
len: 0,
expected: 2
})
);
assert_eq!(
<u16>::from_ssz_bytes(&[0, 1, 2]),
Err(DecodeError::InvalidByteLength {
len: 3,
expected: 2
})
);
}
#[test]
fn tuple() {
assert_eq!(<(u16, u16)>::from_ssz_bytes(&[0, 0, 0, 0]), Ok((0, 0)));
assert_eq!(<(u16, u16)>::from_ssz_bytes(&[16, 0, 17, 0]), Ok((16, 17)));
assert_eq!(<(u16, u16)>::from_ssz_bytes(&[0, 1, 2, 0]), Ok((256, 2)));
assert_eq!(
<(u16, u16)>::from_ssz_bytes(&[255, 255, 0, 0]),
Ok((65535, 0))
);
}
}

202
consensus/ssz/src/encode.rs Normal file
View File

@@ -0,0 +1,202 @@
use super::*;
mod impls;
/// Provides SSZ encoding (serialization) via the `as_ssz_bytes(&self)` method.
///
/// See `examples/` for manual implementations or the crate root for implementations using
/// `#[derive(Encode)]`.
pub trait Encode {
/// Returns `true` if this object has a fixed-length.
///
/// I.e., there are no variable length items in this object or any of it's contained objects.
fn is_ssz_fixed_len() -> bool;
/// Append the encoding `self` to `buf`.
///
/// Note, variable length objects need only to append their "variable length" portion, they do
/// not need to provide their offset.
fn ssz_append(&self, buf: &mut Vec<u8>);
/// The number of bytes this object occupies in the fixed-length portion of the SSZ bytes.
///
/// By default, this is set to `BYTES_PER_LENGTH_OFFSET` which is suitable for variable length
/// objects, but not fixed-length objects. Fixed-length objects _must_ return a value which
/// represents their length.
fn ssz_fixed_len() -> usize {
BYTES_PER_LENGTH_OFFSET
}
/// Returns the size (in bytes) when `self` is serialized.
///
/// Returns the same value as `self.as_ssz_bytes().len()` but this method is significantly more
/// efficient.
fn ssz_bytes_len(&self) -> usize;
/// Returns the full-form encoding of this object.
///
/// The default implementation of this method should suffice for most cases.
fn as_ssz_bytes(&self) -> Vec<u8> {
let mut buf = vec![];
self.ssz_append(&mut buf);
buf
}
}
/// Allow for encoding an ordered series of distinct or indistinct objects as SSZ bytes.
///
/// **You must call `finalize(..)` after the final `append(..)` call** to ensure the bytes are
/// written to `buf`.
///
/// ## Example
///
/// Use `SszEncoder` to produce identical output to `foo.as_ssz_bytes()`:
///
/// ```rust
/// use ssz_derive::{Encode, Decode};
/// use ssz::{Decode, Encode, SszEncoder};
///
/// #[derive(PartialEq, Debug, Encode, Decode)]
/// struct Foo {
/// a: u64,
/// b: Vec<u16>,
/// }
///
/// fn ssz_encode_example() {
/// let foo = Foo {
/// a: 42,
/// b: vec![1, 3, 3, 7]
/// };
///
/// let mut buf: Vec<u8> = vec![];
/// let offset = <u64 as Encode>::ssz_fixed_len() + <Vec<u16> as Encode>::ssz_fixed_len();
///
/// let mut encoder = SszEncoder::container(&mut buf, offset);
///
/// encoder.append(&foo.a);
/// encoder.append(&foo.b);
///
/// encoder.finalize();
///
/// assert_eq!(foo.as_ssz_bytes(), buf);
/// }
///
/// ```
pub struct SszEncoder<'a> {
offset: usize,
buf: &'a mut Vec<u8>,
variable_bytes: Vec<u8>,
}
impl<'a> SszEncoder<'a> {
/// Instantiate a new encoder for encoding a SSZ list.
///
/// Identical to `Self::container`.
pub fn list(buf: &'a mut Vec<u8>, num_fixed_bytes: usize) -> Self {
Self::container(buf, num_fixed_bytes)
}
/// Instantiate a new encoder for encoding a SSZ container.
pub fn container(buf: &'a mut Vec<u8>, num_fixed_bytes: usize) -> Self {
buf.reserve(num_fixed_bytes);
Self {
offset: num_fixed_bytes,
buf,
variable_bytes: vec![],
}
}
/// Append some `item` to the SSZ bytes.
pub fn append<T: Encode>(&mut self, item: &T) {
if T::is_ssz_fixed_len() {
item.ssz_append(&mut self.buf);
} else {
self.buf
.extend_from_slice(&encode_length(self.offset + self.variable_bytes.len()));
item.ssz_append(&mut self.variable_bytes);
}
}
/// Write the variable bytes to `self.bytes`.
///
/// This method must be called after the final `append(..)` call when serializing
/// variable-length items.
pub fn finalize(&mut self) -> &mut Vec<u8> {
self.buf.append(&mut self.variable_bytes);
&mut self.buf
}
}
/// Encode `index` as a little-endian byte array of `BYTES_PER_LENGTH_OFFSET` length.
///
/// If `len` is larger than `2 ^ BYTES_PER_LENGTH_OFFSET`, a `debug_assert` is raised.
pub fn encode_union_index(index: usize) -> [u8; BYTES_PER_LENGTH_OFFSET] {
encode_length(index)
}
/// Encode `len` as a little-endian byte array of `BYTES_PER_LENGTH_OFFSET` length.
///
/// If `len` is larger than `2 ^ BYTES_PER_LENGTH_OFFSET`, a `debug_assert` is raised.
pub fn encode_length(len: usize) -> [u8; BYTES_PER_LENGTH_OFFSET] {
// Note: it is possible for `len` to be larger than what can be encoded in
// `BYTES_PER_LENGTH_OFFSET` bytes, triggering this debug assertion.
//
// These are the alternatives to using a `debug_assert` here:
//
// 1. Use `assert`.
// 2. Push an error to the caller (e.g., `Option` or `Result`).
// 3. Ignore it completely.
//
// I have avoided (1) because it's basically a choice between "produce invalid SSZ" or "kill
// the entire program". I figure it may be possible for an attacker to trigger this assert and
// take the program down -- I think producing invalid SSZ is a better option than this.
//
// I have avoided (2) because this error will need to be propagated upstream, making encoding a
// function which may fail. I don't think this is ergonomic and the upsides don't outweigh the
// downsides.
//
// I figure a `debug_assertion` is better than (3) as it will give us a change to detect the
// error during testing.
//
// If you have a different opinion, feel free to start an issue and tag @paulhauner.
debug_assert!(len <= MAX_LENGTH_VALUE);
let mut bytes = [0; BYTES_PER_LENGTH_OFFSET];
bytes.copy_from_slice(&len.to_le_bytes()[0..BYTES_PER_LENGTH_OFFSET]);
bytes
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encode_length() {
assert_eq!(encode_length(0), [0; 4]);
assert_eq!(encode_length(1), [1, 0, 0, 0]);
assert_eq!(
encode_length(MAX_LENGTH_VALUE),
[255; BYTES_PER_LENGTH_OFFSET]
);
}
#[test]
#[should_panic]
#[cfg(debug_assertions)]
fn test_encode_length_above_max_debug_panics() {
encode_length(MAX_LENGTH_VALUE + 1);
}
#[test]
#[cfg(not(debug_assertions))]
fn test_encode_length_above_max_not_debug_does_not_panic() {
assert_eq!(&encode_length(MAX_LENGTH_VALUE + 1)[..], &[0; 4]);
}
}

View File

@@ -0,0 +1,533 @@
use super::*;
use core::num::NonZeroUsize;
use ethereum_types::{H256, U128, U256};
use smallvec::SmallVec;
macro_rules! impl_encodable_for_uint {
($type: ident, $bit_size: expr) => {
impl Encode for $type {
fn is_ssz_fixed_len() -> bool {
true
}
fn ssz_fixed_len() -> usize {
$bit_size / 8
}
fn ssz_bytes_len(&self) -> usize {
$bit_size / 8
}
fn ssz_append(&self, buf: &mut Vec<u8>) {
buf.extend_from_slice(&self.to_le_bytes());
}
}
};
}
impl_encodable_for_uint!(u8, 8);
impl_encodable_for_uint!(u16, 16);
impl_encodable_for_uint!(u32, 32);
impl_encodable_for_uint!(u64, 64);
#[cfg(target_pointer_width = "32")]
impl_encodable_for_uint!(usize, 32);
#[cfg(target_pointer_width = "64")]
impl_encodable_for_uint!(usize, 64);
// Based on the `tuple_impls` macro from the standard library.
macro_rules! impl_encode_for_tuples {
($(
$Tuple:ident {
$(($idx:tt) -> $T:ident)+
}
)+) => {
$(
impl<$($T: Encode),+> Encode for ($($T,)+) {
fn is_ssz_fixed_len() -> bool {
$(
<$T as Encode>::is_ssz_fixed_len() &&
)*
true
}
fn ssz_fixed_len() -> usize {
if <Self as Encode>::is_ssz_fixed_len() {
$(
<$T as Encode>::ssz_fixed_len() +
)*
0
} else {
BYTES_PER_LENGTH_OFFSET
}
}
fn ssz_bytes_len(&self) -> usize {
if <Self as Encode>::is_ssz_fixed_len() {
<Self as Encode>::ssz_fixed_len()
} else {
let mut len = 0;
$(
len += if <$T as Encode>::is_ssz_fixed_len() {
<$T as Encode>::ssz_fixed_len()
} else {
BYTES_PER_LENGTH_OFFSET +
self.$idx.ssz_bytes_len()
};
)*
len
}
}
fn ssz_append(&self, buf: &mut Vec<u8>) {
let offset = $(
<$T as Encode>::ssz_fixed_len() +
)*
0;
let mut encoder = SszEncoder::container(buf, offset);
$(
encoder.append(&self.$idx);
)*
encoder.finalize();
}
}
)+
}
}
impl_encode_for_tuples! {
Tuple2 {
(0) -> A
(1) -> B
}
Tuple3 {
(0) -> A
(1) -> B
(2) -> C
}
Tuple4 {
(0) -> A
(1) -> B
(2) -> C
(3) -> D
}
Tuple5 {
(0) -> A
(1) -> B
(2) -> C
(3) -> D
(4) -> E
}
Tuple6 {
(0) -> A
(1) -> B
(2) -> C
(3) -> D
(4) -> E
(5) -> F
}
Tuple7 {
(0) -> A
(1) -> B
(2) -> C
(3) -> D
(4) -> E
(5) -> F
(6) -> G
}
Tuple8 {
(0) -> A
(1) -> B
(2) -> C
(3) -> D
(4) -> E
(5) -> F
(6) -> G
(7) -> H
}
Tuple9 {
(0) -> A
(1) -> B
(2) -> C
(3) -> D
(4) -> E
(5) -> F
(6) -> G
(7) -> H
(8) -> I
}
Tuple10 {
(0) -> A
(1) -> B
(2) -> C
(3) -> D
(4) -> E
(5) -> F
(6) -> G
(7) -> H
(8) -> I
(9) -> J
}
Tuple11 {
(0) -> A
(1) -> B
(2) -> C
(3) -> D
(4) -> E
(5) -> F
(6) -> G
(7) -> H
(8) -> I
(9) -> J
(10) -> K
}
Tuple12 {
(0) -> A
(1) -> B
(2) -> C
(3) -> D
(4) -> E
(5) -> F
(6) -> G
(7) -> H
(8) -> I
(9) -> J
(10) -> K
(11) -> L
}
}
/// The SSZ "union" type.
impl<T: Encode> Encode for Option<T> {
fn is_ssz_fixed_len() -> bool {
false
}
fn ssz_bytes_len(&self) -> usize {
if let Some(some) = self {
let len = if <T as Encode>::is_ssz_fixed_len() {
<T as Encode>::ssz_fixed_len()
} else {
some.ssz_bytes_len()
};
len + BYTES_PER_LENGTH_OFFSET
} else {
BYTES_PER_LENGTH_OFFSET
}
}
fn ssz_append(&self, buf: &mut Vec<u8>) {
match self {
None => buf.extend_from_slice(&encode_union_index(0)),
Some(t) => {
buf.extend_from_slice(&encode_union_index(1));
t.ssz_append(buf);
}
}
}
}
macro_rules! impl_for_vec {
($type: ty) => {
impl<T: Encode> Encode for $type {
fn is_ssz_fixed_len() -> bool {
false
}
fn ssz_bytes_len(&self) -> usize {
if <T as Encode>::is_ssz_fixed_len() {
<T as Encode>::ssz_fixed_len() * self.len()
} else {
let mut len = self.iter().map(|item| item.ssz_bytes_len()).sum();
len += BYTES_PER_LENGTH_OFFSET * self.len();
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 {
item.ssz_append(buf);
}
} else {
let mut encoder = SszEncoder::list(buf, self.len() * BYTES_PER_LENGTH_OFFSET);
for item in self {
encoder.append(item);
}
encoder.finalize();
}
}
}
};
}
impl_for_vec!(Vec<T>);
impl_for_vec!(SmallVec<[T; 1]>);
impl_for_vec!(SmallVec<[T; 2]>);
impl_for_vec!(SmallVec<[T; 3]>);
impl_for_vec!(SmallVec<[T; 4]>);
impl_for_vec!(SmallVec<[T; 5]>);
impl_for_vec!(SmallVec<[T; 6]>);
impl_for_vec!(SmallVec<[T; 7]>);
impl_for_vec!(SmallVec<[T; 8]>);
impl Encode for bool {
fn is_ssz_fixed_len() -> bool {
true
}
fn ssz_fixed_len() -> usize {
1
}
fn ssz_bytes_len(&self) -> usize {
1
}
fn ssz_append(&self, buf: &mut Vec<u8>) {
buf.extend_from_slice(&(*self as u8).to_le_bytes());
}
}
impl Encode for NonZeroUsize {
fn is_ssz_fixed_len() -> bool {
<usize as Encode>::is_ssz_fixed_len()
}
fn ssz_fixed_len() -> usize {
<usize as Encode>::ssz_fixed_len()
}
fn ssz_bytes_len(&self) -> usize {
std::mem::size_of::<usize>()
}
fn ssz_append(&self, buf: &mut Vec<u8>) {
self.get().ssz_append(buf)
}
}
impl Encode for H256 {
fn is_ssz_fixed_len() -> bool {
true
}
fn ssz_fixed_len() -> usize {
32
}
fn ssz_bytes_len(&self) -> usize {
32
}
fn ssz_append(&self, buf: &mut Vec<u8>) {
buf.extend_from_slice(self.as_bytes());
}
}
impl Encode for U256 {
fn is_ssz_fixed_len() -> bool {
true
}
fn ssz_fixed_len() -> usize {
32
}
fn ssz_bytes_len(&self) -> usize {
32
}
fn ssz_append(&self, buf: &mut Vec<u8>) {
let n = <Self as Encode>::ssz_fixed_len();
let s = buf.len();
buf.resize(s + n, 0);
self.to_little_endian(&mut buf[s..]);
}
}
impl Encode for U128 {
fn is_ssz_fixed_len() -> bool {
true
}
fn ssz_fixed_len() -> usize {
16
}
fn ssz_bytes_len(&self) -> usize {
16
}
fn ssz_append(&self, buf: &mut Vec<u8>) {
let n = <Self as Encode>::ssz_fixed_len();
let s = buf.len();
buf.resize(s + n, 0);
self.to_little_endian(&mut buf[s..]);
}
}
macro_rules! impl_encodable_for_u8_array {
($len: expr) => {
impl Encode for [u8; $len] {
fn is_ssz_fixed_len() -> bool {
true
}
fn ssz_fixed_len() -> usize {
$len
}
fn ssz_bytes_len(&self) -> usize {
$len
}
fn ssz_append(&self, buf: &mut Vec<u8>) {
buf.extend_from_slice(&self[..]);
}
}
};
}
impl_encodable_for_u8_array!(4);
impl_encodable_for_u8_array!(32);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn vec_of_u8() {
let vec: Vec<u8> = vec![];
assert_eq!(vec.as_ssz_bytes(), vec![]);
let vec: Vec<u8> = vec![1];
assert_eq!(vec.as_ssz_bytes(), vec![1]);
let vec: Vec<u8> = vec![0, 1, 2, 3];
assert_eq!(vec.as_ssz_bytes(), vec![0, 1, 2, 3]);
}
#[test]
fn vec_of_vec_of_u8() {
let vec: Vec<Vec<u8>> = vec![];
assert_eq!(vec.as_ssz_bytes(), vec![]);
let vec: Vec<Vec<u8>> = vec![vec![]];
assert_eq!(vec.as_ssz_bytes(), vec![4, 0, 0, 0]);
let vec: Vec<Vec<u8>> = vec![vec![], vec![]];
assert_eq!(vec.as_ssz_bytes(), vec![8, 0, 0, 0, 8, 0, 0, 0]);
let vec: Vec<Vec<u8>> = vec![vec![0, 1, 2], vec![11, 22, 33]];
assert_eq!(
vec.as_ssz_bytes(),
vec![8, 0, 0, 0, 11, 0, 0, 0, 0, 1, 2, 11, 22, 33]
);
}
#[test]
fn ssz_encode_option_u16() {
assert_eq!(Some(65535_u16).as_ssz_bytes(), vec![1, 0, 0, 0, 255, 255]);
let none: Option<u16> = None;
assert_eq!(none.as_ssz_bytes(), vec![0, 0, 0, 0]);
}
#[test]
fn ssz_encode_option_vec_u16() {
assert_eq!(
Some(vec![0_u16, 1]).as_ssz_bytes(),
vec![1, 0, 0, 0, 0, 0, 1, 0]
);
let none: Option<Vec<u16>> = None;
assert_eq!(none.as_ssz_bytes(), vec![0, 0, 0, 0]);
}
#[test]
fn ssz_encode_u8() {
assert_eq!(0_u8.as_ssz_bytes(), vec![0]);
assert_eq!(1_u8.as_ssz_bytes(), vec![1]);
assert_eq!(100_u8.as_ssz_bytes(), vec![100]);
assert_eq!(255_u8.as_ssz_bytes(), vec![255]);
}
#[test]
fn ssz_encode_u16() {
assert_eq!(1_u16.as_ssz_bytes(), vec![1, 0]);
assert_eq!(100_u16.as_ssz_bytes(), vec![100, 0]);
assert_eq!((1_u16 << 8).as_ssz_bytes(), vec![0, 1]);
assert_eq!(65535_u16.as_ssz_bytes(), vec![255, 255]);
}
#[test]
fn ssz_encode_u32() {
assert_eq!(1_u32.as_ssz_bytes(), vec![1, 0, 0, 0]);
assert_eq!(100_u32.as_ssz_bytes(), vec![100, 0, 0, 0]);
assert_eq!((1_u32 << 16).as_ssz_bytes(), vec![0, 0, 1, 0]);
assert_eq!((1_u32 << 24).as_ssz_bytes(), vec![0, 0, 0, 1]);
assert_eq!((!0_u32).as_ssz_bytes(), vec![255, 255, 255, 255]);
}
#[test]
fn ssz_encode_u64() {
assert_eq!(1_u64.as_ssz_bytes(), vec![1, 0, 0, 0, 0, 0, 0, 0]);
assert_eq!(
(!0_u64).as_ssz_bytes(),
vec![255, 255, 255, 255, 255, 255, 255, 255]
);
}
#[test]
fn ssz_encode_usize() {
assert_eq!(1_usize.as_ssz_bytes(), vec![1, 0, 0, 0, 0, 0, 0, 0]);
assert_eq!(
(!0_usize).as_ssz_bytes(),
vec![255, 255, 255, 255, 255, 255, 255, 255]
);
}
#[test]
fn ssz_encode_bool() {
assert_eq!(true.as_ssz_bytes(), vec![1]);
assert_eq!(false.as_ssz_bytes(), vec![0]);
}
#[test]
fn ssz_encode_h256() {
assert_eq!(H256::from(&[0; 32]).as_ssz_bytes(), vec![0; 32]);
assert_eq!(H256::from(&[1; 32]).as_ssz_bytes(), vec![1; 32]);
let bytes = vec![
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0,
];
assert_eq!(H256::from_slice(&bytes).as_ssz_bytes(), bytes);
}
#[test]
fn ssz_encode_u8_array_4() {
assert_eq!([0, 0, 0, 0].as_ssz_bytes(), vec![0; 4]);
assert_eq!([1, 0, 0, 0].as_ssz_bytes(), vec![1, 0, 0, 0]);
assert_eq!([1, 2, 3, 4].as_ssz_bytes(), vec![1, 2, 3, 4]);
}
#[test]
fn tuple() {
assert_eq!((10u8, 11u8).as_ssz_bytes(), vec![10, 11]);
assert_eq!((10u32, 11u8).as_ssz_bytes(), vec![10, 0, 0, 0, 11]);
assert_eq!((10u8, 11u8, 12u8).as_ssz_bytes(), vec![10, 11, 12]);
}
}

61
consensus/ssz/src/lib.rs Normal file
View File

@@ -0,0 +1,61 @@
//! Provides encoding (serialization) and decoding (deserialization) in the SimpleSerialize (SSZ)
//! format designed for use in Ethereum 2.0.
//!
//! 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
//!
//! ```rust
//! use ssz_derive::{Encode, Decode};
//! use ssz::{Decode, Encode};
//!
//! #[derive(PartialEq, Debug, Encode, Decode)]
//! struct Foo {
//! a: u64,
//! b: Vec<u16>,
//! }
//!
//! fn ssz_encode_decode_example() {
//! let foo = Foo {
//! a: 42,
//! b: vec![1, 3, 3, 7]
//! };
//!
//! let ssz_bytes: Vec<u8> = foo.as_ssz_bytes();
//!
//! let decoded_foo = Foo::from_ssz_bytes(&ssz_bytes).unwrap();
//!
//! assert_eq!(foo, decoded_foo);
//! }
//!
//! ```
//!
//! See `examples/` for manual implementations of the `Encode` and `Decode` traits.
mod decode;
mod encode;
pub use decode::{
impls::decode_list_of_variable_length_items, Decode, DecodeError, SszDecoder, SszDecoderBuilder,
};
pub use encode::{Encode, SszEncoder};
/// The number of bytes used to represent an offset.
pub const BYTES_PER_LENGTH_OFFSET: usize = 4;
/// The maximum value that can be represented using `BYTES_PER_LENGTH_OFFSET`.
#[cfg(target_pointer_width = "32")]
pub const MAX_LENGTH_VALUE: usize = (std::u32::MAX >> (8 * (4 - BYTES_PER_LENGTH_OFFSET))) as usize;
#[cfg(target_pointer_width = "64")]
pub const MAX_LENGTH_VALUE: usize = (std::u64::MAX >> (8 * (8 - BYTES_PER_LENGTH_OFFSET))) as usize;
/// Convenience function to SSZ encode an object supporting ssz::Encode.
///
/// Equivalent to `val.as_ssz_bytes()`.
pub fn ssz_encode<T>(val: &T) -> Vec<u8>
where
T: Encode,
{
val.as_ssz_bytes()
}

View File

@@ -0,0 +1,381 @@
use ethereum_types::H256;
use ssz::{Decode, DecodeError, Encode};
use ssz_derive::{Decode, Encode};
#[allow(clippy::zero_prefixed_literal)]
mod round_trip {
use super::*;
fn round_trip<T: Encode + Decode + std::fmt::Debug + PartialEq>(items: Vec<T>) {
for item in items {
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 bool() {
let items: Vec<bool> = vec![true, false];
round_trip(items);
}
#[test]
fn u8_array_4() {
let items: Vec<[u8; 4]> = vec![[0, 0, 0, 0], [1, 0, 0, 0], [1, 2, 3, 4], [1, 2, 0, 4]];
round_trip(items);
}
#[test]
fn h256() {
let items: Vec<H256> = vec![H256::zero(), H256::from([1; 32]), H256::random()];
round_trip(items);
}
#[test]
fn vec_of_h256() {
let items: Vec<Vec<H256>> = vec![
vec![],
vec![H256::zero(), H256::from([1; 32]), H256::random()],
];
round_trip(items);
}
#[test]
fn vec_u16() {
let items: Vec<Vec<u16>> = vec![
vec![],
vec![255],
vec![0, 1, 2],
vec![100; 64],
vec![255, 0, 255],
];
round_trip(items);
}
#[test]
fn vec_of_vec_u16() {
let items: Vec<Vec<Vec<u16>>> = vec![
vec![],
vec![vec![]],
vec![vec![1, 2, 3]],
vec![vec![], vec![]],
vec![vec![], vec![1, 2, 3]],
vec![vec![1, 2, 3], vec![1, 2, 3]],
vec![vec![1, 2, 3], vec![], vec![1, 2, 3]],
vec![vec![], vec![], vec![1, 2, 3]],
vec![vec![], vec![1], vec![1, 2, 3]],
vec![vec![], vec![1], vec![1, 2, 3]],
];
round_trip(items);
}
#[derive(Debug, PartialEq, Encode, Decode)]
struct FixedLen {
a: u16,
b: u64,
c: u32,
}
#[test]
fn fixed_len_struct_encoding() {
let items: Vec<FixedLen> = vec![
FixedLen { a: 0, b: 0, c: 0 },
FixedLen { a: 1, b: 1, c: 1 },
FixedLen { a: 1, b: 0, c: 1 },
];
let expected_encodings = vec![
// | u16--| u64----------------------------| u32----------|
vec![00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00],
vec![01, 00, 01, 00, 00, 00, 00, 00, 00, 00, 01, 00, 00, 00],
vec![01, 00, 00, 00, 00, 00, 00, 00, 00, 00, 01, 00, 00, 00],
];
for i in 0..items.len() {
assert_eq!(
items[i].as_ssz_bytes(),
expected_encodings[i],
"Failed on {}",
i
);
}
}
#[test]
fn fixed_len_excess_bytes() {
let fixed = FixedLen { a: 1, b: 2, c: 3 };
let mut bytes = fixed.as_ssz_bytes();
bytes.append(&mut vec![0]);
assert_eq!(
FixedLen::from_ssz_bytes(&bytes),
Err(DecodeError::InvalidByteLength {
len: 15,
expected: 14,
})
);
}
#[test]
fn vec_of_fixed_len_struct() {
let items: Vec<FixedLen> = vec![
FixedLen { a: 0, b: 0, c: 0 },
FixedLen { a: 1, b: 1, c: 1 },
FixedLen { a: 1, b: 0, c: 1 },
];
round_trip(items);
}
#[derive(Debug, PartialEq, Encode, Decode)]
struct VariableLen {
a: u16,
b: Vec<u16>,
c: u32,
}
#[test]
fn offset_into_fixed_bytes() {
let bytes = vec![
// 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// | offset | u32 | variable
01, 00, 09, 00, 00, 00, 01, 00, 00, 00, 00, 00, 01, 00, 02, 00,
];
assert_eq!(
VariableLen::from_ssz_bytes(&bytes),
Err(DecodeError::OffsetIntoFixedPortion(9))
);
}
#[test]
fn variable_len_excess_bytes() {
let variable = VariableLen {
a: 1,
b: vec![2],
c: 3,
};
let mut bytes = variable.as_ssz_bytes();
bytes.append(&mut vec![0]);
// The error message triggered is not so helpful, it's caught by a side-effect. Just
// checking there is _some_ error is fine.
assert!(VariableLen::from_ssz_bytes(&bytes).is_err());
}
#[test]
fn first_offset_skips_byte() {
let bytes = vec![
// 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// | offset | u32 | variable
01, 00, 11, 00, 00, 00, 01, 00, 00, 00, 00, 00, 01, 00, 02, 00,
];
assert_eq!(
VariableLen::from_ssz_bytes(&bytes),
Err(DecodeError::OffsetSkipsVariableBytes(11))
);
}
#[test]
fn variable_len_struct_encoding() {
let items: Vec<VariableLen> = vec![
VariableLen {
a: 0,
b: vec![],
c: 0,
},
VariableLen {
a: 1,
b: vec![0],
c: 1,
},
VariableLen {
a: 1,
b: vec![0, 1, 2],
c: 1,
},
];
let expected_encodings = vec![
// 00..................................09
// | u16--| vec offset-----| u32------------| vec payload --------|
vec![00, 00, 10, 00, 00, 00, 00, 00, 00, 00],
vec![01, 00, 10, 00, 00, 00, 01, 00, 00, 00, 00, 00],
vec![
01, 00, 10, 00, 00, 00, 01, 00, 00, 00, 00, 00, 01, 00, 02, 00,
],
];
for i in 0..items.len() {
assert_eq!(
items[i].as_ssz_bytes(),
expected_encodings[i],
"Failed on {}",
i
);
}
}
#[test]
fn vec_of_variable_len_struct() {
let items: Vec<VariableLen> = vec![
VariableLen {
a: 0,
b: vec![],
c: 0,
},
VariableLen {
a: 255,
b: vec![0, 1, 2, 3],
c: 99,
},
VariableLen {
a: 255,
b: vec![0],
c: 99,
},
VariableLen {
a: 50,
b: vec![0],
c: 0,
},
];
round_trip(items);
}
#[derive(Debug, PartialEq, Encode, Decode)]
struct ThreeVariableLen {
a: u16,
b: Vec<u16>,
c: Vec<u16>,
d: Vec<u16>,
}
#[test]
fn three_variable_len() {
let vec: Vec<ThreeVariableLen> = vec![ThreeVariableLen {
a: 42,
b: vec![0],
c: vec![1],
d: vec![2],
}];
round_trip(vec);
}
#[test]
fn offsets_decreasing() {
let bytes = vec![
// 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// | offset | offset | offset | variable
01, 00, 14, 00, 00, 00, 15, 00, 00, 00, 14, 00, 00, 00, 00, 00,
];
assert_eq!(
ThreeVariableLen::from_ssz_bytes(&bytes),
Err(DecodeError::OffsetsAreDecreasing(14))
);
}
#[derive(Debug, PartialEq, Encode, Decode)]
struct TwoVariableLenOptions {
a: u16,
b: Option<u16>,
c: Option<Vec<u16>>,
d: Option<Vec<u16>>,
}
#[test]
fn two_variable_len_options_encoding() {
let s = TwoVariableLenOptions {
a: 42,
b: None,
c: Some(vec![0]),
d: None,
};
let bytes = vec![
// 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
// | option<u16> | offset | offset | option<u16 | 1st list
42, 00, 14, 00, 00, 00, 18, 00, 00, 00, 24, 00, 00, 00, 00, 00, 00, 00, 01, 00, 00, 00,
// 23 24 25 26 27
// | 2nd list
00, 00, 00, 00, 00, 00,
];
assert_eq!(s.as_ssz_bytes(), bytes);
}
#[test]
fn two_variable_len_options_round_trip() {
let vec: Vec<TwoVariableLenOptions> = vec![
TwoVariableLenOptions {
a: 42,
b: Some(12),
c: Some(vec![0]),
d: Some(vec![1]),
},
TwoVariableLenOptions {
a: 42,
b: Some(12),
c: Some(vec![0]),
d: None,
},
TwoVariableLenOptions {
a: 42,
b: None,
c: Some(vec![0]),
d: None,
},
TwoVariableLenOptions {
a: 42,
b: None,
c: None,
d: None,
},
];
round_trip(vec);
}
#[test]
fn tuple_u8_u16() {
let vec: Vec<(u8, u16)> = vec![
(0, 0),
(0, 1),
(1, 0),
(u8::max_value(), u16::max_value()),
(0, u16::max_value()),
(u8::max_value(), 0),
(42, 12301),
];
round_trip(vec);
}
#[test]
fn tuple_vec_vec() {
let vec: Vec<(u64, Vec<u8>, Vec<Vec<u16>>)> = vec![
(0, vec![], vec![vec![]]),
(99, vec![101], vec![vec![], vec![]]),
(
42,
vec![12, 13, 14],
vec![vec![99, 98, 97, 96], vec![42, 44, 46, 48, 50]],
),
];
round_trip(vec);
}
}