From 6a870d468cbae4da17db45fc9861309d0972c7c2 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 8 Sep 2019 12:23:37 -0400 Subject: [PATCH] Add ssz_fixed_len method to ssz::Encode --- beacon_node/eth2-libp2p/src/rpc/methods.rs | 35 +++++++- eth2/types/src/lib.rs | 5 +- eth2/types/src/slot_epoch_macros.rs | 4 + eth2/utils/bls/src/macros.rs | 4 + eth2/utils/ssz/examples/struct_definition.rs | 7 ++ eth2/utils/ssz/src/encode.rs | 2 + eth2/utils/ssz/src/encode/impls.rs | 68 ++++++++++++++ eth2/utils/ssz/src/lib.rs | 1 - eth2/utils/ssz/src/macros.rs | 95 -------------------- eth2/utils/ssz/tests/tests.rs | 1 + eth2/utils/ssz_derive/src/lib.rs | 25 +++++- eth2/utils/ssz_types/src/bitfield.rs | 35 ++++++++ eth2/utils/ssz_types/src/fixed_vector.rs | 5 ++ eth2/utils/ssz_types/src/variable_list.rs | 5 ++ tests/ef_tests/src/cases/ssz_static.rs | 1 + 15 files changed, 191 insertions(+), 102 deletions(-) diff --git a/beacon_node/eth2-libp2p/src/rpc/methods.rs b/beacon_node/eth2-libp2p/src/rpc/methods.rs index 49813abe9..ee8ad4860 100644 --- a/beacon_node/eth2-libp2p/src/rpc/methods.rs +++ b/beacon_node/eth2-libp2p/src/rpc/methods.rs @@ -1,6 +1,5 @@ //!Available RPC methods types and ids. -use ssz::{impl_decode_via_from, impl_encode_via_from}; use ssz_derive::{Decode, Encode}; use types::{Epoch, Hash256, Slot}; @@ -66,8 +65,38 @@ impl Into for GoodbyeReason { } } -impl_encode_via_from!(GoodbyeReason, u64); -impl_decode_via_from!(GoodbyeReason, u64); +impl ssz::Encode for GoodbyeReason { + fn is_ssz_fixed_len() -> bool { + ::is_ssz_fixed_len() + } + + fn ssz_fixed_len() -> usize { + ::ssz_fixed_len() + } + + fn ssz_bytes_len(&self) -> usize { + 0_u64.ssz_bytes_len() + } + + fn ssz_append(&self, buf: &mut Vec) { + let conv: u64 = self.clone().into(); + conv.ssz_append(buf) + } +} + +impl ssz::Decode for GoodbyeReason { + fn is_ssz_fixed_len() -> bool { + ::is_ssz_fixed_len() + } + + fn ssz_fixed_len() -> usize { + ::ssz_fixed_len() + } + + fn from_ssz_bytes(bytes: &[u8]) -> Result { + u64::from_ssz_bytes(bytes).and_then(|n| Ok(n.into())) + } +} /// Request a number of beacon block roots from a peer. #[derive(Encode, Decode, Clone, Debug, PartialEq)] diff --git a/eth2/types/src/lib.rs b/eth2/types/src/lib.rs index 3edf8b36b..d1eaa393f 100644 --- a/eth2/types/src/lib.rs +++ b/eth2/types/src/lib.rs @@ -86,5 +86,8 @@ pub type AttesterMap = HashMap<(u64, u64), Vec>; /// Maps a slot to a block proposer. pub type ProposerMap = HashMap; -pub use bls::{AggregatePublicKey, AggregateSignature, Keypair, PublicKey, SecretKey, Signature}; +pub use bls::{ + AggregatePublicKey, AggregateSignature, Keypair, PublicKey, PublicKeyBytes, SecretKey, + Signature, SignatureBytes, +}; pub use ssz_types::{typenum, typenum::Unsigned, BitList, BitVector, FixedVector, VariableList}; diff --git a/eth2/types/src/slot_epoch_macros.rs b/eth2/types/src/slot_epoch_macros.rs index 62ca6b3af..3bd54ee2d 100644 --- a/eth2/types/src/slot_epoch_macros.rs +++ b/eth2/types/src/slot_epoch_macros.rs @@ -201,6 +201,10 @@ macro_rules! impl_ssz { ::ssz_fixed_len() } + fn ssz_bytes_len(&self) -> usize { + 0_u64.ssz_bytes_len() + } + fn ssz_append(&self, buf: &mut Vec) { self.0.ssz_append(buf) } diff --git a/eth2/utils/bls/src/macros.rs b/eth2/utils/bls/src/macros.rs index 09838b73e..e8bd3dd04 100644 --- a/eth2/utils/bls/src/macros.rs +++ b/eth2/utils/bls/src/macros.rs @@ -9,6 +9,10 @@ macro_rules! impl_ssz { $byte_size } + fn ssz_bytes_len(&self) -> usize { + $byte_size + } + fn ssz_append(&self, buf: &mut Vec) { buf.append(&mut self.as_bytes()) } diff --git a/eth2/utils/ssz/examples/struct_definition.rs b/eth2/utils/ssz/examples/struct_definition.rs index fa3ed2a64..0971e21da 100644 --- a/eth2/utils/ssz/examples/struct_definition.rs +++ b/eth2/utils/ssz/examples/struct_definition.rs @@ -12,6 +12,13 @@ impl Encode for Foo { ::is_ssz_fixed_len() && as Encode>::is_ssz_fixed_len() } + fn ssz_bytes_len(&self) -> usize { + ::ssz_fixed_len() + + ssz::BYTES_PER_LENGTH_OFFSET + + ::ssz_fixed_len() + + self.b.ssz_bytes_len() + } + fn ssz_append(&self, buf: &mut Vec) { let offset = ::ssz_fixed_len() + as Encode>::ssz_fixed_len() diff --git a/eth2/utils/ssz/src/encode.rs b/eth2/utils/ssz/src/encode.rs index 6ceb08deb..5113fb71a 100644 --- a/eth2/utils/ssz/src/encode.rs +++ b/eth2/utils/ssz/src/encode.rs @@ -27,6 +27,8 @@ pub trait Encode { BYTES_PER_LENGTH_OFFSET } + 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. diff --git a/eth2/utils/ssz/src/encode/impls.rs b/eth2/utils/ssz/src/encode/impls.rs index 3d68d8911..d25e79370 100644 --- a/eth2/utils/ssz/src/encode/impls.rs +++ b/eth2/utils/ssz/src/encode/impls.rs @@ -13,6 +13,10 @@ macro_rules! impl_encodable_for_uint { $bit_size / 8 } + fn ssz_bytes_len(&self) -> usize { + $bit_size / 8 + } + fn ssz_append(&self, buf: &mut Vec) { buf.extend_from_slice(&self.to_le_bytes()); } @@ -58,6 +62,23 @@ macro_rules! impl_encode_for_tuples { } } + fn ssz_bytes_len(&self) -> usize { + if ::is_ssz_fixed_len() { + ::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) { let offset = $( <$T as Encode>::ssz_fixed_len() + @@ -185,6 +206,19 @@ impl Encode for Option { false } + fn ssz_bytes_len(&self) -> usize { + if let Some(some) = self { + let len = if ::is_ssz_fixed_len() { + ::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) { match self { None => buf.append(&mut encode_union_index(0)), @@ -201,6 +235,16 @@ impl Encode for Vec { false } + fn ssz_bytes_len(&self) -> usize { + if ::is_ssz_fixed_len() { + ::ssz_fixed_len() * self.len() + } else { + let mut len = self.into_iter().map(|item| item.ssz_bytes_len()).sum(); + len += BYTES_PER_LENGTH_OFFSET * self.len(); + len + } + } + fn ssz_append(&self, buf: &mut Vec) { if T::is_ssz_fixed_len() { buf.reserve(T::ssz_fixed_len() * self.len()); @@ -229,6 +273,10 @@ impl Encode for bool { 1 } + fn ssz_bytes_len(&self) -> usize { + 1 + } + fn ssz_append(&self, buf: &mut Vec) { buf.extend_from_slice(&(*self as u8).to_le_bytes()); } @@ -243,6 +291,10 @@ impl Encode for NonZeroUsize { ::ssz_fixed_len() } + fn ssz_bytes_len(&self) -> usize { + std::mem::size_of::() + } + fn ssz_append(&self, buf: &mut Vec) { self.get().ssz_append(buf) } @@ -257,6 +309,10 @@ impl Encode for H256 { 32 } + fn ssz_bytes_len(&self) -> usize { + 32 + } + fn ssz_append(&self, buf: &mut Vec) { buf.extend_from_slice(self.as_bytes()); } @@ -271,6 +327,10 @@ impl Encode for U256 { 32 } + fn ssz_bytes_len(&self) -> usize { + 32 + } + fn ssz_append(&self, buf: &mut Vec) { let n = ::ssz_fixed_len(); let s = buf.len(); @@ -289,6 +349,10 @@ impl Encode for U128 { 16 } + fn ssz_bytes_len(&self) -> usize { + 16 + } + fn ssz_append(&self, buf: &mut Vec) { let n = ::ssz_fixed_len(); let s = buf.len(); @@ -309,6 +373,10 @@ macro_rules! impl_encodable_for_u8_array { $len } + fn ssz_bytes_len(&self) -> usize { + $len + } + fn ssz_append(&self, buf: &mut Vec) { buf.extend_from_slice(&self[..]); } diff --git a/eth2/utils/ssz/src/lib.rs b/eth2/utils/ssz/src/lib.rs index 696d36cbf..115633889 100644 --- a/eth2/utils/ssz/src/lib.rs +++ b/eth2/utils/ssz/src/lib.rs @@ -36,7 +36,6 @@ mod decode; mod encode; -mod macros; pub use decode::{ impls::decode_list_of_variable_length_items, Decode, DecodeError, SszDecoder, SszDecoderBuilder, diff --git a/eth2/utils/ssz/src/macros.rs b/eth2/utils/ssz/src/macros.rs index 04147a805..8b1378917 100644 --- a/eth2/utils/ssz/src/macros.rs +++ b/eth2/utils/ssz/src/macros.rs @@ -1,96 +1 @@ -/// Implements `Encode` for `$impl_type` using an implementation of `From<$impl_type> for -/// $from_type`. -/// -/// In effect, this allows for easy implementation of `Encode` for some type that implements a -/// `From` conversion into another type that already has `Encode` implemented. -#[macro_export] -macro_rules! impl_encode_via_from { - ($impl_type: ty, $from_type: ty) => { - impl ssz::Encode for $impl_type { - fn is_ssz_fixed_len() -> bool { - <$from_type as ssz::Encode>::is_ssz_fixed_len() - } - fn ssz_fixed_len() -> usize { - <$from_type as ssz::Encode>::ssz_fixed_len() - } - - fn ssz_append(&self, buf: &mut Vec) { - let conv: $from_type = self.clone().into(); - - conv.ssz_append(buf) - } - } - }; -} - -/// Implements `Decode` for `$impl_type` using an implementation of `From<$impl_type> for -/// $from_type`. -/// -/// In effect, this allows for easy implementation of `Decode` for some type that implements a -/// `From` conversion into another type that already has `Decode` implemented. -#[macro_export] -macro_rules! impl_decode_via_from { - ($impl_type: ty, $from_type: tt) => { - impl ssz::Decode for $impl_type { - fn is_ssz_fixed_len() -> bool { - <$from_type as ssz::Decode>::is_ssz_fixed_len() - } - - fn ssz_fixed_len() -> usize { - <$from_type as ssz::Decode>::ssz_fixed_len() - } - - fn from_ssz_bytes(bytes: &[u8]) -> Result { - $from_type::from_ssz_bytes(bytes).and_then(|dec| Ok(dec.into())) - } - } - }; -} - -#[cfg(test)] -mod tests { - use self::ssz::{Decode, Encode}; - use crate as ssz; - - #[derive(PartialEq, Debug, Clone, Copy)] - struct Wrapper(u64); - - impl From for Wrapper { - fn from(x: u64) -> Wrapper { - Wrapper(x) - } - } - - impl From for u64 { - fn from(x: Wrapper) -> u64 { - x.0 - } - } - - impl_encode_via_from!(Wrapper, u64); - impl_decode_via_from!(Wrapper, u64); - - #[test] - fn impl_encode_via_from() { - let check_encode = |a: u64, b: Wrapper| assert_eq!(a.as_ssz_bytes(), b.as_ssz_bytes()); - - check_encode(0, Wrapper(0)); - check_encode(1, Wrapper(1)); - check_encode(42, Wrapper(42)); - } - - #[test] - fn impl_decode_via_from() { - let check_decode = |bytes: Vec| { - let a = u64::from_ssz_bytes(&bytes).unwrap(); - let b = Wrapper::from_ssz_bytes(&bytes).unwrap(); - - assert_eq!(a, b.into()) - }; - - check_decode(vec![0, 0, 0, 0, 0, 0, 0, 0]); - check_decode(vec![1, 0, 0, 0, 0, 0, 0, 0]); - check_decode(vec![1, 0, 0, 0, 2, 0, 0, 0]); - } -} diff --git a/eth2/utils/ssz/tests/tests.rs b/eth2/utils/ssz/tests/tests.rs index c19e36662..26f2f53ef 100644 --- a/eth2/utils/ssz/tests/tests.rs +++ b/eth2/utils/ssz/tests/tests.rs @@ -8,6 +8,7 @@ mod round_trip { fn round_trip(items: Vec) { 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)); } } diff --git a/eth2/utils/ssz_derive/src/lib.rs b/eth2/utils/ssz_derive/src/lib.rs index 47d96859e..5bdb9ca9d 100644 --- a/eth2/utils/ssz_derive/src/lib.rs +++ b/eth2/utils/ssz_derive/src/lib.rs @@ -81,9 +81,12 @@ pub fn ssz_encode_derive(input: TokenStream) -> TokenStream { }; let field_idents = get_serializable_named_field_idents(&struct_data); + let field_idents_a = get_serializable_named_field_idents(&struct_data); let field_types_a = get_serializable_field_types(&struct_data); let field_types_b = field_types_a.clone(); - let field_types_c = field_types_a.clone(); + let field_types_d = field_types_a.clone(); + let field_types_e = field_types_a.clone(); + let field_types_f = field_types_a.clone(); let output = quote! { impl #impl_generics ssz::Encode for #name #ty_generics #where_clause { @@ -105,9 +108,27 @@ pub fn ssz_encode_derive(input: TokenStream) -> TokenStream { } } + fn ssz_bytes_len(&self) -> usize { + if ::is_ssz_fixed_len() { + ::ssz_fixed_len() + } else { + let mut len = 0; + #( + if <#field_types_d as ssz::Encode>::is_ssz_fixed_len() { + len += <#field_types_e as ssz::Encode>::ssz_fixed_len(); + } else { + len += ssz::BYTES_PER_LENGTH_OFFSET; + len += self.#field_idents_a.ssz_bytes_len(); + } + )* + + len + } + } + fn ssz_append(&self, buf: &mut Vec) { let offset = #( - <#field_types_c as ssz::Encode>::ssz_fixed_len() + + <#field_types_f as ssz::Encode>::ssz_fixed_len() + )* 0; diff --git a/eth2/utils/ssz_types/src/bitfield.rs b/eth2/utils/ssz_types/src/bitfield.rs index 197426046..dbe1addbe 100644 --- a/eth2/utils/ssz_types/src/bitfield.rs +++ b/eth2/utils/ssz_types/src/bitfield.rs @@ -476,6 +476,12 @@ impl Encode for Bitfield> { false } + fn ssz_bytes_len(&self) -> usize { + // We could likely do better than turning this into bytes and reading the length, however + // it is kept this way for simplicity. + self.clone().into_bytes().len() + } + fn ssz_append(&self, buf: &mut Vec) { buf.append(&mut self.clone().into_bytes()) } @@ -498,6 +504,10 @@ impl Encode for Bitfield> { true } + fn ssz_bytes_len(&self) -> usize { + self.as_slice().len() + } + fn ssz_fixed_len() -> usize { bytes_for_bit_len(N::to_usize()) } @@ -616,6 +626,7 @@ mod bitvector { pub type BitVector4 = BitVector; pub type BitVector8 = BitVector; pub type BitVector16 = BitVector; + pub type BitVector64 = BitVector; #[test] fn ssz_encode() { @@ -706,6 +717,18 @@ mod bitvector { fn assert_round_trip(t: T) { assert_eq!(T::from_ssz_bytes(&t.as_ssz_bytes()).unwrap(), t); } + + #[test] + fn ssz_bytes_len() { + for i in 0..64 { + let mut bitfield = BitVector64::new(); + for j in 0..i { + bitfield.set(j, true).expect("should set bit in bounds"); + } + let bytes = bitfield.as_ssz_bytes(); + assert_eq!(bitfield.ssz_bytes_len(), bytes.len(), "i = {}", i); + } + } } #[cfg(test)] @@ -1152,4 +1175,16 @@ mod bitlist { vec![false, false, true, false, false, false, false, false, true] ); } + + #[test] + fn ssz_bytes_len() { + for i in 1..64 { + let mut bitfield = BitList1024::with_capacity(i).unwrap(); + for j in 0..i { + bitfield.set(j, true).expect("should set bit in bounds"); + } + let bytes = bitfield.as_ssz_bytes(); + assert_eq!(bitfield.ssz_bytes_len(), bytes.len(), "i = {}", i); + } + } } diff --git a/eth2/utils/ssz_types/src/fixed_vector.rs b/eth2/utils/ssz_types/src/fixed_vector.rs index 090d04d84..f9c896331 100644 --- a/eth2/utils/ssz_types/src/fixed_vector.rs +++ b/eth2/utils/ssz_types/src/fixed_vector.rs @@ -183,6 +183,10 @@ where } } + fn ssz_bytes_len(&self) -> usize { + self.vec.ssz_bytes_len() + } + fn ssz_append(&self, buf: &mut Vec) { if T::is_ssz_fixed_len() { buf.reserve(T::ssz_fixed_len() * self.len()); @@ -318,6 +322,7 @@ mod test { fn ssz_round_trip(item: T) { let encoded = &item.as_ssz_bytes(); + assert_eq!(item.ssz_bytes_len(), encoded.len()); assert_eq!(T::from_ssz_bytes(&encoded), Ok(item)); } diff --git a/eth2/utils/ssz_types/src/variable_list.rs b/eth2/utils/ssz_types/src/variable_list.rs index beb7e6a93..feb656745 100644 --- a/eth2/utils/ssz_types/src/variable_list.rs +++ b/eth2/utils/ssz_types/src/variable_list.rs @@ -208,6 +208,10 @@ where >::ssz_fixed_len() } + fn ssz_bytes_len(&self) -> usize { + self.vec.ssz_bytes_len() + } + fn ssz_append(&self, buf: &mut Vec) { self.vec.ssz_append(buf) } @@ -304,6 +308,7 @@ mod test { fn round_trip(item: T) { let encoded = &item.as_ssz_bytes(); + assert_eq!(item.ssz_bytes_len(), encoded.len()); assert_eq!(T::from_ssz_bytes(&encoded), Ok(item)); } diff --git a/tests/ef_tests/src/cases/ssz_static.rs b/tests/ef_tests/src/cases/ssz_static.rs index 6e4a672cb..62f285d58 100644 --- a/tests/ef_tests/src/cases/ssz_static.rs +++ b/tests/ef_tests/src/cases/ssz_static.rs @@ -58,6 +58,7 @@ impl LoadCase for SszStaticSR { pub fn check_serialization(value: &T, serialized: &[u8]) -> Result<(), Error> { // Check serialization let serialized_result = value.as_ssz_bytes(); + compare_result::(&Ok(value.ssz_bytes_len()), &Some(serialized.len()))?; compare_result::, Error>(&Ok(serialized_result), &Some(serialized.to_vec()))?; // Check deserialization