alloy_primitives/utils/
keccak_cache.rs1use super::{hint::unlikely, keccak256_impl as keccak256};
9use crate::{B256, KECCAK256_EMPTY};
10use std::{mem::MaybeUninit, sync::OnceLock};
11
12#[cfg(feature = "keccak-cache-stats")]
13pub use stats::{KECCAK_CACHE_STATS, KeccakCacheStats};
14
15pub(super) const MAX_INPUT_LEN: usize =
17 128 - size_of::<B256>() - size_of::<u8>() - size_of::<usize>();
18
19const DEFAULT_COUNT: usize = 1 << 17; static CACHE: OnceLock<Cache> = OnceLock::new();
21
22type Cache = fixed_cache::Cache<Key, B256, BuildHasher, CacheConfig>;
23
24struct CacheConfig {}
25impl fixed_cache::CacheConfig for CacheConfig {
26 const STATS: bool = cfg!(feature = "keccak-cache-stats");
27 const EPOCHS: bool = false;
28}
29
30#[must_use]
40pub fn init_keccak_cache(entries: usize) -> bool {
41 init_cache(&CACHE, entries)
42}
43
44pub(super) fn compute(input: &[u8], imp: impl FnOnce(&[u8]) -> B256) -> B256 {
45 if unlikely(input.is_empty() | (input.len() > MAX_INPUT_LEN)) {
46 return if input.is_empty() { KECCAK256_EMPTY } else { keccak256(input) };
47 }
48
49 let cache = CACHE.get_or_init(default_cache);
50 cache.get_or_insert_with_ref(input, imp, |input| {
51 let mut data = [MaybeUninit::uninit(); MAX_INPUT_LEN];
52 unsafe {
53 std::ptr::copy_nonoverlapping(input.as_ptr(), data.as_mut_ptr().cast(), input.len())
54 };
55 Key { len: input.len() as u8, data }
56 })
57}
58
59fn init_cache(cache: &OnceLock<Cache>, entries: usize) -> bool {
60 if cache.get().is_some() {
61 return false;
62 }
63 cache.set(new_cache(entries)).is_ok()
64}
65
66fn default_cache() -> Cache {
67 new_cache(DEFAULT_COUNT)
68}
69
70fn new_cache(entries: usize) -> Cache {
71 let cache = fixed_cache::Cache::new(entries, BuildHasher::new());
72 #[cfg(feature = "keccak-cache-stats")]
73 let cache = cache.with_stats(Some(fixed_cache::Stats::new(&stats::KECCAK_CACHE_STATS)));
74 cache
75}
76
77type BuildHasher = std::hash::BuildHasherDefault<Hasher>;
78#[derive(Default)]
79struct Hasher(u64);
80
81impl std::hash::Hasher for Hasher {
82 #[inline]
83 fn finish(&self) -> u64 {
84 self.0
85 }
86
87 #[inline]
88 fn write(&mut self, bytes: &[u8]) {
89 unsafe { core::hint::assert_unchecked(bytes.len() <= MAX_INPUT_LEN) };
95 if bytes.len() <= 16 {
96 super::hint::cold_path();
97 }
98 self.0 = rapidhash::v3::rapidhash_v3_micro_inline::<false, false>(
99 bytes,
100 const { &rapidhash::v3::RapidSecrets::seed(0) },
101 );
102 }
103
104 #[inline]
109 fn write_usize(&mut self, i: usize) {
110 debug_assert!(i <= MAX_INPUT_LEN, "{i} > {MAX_INPUT_LEN}")
111 }
112
113 #[cfg(feature = "nightly")]
114 #[inline]
115 fn write_length_prefix(&mut self, len: usize) {
116 debug_assert!(len <= MAX_INPUT_LEN, "{len} > {MAX_INPUT_LEN}")
117 }
118}
119
120#[derive(Clone, Copy)]
121struct Key {
122 len: u8,
123 data: [MaybeUninit<u8>; MAX_INPUT_LEN],
124}
125
126impl PartialEq for Key {
127 #[inline]
128 fn eq(&self, other: &Self) -> bool {
129 self.get() == other.get()
130 }
131}
132impl Eq for Key {}
133
134impl std::borrow::Borrow<[u8]> for Key {
135 #[inline]
136 fn borrow(&self) -> &[u8] {
137 self.get()
138 }
139}
140
141impl std::hash::Hash for Key {
142 #[inline]
143 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
144 state.write(self.get());
145 }
146}
147
148impl Key {
149 #[inline]
150 const fn get(&self) -> &[u8] {
151 unsafe { std::slice::from_raw_parts(self.data.as_ptr().cast(), self.len as usize) }
152 }
153}
154
155#[cfg(feature = "keccak-cache-stats")]
156mod stats {
157 use super::Key;
158 use crate::B256;
159 use std::sync::atomic::{AtomicU64, Ordering};
160
161 #[derive(Debug, Default)]
167 pub struct KeccakCacheStats {
168 hits: AtomicU64,
169 misses: AtomicU64,
170 inserts: AtomicU64,
171 collisions: AtomicU64,
172 }
173
174 impl KeccakCacheStats {
175 const fn new() -> Self {
176 Self {
177 hits: AtomicU64::new(0),
178 misses: AtomicU64::new(0),
179 inserts: AtomicU64::new(0),
180 collisions: AtomicU64::new(0),
181 }
182 }
183
184 #[inline]
186 pub fn hits(&self) -> u64 {
187 self.hits.load(Ordering::Relaxed)
188 }
189
190 #[inline]
192 pub fn misses(&self) -> u64 {
193 self.misses.load(Ordering::Relaxed)
194 }
195
196 #[inline]
201 pub fn inserts(&self) -> u64 {
202 self.inserts.load(Ordering::Relaxed)
203 }
204
205 #[inline]
207 pub fn collisions(&self) -> u64 {
208 self.collisions.load(Ordering::Relaxed)
209 }
210
211 pub fn reset(&self) {
213 self.hits.store(0, Ordering::Relaxed);
214 self.misses.store(0, Ordering::Relaxed);
215 self.inserts.store(0, Ordering::Relaxed);
216 self.collisions.store(0, Ordering::Relaxed);
217 }
218 }
219
220 impl fixed_cache::StatsHandler<Key, B256> for &'static KeccakCacheStats {
221 #[inline]
222 fn on_hit(&self, _key: &Key, _value: &B256) {
223 self.hits.fetch_add(1, Ordering::Relaxed);
224 }
225
226 #[inline]
227 fn on_miss(&self, _key: fixed_cache::AnyRef<'_>) {
228 self.misses.fetch_add(1, Ordering::Relaxed);
229 }
230
231 #[inline]
232 fn on_insert(&self, key: &Key, _value: &B256, evicted: Option<(&Key, &B256)>) {
233 match evicted {
234 Some((old, _)) if old == key => {}
237 Some(_) => {
238 self.inserts.fetch_add(1, Ordering::Relaxed);
239 self.collisions.fetch_add(1, Ordering::Relaxed);
240 }
241 None => {
242 self.inserts.fetch_add(1, Ordering::Relaxed);
243 }
244 }
245 }
246 }
247
248 pub static KECCAK_CACHE_STATS: KeccakCacheStats = KeccakCacheStats::new();
252}
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257
258 #[test]
259 fn sizes() {
260 assert_eq!(size_of::<Key>(), MAX_INPUT_LEN + 1);
261 assert_eq!(size_of::<fixed_cache::Bucket<(Key, B256)>>(), 128);
262 }
263
264 #[test]
265 fn caching() {
266 let mut count: usize = 0;
267 let mut compute = |input| {
268 compute(input, |x| {
269 count += 1;
270 keccak256(x)
271 })
272 };
273
274 let input = b"Hello World!";
275 let input2 = b"Hello World! 2";
276
277 let a = compute(input);
278 let b = compute(input);
279 let c = compute(input);
280 assert_eq!(a, b);
281 assert_eq!(a, c);
282
283 let d = compute(input2);
284 let e = compute(input2);
285 assert_ne!(a, d);
286 assert_eq!(d, e);
287
288 assert_eq!(count, 2);
289 }
290}