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 its own meta bytes hash to
235 /// the meta hash it claims for them.
236 pub fn insert_verified(
237 &mut self,
238 key: &[u8],
239 deployer: NPE2Deployer,
240 ) -> Result<&NPE2Deployer, Error> {
241 if keccak256(&deployer.meta_bytes).0.as_slice() != deployer.meta_hash.as_slice() {
242 return Err(Error::CorruptRecord(format!(
243 "deployer meta bytes do not hash to its own meta hash {}",
244 hex::encode_prefixed(&deployer.meta_hash)
245 )));
246 }
247 self.inner.insert(key.to_vec(), deployer);
248 self.inner.get(key).ok_or(Error::NoRecordFound)
249 }
250
251 /// The deployer cached under `key`, if any.
252 pub fn get(&self, key: &[u8]) -> Option<&NPE2Deployer> {
253 self.inner.get(key)
254 }
255
256 /// Whether anything is cached under `key`.
257 pub fn contains_key(&self, key: &[u8]) -> bool {
258 self.inner.contains_key(key)
259 }
260
261 /// Every cached pair. Entries are verified by construction.
262 pub fn iter(&self) -> impl Iterator<Item = (&Vec<u8>, &NPE2Deployer)> {
263 self.inner.iter()
264 }
265
266 /// Whether anything is cached at all.
267 pub fn is_empty(&self) -> bool {
268 self.inner.is_empty()
269 }
270}
271
272/// Deserializing is a way in, so it goes through the gate, as for [MetaCache].
273impl<'de> Deserialize<'de> for DeployerCache {
274 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
275 #[derive(Deserialize)]
276 struct Wire {
277 inner: BTreeMap<Vec<u8>, NPE2Deployer>,
278 }
279
280 let wire = Wire::deserialize(deserializer)?;
281 let mut cache = DeployerCache::default();
282 for (key, deployer) in wire.inner {
283 cache
284 .insert_verified(&key, deployer)
285 .map_err(serde::de::Error::custom)?;
286 }
287 Ok(cache)
288 }
289}