diff --git a/Cargo.toml b/Cargo.toml index 2574d328f..b419d32e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ members = [ "eth2/utils/ssz_derive", "eth2/utils/swap_or_not_shuffle", "eth2/utils/tree_hash", + "eth2/utils/tree_hash_derive", "eth2/utils/fisher_yates_shuffle", "eth2/utils/test_random_derive", "beacon_node", diff --git a/eth2/utils/tree_hash/src/btree_overlay.rs b/eth2/utils/tree_hash/src/btree_overlay.rs index 8c859d046..1e188da60 100644 --- a/eth2/utils/tree_hash/src/btree_overlay.rs +++ b/eth2/utils/tree_hash/src/btree_overlay.rs @@ -12,7 +12,7 @@ pub struct BTreeOverlay { impl BTreeOverlay { pub fn new(item: &T, initial_offset: usize) -> Result where - T: CachedTreeHashSubtree, + T: CachedTreeHashSubTree, { item.btree_overlay(initial_offset) } diff --git a/eth2/utils/tree_hash/src/cached_tree_hash.rs b/eth2/utils/tree_hash/src/cached_tree_hash.rs index 97f9388a1..048d4bab5 100644 --- a/eth2/utils/tree_hash/src/cached_tree_hash.rs +++ b/eth2/utils/tree_hash/src/cached_tree_hash.rs @@ -15,7 +15,7 @@ impl Into> for TreeHashCache { impl TreeHashCache { pub fn new(item: &T) -> Result where - T: CachedTreeHashSubtree, + T: CachedTreeHashSubTree, { item.new_cache() } @@ -32,7 +32,7 @@ impl TreeHashCache { leaves_and_subtrees: Vec, ) -> Result where - T: CachedTreeHashSubtree, + T: CachedTreeHashSubTree, { let offset_handler = BTreeOverlay::new(item, 0)?; diff --git a/eth2/utils/tree_hash/src/impls.rs b/eth2/utils/tree_hash/src/impls.rs index bd5c352c9..982e98724 100644 --- a/eth2/utils/tree_hash/src/impls.rs +++ b/eth2/utils/tree_hash/src/impls.rs @@ -4,7 +4,7 @@ use ssz::ssz_encode; mod vec; -impl CachedTreeHashSubtree for u64 { +impl CachedTreeHashSubTree for u64 { fn item_type() -> ItemType { ItemType::Basic } diff --git a/eth2/utils/tree_hash/src/impls/vec.rs b/eth2/utils/tree_hash/src/impls/vec.rs index c02460cf3..a6fad9ba6 100644 --- a/eth2/utils/tree_hash/src/impls/vec.rs +++ b/eth2/utils/tree_hash/src/impls/vec.rs @@ -1,8 +1,8 @@ use super::*; -impl CachedTreeHashSubtree> for Vec +impl CachedTreeHashSubTree> for Vec where - T: CachedTreeHashSubtree, + T: CachedTreeHashSubTree, { fn item_type() -> ItemType { ItemType::List @@ -168,7 +168,7 @@ where fn get_packed_leaves(vec: &Vec) -> Result, Error> where - T: CachedTreeHashSubtree, + T: CachedTreeHashSubTree, { let num_packed_bytes = (BYTES_PER_CHUNK / T::packing_factor()) * vec.len(); let num_leaves = num_sanitized_leaves(num_packed_bytes); diff --git a/eth2/utils/tree_hash/src/lib.rs b/eth2/utils/tree_hash/src/lib.rs index 179f557ce..5ec2b0283 100644 --- a/eth2/utils/tree_hash/src/lib.rs +++ b/eth2/utils/tree_hash/src/lib.rs @@ -35,7 +35,7 @@ pub enum ItemType { Composite, } -pub trait CachedTreeHash: CachedTreeHashSubtree + Sized { +pub trait CachedTreeHash: CachedTreeHashSubTree + Sized { fn update_internal_tree_hash_cache(self, old: T) -> Result<(Self, Self), Error>; fn cached_tree_hash_root(&self) -> Option>; @@ -43,7 +43,7 @@ pub trait CachedTreeHash: CachedTreeHashSubtree + Sized { fn clone_without_tree_hash_cache(&self) -> Self; } -pub trait CachedTreeHashSubtree { +pub trait CachedTreeHashSubTree { fn item_type() -> ItemType; fn btree_overlay(&self, chunk_offset: usize) -> Result; diff --git a/eth2/utils/tree_hash/tests/tests.rs b/eth2/utils/tree_hash/tests/tests.rs index c61a010ca..ead6d8c00 100644 --- a/eth2/utils/tree_hash/tests/tests.rs +++ b/eth2/utils/tree_hash/tests/tests.rs @@ -63,7 +63,7 @@ fn works_when_embedded() { assert_eq!(&merkle[0..32], &root[..]); } -impl CachedTreeHashSubtree for InternalCache { +impl CachedTreeHashSubTree for InternalCache { fn item_type() -> ItemType { ItemType::Composite } @@ -131,7 +131,7 @@ pub struct Inner { pub d: u64, } -impl CachedTreeHashSubtree for Inner { +impl CachedTreeHashSubTree for Inner { fn item_type() -> ItemType { ItemType::Composite } @@ -203,7 +203,7 @@ pub struct Outer { pub c: u64, } -impl CachedTreeHashSubtree for Outer { +impl CachedTreeHashSubTree for Outer { fn item_type() -> ItemType { ItemType::Composite } diff --git a/eth2/utils/tree_hash_derive/Cargo.toml b/eth2/utils/tree_hash_derive/Cargo.toml new file mode 100644 index 000000000..f227d7954 --- /dev/null +++ b/eth2/utils/tree_hash_derive/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "tree_hash_derive" +version = "0.1.0" +authors = ["Paul Hauner "] +edition = "2018" +description = "Procedural derive macros for SSZ tree hashing." + +[lib] +proc-macro = true + +[dev-dependencies] +tree_hash = { path = "../tree_hash" } + +[dependencies] +syn = "0.15" +quote = "0.6" diff --git a/eth2/utils/tree_hash_derive/src/lib.rs b/eth2/utils/tree_hash_derive/src/lib.rs new file mode 100644 index 000000000..217e91c24 --- /dev/null +++ b/eth2/utils/tree_hash_derive/src/lib.rs @@ -0,0 +1,125 @@ +#![recursion_limit = "256"] +extern crate proc_macro; + +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, DeriveInput}; + +/// Returns a Vec of `syn::Ident` for each named field in the struct, whilst filtering out fields +/// that should not be hashed. +/// +/// # Panics +/// Any unnamed struct field (like in a tuple struct) will raise a panic at compile time. +fn get_hashable_named_field_idents<'a>(struct_data: &'a syn::DataStruct) -> Vec<&'a syn::Ident> { + struct_data + .fields + .iter() + .filter_map(|f| { + if should_skip_hashing(&f) { + None + } else { + Some(match &f.ident { + Some(ref ident) => ident, + _ => panic!("tree_hash_derive only supports named struct fields."), + }) + } + }) + .collect() +} + +/// Returns true if some field has an attribute declaring it should not be hashedd. +/// +/// The field attribute is: `#[tree_hash(skip_hashing)]` +fn should_skip_hashing(field: &syn::Field) -> bool { + for attr in &field.attrs { + if attr.tts.to_string() == "( skip_hashing )" { + return true; + } + } + false +} + +/// Implements `ssz::Encodable` for some `struct`. +/// +/// Fields are encoded in the order they are defined. +#[proc_macro_derive(CachedTreeHashSubTree, attributes(tree_hash))] +pub fn subtree_derive(input: TokenStream) -> TokenStream { + let item = parse_macro_input!(input as DeriveInput); + + let name = &item.ident; + + let struct_data = match &item.data { + syn::Data::Struct(s) => s, + _ => panic!("tree_hash_derive only supports structs."), + }; + + let idents_a = get_hashable_named_field_idents(&struct_data); + let idents_b = idents_a.clone(); + let idents_c = idents_a.clone(); + let idents_d = idents_a.clone(); + + let output = quote! { + impl tree_hash::CachedTreeHashSubTree<#name> for #name { + fn item_type() -> tree_hash::ItemType { + tree_hash::ItemType::Composite + } + + fn new_cache(&self) -> Result { + let tree = tree_hash::TreeHashCache::from_leaves_and_subtrees( + self, + vec![ + #( + self.#idents_a.new_cache()?, + )* + ], + )?; + + Ok(tree) + } + + fn btree_overlay(&self, chunk_offset: usize) -> Result { + let mut lengths = vec![]; + + #( + lengths.push(tree_hash::BTreeOverlay::new(&self.#idents_b, 0)?.total_nodes()); + )* + + tree_hash::BTreeOverlay::from_lengths(chunk_offset, lengths) + } + + fn packed_encoding(&self) -> Result, tree_hash::Error> { + Err(tree_hash::Error::ShouldNeverBePacked(Self::item_type())) + } + + fn packing_factor() -> usize { + 1 + } + + fn update_cache( + &self, + other: &Self, + cache: &mut tree_hash::TreeHashCache, + chunk: usize, + ) -> Result { + let offset_handler = tree_hash::BTreeOverlay::new(self, chunk)?; + + // Skip past the internal nodes and update any changed leaf nodes. + { + let chunk = offset_handler.first_leaf_node()?; + #( + let chunk = self.#idents_c.update_cache(&other.#idents_d, cache, chunk)?; + )* + } + + for (&parent, children) in offset_handler.iter_internal_nodes().rev() { + if cache.either_modified(children)? { + cache.modify_chunk(parent, &cache.hash_children(children)?)?; + } + } + + Ok(offset_handler.next_node) + } + } + }; + output.into() +} diff --git a/eth2/utils/tree_hash_derive/tests/tests.rs b/eth2/utils/tree_hash_derive/tests/tests.rs new file mode 100644 index 000000000..a5ab112a2 --- /dev/null +++ b/eth2/utils/tree_hash_derive/tests/tests.rs @@ -0,0 +1,9 @@ +use tree_hash_derive::CachedTreeHashSubTree; + +#[derive(Clone, Debug, CachedTreeHashSubTree)] +pub struct Inner { + pub a: u64, + pub b: u64, + pub c: u64, + pub d: u64, +}