Skip to main content

rain_metadata/meta/
cache.rs

1// SPDX-License-Identifier: LicenseRef-DCL-1.0
2// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd
3//! The meta cache, and the one way into it.
4//!
5//! A hash is a claim about the bytes it keys. Caching bytes that do not hash to
6//! their key stores a lie the rest of the crate reads back as truth, and
7//! `cas.md` puts the check at exactly this point - "before the content is
8//! stored under the hash" - so everything downstream can stop asking.
9//!
10//! Keeping that as a convention did not hold. The map was a bare `HashMap`
11//! field on `Store`, so any method could reach past the check, and several did:
12//! `update` shipped without it, `search_deployer`, `set_deployer` and
13//! `set_deployer_from_query_response` each wrote to the cache directly. Each
14//! was found separately, after the fact.
15//!
16//! So the map lives here with a private field and no unguarded insert. Every
17//! write goes through [MetaCache::insert_verified] because the type system
18//! offers nothing else, including from code written long after this.
19
20use std::collections::BTreeMap;
21
22use alloy::primitives::{hex, keccak256};
23use serde::{Deserialize, Deserializer};
24
25use crate::error::Error;
26use crate::meta::NPE2Deployer;
27
28/// Meta bytes keyed by their own keccak256 hash.
29///
30/// The key is not a name for the bytes, it is a digest of them, and this type
31/// exists to make that true by construction rather than by discipline.
32/// The map is a [BTreeMap] so serializing twice gives the same bytes.
33/// [std::collections::HashMap] iterates in an order randomized per process,
34/// which would make a serialized cache unreproducible for no gain - every
35/// access here is by key.
36#[derive(Clone, Debug, Default, PartialEq, serde::Serialize)]
37pub struct MetaCache {
38    inner: BTreeMap<Vec<u8>, Vec<u8>>,
39}
40
41/// Deserializing is a way into the cache, so it goes through the same gate.
42///
43/// A derived impl would build `inner` directly, which is how the invariant
44/// leaked the first time this type was written: entries refused by
45/// [MetaCache::insert_verified] were accepted wholesale off the wire. A cache
46/// is only as good as the worst entry in it, so one bad pair rejects the whole
47/// map rather than being dropped quietly - unlike a responder's single answer,
48/// a serialized cache is something this process wrote and should not be able
49/// to get wrong.
50impl<'de> Deserialize<'de> for MetaCache {
51    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
52        #[derive(Deserialize)]
53        struct Wire {
54            inner: BTreeMap<Vec<u8>, Vec<u8>>,
55        }
56
57        let wire = Wire::deserialize(deserializer)?;
58        let mut cache = MetaCache::default();
59        for (hash, bytes) in wire.inner {
60            cache
61                .insert_verified(&hash, bytes)
62                .map_err(serde::de::Error::custom)?;
63        }
64        Ok(cache)
65    }
66}
67
68impl MetaCache {
69    /// Caches `bytes` under `hash`, and only if they hash to it.
70    ///
71    /// A mismatch is [Error::CorruptRecord] rather than a miss: the responder
72    /// answered a question about one hash with bytes that are another, which
73    /// is not the same fact as the hash being absent.
74    /// rainlanguage/rain.metadata#234 and #213 settled that distinction for the
75    /// query layer; this is the same distinction at the cache.
76    pub fn insert_verified(&mut self, hash: &[u8], bytes: Vec<u8>) -> Result<&Vec<u8>, Error> {
77        if keccak256(&bytes).0 != hash {
78            return Err(Error::CorruptRecord(format!(
79                "bytes do not hash to the requested {}",
80                hex::encode_prefixed(hash)
81            )));
82        }
83        self.inner.insert(hash.to_vec(), bytes);
84        self.inner.get(hash).ok_or(Error::NoRecordFound)
85    }
86
87    /// The bytes cached under `hash`, if any.
88    pub fn get(&self, hash: &[u8]) -> Option<&Vec<u8>> {
89        self.inner.get(hash)
90    }
91
92    /// Whether anything is cached under `hash`.
93    pub fn contains_key(&self, hash: &[u8]) -> bool {
94        self.inner.contains_key(hash)
95    }
96
97    /// Drops whatever is cached under `hash`. Removing cannot break the
98    /// invariant, so it needs no check.
99    pub fn remove(&mut self, hash: &[u8]) {
100        self.inner.remove(hash);
101    }
102
103    /// Every cached pair. Entries are verified by construction, so copying one
104    /// into another [MetaCache] cannot introduce an unverified entry.
105    pub fn iter(&self) -> impl Iterator<Item = (&Vec<u8>, &Vec<u8>)> {
106        self.inner.iter()
107    }
108
109    /// Whether anything is cached at all.
110    pub fn is_empty(&self) -> bool {
111        self.inner.is_empty()
112    }
113
114    /// How many metas are cached.
115    pub fn len(&self) -> usize {
116        self.inner.len()
117    }
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123
124    fn hashed(bytes: &[u8]) -> Vec<u8> {
125        keccak256(bytes).0.to_vec()
126    }
127
128    /// Bytes that hash to their key are cached and readable back.
129    #[test]
130    fn test_insert_verified_accepts_matching_bytes() {
131        let bytes = b"content".to_vec();
132        let hash = hashed(&bytes);
133        let mut cache = MetaCache::default();
134
135        assert_eq!(cache.insert_verified(&hash, bytes.clone()).unwrap(), &bytes);
136        assert_eq!(cache.get(&hash), Some(&bytes));
137        assert!(cache.contains_key(&hash));
138        assert_eq!(cache.len(), 1);
139    }
140
141    /// Bytes that do not hash to their key are refused, and refused as corrupt
142    /// rather than as a miss, with the requested hash named.
143    #[test]
144    fn test_insert_verified_rejects_mismatched_bytes_as_corrupt() {
145        let wrong_hash = vec![0x99u8; 32];
146        let mut cache = MetaCache::default();
147
148        match cache
149            .insert_verified(&wrong_hash, b"content".to_vec())
150            .unwrap_err()
151        {
152            Error::CorruptRecord(message) => assert!(
153                message.contains(&hex::encode_prefixed(&wrong_hash)),
154                "{}",
155                message
156            ),
157            other => panic!("expected CorruptRecord, got {:?}", other),
158        }
159
160        // and nothing was cached on the way out
161        assert!(cache.is_empty());
162        assert!(!cache.contains_key(&wrong_hash));
163    }
164
165    /// Deserializing is a way in, so it is gated too. A derived impl would
166    /// build the map directly and accept off the wire exactly what
167    /// insert_verified refuses in process.
168    #[test]
169    fn test_deserialize_rejects_an_unverified_entry() {
170        #[derive(serde::Serialize)]
171        struct Wire {
172            inner: std::collections::BTreeMap<Vec<u8>, Vec<u8>>,
173        }
174        let planted = Wire {
175            inner: std::collections::BTreeMap::from([(
176                vec![0x99u8; 32],
177                b"not the preimage".to_vec(),
178            )]),
179        };
180
181        let wire = serde_cbor::to_vec(&planted).unwrap();
182        let round: Result<MetaCache, _> = serde_cbor::from_slice(&wire);
183        assert!(round.is_err(), "an unverified entry round tripped in");
184    }
185
186    /// A verified entry survives the round trip, so the gate rejects lies
187    /// rather than everything.
188    #[test]
189    fn test_deserialize_keeps_a_verified_entry() {
190        let bytes = b"content".to_vec();
191        let hash = hashed(&bytes);
192        let mut cache = MetaCache::default();
193        cache.insert_verified(&hash, bytes.clone()).unwrap();
194
195        let wire = serde_cbor::to_vec(&cache).unwrap();
196        let round: MetaCache = serde_cbor::from_slice(&wire).unwrap();
197        assert_eq!(round.get(&hash), Some(&bytes));
198    }
199
200    /// Serializing the same cache twice gives the same bytes, which a
201    /// HashMap would not guarantee across processes.
202    #[test]
203    fn test_serialization_is_deterministic() {
204        let mut cache = MetaCache::default();
205        for content in [b"one".to_vec(), b"two".to_vec(), b"three".to_vec()] {
206            let hash = hashed(&content);
207            cache.insert_verified(&hash, content).unwrap();
208        }
209        let a = serde_cbor::to_vec(&cache).unwrap();
210        let b = serde_cbor::to_vec(&cache.clone()).unwrap();
211        assert_eq!(a, b);
212    }
213}
214
215/// Deployer records keyed by their bytecode meta hash.
216///
217/// The key here is not a digest of the value - a deployer is keyed by its
218/// bytecode meta hash while carrying a constructor meta of its own - so the
219/// invariant is internal: `meta_bytes` must hash to `meta_hash`. A record that
220/// gets that wrong describes a deployer whose constructor meta is not the meta
221/// it names, and [crate::meta::Store] copies exactly those bytes into the
222/// [MetaCache] under exactly that hash.
223///
224/// Same shape as [MetaCache], for the same reason: the check was a convention
225/// spread across call sites, and `set_deployer` and
226/// `set_deployer_from_query_response` both missed it.
227/// rainlanguage/rain.metadata#170.
228#[derive(Clone, Debug, Default, PartialEq, serde::Serialize)]
229pub struct DeployerCache {
230    inner: BTreeMap<Vec<u8>, NPE2Deployer>,
231}
232
233impl DeployerCache {
234    /// Caches `deployer` under `key`, and only if the key is a hash, every
235    /// field needed to reproduce the deployer is present, and its own meta
236    /// bytes hash to the meta hash it claims for them.
237    ///
238    /// The key check has no counterpart in [MetaCache], which gets it for
239    /// free: there the key is a digest of the value, so a key of the wrong
240    /// length cannot equal one. Here the key is a bytecode meta hash while the
241    /// value carries a constructor meta of its own, so nothing about the value
242    /// constrains the key and the length is checked outright.
243    pub fn insert_verified(
244        &mut self,
245        key: &[u8],
246        deployer: NPE2Deployer,
247    ) -> Result<&NPE2Deployer, Error> {
248        if key.len() != 32 {
249            return Err(Error::CorruptRecord(format!(
250                "deployer key {} is {} bytes, not a 32 byte hash",
251                hex::encode_prefixed(key),
252                key.len()
253            )));
254        }
255        if let Some(field) = deployer.corrupt_field() {
256            return Err(Error::CorruptRecord(format!(
257                "deployer {} is empty, so it cannot be reproduced",
258                field
259            )));
260        }
261        if keccak256(&deployer.meta_bytes).0.as_slice() != deployer.meta_hash.as_slice() {
262            return Err(Error::CorruptRecord(format!(
263                "deployer meta bytes do not hash to its own meta hash {}",
264                hex::encode_prefixed(&deployer.meta_hash)
265            )));
266        }
267        self.inner.insert(key.to_vec(), deployer);
268        self.inner.get(key).ok_or(Error::NoRecordFound)
269    }
270
271    /// The deployer cached under `key`, if any.
272    pub fn get(&self, key: &[u8]) -> Option<&NPE2Deployer> {
273        self.inner.get(key)
274    }
275
276    /// Whether anything is cached under `key`.
277    pub fn contains_key(&self, key: &[u8]) -> bool {
278        self.inner.contains_key(key)
279    }
280
281    /// Every cached pair. Entries are verified by construction.
282    pub fn iter(&self) -> impl Iterator<Item = (&Vec<u8>, &NPE2Deployer)> {
283        self.inner.iter()
284    }
285
286    /// Whether anything is cached at all.
287    pub fn is_empty(&self) -> bool {
288        self.inner.is_empty()
289    }
290}
291
292/// Deserializing is a way in, so it goes through the gate, as for [MetaCache].
293impl<'de> Deserialize<'de> for DeployerCache {
294    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
295        #[derive(Deserialize)]
296        struct Wire {
297            inner: BTreeMap<Vec<u8>, NPE2Deployer>,
298        }
299
300        let wire = Wire::deserialize(deserializer)?;
301        let mut cache = DeployerCache::default();
302        for (key, deployer) in wire.inner {
303            cache
304                .insert_verified(&key, deployer)
305                .map_err(serde::de::Error::custom)?;
306        }
307        Ok(cache)
308    }
309}
310
311#[cfg(test)]
312mod deployer_tests {
313    use super::*;
314
315    /// A deployer whose meta bytes hash to its meta hash and whose every
316    /// reproduction field is populated.
317    fn sound_deployer() -> NPE2Deployer {
318        let meta_bytes = b"constructor meta".to_vec();
319        NPE2Deployer {
320            meta_hash: keccak256(&meta_bytes).0.to_vec(),
321            meta_bytes,
322            bytecode: vec![0x01],
323            parser: vec![0x02],
324            store: vec![0x03],
325            interpreter: vec![0x04],
326            authoring_meta: None,
327        }
328    }
329
330    fn corrupt_message(result: Result<&NPE2Deployer, Error>) -> String {
331        match result.unwrap_err() {
332            Error::CorruptRecord(message) => message,
333            other => panic!("expected CorruptRecord, got {:?}", other),
334        }
335    }
336
337    /// A sound record under a 32 byte key is cached and readable back.
338    #[test]
339    fn test_deployer_insert_verified_accepts_a_sound_record() {
340        let deployer = sound_deployer();
341        let key = vec![0x11u8; 32];
342        let mut cache = DeployerCache::default();
343
344        assert_eq!(
345            cache.insert_verified(&key, deployer.clone()).unwrap(),
346            &deployer
347        );
348        assert_eq!(cache.get(&key), Some(&deployer));
349    }
350
351    /// The key must be a 32 byte hash. MetaCache gets this for free because
352    /// its key is a digest of its value; here nothing about the value
353    /// constrains the key, so an unchecked gate would take any length.
354    #[test]
355    fn test_deployer_insert_verified_rejects_a_key_that_is_not_a_hash() {
356        let mut cache = DeployerCache::default();
357
358        for key in [vec![], vec![0x11u8; 31], vec![0x11u8; 33]] {
359            let message = corrupt_message(cache.insert_verified(&key, sound_deployer()));
360            assert!(
361                message.contains("not a 32 byte hash"),
362                "{} bytes: {}",
363                key.len(),
364                message
365            );
366            assert!(cache.is_empty());
367        }
368    }
369
370    /// Every field is needed to reproduce the deployer on a local evm, so an
371    /// empty one is refused and the error names which.
372    #[test]
373    fn test_deployer_insert_verified_rejects_a_record_missing_a_field() {
374        let key = vec![0x11u8; 32];
375
376        for field in [
377            "meta_hash",
378            "meta_bytes",
379            "bytecode",
380            "parser",
381            "store",
382            "interpreter",
383        ] {
384            let mut deployer = sound_deployer();
385            match field {
386                "meta_hash" => deployer.meta_hash = vec![],
387                "meta_bytes" => deployer.meta_bytes = vec![],
388                "bytecode" => deployer.bytecode = vec![],
389                "parser" => deployer.parser = vec![],
390                "store" => deployer.store = vec![],
391                "interpreter" => deployer.interpreter = vec![],
392                _ => unreachable!(),
393            }
394
395            let mut cache = DeployerCache::default();
396            let message = corrupt_message(cache.insert_verified(&key, deployer));
397            assert!(message.contains(field), "{}: {}", field, message);
398            assert!(cache.is_empty());
399        }
400    }
401
402    /// Meta bytes that do not hash to the meta hash the record claims for them
403    /// are refused, so the record cannot seed the meta cache off a content
404    /// address that is not the content's.
405    #[test]
406    fn test_deployer_insert_verified_rejects_a_lying_meta_hash() {
407        let mut deployer = sound_deployer();
408        deployer.meta_bytes = b"different bytes".to_vec();
409        let mut cache = DeployerCache::default();
410
411        let message = corrupt_message(cache.insert_verified(&[0x11u8; 32], deployer));
412        assert!(message.contains("do not hash to"), "{}", message);
413        assert!(cache.is_empty());
414    }
415
416    /// Deserializing is a way in, so the same three checks apply off the wire.
417    #[test]
418    fn test_deployer_deserialize_rejects_an_unverified_entry() {
419        #[derive(serde::Serialize)]
420        struct Wire {
421            inner: BTreeMap<Vec<u8>, NPE2Deployer>,
422        }
423
424        for (key, deployer) in [
425            (vec![0x11u8; 31], sound_deployer()),
426            (vec![0x11u8; 32], {
427                let mut d = sound_deployer();
428                d.parser = vec![];
429                d
430            }),
431            (vec![0x11u8; 32], {
432                let mut d = sound_deployer();
433                d.meta_bytes = b"different bytes".to_vec();
434                d
435            }),
436        ] {
437            let planted = Wire {
438                inner: BTreeMap::from([(key, deployer)]),
439            };
440            let wire = serde_cbor::to_vec(&planted).unwrap();
441            let round: Result<DeployerCache, _> = serde_cbor::from_slice(&wire);
442            assert!(round.is_err(), "an unverified entry round tripped in");
443        }
444    }
445
446    /// A sound entry survives the round trip, so the gate rejects lies rather
447    /// than everything.
448    #[test]
449    fn test_deployer_deserialize_keeps_a_verified_entry() {
450        let deployer = sound_deployer();
451        let key = vec![0x11u8; 32];
452        let mut cache = DeployerCache::default();
453        cache.insert_verified(&key, deployer.clone()).unwrap();
454
455        let wire = serde_cbor::to_vec(&cache).unwrap();
456        let round: DeployerCache = serde_cbor::from_slice(&wire).unwrap();
457        assert_eq!(round.get(&key), Some(&deployer));
458    }
459}