mirror of
https://github.com/sigp/lighthouse.git
synced 2026-05-08 17:26:04 +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:
20
consensus/ssz/Cargo.toml
Normal file
20
consensus/ssz/Cargo.toml
Normal 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
3
consensus/ssz/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# simpleserialize (ssz)
|
||||
|
||||
[<img src="https://img.shields.io/crates/v/eth2_ssz">](https://crates.io/crates/eth2_ssz)
|
||||
15
consensus/ssz/examples/large_list.rs
Normal file
15
consensus/ssz/examples/large_list.rs
Normal 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());
|
||||
}
|
||||
31
consensus/ssz/examples/large_list_of_structs.rs
Normal file
31
consensus/ssz/examples/large_list_of_structs.rs
Normal 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());
|
||||
}
|
||||
73
consensus/ssz/examples/struct_definition.rs
Normal file
73
consensus/ssz/examples/struct_definition.rs
Normal 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
310
consensus/ssz/src/decode.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
712
consensus/ssz/src/decode/impls.rs
Normal file
712
consensus/ssz/src/decode/impls.rs
Normal 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
202
consensus/ssz/src/encode.rs
Normal 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]);
|
||||
}
|
||||
}
|
||||
533
consensus/ssz/src/encode/impls.rs
Normal file
533
consensus/ssz/src/encode/impls.rs
Normal 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
61
consensus/ssz/src/lib.rs
Normal 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()
|
||||
}
|
||||
381
consensus/ssz/tests/tests.rs
Normal file
381
consensus/ssz/tests/tests.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user