I started thinking about redesigning the TreeHash trait while working on this PR to fix some broken implementations:
The current trait is arguably dangerous to use, due to the prevalence of panicky methods:
|
impl<N: Unsigned + Clone> TreeHash for Bitfield<Variable<N>> { |
|
fn tree_hash_type() -> TreeHashType { |
|
TreeHashType::List |
|
} |
|
|
|
fn tree_hash_packed_encoding(&self) -> PackedEncoding { |
|
unreachable!("List should never be packed.") |
|
} |
|
|
|
fn tree_hash_packing_factor() -> usize { |
|
unreachable!("List should never be packed.") |
|
} |
This is due to the duplication of packing-related information across multiple methods. If we allow ourselves a breaking change, we could extend TreeHashType with the packing factor, and combine tree_hash_type and tree_hash_packing_factor into a single method:
pub enum TreeHashType {
Basic { packing_factor: usize },
Vector,
List,
Container,
}
pub trait TreeHash {
fn tree_hash_type() -> TreeHashType;
fn tree_hash_packed_encoding(&self) -> PackedEncoding;
fn tree_hash_root(&self) -> Hash256;
}
This makes invalid states (like TreeHashType::Vector and a call to tree_hash_packing_factor) unrepresentable, which is great. However we still have the issue of the tree_hash_packed_encoding method. It is not well-defined for types other than Basic ones.
Perhaps the simplest option would be to make tree_hash_packed_encoding return a Result<PackedEncoding>, and error for non-basic types.
A more "techy" option would be to include some opaque struct to act as a token for unlocking the tree_hash_packed_encoding method. Something like:
/// This struct can't be constructed except by the library.
pub struct PackedEncodingToken<T> {
_phantom: PhantomData<T>,
}
pub enum TreeHashType<T> {
Basic {
packing_factor: usize,
token: PackedEncodingToken<T>,
},
Vector,
List,
Container,
}
pub trait TreeHash {
fn tree_hash_type() -> TreeHashType<Self>;
fn tree_hash_packed_encoding(&self, token: PackedEncodingToken<Self>) -> PackedEncoding;
fn tree_hash_root(&self) -> Hash256;
}
This design prevents downstream users from calling tree_hash_packed_encoding unless they've obtained a PackedEncodingToken from calling tree_hash_type. It does not prevent library maintainers from adding incorrect implementations of tree_hash_packed_encoding, but would prevent those implementations from being reachable.
I started thinking about redesigning the
TreeHashtrait while working on this PR to fix some broken implementations:FixedBytesimplementation and bug fixes #28The current trait is arguably dangerous to use, due to the prevalence of panicky methods:
tree_hash/tree_hash/src/impls.rs
Lines 219 to 230 in e9708cd
This is due to the duplication of packing-related information across multiple methods. If we allow ourselves a breaking change, we could extend
TreeHashTypewith the packing factor, and combinetree_hash_typeandtree_hash_packing_factorinto a single method:This makes invalid states (like
TreeHashType::Vectorand a call totree_hash_packing_factor) unrepresentable, which is great. However we still have the issue of thetree_hash_packed_encodingmethod. It is not well-defined for types other thanBasicones.Perhaps the simplest option would be to make
tree_hash_packed_encodingreturn aResult<PackedEncoding>, and error for non-basic types.A more "techy" option would be to include some opaque struct to act as a token for unlocking the
tree_hash_packed_encodingmethod. Something like:This design prevents downstream users from calling
tree_hash_packed_encodingunless they've obtained aPackedEncodingTokenfrom callingtree_hash_type. It does not prevent library maintainers from adding incorrect implementations oftree_hash_packed_encoding, but would prevent those implementations from being reachable.