Add optimized SSZ decoding for fixed-len items (#865)

* Add custom SSZ decode for Validator

* Move efficient decode into macro

* Don't allocate SSZ offset to heap

* Use smallvec in SszDecoder

* Fix test compile error
This commit is contained in:
Paul Hauner
2020-03-04 11:45:01 +11:00
committed by GitHub
parent 58fb144276
commit 871163aecc
6 changed files with 79 additions and 32 deletions

View File

@@ -14,3 +14,4 @@ eth2_ssz_derive = "0.1.0"
[dependencies]
ethereum-types = "0.8.0"
smallvec = "1.2.0"

View File

@@ -1,4 +1,7 @@
use super::*;
use smallvec::{smallvec, SmallVec};
type SmallVec8<T> = SmallVec<[T; 8]>;
pub mod impls;
@@ -62,8 +65,8 @@ pub struct Offset {
/// See [`SszDecoder`](struct.SszDecoder.html) for usage examples.
pub struct SszDecoderBuilder<'a> {
bytes: &'a [u8],
items: Vec<&'a [u8]>,
offsets: Vec<Offset>,
items: SmallVec8<&'a [u8]>,
offsets: SmallVec8<Offset>,
items_index: usize,
}
@@ -73,8 +76,8 @@ impl<'a> SszDecoderBuilder<'a> {
pub fn new(bytes: &'a [u8]) -> Self {
Self {
bytes,
items: vec![],
offsets: vec![],
items: smallvec![],
offsets: smallvec![],
items_index: 0,
}
}
@@ -204,7 +207,7 @@ impl<'a> SszDecoderBuilder<'a> {
///
/// ```
pub struct SszDecoder<'a> {
items: Vec<&'a [u8]>,
items: SmallVec8<&'a [u8]>,
}
impl<'a> SszDecoder<'a> {

View File

@@ -115,7 +115,7 @@ impl<'a> SszEncoder<'a> {
item.ssz_append(&mut self.buf);
} else {
self.buf
.append(&mut encode_length(self.offset + self.variable_bytes.len()));
.extend_from_slice(&encode_length(self.offset + self.variable_bytes.len()));
item.ssz_append(&mut self.variable_bytes);
}
@@ -132,17 +132,17 @@ impl<'a> SszEncoder<'a> {
}
}
/// Encode `index` as a little-endian byte vec of `BYTES_PER_LENGTH_OFFSET` length.
/// 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) -> Vec<u8> {
pub fn encode_union_index(index: usize) -> [u8; BYTES_PER_LENGTH_OFFSET] {
encode_length(index)
}
/// Encode `len` as a little-endian byte vec of `BYTES_PER_LENGTH_OFFSET` length.
/// 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) -> Vec<u8> {
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.
//
@@ -166,7 +166,9 @@ pub fn encode_length(len: usize) -> Vec<u8> {
// If you have a different opinion, feel free to start an issue and tag @paulhauner.
debug_assert!(len <= MAX_LENGTH_VALUE);
len.to_le_bytes()[0..BYTES_PER_LENGTH_OFFSET].to_vec()
let mut bytes = [0; BYTES_PER_LENGTH_OFFSET];
bytes.copy_from_slice(&len.to_le_bytes()[0..BYTES_PER_LENGTH_OFFSET]);
bytes
}
#[cfg(test)]
@@ -175,13 +177,13 @@ mod tests {
#[test]
fn test_encode_length() {
assert_eq!(encode_length(0), vec![0; 4]);
assert_eq!(encode_length(0), [0; 4]);
assert_eq!(encode_length(1), vec![1, 0, 0, 0]);
assert_eq!(encode_length(1), [1, 0, 0, 0]);
assert_eq!(
encode_length(MAX_LENGTH_VALUE),
vec![255; BYTES_PER_LENGTH_OFFSET]
[255; BYTES_PER_LENGTH_OFFSET]
);
}
@@ -195,6 +197,6 @@ mod tests {
#[test]
#[cfg(not(debug_assertions))]
fn test_encode_length_above_max_not_debug_does_not_panic() {
assert_eq!(encode_length(MAX_LENGTH_VALUE + 1), vec![0; 4]);
assert_eq!(&encode_length(MAX_LENGTH_VALUE + 1)[..], &[0; 4]);
}
}

View File

@@ -221,9 +221,9 @@ impl<T: Encode> Encode for Option<T> {
fn ssz_append(&self, buf: &mut Vec<u8>) {
match self {
None => buf.append(&mut encode_union_index(0)),
None => buf.extend_from_slice(&encode_union_index(0)),
Some(t) => {
buf.append(&mut encode_union_index(1));
buf.extend_from_slice(&encode_union_index(1));
t.ssz_append(buf);
}
}

View File

@@ -1,4 +1,4 @@
#![recursion_limit = "128"]
#![recursion_limit = "256"]
//! Provides procedural derive macros for the `Encode` and `Decode` traits of the `eth2_ssz` crate.
//!
//! Supports field attributes, see each derive macro for more information.
@@ -173,6 +173,7 @@ pub fn ssz_decode_derive(input: TokenStream) -> TokenStream {
};
let mut register_types = vec![];
let mut fixed_decodes = vec![];
let mut decodes = vec![];
let mut is_fixed_lens = vec![];
let mut fixed_lens = vec![];
@@ -187,6 +188,10 @@ pub fn ssz_decode_derive(input: TokenStream) -> TokenStream {
decodes.push(quote! {
#ident: <_>::default()
});
fixed_decodes.push(quote! {
#ident: <_>::default()
});
} else {
let ty = &field.ty;
@@ -198,6 +203,10 @@ pub fn ssz_decode_derive(input: TokenStream) -> TokenStream {
#ident: decoder.decode_next()?
});
fixed_decodes.push(quote! {
#ident: decode_field!(#ty)
});
is_fixed_lens.push(quote! {
<#ty as ssz::Decode>::is_ssz_fixed_len()
});
@@ -232,19 +241,50 @@ pub fn ssz_decode_derive(input: TokenStream) -> TokenStream {
}
fn from_ssz_bytes(bytes: &[u8]) -> std::result::Result<Self, ssz::DecodeError> {
let mut builder = ssz::SszDecoderBuilder::new(bytes);
if <Self as ssz::Decode>::is_ssz_fixed_len() {
if bytes.len() != <Self as ssz::Decode>::ssz_fixed_len() {
return Err(ssz::DecodeError::InvalidByteLength {
len: bytes.len(),
expected: <Self as ssz::Decode>::ssz_fixed_len(),
});
}
#(
#register_types
)*
let mut start = 0;
let mut end = start;
let mut decoder = builder.build()?;
macro_rules! decode_field {
($type: ty) => {{
start = end;
end += <$type as ssz::Decode>::ssz_fixed_len();
let slice = bytes.get(start..end)
.ok_or_else(|| ssz::DecodeError::InvalidByteLength {
len: bytes.len(),
expected: end
})?;
<$type as ssz::Decode>::from_ssz_bytes(slice)?
}};
}
Ok(Self {
#(
#fixed_decodes,
)*
})
} else {
let mut builder = ssz::SszDecoderBuilder::new(bytes);
Ok(Self {
#(
#decodes,
#register_types
)*
})
let mut decoder = builder.build()?;
Ok(Self {
#(
#decodes,
)*
})
}
}
}
};