Skip to main content

linera_base/
hashed.rs

1// Copyright (c) Facebook, Inc. and its affiliates.
2// Copyright (c) Zefchain Labs, Inc.
3// SPDX-License-Identifier: Apache-2.0
4
5//! A wrapper for hashable types to memoize the hash.
6
7use std::borrow::Cow;
8
9use allocative::Allocative;
10use custom_debug_derive::Debug;
11use serde::{Deserialize, Serialize};
12
13use crate::crypto::{BcsHashable, CryptoHash};
14
15/// Wrapper type around hashed instance of `T` type.
16#[derive(Debug, Allocative)]
17pub struct Hashed<T> {
18    value: T,
19    /// Hash of the value (used as key for storage).
20    hash: CryptoHash,
21}
22
23impl<T> Hashed<T> {
24    /// Creates an instance of [`Hashed`] with the given `value`.
25    pub fn new<'de>(value: T) -> Self
26    where
27        T: BcsHashable<'de>,
28    {
29        let hash = CryptoHash::new(&value);
30        Self { value, hash }
31    }
32
33    /// Creates a [`Hashed`] from a value and a precomputed hash, without recomputing it.
34    ///
35    /// The caller is responsible for the hash being the canonical hash of `value`.
36    pub fn with_hash(value: T, hash: CryptoHash) -> Self {
37        Self { value, hash }
38    }
39
40    /// Returns the hash.
41    pub fn hash(&self) -> CryptoHash {
42        self.hash
43    }
44
45    /// Returns a reference to the value, without the hash.
46    pub fn inner(&self) -> &T {
47        &self.value
48    }
49
50    /// Consumes the hashed value and returns the value without the hash.
51    pub fn into_inner(self) -> T {
52        self.value
53    }
54}
55
56impl<T: Serialize> Serialize for Hashed<T> {
57    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
58    where
59        S: serde::Serializer,
60    {
61        self.value.serialize(serializer)
62    }
63}
64
65impl<'de, T: BcsHashable<'de>> Deserialize<'de> for Hashed<T> {
66    fn deserialize<D>(deserializer: D) -> Result<Hashed<T>, D::Error>
67    where
68        D: serde::Deserializer<'de>,
69    {
70        Ok(Hashed::new(T::deserialize(deserializer)?))
71    }
72}
73
74impl<T: Clone> Clone for Hashed<T> {
75    fn clone(&self) -> Self {
76        Self {
77            value: self.value.clone(),
78            hash: self.hash,
79        }
80    }
81}
82
83impl<T: async_graphql::OutputType> async_graphql::TypeName for Hashed<T> {
84    fn type_name() -> Cow<'static, str> {
85        format!("Hashed{}", T::type_name()).into()
86    }
87}
88
89#[async_graphql::Object(cache_control(no_cache), name_type)]
90impl<T: async_graphql::OutputType + Clone> Hashed<T> {
91    #[graphql(derived(name = "hash"))]
92    async fn _hash(&self) -> CryptoHash {
93        self.hash()
94    }
95
96    #[graphql(derived(name = "value"))]
97    async fn _value(&self) -> T {
98        self.inner().clone()
99    }
100}
101
102impl<T> PartialEq for Hashed<T> {
103    fn eq(&self, other: &Self) -> bool {
104        self.hash() == other.hash()
105    }
106}
107
108impl<T> Eq for Hashed<T> {}
109
110#[cfg(test)]
111mod tests {
112    use crate::{
113        crypto::{BcsHashable, CryptoHash},
114        hashed::Hashed,
115    };
116
117    #[derive(serde::Serialize, serde::Deserialize)]
118    struct Dummy(u8);
119    impl BcsHashable<'_> for Dummy {}
120
121    #[test]
122    fn with_hash_stores_provided_hash() {
123        let forced = CryptoHash::from([9u8; 32]);
124        let hashed = Hashed::with_hash(Dummy(7), forced);
125        assert_eq!(hashed.hash(), forced);
126        assert_ne!(hashed.hash(), CryptoHash::new(&Dummy(7)));
127    }
128}