diff --git a/Cargo.lock b/Cargo.lock index 754e2b9520..5bb73bdf45 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1674,6 +1674,7 @@ version = "0.4.1" dependencies = [ "eth2_ssz_derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "ethereum-types 0.12.1", + "itertools", "smallvec", ] diff --git a/consensus/ssz/Cargo.toml b/consensus/ssz/Cargo.toml index 7ba3e0678c..a153c2efc1 100644 --- a/consensus/ssz/Cargo.toml +++ b/consensus/ssz/Cargo.toml @@ -14,7 +14,8 @@ eth2_ssz_derive = "0.3.0" [dependencies] ethereum-types = "0.12.1" -smallvec = "1.6.1" +smallvec = { version = "1.6.1", features = ["const_generics"] } +itertools = "0.10.3" [features] arbitrary = ["ethereum-types/arbitrary"] diff --git a/consensus/ssz/src/decode.rs b/consensus/ssz/src/decode.rs index 604cc68d7b..10b3573b16 100644 --- a/consensus/ssz/src/decode.rs +++ b/consensus/ssz/src/decode.rs @@ -5,6 +5,7 @@ use std::cmp::Ordering; type SmallVec8 = SmallVec<[T; 8]>; pub mod impls; +pub mod try_from_iter; /// Returned when SSZ decoding fails. #[derive(Debug, PartialEq, Clone)] diff --git a/consensus/ssz/src/decode/impls.rs b/consensus/ssz/src/decode/impls.rs index 9dae5be63d..078f0a96dd 100644 --- a/consensus/ssz/src/decode/impls.rs +++ b/consensus/ssz/src/decode/impls.rs @@ -1,6 +1,8 @@ use super::*; +use crate::decode::try_from_iter::{TryCollect, TryFromIter}; use core::num::NonZeroUsize; use ethereum_types::{H160, H256, U128, U256}; +use itertools::process_results; use smallvec::SmallVec; use std::collections::BTreeMap; use std::iter::{self, FromIterator}; @@ -397,14 +399,14 @@ macro_rules! impl_for_vec { } impl_for_vec!(Vec, 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)); +impl_for_vec!(SmallVec<[T; 1]>, None); +impl_for_vec!(SmallVec<[T; 2]>, None); +impl_for_vec!(SmallVec<[T; 3]>, None); +impl_for_vec!(SmallVec<[T; 4]>, None); +impl_for_vec!(SmallVec<[T; 5]>, None); +impl_for_vec!(SmallVec<[T; 6]>, None); +impl_for_vec!(SmallVec<[T; 7]>, None); +impl_for_vec!(SmallVec<[T; 8]>, None); impl Decode for BTreeMap where @@ -434,12 +436,14 @@ where /// The `ssz::SszDecoder` can also perform this functionality, however this function is /// 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>( +pub fn decode_list_of_variable_length_items>( bytes: &[u8], max_len: Option, ) -> Result { if bytes.is_empty() { - return Ok(Container::from_iter(iter::empty())); + return Container::try_from_iter(iter::empty()).map_err(|e| { + DecodeError::BytesInvalid(format!("Error trying to collect empty list: {:?}", e)) + }); } let first_offset = read_offset(bytes)?; @@ -459,8 +463,8 @@ pub fn decode_list_of_variable_length_items: Sized { + type Error: Debug; + + fn try_from_iter(iter: I) -> Result + where + I: IntoIterator; +} + +// It would be nice to be able to do a blanket impl, e.g. +// +// `impl TryFromIter for C where C: FromIterator` +// +// However this runs into trait coherence issues due to the type parameter `T` on `TryFromIter`. +// +// E.g. If we added an impl downstream for `List` then another crate downstream of that +// could legally add an impl of `FromIterator for List` which would create +// two conflicting implementations for `List`. Hence the `List` impl is disallowed +// by the compiler in the presence of the blanket impl. That's obviously annoying, so we opt to +// abandon the blanket impl in favour of impls for selected types. +impl TryFromIter for Vec { + type Error = Infallible; + + fn try_from_iter(iter: I) -> Result + where + I: IntoIterator, + { + Ok(Self::from_iter(iter)) + } +} + +impl TryFromIter for SmallVec<[T; N]> { + type Error = Infallible; + + fn try_from_iter(iter: I) -> Result + where + I: IntoIterator, + { + Ok(Self::from_iter(iter)) + } +} + +impl TryFromIter<(K, V)> for BTreeMap +where + K: Ord, +{ + type Error = Infallible; + + fn try_from_iter(iter: I) -> Result + where + I: IntoIterator, + { + Ok(Self::from_iter(iter)) + } +} + +/// Partial variant of `collect`. +pub trait TryCollect: Iterator { + fn try_collect(self) -> Result + where + C: TryFromIter; +} + +impl TryCollect for I +where + I: Iterator, +{ + fn try_collect(self) -> Result + where + C: TryFromIter, + { + C::try_from_iter(self) + } +} diff --git a/consensus/ssz/src/lib.rs b/consensus/ssz/src/lib.rs index df00c514e2..e71157a3ee 100644 --- a/consensus/ssz/src/lib.rs +++ b/consensus/ssz/src/lib.rs @@ -40,8 +40,8 @@ pub mod legacy; mod union_selector; pub use decode::{ - impls::decode_list_of_variable_length_items, read_offset, split_union_bytes, Decode, - DecodeError, SszDecoder, SszDecoderBuilder, + impls::decode_list_of_variable_length_items, read_offset, split_union_bytes, + try_from_iter::TryFromIter, Decode, DecodeError, SszDecoder, SszDecoderBuilder, }; pub use encode::{encode_length, Encode, SszEncoder}; pub use union_selector::UnionSelector;