diff --git a/Cargo.lock b/Cargo.lock index 08be60826..2376a7175 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1791,9 +1791,10 @@ dependencies = [ [[package]] name = "eth2_ssz_derive" -version = "0.3.0" +version = "0.3.1" dependencies = [ "darling", + "eth2_ssz", "proc-macro2", "quote", "syn", diff --git a/beacon_node/beacon_chain/Cargo.toml b/beacon_node/beacon_chain/Cargo.toml index dd185ac75..5b8583304 100644 --- a/beacon_node/beacon_chain/Cargo.toml +++ b/beacon_node/beacon_chain/Cargo.toml @@ -33,7 +33,7 @@ slot_clock = { path = "../../common/slot_clock" } eth2_hashing = "0.3.0" eth2_ssz = "0.4.1" eth2_ssz_types = "0.2.2" -eth2_ssz_derive = "0.3.0" +eth2_ssz_derive = "0.3.1" state_processing = { path = "../../consensus/state_processing" } tree_hash = "0.4.1" types = { path = "../../consensus/types" } diff --git a/beacon_node/eth1/Cargo.toml b/beacon_node/eth1/Cargo.toml index 7e99c43e7..e0dd797bf 100644 --- a/beacon_node/eth1/Cargo.toml +++ b/beacon_node/eth1/Cargo.toml @@ -21,7 +21,7 @@ hex = "0.4.2" types = { path = "../../consensus/types"} merkle_proof = { path = "../../consensus/merkle_proof"} eth2_ssz = "0.4.1" -eth2_ssz_derive = "0.3.0" +eth2_ssz_derive = "0.3.1" tree_hash = "0.4.1" parking_lot = "0.12.0" slog = "2.5.2" diff --git a/beacon_node/operation_pool/Cargo.toml b/beacon_node/operation_pool/Cargo.toml index 1d67ecdcc..848323358 100644 --- a/beacon_node/operation_pool/Cargo.toml +++ b/beacon_node/operation_pool/Cargo.toml @@ -13,7 +13,7 @@ parking_lot = "0.12.0" types = { path = "../../consensus/types" } state_processing = { path = "../../consensus/state_processing" } eth2_ssz = "0.4.1" -eth2_ssz_derive = "0.3.0" +eth2_ssz_derive = "0.3.1" rayon = "1.5.0" serde = "1.0.116" serde_derive = "1.0.116" diff --git a/beacon_node/store/Cargo.toml b/beacon_node/store/Cargo.toml index 20ae37b3b..09d960535 100644 --- a/beacon_node/store/Cargo.toml +++ b/beacon_node/store/Cargo.toml @@ -14,7 +14,7 @@ leveldb = { version = "0.8.6", default-features = false } parking_lot = "0.12.0" itertools = "0.10.0" eth2_ssz = "0.4.1" -eth2_ssz_derive = "0.3.0" +eth2_ssz_derive = "0.3.1" types = { path = "../../consensus/types" } state_processing = { path = "../../consensus/state_processing" } slog = "2.5.2" diff --git a/common/eth2/Cargo.toml b/common/eth2/Cargo.toml index 294f8ec8a..eca086d83 100644 --- a/common/eth2/Cargo.toml +++ b/common/eth2/Cargo.toml @@ -21,7 +21,7 @@ bytes = "1.0.1" account_utils = { path = "../../common/account_utils" } sensitive_url = { path = "../../common/sensitive_url" } eth2_ssz = "0.4.1" -eth2_ssz_derive = "0.3.0" +eth2_ssz_derive = "0.3.1" futures-util = "0.3.8" futures = "0.3.8" store = { path = "../../beacon_node/store", optional = true } diff --git a/consensus/cached_tree_hash/Cargo.toml b/consensus/cached_tree_hash/Cargo.toml index f9433e4a4..c362af83c 100644 --- a/consensus/cached_tree_hash/Cargo.toml +++ b/consensus/cached_tree_hash/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" ethereum-types = "0.12.1" eth2_ssz_types = "0.2.2" eth2_hashing = "0.3.0" -eth2_ssz_derive = "0.3.0" +eth2_ssz_derive = "0.3.1" eth2_ssz = "0.4.1" tree_hash = "0.4.1" smallvec = "1.6.1" diff --git a/consensus/fork_choice/Cargo.toml b/consensus/fork_choice/Cargo.toml index 52a738351..f0381e5ad 100644 --- a/consensus/fork_choice/Cargo.toml +++ b/consensus/fork_choice/Cargo.toml @@ -11,7 +11,7 @@ types = { path = "../types" } state_processing = { path = "../state_processing" } proto_array = { path = "../proto_array" } eth2_ssz = "0.4.1" -eth2_ssz_derive = "0.3.0" +eth2_ssz_derive = "0.3.1" slog = { version = "2.5.2", features = ["max_level_trace", "release_max_level_trace"] } [dev-dependencies] diff --git a/consensus/proto_array/Cargo.toml b/consensus/proto_array/Cargo.toml index ad79ecc1e..1c7b19bf1 100644 --- a/consensus/proto_array/Cargo.toml +++ b/consensus/proto_array/Cargo.toml @@ -11,7 +11,7 @@ path = "src/bin.rs" [dependencies] types = { path = "../types" } eth2_ssz = "0.4.1" -eth2_ssz_derive = "0.3.0" +eth2_ssz_derive = "0.3.1" serde = "1.0.116" serde_derive = "1.0.116" serde_yaml = "0.8.13" diff --git a/consensus/ssz/Cargo.toml b/consensus/ssz/Cargo.toml index a153c2efc..e521853c2 100644 --- a/consensus/ssz/Cargo.toml +++ b/consensus/ssz/Cargo.toml @@ -10,7 +10,7 @@ license = "Apache-2.0" name = "ssz" [dev-dependencies] -eth2_ssz_derive = "0.3.0" +eth2_ssz_derive = "0.3.1" [dependencies] ethereum-types = "0.12.1" diff --git a/consensus/ssz/tests/tests.rs b/consensus/ssz/tests/tests.rs index b4b91da4b..f52d2c5cd 100644 --- a/consensus/ssz/tests/tests.rs +++ b/consensus/ssz/tests/tests.rs @@ -388,145 +388,3 @@ mod round_trip { round_trip(data); } } - -mod derive_macro { - use ssz::{Decode, Encode}; - use ssz_derive::{Decode, Encode}; - use std::fmt::Debug; - - fn assert_encode(item: &T, bytes: &[u8]) { - assert_eq!(item.as_ssz_bytes(), bytes); - } - - fn assert_encode_decode(item: &T, bytes: &[u8]) { - assert_encode(item, bytes); - assert_eq!(T::from_ssz_bytes(bytes).unwrap(), *item); - } - - #[derive(PartialEq, Debug, Encode, Decode)] - #[ssz(enum_behaviour = "union")] - enum TwoFixedUnion { - U8(u8), - U16(u16), - } - - #[derive(PartialEq, Debug, Encode, Decode)] - struct TwoFixedUnionStruct { - a: TwoFixedUnion, - } - - #[test] - fn two_fixed_union() { - let eight = TwoFixedUnion::U8(1); - let sixteen = TwoFixedUnion::U16(1); - - assert_encode_decode(&eight, &[0, 1]); - assert_encode_decode(&sixteen, &[1, 1, 0]); - - assert_encode_decode(&TwoFixedUnionStruct { a: eight }, &[4, 0, 0, 0, 0, 1]); - assert_encode_decode(&TwoFixedUnionStruct { a: sixteen }, &[4, 0, 0, 0, 1, 1, 0]); - } - - #[derive(PartialEq, Debug, Encode, Decode)] - struct VariableA { - a: u8, - b: Vec, - } - - #[derive(PartialEq, Debug, Encode, Decode)] - struct VariableB { - a: Vec, - b: u8, - } - - #[derive(PartialEq, Debug, Encode)] - #[ssz(enum_behaviour = "transparent")] - enum TwoVariableTrans { - A(VariableA), - B(VariableB), - } - - #[derive(PartialEq, Debug, Encode)] - struct TwoVariableTransStruct { - a: TwoVariableTrans, - } - - #[derive(PartialEq, Debug, Encode, Decode)] - #[ssz(enum_behaviour = "union")] - enum TwoVariableUnion { - A(VariableA), - B(VariableB), - } - - #[derive(PartialEq, Debug, Encode, Decode)] - struct TwoVariableUnionStruct { - a: TwoVariableUnion, - } - - #[test] - fn two_variable_trans() { - let trans_a = TwoVariableTrans::A(VariableA { - a: 1, - b: vec![2, 3], - }); - let trans_b = TwoVariableTrans::B(VariableB { - a: vec![1, 2], - b: 3, - }); - - assert_encode(&trans_a, &[1, 5, 0, 0, 0, 2, 3]); - assert_encode(&trans_b, &[5, 0, 0, 0, 3, 1, 2]); - - assert_encode( - &TwoVariableTransStruct { a: trans_a }, - &[4, 0, 0, 0, 1, 5, 0, 0, 0, 2, 3], - ); - assert_encode( - &TwoVariableTransStruct { a: trans_b }, - &[4, 0, 0, 0, 5, 0, 0, 0, 3, 1, 2], - ); - } - - #[test] - fn two_variable_union() { - let union_a = TwoVariableUnion::A(VariableA { - a: 1, - b: vec![2, 3], - }); - let union_b = TwoVariableUnion::B(VariableB { - a: vec![1, 2], - b: 3, - }); - - assert_encode_decode(&union_a, &[0, 1, 5, 0, 0, 0, 2, 3]); - assert_encode_decode(&union_b, &[1, 5, 0, 0, 0, 3, 1, 2]); - - assert_encode_decode( - &TwoVariableUnionStruct { a: union_a }, - &[4, 0, 0, 0, 0, 1, 5, 0, 0, 0, 2, 3], - ); - assert_encode_decode( - &TwoVariableUnionStruct { a: union_b }, - &[4, 0, 0, 0, 1, 5, 0, 0, 0, 3, 1, 2], - ); - } - - #[derive(PartialEq, Debug, Encode, Decode)] - #[ssz(enum_behaviour = "union")] - enum TwoVecUnion { - A(Vec), - B(Vec), - } - - #[test] - fn two_vec_union() { - assert_encode_decode(&TwoVecUnion::A(vec![]), &[0]); - assert_encode_decode(&TwoVecUnion::B(vec![]), &[1]); - - assert_encode_decode(&TwoVecUnion::A(vec![0]), &[0, 0]); - assert_encode_decode(&TwoVecUnion::B(vec![0]), &[1, 0]); - - assert_encode_decode(&TwoVecUnion::A(vec![0, 1]), &[0, 0, 1]); - assert_encode_decode(&TwoVecUnion::B(vec![0, 1]), &[1, 0, 1]); - } -} diff --git a/consensus/ssz_derive/Cargo.toml b/consensus/ssz_derive/Cargo.toml index cac617d39..d3b2865a6 100644 --- a/consensus/ssz_derive/Cargo.toml +++ b/consensus/ssz_derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "eth2_ssz_derive" -version = "0.3.0" +version = "0.3.1" authors = ["Paul Hauner "] edition = "2021" description = "Procedural derive macros to accompany the eth2_ssz crate." @@ -15,3 +15,6 @@ syn = "1.0.42" proc-macro2 = "1.0.23" quote = "1.0.7" darling = "0.13.0" + +[dev-dependencies] +eth2_ssz = "0.4.1" diff --git a/consensus/ssz_derive/src/lib.rs b/consensus/ssz_derive/src/lib.rs index a5a5a0ddd..40d63fd02 100644 --- a/consensus/ssz_derive/src/lib.rs +++ b/consensus/ssz_derive/src/lib.rs @@ -1,7 +1,147 @@ #![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. +//! ## Attributes +//! +//! The following struct/enum attributes are available: +//! +//! - `#[ssz(enum_behaviour = "union")]`: encodes and decodes an `enum` with a one-byte variant selector. +//! - `#[ssz(enum_behaviour = "transparent")]`: allows encoding an `enum` by serializing only the +//! value whilst ignoring outermost the `enum`. +//! - `#[ssz(struct_behaviour = "container")]`: encodes and decodes the `struct` as an SSZ +//! "container". +//! - `#[ssz(struct_behaviour = "transparent")]`: encodes and decodes a `struct` with exactly one +//! non-skipped field as if the outermost `struct` does not exist. +//! +//! The following field attributes are available: +//! +//! - `#[ssz(with = "module")]`: uses the methods in `module` to implement `ssz::Encode` and +//! `ssz::Decode`. This is useful when it's not possible to create an `impl` for that type +//! (e.g. the type is defined in another crate). +//! - `#[ssz(skip_serializing)]`: this field will not be included in the serialized SSZ vector. +//! - `#[ssz(skip_deserializing)]`: this field will not be expected in the serialized +//! SSZ vector and it will be initialized from a `Default` implementation. +//! +//! ## Examples +//! +//! ### Structs +//! +//! ```rust +//! use ssz::{Encode, Decode}; +//! use ssz_derive::{Encode, Decode}; +//! +//! /// Represented as an SSZ "list" wrapped in an SSZ "container". +//! #[derive(Debug, PartialEq, Encode, Decode)] +//! #[ssz(struct_behaviour = "container")] // "container" is the default behaviour +//! struct TypicalStruct { +//! foo: Vec +//! } +//! +//! assert_eq!( +//! TypicalStruct { foo: vec![42] }.as_ssz_bytes(), +//! vec![4, 0, 0, 0, 42] +//! ); +//! +//! assert_eq!( +//! TypicalStruct::from_ssz_bytes(&[4, 0, 0, 0, 42]).unwrap(), +//! TypicalStruct { foo: vec![42] }, +//! ); +//! +//! /// Represented as an SSZ "list" *without* an SSZ "container". +//! #[derive(Encode, Decode)] +//! #[ssz(struct_behaviour = "transparent")] +//! struct WrapperStruct { +//! foo: Vec +//! } +//! +//! assert_eq!( +//! WrapperStruct { foo: vec![42] }.as_ssz_bytes(), +//! vec![42] +//! ); +//! +//! /// Represented as an SSZ "list" *without* an SSZ "container". The `bar` byte is ignored. +//! #[derive(Debug, PartialEq, Encode, Decode)] +//! #[ssz(struct_behaviour = "transparent")] +//! struct WrapperStructSkippedField { +//! foo: Vec, +//! #[ssz(skip_serializing, skip_deserializing)] +//! bar: u8, +//! } +//! +//! assert_eq!( +//! WrapperStructSkippedField { foo: vec![42], bar: 99 }.as_ssz_bytes(), +//! vec![42] +//! ); +//! assert_eq!( +//! WrapperStructSkippedField::from_ssz_bytes(&[42]).unwrap(), +//! WrapperStructSkippedField { foo: vec![42], bar: 0 } +//! ); +//! +//! /// Represented as an SSZ "list" *without* an SSZ "container". +//! #[derive(Encode, Decode)] +//! #[ssz(struct_behaviour = "transparent")] +//! struct NewType(Vec); +//! +//! assert_eq!( +//! NewType(vec![42]).as_ssz_bytes(), +//! vec![42] +//! ); +//! +//! /// Represented as an SSZ "list" *without* an SSZ "container". The `bar` byte is ignored. +//! #[derive(Debug, PartialEq, Encode, Decode)] +//! #[ssz(struct_behaviour = "transparent")] +//! struct NewTypeSkippedField(Vec, #[ssz(skip_serializing, skip_deserializing)] u8); +//! +//! assert_eq!( +//! NewTypeSkippedField(vec![42], 99).as_ssz_bytes(), +//! vec![42] +//! ); +//! assert_eq!( +//! NewTypeSkippedField::from_ssz_bytes(&[42]).unwrap(), +//! NewTypeSkippedField(vec![42], 0) +//! ); +//! ``` +//! +//! ### Enums +//! +//! ```rust +//! use ssz::{Encode, Decode}; +//! use ssz_derive::{Encode, Decode}; +//! +//! /// Represented as an SSZ "union". +//! #[derive(Debug, PartialEq, Encode, Decode)] +//! #[ssz(enum_behaviour = "union")] +//! enum UnionEnum { +//! Foo(u8), +//! Bar(Vec), +//! } +//! +//! assert_eq!( +//! UnionEnum::Foo(42).as_ssz_bytes(), +//! vec![0, 42] +//! ); +//! assert_eq!( +//! UnionEnum::from_ssz_bytes(&[1, 42, 42]).unwrap(), +//! UnionEnum::Bar(vec![42, 42]), +//! ); +//! +//! /// Represented as only the value in the enum variant. +//! #[derive(Debug, PartialEq, Encode)] +//! #[ssz(enum_behaviour = "transparent")] +//! enum TransparentEnum { +//! Foo(u8), +//! Bar(Vec), +//! } +//! +//! assert_eq!( +//! TransparentEnum::Foo(42).as_ssz_bytes(), +//! vec![42] +//! ); +//! assert_eq!( +//! TransparentEnum::Bar(vec![42, 42]).as_ssz_bytes(), +//! vec![42, 42] +//! ); +//! ``` use darling::{FromDeriveInput, FromMeta}; use proc_macro::TokenStream; @@ -13,11 +153,18 @@ use syn::{parse_macro_input, DataEnum, DataStruct, DeriveInput, Ident}; /// extensions). const MAX_UNION_SELECTOR: u8 = 127; +const ENUM_TRANSPARENT: &str = "transparent"; +const ENUM_UNION: &str = "union"; +const NO_ENUM_BEHAVIOUR_ERROR: &str = "enums require an \"enum_behaviour\" attribute with \ + a \"transparent\" or \"union\" value, e.g., #[ssz(enum_behaviour = \"transparent\")]"; + #[derive(Debug, FromDeriveInput)] #[darling(attributes(ssz))] struct StructOpts { #[darling(default)] enum_behaviour: Option, + #[darling(default)] + struct_behaviour: Option, } /// Field-level configuration. @@ -31,40 +178,87 @@ struct FieldOpts { skip_deserializing: bool, } -const ENUM_TRANSPARENT: &str = "transparent"; -const ENUM_UNION: &str = "union"; -const ENUM_VARIANTS: &[&str] = &[ENUM_TRANSPARENT, ENUM_UNION]; -const NO_ENUM_BEHAVIOUR_ERROR: &str = "enums require an \"enum_behaviour\" attribute, \ - e.g., #[ssz(enum_behaviour = \"transparent\")]"; - -enum EnumBehaviour { - Transparent, - Union, +enum Procedure<'a> { + Struct { + data: &'a syn::DataStruct, + behaviour: StructBehaviour, + }, + Enum { + data: &'a syn::DataEnum, + behaviour: EnumBehaviour, + }, } -impl EnumBehaviour { - pub fn new(s: Option) -> Option { - s.map(|s| match s.as_ref() { - ENUM_TRANSPARENT => EnumBehaviour::Transparent, - ENUM_UNION => EnumBehaviour::Union, - other => panic!( - "{} is an invalid enum_behaviour, use either {:?}", - other, ENUM_VARIANTS - ), - }) +enum StructBehaviour { + Container, + Transparent, +} + +enum EnumBehaviour { + Union, + Transparent, +} + +impl<'a> Procedure<'a> { + fn read(item: &'a DeriveInput) -> Self { + let opts = StructOpts::from_derive_input(item).unwrap(); + + match &item.data { + syn::Data::Struct(data) => { + if opts.enum_behaviour.is_some() { + panic!("cannot use \"enum_behaviour\" for a struct"); + } + + match opts.struct_behaviour.as_deref() { + Some("container") | None => Procedure::Struct { + data, + behaviour: StructBehaviour::Container, + }, + Some("transparent") => Procedure::Struct { + data, + behaviour: StructBehaviour::Transparent, + }, + Some(other) => panic!( + "{} is not a valid struct behaviour, use \"container\" or \"transparent\"", + other + ), + } + } + syn::Data::Enum(data) => { + if opts.struct_behaviour.is_some() { + panic!("cannot use \"struct_behaviour\" for an enum"); + } + + match opts.enum_behaviour.as_deref() { + Some("union") => Procedure::Enum { + data, + behaviour: EnumBehaviour::Union, + }, + Some("transparent") => Procedure::Enum { + data, + behaviour: EnumBehaviour::Transparent, + }, + Some(other) => panic!( + "{} is not a valid enum behaviour, use \"container\" or \"transparent\"", + other + ), + None => panic!("{}", NO_ENUM_BEHAVIOUR_ERROR), + } + } + _ => panic!("ssz_derive only supports structs and enums"), + } } } -fn parse_ssz_fields(struct_data: &syn::DataStruct) -> Vec<(&syn::Type, &syn::Ident, FieldOpts)> { +fn parse_ssz_fields( + struct_data: &syn::DataStruct, +) -> Vec<(&syn::Type, Option<&syn::Ident>, FieldOpts)> { struct_data .fields .iter() .map(|field| { let ty = &field.ty; - let ident = match &field.ident { - Some(ref ident) => ident, - _ => panic!("ssz_derive only supports named struct fields."), - }; + let ident = field.ident.as_ref(); let field_opts_candidates = field .attrs @@ -93,21 +287,17 @@ fn parse_ssz_fields(struct_data: &syn::DataStruct) -> Vec<(&syn::Type, &syn::Ide #[proc_macro_derive(Encode, attributes(ssz))] pub fn ssz_encode_derive(input: TokenStream) -> TokenStream { let item = parse_macro_input!(input as DeriveInput); - let opts = StructOpts::from_derive_input(&item).unwrap(); - let enum_opt = EnumBehaviour::new(opts.enum_behaviour); + let procedure = Procedure::read(&item); - match &item.data { - syn::Data::Struct(s) => { - if enum_opt.is_some() { - panic!("enum_behaviour is invalid for structs"); - } - ssz_encode_derive_struct(&item, s) - } - syn::Data::Enum(s) => match enum_opt.expect(NO_ENUM_BEHAVIOUR_ERROR) { - EnumBehaviour::Transparent => ssz_encode_derive_enum_transparent(&item, s), - EnumBehaviour::Union => ssz_encode_derive_enum_union(&item, s), + match procedure { + Procedure::Struct { data, behaviour } => match behaviour { + StructBehaviour::Transparent => ssz_encode_derive_struct_transparent(&item, data), + StructBehaviour::Container => ssz_encode_derive_struct(&item, data), + }, + Procedure::Enum { data, behaviour } => match behaviour { + EnumBehaviour::Transparent => ssz_encode_derive_enum_transparent(&item, data), + EnumBehaviour::Union => ssz_encode_derive_enum_union(&item, data), }, - _ => panic!("ssz_derive only supports structs and enums"), } } @@ -132,6 +322,13 @@ fn ssz_encode_derive_struct(derive_input: &DeriveInput, struct_data: &DataStruct continue; } + let ident = match ident { + Some(ref ident) => ident, + _ => panic!( + "#[ssz(struct_behaviour = \"container\")] only supports named struct fields." + ), + }; + if let Some(module) = field_opts.with { let module = quote! { #module::encode }; field_is_ssz_fixed_len.push(quote! { #module::is_ssz_fixed_len() }); @@ -219,6 +416,82 @@ fn ssz_encode_derive_struct(derive_input: &DeriveInput, struct_data: &DataStruct output.into() } +/// Derive `ssz::Encode` "transparently" for a struct which has exactly one non-skipped field. +/// +/// The single field is encoded directly, making the outermost `struct` transparent. +/// +/// ## Field attributes +/// +/// - `#[ssz(skip_serializing)]`: the field will not be serialized. +fn ssz_encode_derive_struct_transparent( + derive_input: &DeriveInput, + struct_data: &DataStruct, +) -> TokenStream { + let name = &derive_input.ident; + let (impl_generics, ty_generics, where_clause) = &derive_input.generics.split_for_impl(); + let ssz_fields = parse_ssz_fields(struct_data); + let num_fields = ssz_fields + .iter() + .filter(|(_, _, field_opts)| !field_opts.skip_deserializing) + .count(); + + if num_fields != 1 { + panic!( + "A \"transparent\" struct must have exactly one non-skipped field ({} fields found)", + num_fields + ); + } + + let (ty, ident, _field_opts) = ssz_fields + .iter() + .find(|(_, _, field_opts)| !field_opts.skip_deserializing) + .expect("\"transparent\" struct must have at least one non-skipped field"); + + let output = if let Some(field_name) = ident { + quote! { + impl #impl_generics ssz::Encode for #name #ty_generics #where_clause { + fn is_ssz_fixed_len() -> bool { + <#ty as ssz::Encode>::is_ssz_fixed_len() + } + + fn ssz_fixed_len() -> usize { + <#ty as ssz::Encode>::ssz_fixed_len() + } + + fn ssz_bytes_len(&self) -> usize { + self.#field_name.ssz_bytes_len() + } + + fn ssz_append(&self, buf: &mut Vec) { + self.#field_name.ssz_append(buf) + } + } + } + } else { + quote! { + impl #impl_generics ssz::Encode for #name #ty_generics #where_clause { + fn is_ssz_fixed_len() -> bool { + <#ty as ssz::Encode>::is_ssz_fixed_len() + } + + fn ssz_fixed_len() -> usize { + <#ty as ssz::Encode>::ssz_fixed_len() + } + + fn ssz_bytes_len(&self) -> usize { + self.0.ssz_bytes_len() + } + + fn ssz_append(&self, buf: &mut Vec) { + self.0.ssz_append(buf) + } + } + } + }; + + output.into() +} + /// Derive `ssz::Encode` for an enum in the "transparent" method. /// /// The "transparent" method is distinct from the "union" method specified in the SSZ specification. @@ -367,24 +640,20 @@ fn ssz_encode_derive_enum_union(derive_input: &DeriveInput, enum_data: &DataEnum #[proc_macro_derive(Decode, attributes(ssz))] pub fn ssz_decode_derive(input: TokenStream) -> TokenStream { let item = parse_macro_input!(input as DeriveInput); - let opts = StructOpts::from_derive_input(&item).unwrap(); - let enum_opt = EnumBehaviour::new(opts.enum_behaviour); + let procedure = Procedure::read(&item); - match &item.data { - syn::Data::Struct(s) => { - if enum_opt.is_some() { - panic!("enum_behaviour is invalid for structs"); - } - ssz_decode_derive_struct(&item, s) - } - syn::Data::Enum(s) => match enum_opt.expect(NO_ENUM_BEHAVIOUR_ERROR) { + match procedure { + Procedure::Struct { data, behaviour } => match behaviour { + StructBehaviour::Transparent => ssz_decode_derive_struct_transparent(&item, data), + StructBehaviour::Container => ssz_decode_derive_struct(&item, data), + }, + Procedure::Enum { data, behaviour } => match behaviour { + EnumBehaviour::Union => ssz_decode_derive_enum_union(&item, data), EnumBehaviour::Transparent => panic!( "Decode cannot be derived for enum_behaviour \"{}\", only \"{}\" is valid.", ENUM_TRANSPARENT, ENUM_UNION ), - EnumBehaviour::Union => ssz_decode_derive_enum_union(&item, s), }, - _ => panic!("ssz_derive only supports structs and enums"), } } @@ -409,6 +678,13 @@ fn ssz_decode_derive_struct(item: &DeriveInput, struct_data: &DataStruct) -> Tok let mut fixed_lens = vec![]; for (ty, ident, field_opts) in parse_ssz_fields(struct_data) { + let ident = match ident { + Some(ref ident) => ident, + _ => panic!( + "#[ssz(struct_behaviour = \"container\")] only supports named struct fields." + ), + }; + field_names.push(quote! { #ident }); @@ -545,6 +821,90 @@ fn ssz_decode_derive_struct(item: &DeriveInput, struct_data: &DataStruct) -> Tok output.into() } +/// Implements `ssz::Decode` "transparently" for a `struct` with exactly one non-skipped field. +/// +/// The bytes will be decoded as if they are the inner field, without the outermost struct. The +/// outermost struct will then be applied artificially. +/// +/// ## Field attributes +/// +/// - `#[ssz(skip_deserializing)]`: during de-serialization the field will be instantiated from a +/// `Default` implementation. The decoder will assume that the field was not serialized at all +/// (e.g., if it has been serialized, an error will be raised instead of `Default` overriding it). +fn ssz_decode_derive_struct_transparent( + item: &DeriveInput, + struct_data: &DataStruct, +) -> TokenStream { + let name = &item.ident; + let (impl_generics, ty_generics, where_clause) = &item.generics.split_for_impl(); + let ssz_fields = parse_ssz_fields(struct_data); + let num_fields = ssz_fields + .iter() + .filter(|(_, _, field_opts)| !field_opts.skip_deserializing) + .count(); + + if num_fields != 1 { + panic!( + "A \"transparent\" struct must have exactly one non-skipped field ({} fields found)", + num_fields + ); + } + + let mut fields = vec![]; + let mut wrapped_type = None; + + for (i, (ty, ident, field_opts)) in ssz_fields.into_iter().enumerate() { + if let Some(name) = ident { + if field_opts.skip_deserializing { + fields.push(quote! { + #name: <_>::default(), + }); + } else { + fields.push(quote! { + #name: <_>::from_ssz_bytes(bytes)?, + }); + wrapped_type = Some(ty); + } + } else { + let index = syn::Index::from(i); + if field_opts.skip_deserializing { + fields.push(quote! { + #index:<_>::default(), + }); + } else { + fields.push(quote! { + #index:<_>::from_ssz_bytes(bytes)?, + }); + wrapped_type = Some(ty); + } + } + } + + let ty = wrapped_type.unwrap(); + + let output = quote! { + impl #impl_generics ssz::Decode for #name #ty_generics #where_clause { + fn is_ssz_fixed_len() -> bool { + <#ty as ssz::Decode>::is_ssz_fixed_len() + } + + fn ssz_fixed_len() -> usize { + <#ty as ssz::Decode>::ssz_fixed_len() + } + + fn from_ssz_bytes(bytes: &[u8]) -> std::result::Result { + Ok(Self { + #( + #fields + )* + + }) + } + } + }; + output.into() +} + /// Derive `ssz::Decode` for an `enum` following the "union" SSZ spec. fn ssz_decode_derive_enum_union(derive_input: &DeriveInput, enum_data: &DataEnum) -> TokenStream { let name = &derive_input.ident; diff --git a/consensus/ssz_derive/tests/tests.rs b/consensus/ssz_derive/tests/tests.rs new file mode 100644 index 000000000..2eeb3a48d --- /dev/null +++ b/consensus/ssz_derive/tests/tests.rs @@ -0,0 +1,215 @@ +use ssz::{Decode, Encode}; +use ssz_derive::{Decode, Encode}; +use std::fmt::Debug; +use std::marker::PhantomData; + +fn assert_encode(item: &T, bytes: &[u8]) { + assert_eq!(item.as_ssz_bytes(), bytes); +} + +fn assert_encode_decode(item: &T, bytes: &[u8]) { + assert_encode(item, bytes); + assert_eq!(T::from_ssz_bytes(bytes).unwrap(), *item); +} + +#[derive(PartialEq, Debug, Encode, Decode)] +#[ssz(enum_behaviour = "union")] +enum TwoFixedUnion { + U8(u8), + U16(u16), +} + +#[derive(PartialEq, Debug, Encode, Decode)] +struct TwoFixedUnionStruct { + a: TwoFixedUnion, +} + +#[test] +fn two_fixed_union() { + let eight = TwoFixedUnion::U8(1); + let sixteen = TwoFixedUnion::U16(1); + + assert_encode_decode(&eight, &[0, 1]); + assert_encode_decode(&sixteen, &[1, 1, 0]); + + assert_encode_decode(&TwoFixedUnionStruct { a: eight }, &[4, 0, 0, 0, 0, 1]); + assert_encode_decode(&TwoFixedUnionStruct { a: sixteen }, &[4, 0, 0, 0, 1, 1, 0]); +} + +#[derive(PartialEq, Debug, Encode, Decode)] +struct VariableA { + a: u8, + b: Vec, +} + +#[derive(PartialEq, Debug, Encode, Decode)] +struct VariableB { + a: Vec, + b: u8, +} + +#[derive(PartialEq, Debug, Encode)] +#[ssz(enum_behaviour = "transparent")] +enum TwoVariableTrans { + A(VariableA), + B(VariableB), +} + +#[derive(PartialEq, Debug, Encode)] +struct TwoVariableTransStruct { + a: TwoVariableTrans, +} + +#[derive(PartialEq, Debug, Encode, Decode)] +#[ssz(enum_behaviour = "union")] +enum TwoVariableUnion { + A(VariableA), + B(VariableB), +} + +#[derive(PartialEq, Debug, Encode, Decode)] +struct TwoVariableUnionStruct { + a: TwoVariableUnion, +} + +#[test] +fn two_variable_trans() { + let trans_a = TwoVariableTrans::A(VariableA { + a: 1, + b: vec![2, 3], + }); + let trans_b = TwoVariableTrans::B(VariableB { + a: vec![1, 2], + b: 3, + }); + + assert_encode(&trans_a, &[1, 5, 0, 0, 0, 2, 3]); + assert_encode(&trans_b, &[5, 0, 0, 0, 3, 1, 2]); + + assert_encode( + &TwoVariableTransStruct { a: trans_a }, + &[4, 0, 0, 0, 1, 5, 0, 0, 0, 2, 3], + ); + assert_encode( + &TwoVariableTransStruct { a: trans_b }, + &[4, 0, 0, 0, 5, 0, 0, 0, 3, 1, 2], + ); +} + +#[test] +fn two_variable_union() { + let union_a = TwoVariableUnion::A(VariableA { + a: 1, + b: vec![2, 3], + }); + let union_b = TwoVariableUnion::B(VariableB { + a: vec![1, 2], + b: 3, + }); + + assert_encode_decode(&union_a, &[0, 1, 5, 0, 0, 0, 2, 3]); + assert_encode_decode(&union_b, &[1, 5, 0, 0, 0, 3, 1, 2]); + + assert_encode_decode( + &TwoVariableUnionStruct { a: union_a }, + &[4, 0, 0, 0, 0, 1, 5, 0, 0, 0, 2, 3], + ); + assert_encode_decode( + &TwoVariableUnionStruct { a: union_b }, + &[4, 0, 0, 0, 1, 5, 0, 0, 0, 3, 1, 2], + ); +} + +#[derive(PartialEq, Debug, Encode, Decode)] +#[ssz(enum_behaviour = "union")] +enum TwoVecUnion { + A(Vec), + B(Vec), +} + +#[test] +fn two_vec_union() { + assert_encode_decode(&TwoVecUnion::A(vec![]), &[0]); + assert_encode_decode(&TwoVecUnion::B(vec![]), &[1]); + + assert_encode_decode(&TwoVecUnion::A(vec![0]), &[0, 0]); + assert_encode_decode(&TwoVecUnion::B(vec![0]), &[1, 0]); + + assert_encode_decode(&TwoVecUnion::A(vec![0, 1]), &[0, 0, 1]); + assert_encode_decode(&TwoVecUnion::B(vec![0, 1]), &[1, 0, 1]); +} + +#[derive(PartialEq, Debug, Encode, Decode)] +#[ssz(struct_behaviour = "transparent")] +struct TransparentStruct { + inner: Vec, +} + +impl TransparentStruct { + fn new(inner: u8) -> Self { + Self { inner: vec![inner] } + } +} + +#[test] +fn transparent_struct() { + assert_encode_decode(&TransparentStruct::new(42), &vec![42_u8].as_ssz_bytes()); +} + +#[derive(PartialEq, Debug, Encode, Decode)] +#[ssz(struct_behaviour = "transparent")] +struct TransparentStructSkippedField { + inner: Vec, + #[ssz(skip_serializing, skip_deserializing)] + skipped: PhantomData, +} + +impl TransparentStructSkippedField { + fn new(inner: u8) -> Self { + Self { + inner: vec![inner], + skipped: PhantomData, + } + } +} + +#[test] +fn transparent_struct_skipped_field() { + assert_encode_decode( + &TransparentStructSkippedField::new(42), + &vec![42_u8].as_ssz_bytes(), + ); +} + +#[derive(PartialEq, Debug, Encode, Decode)] +#[ssz(struct_behaviour = "transparent")] +struct TransparentStructNewType(Vec); + +#[test] +fn transparent_struct_newtype() { + assert_encode_decode( + &TransparentStructNewType(vec![42_u8]), + &vec![42_u8].as_ssz_bytes(), + ); +} + +#[derive(PartialEq, Debug, Encode, Decode)] +#[ssz(struct_behaviour = "transparent")] +struct TransparentStructNewTypeSkippedField( + Vec, + #[ssz(skip_serializing, skip_deserializing)] PhantomData, +); + +impl TransparentStructNewTypeSkippedField { + fn new(inner: Vec) -> Self { + Self(inner, PhantomData) + } +} + +#[test] +fn transparent_struct_newtype_skipped_field() { + assert_encode_decode( + &TransparentStructNewTypeSkippedField::new(vec![42_u8]), + &vec![42_u8].as_ssz_bytes(), + ); +} diff --git a/consensus/state_processing/Cargo.toml b/consensus/state_processing/Cargo.toml index 46ac2bae5..ccb41830b 100644 --- a/consensus/state_processing/Cargo.toml +++ b/consensus/state_processing/Cargo.toml @@ -14,7 +14,7 @@ bls = { path = "../../crypto/bls" } integer-sqrt = "0.1.5" itertools = "0.10.0" eth2_ssz = "0.4.1" -eth2_ssz_derive = "0.3.0" +eth2_ssz_derive = "0.3.1" eth2_ssz_types = "0.2.2" merkle_proof = { path = "../merkle_proof" } safe_arith = { path = "../safe_arith" } diff --git a/consensus/tree_hash/Cargo.toml b/consensus/tree_hash/Cargo.toml index ab080eac0..1f004724f 100644 --- a/consensus/tree_hash/Cargo.toml +++ b/consensus/tree_hash/Cargo.toml @@ -12,7 +12,7 @@ tree_hash_derive = "0.4.0" types = { path = "../types" } beacon_chain = { path = "../../beacon_node/beacon_chain" } eth2_ssz = "0.4.1" -eth2_ssz_derive = "0.3.0" +eth2_ssz_derive = "0.3.1" [dependencies] ethereum-types = "0.12.1" diff --git a/consensus/types/Cargo.toml b/consensus/types/Cargo.toml index 19dbcc924..d04d9d650 100644 --- a/consensus/types/Cargo.toml +++ b/consensus/types/Cargo.toml @@ -26,7 +26,7 @@ serde = {version = "1.0.116" , features = ["rc"] } serde_derive = "1.0.116" slog = "2.5.2" eth2_ssz = "0.4.1" -eth2_ssz_derive = "0.3.0" +eth2_ssz_derive = "0.3.1" eth2_ssz_types = "0.2.2" swap_or_not_shuffle = { path = "../swap_or_not_shuffle" } test_random_derive = { path = "../../common/test_random_derive" } diff --git a/consensus/types/src/beacon_state.rs b/consensus/types/src/beacon_state.rs index 10596a769..3e7cbba92 100644 --- a/consensus/types/src/beacon_state.rs +++ b/consensus/types/src/beacon_state.rs @@ -120,6 +120,7 @@ pub enum Error { ArithError(ArithError), MissingBeaconBlock(SignedBeaconBlockHash), MissingBeaconState(BeaconStateHash), + PayloadConversionLogicFlaw, SyncCommitteeNotKnown { current_epoch: Epoch, epoch: Epoch, diff --git a/consensus/types/src/execution_payload.rs b/consensus/types/src/execution_payload.rs index 05dadb434..6110b7f4f 100644 --- a/consensus/types/src/execution_payload.rs +++ b/consensus/types/src/execution_payload.rs @@ -121,8 +121,4 @@ impl ExecutionPayload { // Max size of variable length `withdrawals` field + (T::max_withdrawals_per_payload() * ::ssz_fixed_len()) } - - pub fn blob_txns_iter(&self) -> Iter<'_, Transaction> { - self.transactions().iter() - } } diff --git a/consensus/types/src/execution_payload_header.rs b/consensus/types/src/execution_payload_header.rs index 41aa2f6d2..342a2d97e 100644 --- a/consensus/types/src/execution_payload_header.rs +++ b/consensus/types/src/execution_payload_header.rs @@ -79,6 +79,12 @@ pub struct ExecutionPayloadHeader { pub withdrawals_root: Hash256, } +impl ExecutionPayloadHeader { + pub fn transactions(&self) -> Option<&Transactions> { + None + } +} + impl<'a, T: EthSpec> ExecutionPayloadHeaderRef<'a, T> { // FIXME: maybe this could be a derived trait.. pub fn is_default(self) -> bool { @@ -210,6 +216,34 @@ impl From> for ExecutionPayloadHeaderEip4 } } +impl From> for ExecutionPayloadHeader { + fn from(payload: ExecutionPayloadMerge) -> Self { + Self::Merge(ExecutionPayloadHeaderMerge::from(payload)) + } +} + +impl From> for ExecutionPayloadHeader { + fn from(payload: ExecutionPayloadCapella) -> Self { + Self::Capella(ExecutionPayloadHeaderCapella::from(payload)) + } +} + +impl From> for ExecutionPayloadHeader { + fn from(payload: ExecutionPayloadEip4844) -> Self { + Self::Eip4844(ExecutionPayloadHeaderEip4844::from(payload)) + } +} + +impl From> for ExecutionPayloadHeader { + fn from(payload: ExecutionPayload) -> Self { + match payload { + ExecutionPayload::Merge(payload) => Self::from(payload), + ExecutionPayload::Capella(payload) => Self::from(payload), + ExecutionPayload::Eip4844(payload) => Self::from(payload), + } + } +} + impl TryFrom> for ExecutionPayloadHeaderMerge { type Error = BeaconStateError; fn try_from(header: ExecutionPayloadHeader) -> Result { diff --git a/consensus/types/src/kzg_commitment.rs b/consensus/types/src/kzg_commitment.rs index 64ed24d9a..eaa429a13 100644 --- a/consensus/types/src/kzg_commitment.rs +++ b/consensus/types/src/kzg_commitment.rs @@ -2,13 +2,14 @@ use crate::test_utils::TestRandom; use crate::*; use derivative::Derivative; use serde_derive::{Deserialize, Serialize}; -use ssz::{Decode, DecodeError, Encode}; +use ssz_derive::{Decode, Encode}; use std::fmt; use std::fmt::{Display, Formatter}; use tree_hash::{PackedEncoding, TreeHash}; -#[derive(Derivative, Debug, Clone, Serialize, Deserialize)] +#[derive(Derivative, Debug, Clone, Encode, Decode, Serialize, Deserialize)] #[derivative(PartialEq, Eq, Hash)] +#[ssz(struct_behaviour = "transparent")] pub struct KzgCommitment(#[serde(with = "BigArray")] pub [u8; 48]); impl Display for KzgCommitment { @@ -40,27 +41,3 @@ impl TestRandom for KzgCommitment { KzgCommitment(<[u8; 48] as TestRandom>::random_for_test(rng)) } } - -impl Decode for KzgCommitment { - fn is_ssz_fixed_len() -> bool { - <[u8; 48] as Decode>::is_ssz_fixed_len() - } - - fn from_ssz_bytes(bytes: &[u8]) -> Result { - <[u8; 48] as Decode>::from_ssz_bytes(bytes).map(KzgCommitment) - } -} - -impl Encode for KzgCommitment { - fn is_ssz_fixed_len() -> bool { - <[u8; 48] as Encode>::is_ssz_fixed_len() - } - - fn ssz_append(&self, buf: &mut Vec) { - self.0.ssz_append(buf) - } - - fn ssz_bytes_len(&self) -> usize { - self.0.ssz_bytes_len() - } -} diff --git a/consensus/types/src/kzg_proof.rs b/consensus/types/src/kzg_proof.rs index 92a994a85..7cd6a8e58 100644 --- a/consensus/types/src/kzg_proof.rs +++ b/consensus/types/src/kzg_proof.rs @@ -2,13 +2,15 @@ use crate::test_utils::{RngCore, TestRandom}; use serde::{Deserialize, Serialize}; use serde_big_array::BigArray; use ssz::{Decode, DecodeError, Encode}; +use ssz_derive::{Decode, Encode}; use std::fmt; use tree_hash::{PackedEncoding, TreeHash}; const KZG_PROOF_BYTES_LEN: usize = 48; -#[derive(Debug, PartialEq, Hash, Clone, Copy, Serialize, Deserialize)] +#[derive(Debug, PartialEq, Hash, Clone, Copy, Encode, Decode, Serialize, Deserialize)] #[serde(transparent)] +#[ssz(struct_behaviour = "transparent")] pub struct KzgProof(#[serde(with = "BigArray")] pub [u8; KZG_PROOF_BYTES_LEN]); impl fmt::Display for KzgProof { @@ -35,38 +37,6 @@ impl Into<[u8; KZG_PROOF_BYTES_LEN]> for KzgProof { } } -impl Encode for KzgProof { - fn is_ssz_fixed_len() -> bool { - <[u8; KZG_PROOF_BYTES_LEN] as Encode>::is_ssz_fixed_len() - } - - fn ssz_fixed_len() -> usize { - <[u8; KZG_PROOF_BYTES_LEN] as Encode>::ssz_fixed_len() - } - - fn ssz_bytes_len(&self) -> usize { - self.0.ssz_bytes_len() - } - - fn ssz_append(&self, buf: &mut Vec) { - self.0.ssz_append(buf) - } -} - -impl Decode for KzgProof { - fn is_ssz_fixed_len() -> bool { - <[u8; KZG_PROOF_BYTES_LEN] as Decode>::is_ssz_fixed_len() - } - - fn ssz_fixed_len() -> usize { - <[u8; KZG_PROOF_BYTES_LEN] as Decode>::ssz_fixed_len() - } - - fn from_ssz_bytes(bytes: &[u8]) -> Result { - <[u8; KZG_PROOF_BYTES_LEN]>::from_ssz_bytes(bytes).map(Self) - } -} - impl TreeHash for KzgProof { fn tree_hash_type() -> tree_hash::TreeHashType { <[u8; KZG_PROOF_BYTES_LEN]>::tree_hash_type() diff --git a/consensus/types/src/payload.rs b/consensus/types/src/payload.rs index ea483efaf..5b457daee 100644 --- a/consensus/types/src/payload.rs +++ b/consensus/types/src/payload.rs @@ -17,16 +17,17 @@ pub enum BlockType { Full, } -// + TryFrom> +/// A trait representing behavior of an `ExecutionPayload` that either has a full list of transactions +/// or a transaction hash in it's place. pub trait ExecPayload: Debug + Clone + PartialEq + Hash + TreeHash + Send { fn block_type() -> BlockType; /// Convert the payload into a payload header. fn to_execution_payload_header(&self) -> ExecutionPayloadHeader; - // We provide a subset of field accessors, for the fields used in `consensus`. - // - // More fields can be added here if you wish. + /// We provide a subset of field accessors, for the fields used in `consensus`. + /// + /// More fields can be added here if you wish. fn parent_hash(&self) -> ExecutionBlockHash; fn prev_randao(&self) -> Hash256; fn block_number(&self) -> u64; @@ -34,14 +35,13 @@ pub trait ExecPayload: Debug + Clone + PartialEq + Hash + TreeHash + fn block_hash(&self) -> ExecutionBlockHash; fn fee_recipient(&self) -> Address; fn gas_limit(&self) -> u64; - - /// This will return `None` on blinded blocks or pre-merge blocks. fn transactions(&self) -> Option<&Transactions>; - // Is this a default payload? (pre-merge) + /// Is this a default payload? (pre-merge) fn is_default(&self) -> bool; } +/// `ExecPayload` functionality the requires ownership. pub trait OwnedExecPayload: ExecPayload + Default + Serialize + DeserializeOwned + Encode + Decode + TestRandom + 'static { @@ -106,13 +106,15 @@ pub trait AbstractExecPayload: ), derivative(PartialEq, Hash(bound = "T: EthSpec")), serde(bound = "T: EthSpec", deny_unknown_fields), - cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary)) + cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary)), + ssz(struct_behaviour = "transparent"), ), ref_attributes( derive(Debug, Derivative, TreeHash), derivative(PartialEq, Hash(bound = "T: EthSpec")), tree_hash(enum_behaviour = "transparent"), ), + map_into(ExecutionPayload), cast_error(ty = "Error", expr = "BeaconStateError::IncorrectStateVariant"), partial_getter_error(ty = "Error", expr = "BeaconStateError::IncorrectStateVariant") )] @@ -155,162 +157,16 @@ impl<'a, T: EthSpec> From> for ExecutionPayload { } } -impl ExecPayload for FullPayloadMerge { - fn block_type() -> BlockType { - BlockType::Full - } - - fn to_execution_payload_header(&self) -> ExecutionPayloadHeader { - ExecutionPayloadHeader::Merge(ExecutionPayloadHeaderMerge::from( - self.execution_payload.clone(), - )) - } - - fn parent_hash(&self) -> ExecutionBlockHash { - self.execution_payload.parent_hash - } - - fn prev_randao(&self) -> Hash256 { - self.execution_payload.prev_randao - } - - fn block_number(&self) -> u64 { - self.execution_payload.block_number - } - - fn timestamp(&self) -> u64 { - self.execution_payload.timestamp - } - - fn block_hash(&self) -> ExecutionBlockHash { - self.execution_payload.block_hash - } - - fn fee_recipient(&self) -> Address { - self.execution_payload.fee_recipient - } - - fn gas_limit(&self) -> u64 { - self.execution_payload.gas_limit - } - - fn transactions(&self) -> Option<&Transactions> { - Some(&self.execution_payload.transactions) - } - - // TODO: can this function be optimized? - fn is_default(&self) -> bool { - self.execution_payload == ExecutionPayloadMerge::default() - } -} -impl ExecPayload for FullPayloadCapella { - fn block_type() -> BlockType { - BlockType::Full - } - - fn to_execution_payload_header(&self) -> ExecutionPayloadHeader { - ExecutionPayloadHeader::Capella(ExecutionPayloadHeaderCapella::from( - self.execution_payload.clone(), - )) - } - - fn parent_hash(&self) -> ExecutionBlockHash { - self.execution_payload.parent_hash - } - - fn prev_randao(&self) -> Hash256 { - self.execution_payload.prev_randao - } - - fn block_number(&self) -> u64 { - self.execution_payload.block_number - } - - fn timestamp(&self) -> u64 { - self.execution_payload.timestamp - } - - fn block_hash(&self) -> ExecutionBlockHash { - self.execution_payload.block_hash - } - - fn fee_recipient(&self) -> Address { - self.execution_payload.fee_recipient - } - - fn gas_limit(&self) -> u64 { - self.execution_payload.gas_limit - } - - fn transactions(&self) -> Option<&Transactions> { - Some(&self.execution_payload.transactions) - } - - // TODO: can this function be optimized? - fn is_default(&self) -> bool { - self.execution_payload == ExecutionPayloadCapella::default() - } -} -impl ExecPayload for FullPayloadEip4844 { - fn block_type() -> BlockType { - BlockType::Full - } - - fn to_execution_payload_header(&self) -> ExecutionPayloadHeader { - ExecutionPayloadHeader::Eip4844(ExecutionPayloadHeaderEip4844::from( - self.execution_payload.clone(), - )) - } - - fn parent_hash(&self) -> ExecutionBlockHash { - self.execution_payload.parent_hash - } - - fn prev_randao(&self) -> Hash256 { - self.execution_payload.prev_randao - } - - fn block_number(&self) -> u64 { - self.execution_payload.block_number - } - - fn timestamp(&self) -> u64 { - self.execution_payload.timestamp - } - - fn block_hash(&self) -> ExecutionBlockHash { - self.execution_payload.block_hash - } - - fn fee_recipient(&self) -> Address { - self.execution_payload.fee_recipient - } - - fn gas_limit(&self) -> u64 { - self.execution_payload.gas_limit - } - - fn transactions(&self) -> Option<&Transactions> { - Some(&self.execution_payload.transactions) - } - - // TODO: can this function be optimized? - fn is_default(&self) -> bool { - self.execution_payload == ExecutionPayloadEip4844::default() - } -} - impl ExecPayload for FullPayload { fn block_type() -> BlockType { BlockType::Full } fn to_execution_payload_header(&self) -> ExecutionPayloadHeader { - match self { - Self::Merge(payload) => payload.to_execution_payload_header(), - Self::Capella(payload) => payload.to_execution_payload_header(), - Self::Eip4844(payload) => payload.to_execution_payload_header(), - } + let payload = map_full_payload_into_execution_payload!(self.clone(), |inner, cons| { + cons(inner.execution_payload) + }); + ExecutionPayloadHeader::from(payload) } fn parent_hash<'a>(&'a self) -> ExecutionBlockHash { @@ -362,29 +218,26 @@ impl ExecPayload for FullPayload { }) } - fn transactions<'a>(&'a self) -> Option<&'a Transactions> { + fn transactions<'a>(&'a self) -> Option<&Transactions> { map_full_payload_ref!(&'a _, self.to_ref(), move |payload, cons| { cons(payload); Some(&payload.execution_payload.transactions) }) } - fn is_default(&self) -> bool { - match self { - Self::Merge(payload) => payload.is_default(), - Self::Capella(payload) => payload.is_default(), - Self::Eip4844(payload) => payload.is_default(), - } + fn is_default<'a>(&'a self) -> bool { + map_full_payload_ref!(&'a _, self.to_ref(), move |payload, cons| { + cons(payload); + payload.execution_payload == <_>::default() + }) } } impl FullPayload { pub fn execution_payload(&self) -> ExecutionPayload { - match self { - Self::Merge(full) => ExecutionPayload::Merge(full.execution_payload.clone()), - Self::Capella(full) => ExecutionPayload::Capella(full.execution_payload.clone()), - Self::Eip4844(full) => ExecutionPayload::Eip4844(full.execution_payload.clone()), - } + map_full_payload_into_execution_payload!(self.clone(), |inner, cons| { + cons(inner.execution_payload) + }) } } @@ -393,12 +246,11 @@ impl<'b, T: EthSpec> ExecPayload for FullPayloadRef<'b, T> { BlockType::Full } - fn to_execution_payload_header(&self) -> ExecutionPayloadHeader { - match self { - Self::Merge(payload) => payload.to_execution_payload_header(), - Self::Capella(payload) => payload.to_execution_payload_header(), - Self::Eip4844(payload) => payload.to_execution_payload_header(), - } + fn to_execution_payload_header<'a>(&'a self) -> ExecutionPayloadHeader { + map_full_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + ExecutionPayloadHeader::from(payload.to_execution_payload_header()) + }) } fn parent_hash<'a>(&'a self) -> ExecutionBlockHash { @@ -450,7 +302,7 @@ impl<'b, T: EthSpec> ExecPayload for FullPayloadRef<'b, T> { }) } - fn transactions<'a>(&'a self) -> Option<&'a Transactions> { + fn transactions<'a>(&'a self) -> Option<&Transactions> { map_full_payload_ref!(&'a _, self, move |payload, cons| { cons(payload); Some(&payload.execution_payload.transactions) @@ -459,17 +311,10 @@ impl<'b, T: EthSpec> ExecPayload for FullPayloadRef<'b, T> { // TODO: can this function be optimized? fn is_default<'a>(&'a self) -> bool { - match self { - Self::Merge(payload_ref) => { - payload_ref.execution_payload == ExecutionPayloadMerge::default() - } - Self::Capella(payload_ref) => { - payload_ref.execution_payload == ExecutionPayloadCapella::default() - } - Self::Eip4844(payload_ref) => { - payload_ref.execution_payload == ExecutionPayloadEip4844::default() - } - } + map_full_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + payload.execution_payload == <_>::default() + }) } } @@ -490,41 +335,6 @@ impl AbstractExecPayload for FullPayload { } } -//FIXME(sean) fix errors -impl TryInto> for FullPayload { - type Error = (); - - fn try_into(self) -> Result, Self::Error> { - match self { - FullPayload::Merge(payload) => Ok(payload), - FullPayload::Capella(_) => Err(()), - FullPayload::Eip4844(_) => Err(()), - } - } -} -impl TryInto> for FullPayload { - type Error = (); - - fn try_into(self) -> Result, Self::Error> { - match self { - FullPayload::Merge(_) => Err(()), - FullPayload::Capella(payload) => Ok(payload), - FullPayload::Eip4844(_) => Err(()), - } - } -} -impl TryInto> for FullPayload { - type Error = (); - - fn try_into(self) -> Result, Self::Error> { - match self { - FullPayload::Merge(_) => Err(()), - FullPayload::Capella(_) => Err(()), - FullPayload::Eip4844(payload) => Ok(payload), - } - } -} - impl From> for FullPayload { fn from(execution_payload: ExecutionPayload) -> Self { match execution_payload { @@ -548,60 +358,6 @@ impl TryFrom> for FullPayload { } } -impl From> for FullPayloadMerge { - fn from(execution_payload: ExecutionPayloadMerge) -> Self { - Self { execution_payload } - } -} -impl From> for FullPayloadCapella { - fn from(execution_payload: ExecutionPayloadCapella) -> Self { - Self { execution_payload } - } -} -impl From> for FullPayloadEip4844 { - fn from(execution_payload: ExecutionPayloadEip4844) -> Self { - Self { execution_payload } - } -} - -impl TryFrom> for FullPayloadMerge { - type Error = (); - fn try_from(_: ExecutionPayloadHeader) -> Result { - Err(()) - } -} -impl TryFrom> for FullPayloadCapella { - type Error = (); - fn try_from(_: ExecutionPayloadHeader) -> Result { - Err(()) - } -} -impl TryFrom> for FullPayloadEip4844 { - type Error = (); - fn try_from(_: ExecutionPayloadHeader) -> Result { - Err(()) - } -} - -impl TryFrom> for FullPayloadMerge { - type Error = (); - fn try_from(_: ExecutionPayloadHeaderMerge) -> Result { - Err(()) - } -} -impl TryFrom> for FullPayloadCapella { - type Error = (); - fn try_from(_: ExecutionPayloadHeaderCapella) -> Result { - Err(()) - } -} -impl TryFrom> for FullPayloadEip4844 { - type Error = (); - fn try_from(_: ExecutionPayloadHeaderEip4844) -> Result { - Err(()) - } -} - #[superstruct( variants(Merge, Capella, Eip4844), variant_attributes( @@ -618,13 +374,15 @@ impl TryFrom> for FullPayloadEip484 ), derivative(PartialEq, Hash(bound = "T: EthSpec")), serde(bound = "T: EthSpec", deny_unknown_fields), - cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary)) + cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary)), + ssz(struct_behaviour = "transparent"), ), ref_attributes( derive(Debug, Derivative, TreeHash), derivative(PartialEq, Hash(bound = "T: EthSpec")), tree_hash(enum_behaviour = "transparent"), ), + map_into(ExecutionPayloadHeader), cast_error(ty = "Error", expr = "BeaconStateError::IncorrectStateVariant"), partial_getter_error(ty = "Error", expr = "BeaconStateError::IncorrectStateVariant") )] @@ -647,326 +405,340 @@ impl ExecPayload for BlindedPayload { } fn to_execution_payload_header(&self) -> ExecutionPayloadHeader { - match self { - Self::Merge(payload) => { - ExecutionPayloadHeader::Merge(payload.execution_payload_header.clone()) - } - Self::Capella(payload) => { - ExecutionPayloadHeader::Capella(payload.execution_payload_header.clone()) - } - Self::Eip4844(payload) => { - ExecutionPayloadHeader::Eip4844(payload.execution_payload_header.clone()) - } - } + map_blinded_payload_into_execution_payload_header!(self.clone(), |inner, cons| { + cons(inner.execution_payload_header) + }) } - fn parent_hash(&self) -> ExecutionBlockHash { - match self { - Self::Merge(payload) => payload.execution_payload_header.parent_hash, - Self::Capella(payload) => payload.execution_payload_header.parent_hash, - Self::Eip4844(payload) => payload.execution_payload_header.parent_hash, - } + fn parent_hash<'a>(&'a self) -> ExecutionBlockHash { + map_blinded_payload_ref!(&'a _, self.to_ref(), move |payload, cons| { + cons(payload); + payload.execution_payload_header.parent_hash + }) } - fn prev_randao(&self) -> Hash256 { - match self { - Self::Merge(payload) => payload.execution_payload_header.prev_randao, - Self::Capella(payload) => payload.execution_payload_header.prev_randao, - Self::Eip4844(payload) => payload.execution_payload_header.prev_randao, - } + fn prev_randao<'a>(&'a self) -> Hash256 { + map_blinded_payload_ref!(&'a _, self.to_ref(), move |payload, cons| { + cons(payload); + payload.execution_payload_header.prev_randao + }) } - fn block_number(&self) -> u64 { - match self { - Self::Merge(payload) => payload.execution_payload_header.block_number, - Self::Capella(payload) => payload.execution_payload_header.block_number, - Self::Eip4844(payload) => payload.execution_payload_header.block_number, - } + fn block_number<'a>(&'a self) -> u64 { + map_blinded_payload_ref!(&'a _, self.to_ref(), move |payload, cons| { + cons(payload); + payload.execution_payload_header.block_number + }) } - fn timestamp(&self) -> u64 { - match self { - Self::Merge(payload) => payload.execution_payload_header.timestamp, - Self::Capella(payload) => payload.execution_payload_header.timestamp, - Self::Eip4844(payload) => payload.execution_payload_header.timestamp, - } + fn timestamp<'a>(&'a self) -> u64 { + map_blinded_payload_ref!(&'a _, self.to_ref(), move |payload, cons| { + cons(payload); + payload.execution_payload_header.timestamp + }) } - fn block_hash(&self) -> ExecutionBlockHash { - match self { - Self::Merge(payload) => payload.execution_payload_header.block_hash, - Self::Capella(payload) => payload.execution_payload_header.block_hash, - Self::Eip4844(payload) => payload.execution_payload_header.block_hash, - } + fn block_hash<'a>(&'a self) -> ExecutionBlockHash { + map_blinded_payload_ref!(&'a _, self.to_ref(), move |payload, cons| { + cons(payload); + payload.execution_payload_header.block_hash + }) } - fn fee_recipient(&self) -> Address { - match self { - Self::Merge(payload) => payload.execution_payload_header.fee_recipient, - Self::Capella(payload) => payload.execution_payload_header.fee_recipient, - Self::Eip4844(payload) => payload.execution_payload_header.fee_recipient, - } + fn fee_recipient<'a>(&'a self) -> Address { + map_blinded_payload_ref!(&'a _, self.to_ref(), move |payload, cons| { + cons(payload); + payload.execution_payload_header.fee_recipient + }) } - fn gas_limit(&self) -> u64 { - match self { - Self::Merge(payload) => payload.execution_payload_header.gas_limit, - Self::Capella(payload) => payload.execution_payload_header.gas_limit, - Self::Eip4844(payload) => payload.execution_payload_header.gas_limit, - } + fn gas_limit<'a>(&'a self) -> u64 { + map_blinded_payload_ref!(&'a _, self.to_ref(), move |payload, cons| { + cons(payload); + payload.execution_payload_header.gas_limit + }) } - fn transactions(&self) -> Option<&Transactions> { + fn transactions<'a>(&'a self) -> Option<&Transactions> { None } - // TODO: can this function be optimized? - fn is_default(&self) -> bool { - match self { - Self::Merge(payload) => payload.is_default(), - Self::Capella(payload) => payload.is_default(), - Self::Eip4844(payload) => payload.is_default(), - } + fn is_default<'a>(&'a self) -> bool { + map_blinded_payload_ref!(&'a _, self.to_ref(), move |payload, cons| { + cons(payload); + payload.execution_payload_header == <_>::default() + }) } } -// FIXME(sproul): deduplicate this impl<'b, T: EthSpec> ExecPayload for BlindedPayloadRef<'b, T> { fn block_type() -> BlockType { BlockType::Blinded } - fn to_execution_payload_header(&self) -> ExecutionPayloadHeader { - match self { - Self::Merge(payload) => { - ExecutionPayloadHeader::Merge(payload.execution_payload_header.clone()) - } - Self::Capella(payload) => { - ExecutionPayloadHeader::Capella(payload.execution_payload_header.clone()) - } - Self::Eip4844(payload) => { - ExecutionPayloadHeader::Eip4844(payload.execution_payload_header.clone()) - } - } + fn to_execution_payload_header<'a>(&'a self) -> ExecutionPayloadHeader { + map_blinded_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + payload.to_execution_payload_header() + }) } - fn parent_hash(&self) -> ExecutionBlockHash { - match self { - Self::Merge(payload) => payload.execution_payload_header.parent_hash, - Self::Capella(payload) => payload.execution_payload_header.parent_hash, - Self::Eip4844(payload) => payload.execution_payload_header.parent_hash, - } + fn parent_hash<'a>(&'a self) -> ExecutionBlockHash { + map_blinded_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + payload.execution_payload_header.parent_hash + }) } - fn prev_randao(&self) -> Hash256 { - match self { - Self::Merge(payload) => payload.execution_payload_header.prev_randao, - Self::Capella(payload) => payload.execution_payload_header.prev_randao, - Self::Eip4844(payload) => payload.execution_payload_header.prev_randao, - } + fn prev_randao<'a>(&'a self) -> Hash256 { + map_blinded_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + payload.execution_payload_header.prev_randao + }) } - fn block_number(&self) -> u64 { - match self { - Self::Merge(payload) => payload.execution_payload_header.block_number, - Self::Capella(payload) => payload.execution_payload_header.block_number, - Self::Eip4844(payload) => payload.execution_payload_header.block_number, - } + fn block_number<'a>(&'a self) -> u64 { + map_blinded_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + payload.execution_payload_header.block_number + }) } - fn timestamp(&self) -> u64 { - match self { - Self::Merge(payload) => payload.execution_payload_header.timestamp, - Self::Capella(payload) => payload.execution_payload_header.timestamp, - Self::Eip4844(payload) => payload.execution_payload_header.timestamp, - } + fn timestamp<'a>(&'a self) -> u64 { + map_blinded_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + payload.execution_payload_header.timestamp + }) } - fn block_hash(&self) -> ExecutionBlockHash { - match self { - Self::Merge(payload) => payload.execution_payload_header.block_hash, - Self::Capella(payload) => payload.execution_payload_header.block_hash, - Self::Eip4844(payload) => payload.execution_payload_header.block_hash, - } + fn block_hash<'a>(&'a self) -> ExecutionBlockHash { + map_blinded_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + payload.execution_payload_header.block_hash + }) } - fn fee_recipient(&self) -> Address { - match self { - Self::Merge(payload) => payload.execution_payload_header.fee_recipient, - Self::Capella(payload) => payload.execution_payload_header.fee_recipient, - Self::Eip4844(payload) => payload.execution_payload_header.fee_recipient, - } + fn fee_recipient<'a>(&'a self) -> Address { + map_blinded_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + payload.execution_payload_header.fee_recipient + }) } - fn gas_limit(&self) -> u64 { - match self { - Self::Merge(payload) => payload.execution_payload_header.gas_limit, - Self::Capella(payload) => payload.execution_payload_header.gas_limit, - Self::Eip4844(payload) => payload.execution_payload_header.gas_limit, - } + fn gas_limit<'a>(&'a self) -> u64 { + map_blinded_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + payload.execution_payload_header.gas_limit + }) } - fn transactions(&self) -> Option<&Transactions> { + fn transactions<'a>(&'a self) -> Option<&Transactions> { None } // TODO: can this function be optimized? fn is_default<'a>(&'a self) -> bool { - match self { - Self::Merge(payload) => { - payload.execution_payload_header == ExecutionPayloadHeaderMerge::default() + map_blinded_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + payload.execution_payload_header == <_>::default() + }) + } +} + +macro_rules! impl_exec_payload_common { + ($wrapper_type:ident, $wrapped_type_full:ident, $wrapped_header_type:ident, $wrapped_field:ident, $fork_variant:ident, $block_type_variant:ident, $f:block) => { + impl ExecPayload for $wrapper_type { + fn block_type() -> BlockType { + BlockType::$block_type_variant } - Self::Capella(payload) => { - payload.execution_payload_header == ExecutionPayloadHeaderCapella::default() + + fn to_execution_payload_header(&self) -> ExecutionPayloadHeader { + ExecutionPayloadHeader::$fork_variant($wrapped_header_type::from( + self.$wrapped_field.clone(), + )) } - Self::Eip4844(payload) => { - payload.execution_payload_header == ExecutionPayloadHeaderEip4844::default() + + fn parent_hash(&self) -> ExecutionBlockHash { + self.$wrapped_field.parent_hash + } + + fn prev_randao(&self) -> Hash256 { + self.$wrapped_field.prev_randao + } + + fn block_number(&self) -> u64 { + self.$wrapped_field.block_number + } + + fn timestamp(&self) -> u64 { + self.$wrapped_field.timestamp + } + + fn block_hash(&self) -> ExecutionBlockHash { + self.$wrapped_field.block_hash + } + + fn fee_recipient(&self) -> Address { + self.$wrapped_field.fee_recipient + } + + fn gas_limit(&self) -> u64 { + self.$wrapped_field.gas_limit + } + + fn is_default(&self) -> bool { + self.$wrapped_field == $wrapped_type_full::default() + } + + fn transactions(&self) -> Option<&Transactions> { + let f = $f; + f(self) } } - } + + impl From<$wrapped_type_full> for $wrapper_type { + fn from($wrapped_field: $wrapped_type_full) -> Self { + Self { $wrapped_field } + } + } + }; } -impl ExecPayload for BlindedPayloadMerge { - fn block_type() -> BlockType { - BlockType::Full - } +macro_rules! impl_exec_payload_for_fork { + ($wrapper_type_header:ident, $wrapper_type_full:ident, $wrapped_type_header:ident, $wrapped_type_full:ident, $fork_variant:ident) => { + //*************** Blinded payload implementations ******************// - fn to_execution_payload_header(&self) -> ExecutionPayloadHeader { - ExecutionPayloadHeader::Merge(ExecutionPayloadHeaderMerge::from( - self.execution_payload_header.clone(), - )) - } + impl_exec_payload_common!( + $wrapper_type_header, + $wrapped_type_header, + $wrapped_type_header, + execution_payload_header, + $fork_variant, + Blinded, + { |_| { None } } + ); - fn parent_hash(&self) -> ExecutionBlockHash { - self.execution_payload_header.parent_hash - } + impl TryInto<$wrapper_type_header> for BlindedPayload { + type Error = Error; - fn prev_randao(&self) -> Hash256 { - self.execution_payload_header.prev_randao - } + fn try_into(self) -> Result<$wrapper_type_header, Self::Error> { + match self { + BlindedPayload::$fork_variant(payload) => Ok(payload), + _ => Err(Error::IncorrectStateVariant), + } + } + } - fn block_number(&self) -> u64 { - self.execution_payload_header.block_number - } + // NOTE: the `Default` implementation for `BlindedPayload` needs to be different from the `Default` + // implementation for `ExecutionPayloadHeader` because payloads are checked for equality against the + // default payload in `is_merge_transition_block` to determine whether the merge has occurred. + // + // The default `BlindedPayload` is therefore the payload header that results from blinding the + // default `ExecutionPayload`, which differs from the default `ExecutionPayloadHeader` in that + // its `transactions_root` is the hash of the empty list rather than 0x0. + impl Default for $wrapper_type_header { + fn default() -> Self { + Self { + execution_payload_header: $wrapped_type_header::from( + $wrapped_type_full::default(), + ), + } + } + } - fn timestamp(&self) -> u64 { - self.execution_payload_header.timestamp - } + impl TryFrom> for $wrapper_type_header { + type Error = Error; + fn try_from(header: ExecutionPayloadHeader) -> Result { + match header { + ExecutionPayloadHeader::$fork_variant(execution_payload_header) => { + Ok(execution_payload_header.into()) + } + _ => Err(Error::PayloadConversionLogicFlaw), + } + } + } - fn block_hash(&self) -> ExecutionBlockHash { - self.execution_payload_header.block_hash - } + // FIXME(sproul): consider adding references to these From impls + impl From<$wrapped_type_full> for $wrapper_type_header { + fn from(execution_payload: $wrapped_type_full) -> Self { + Self { + execution_payload_header: $wrapped_type_header::from(execution_payload), + } + } + } - fn fee_recipient(&self) -> Address { - self.execution_payload_header.fee_recipient - } + //*************** Full payload implementations ******************// - fn gas_limit(&self) -> u64 { - self.execution_payload_header.gas_limit - } + impl_exec_payload_common!( + $wrapper_type_full, + $wrapped_type_full, + $wrapped_type_header, + execution_payload, + $fork_variant, + Full, + { + let c: for<'a> fn(&'a $wrapper_type_full) -> Option<&'a Transactions> = + |payload: &$wrapper_type_full| Some(&payload.execution_payload.transactions); + c + } + ); - fn transactions(&self) -> Option<&Transactions> { - None - } + impl Default for $wrapper_type_full { + fn default() -> Self { + Self { + execution_payload: $wrapped_type_full::default(), + } + } + } - fn is_default(&self) -> bool { - self.execution_payload_header == ExecutionPayloadHeaderMerge::default() - } + impl TryFrom> for $wrapper_type_full { + type Error = Error; + fn try_from(_: ExecutionPayloadHeader) -> Result { + Err(Error::PayloadConversionLogicFlaw) + } + } + + impl TryFrom<$wrapped_type_header> for $wrapper_type_full { + type Error = Error; + fn try_from(_: $wrapped_type_header) -> Result { + Err(Error::PayloadConversionLogicFlaw) + } + } + + impl TryInto<$wrapper_type_full> for FullPayload { + type Error = Error; + + fn try_into(self) -> Result<$wrapper_type_full, Self::Error> { + match self { + FullPayload::$fork_variant(payload) => Ok(payload), + _ => Err(Error::PayloadConversionLogicFlaw), + } + } + } + }; } -impl ExecPayload for BlindedPayloadCapella { - fn block_type() -> BlockType { - BlockType::Full - } - fn to_execution_payload_header(&self) -> ExecutionPayloadHeader { - ExecutionPayloadHeader::Capella(ExecutionPayloadHeaderCapella::from( - self.execution_payload_header.clone(), - )) - } - - fn parent_hash(&self) -> ExecutionBlockHash { - self.execution_payload_header.parent_hash - } - - fn prev_randao(&self) -> Hash256 { - self.execution_payload_header.prev_randao - } - - fn block_number(&self) -> u64 { - self.execution_payload_header.block_number - } - - fn timestamp(&self) -> u64 { - self.execution_payload_header.timestamp - } - - fn block_hash(&self) -> ExecutionBlockHash { - self.execution_payload_header.block_hash - } - - fn fee_recipient(&self) -> Address { - self.execution_payload_header.fee_recipient - } - - fn gas_limit(&self) -> u64 { - self.execution_payload_header.gas_limit - } - - fn transactions(&self) -> Option<&Transactions> { - None - } - - fn is_default(&self) -> bool { - self.execution_payload_header == ExecutionPayloadHeaderCapella::default() - } -} -impl ExecPayload for BlindedPayloadEip4844 { - fn block_type() -> BlockType { - BlockType::Full - } - - fn to_execution_payload_header(&self) -> ExecutionPayloadHeader { - ExecutionPayloadHeader::Eip4844(ExecutionPayloadHeaderEip4844::from( - self.execution_payload_header.clone(), - )) - } - - fn parent_hash(&self) -> ExecutionBlockHash { - self.execution_payload_header.parent_hash - } - - fn prev_randao(&self) -> Hash256 { - self.execution_payload_header.prev_randao - } - - fn block_number(&self) -> u64 { - self.execution_payload_header.block_number - } - - fn timestamp(&self) -> u64 { - self.execution_payload_header.timestamp - } - - fn block_hash(&self) -> ExecutionBlockHash { - self.execution_payload_header.block_hash - } - - fn fee_recipient(&self) -> Address { - self.execution_payload_header.fee_recipient - } - - fn gas_limit(&self) -> u64 { - self.execution_payload_header.gas_limit - } - - fn transactions(&self) -> Option<&Transactions> { - None - } - - fn is_default(&self) -> bool { - self.execution_payload_header == ExecutionPayloadHeaderEip4844::default() - } -} +impl_exec_payload_for_fork!( + BlindedPayloadMerge, + FullPayloadMerge, + ExecutionPayloadHeaderMerge, + ExecutionPayloadMerge, + Merge +); +impl_exec_payload_for_fork!( + BlindedPayloadCapella, + FullPayloadCapella, + ExecutionPayloadHeaderCapella, + ExecutionPayloadCapella, + Capella +); +impl_exec_payload_for_fork!( + BlindedPayloadEip4844, + FullPayloadEip4844, + ExecutionPayloadHeaderEip4844, + ExecutionPayloadEip4844, + Eip4844 +); impl AbstractExecPayload for BlindedPayload { type Ref<'a> = BlindedPayloadRef<'a, T>; @@ -985,110 +757,6 @@ impl AbstractExecPayload for BlindedPayload { } } -//FIXME(sean) fix errors -impl TryInto> for BlindedPayload { - type Error = (); - - fn try_into(self) -> Result, Self::Error> { - match self { - BlindedPayload::Merge(payload) => Ok(payload), - BlindedPayload::Capella(_) => Err(()), - BlindedPayload::Eip4844(_) => Err(()), - } - } -} -impl TryInto> for BlindedPayload { - type Error = (); - - fn try_into(self) -> Result, Self::Error> { - match self { - BlindedPayload::Merge(_) => Err(()), - BlindedPayload::Capella(payload) => Ok(payload), - BlindedPayload::Eip4844(_) => Err(()), - } - } -} -impl TryInto> for BlindedPayload { - type Error = (); - - fn try_into(self) -> Result, Self::Error> { - match self { - BlindedPayload::Merge(_) => Err(()), - BlindedPayload::Capella(_) => Err(()), - BlindedPayload::Eip4844(payload) => Ok(payload), - } - } -} - -impl Default for FullPayloadMerge { - fn default() -> Self { - Self { - execution_payload: ExecutionPayloadMerge::default(), - } - } -} -impl Default for FullPayloadCapella { - fn default() -> Self { - Self { - execution_payload: ExecutionPayloadCapella::default(), - } - } -} -impl Default for FullPayloadEip4844 { - fn default() -> Self { - Self { - execution_payload: ExecutionPayloadEip4844::default(), - } - } -} - -// NOTE: the `Default` implementation for `BlindedPayload` needs to be different from the `Default` -// implementation for `ExecutionPayloadHeader` because payloads are checked for equality against the -// default payload in `is_merge_transition_block` to determine whether the merge has occurred. -// -// The default `BlindedPayload` is therefore the payload header that results from blinding the -// default `ExecutionPayload`, which differs from the default `ExecutionPayloadHeader` in that -// its `transactions_root` is the hash of the empty list rather than 0x0. -/* -impl Default for BlindedPayload { - fn default() -> Self { - Self { - execution_payload_header: ExecutionPayloadHeader::from(&ExecutionPayload::default()), - } - } -} -*/ - -impl Default for BlindedPayloadMerge { - fn default() -> Self { - Self { - execution_payload_header: ExecutionPayloadHeaderMerge::from( - ExecutionPayloadMerge::default(), - ), - } - } -} - -impl Default for BlindedPayloadCapella { - fn default() -> Self { - Self { - execution_payload_header: ExecutionPayloadHeaderCapella::from( - ExecutionPayloadCapella::default(), - ), - } - } -} - -impl Default for BlindedPayloadEip4844 { - fn default() -> Self { - Self { - execution_payload_header: ExecutionPayloadHeaderEip4844::from( - ExecutionPayloadEip4844::default(), - ), - } - } -} - impl From> for BlindedPayload { fn from(payload: ExecutionPayload) -> Self { match payload { @@ -1121,28 +789,6 @@ impl From> for BlindedPayload { } } -impl From> for BlindedPayloadMerge { - fn from(execution_payload_header: ExecutionPayloadHeaderMerge) -> Self { - Self { - execution_payload_header, - } - } -} -impl From> for BlindedPayloadCapella { - fn from(execution_payload_header: ExecutionPayloadHeaderCapella) -> Self { - Self { - execution_payload_header, - } - } -} -impl From> for BlindedPayloadEip4844 { - fn from(execution_payload_header: ExecutionPayloadHeaderEip4844) -> Self { - Self { - execution_payload_header, - } - } -} - impl From> for ExecutionPayloadHeader { fn from(blinded: BlindedPayload) -> Self { match blinded { @@ -1158,143 +804,3 @@ impl From> for ExecutionPayloadHeader { } } } - -// FIXME(sproul): consider adding references to these From impls -impl From> for BlindedPayloadMerge { - fn from(execution_payload: ExecutionPayloadMerge) -> Self { - Self { - execution_payload_header: ExecutionPayloadHeaderMerge::from(execution_payload), - } - } -} -impl From> for BlindedPayloadCapella { - fn from(execution_payload: ExecutionPayloadCapella) -> Self { - Self { - execution_payload_header: ExecutionPayloadHeaderCapella::from(execution_payload), - } - } -} -impl From> for BlindedPayloadEip4844 { - fn from(execution_payload: ExecutionPayloadEip4844) -> Self { - Self { - execution_payload_header: ExecutionPayloadHeaderEip4844::from(execution_payload), - } - } -} - -impl TryFrom> for BlindedPayloadMerge { - type Error = (); - fn try_from(header: ExecutionPayloadHeader) -> Result { - match header { - ExecutionPayloadHeader::Merge(execution_payload_header) => { - Ok(execution_payload_header.into()) - } - _ => Err(()), - } - } -} -impl TryFrom> for BlindedPayloadCapella { - type Error = (); - fn try_from(header: ExecutionPayloadHeader) -> Result { - match header { - ExecutionPayloadHeader::Capella(execution_payload_header) => { - Ok(execution_payload_header.into()) - } - _ => Err(()), - } - } -} - -impl TryFrom> for BlindedPayloadEip4844 { - type Error = (); - fn try_from(header: ExecutionPayloadHeader) -> Result { - match header { - ExecutionPayloadHeader::Eip4844(execution_payload_header) => { - Ok(execution_payload_header.into()) - } - _ => Err(()), - } - } -} - -/* -impl Decode for BlindedPayload { - fn is_ssz_fixed_len() -> bool { - as Decode>::is_ssz_fixed_len() - } - - fn ssz_fixed_len() -> usize { - as Decode>::ssz_fixed_len() - } - - fn from_ssz_bytes(bytes: &[u8]) -> Result { - Ok(Self { - execution_payload_header: ExecutionPayloadHeader::from_ssz_bytes(bytes)?, - }) - } -} - */ - -/* -impl Encode for BlindedPayload { - fn is_ssz_fixed_len() -> bool { - as Encode>::is_ssz_fixed_len() - } - - fn ssz_append(&self, buf: &mut Vec) { - self.execution_payload_header.ssz_append(buf) - } - - fn ssz_bytes_len(&self) -> usize { - self.execution_payload_header.ssz_bytes_len() - } -} -*/ - -/* -impl TreeHash for FullPayload { - fn tree_hash_type() -> tree_hash::TreeHashType { - >::tree_hash_type() - } - - fn tree_hash_packed_encoding(&self) -> tree_hash::PackedEncoding { - self.execution_payload.tree_hash_packed_encoding() - } - - fn tree_hash_packing_factor() -> usize { - >::tree_hash_packing_factor() - } - - fn tree_hash_root(&self) -> tree_hash::Hash256 { - self.execution_payload.tree_hash_root() - } -} -*/ - -/* -impl Decode for FullPayload { - fn is_ssz_fixed_len() -> bool { - as Decode>::is_ssz_fixed_len() - } - - fn from_ssz_bytes(bytes: &[u8]) -> Result { - Ok(FullPayload { - execution_payload: Decode::from_ssz_bytes(bytes)?, - }) - } -} - -impl Encode for FullPayload { - fn is_ssz_fixed_len() -> bool { - as Encode>::is_ssz_fixed_len() - } - - fn ssz_append(&self, buf: &mut Vec) { - self.execution_payload.ssz_append(buf) - } - - fn ssz_bytes_len(&self) -> usize { - self.execution_payload.ssz_bytes_len() - } -} -*/ diff --git a/slasher/Cargo.toml b/slasher/Cargo.toml index 0f24fe9f0..c5ce8793a 100644 --- a/slasher/Cargo.toml +++ b/slasher/Cargo.toml @@ -13,7 +13,7 @@ lmdb = ["lmdb-rkv", "lmdb-rkv-sys"] bincode = "1.3.1" byteorder = "1.3.4" eth2_ssz = "0.4.1" -eth2_ssz_derive = "0.3.0" +eth2_ssz_derive = "0.3.1" flate2 = { version = "1.0.14", features = ["zlib"], default-features = false } lazy_static = "1.4.0" lighthouse_metrics = { path = "../common/lighthouse_metrics" } diff --git a/testing/ef_tests/Cargo.toml b/testing/ef_tests/Cargo.toml index 04a222c7a..1f9ed4da3 100644 --- a/testing/ef_tests/Cargo.toml +++ b/testing/ef_tests/Cargo.toml @@ -23,7 +23,7 @@ serde_derive = "1.0.116" serde_repr = "0.1.6" serde_yaml = "0.8.13" eth2_ssz = "0.4.1" -eth2_ssz_derive = "0.3.0" +eth2_ssz_derive = "0.3.1" tree_hash = "0.4.1" tree_hash_derive = "0.4.0" cached_tree_hash = { path = "../../consensus/cached_tree_hash" }