foundry_fork_db/
cache.rs

1//! Cache related abstraction
2use alloy_consensus::BlockHeader;
3use alloy_primitives::{Address, B256, U256};
4use alloy_provider::network::TransactionResponse;
5use parking_lot::RwLock;
6use revm::{
7    primitives::{
8        map::{AddressHashMap, HashMap},
9        Account, AccountInfo, AccountStatus, BlobExcessGasAndPrice, BlockEnv, CfgEnv, KECCAK_EMPTY,
10    },
11    DatabaseCommit,
12};
13use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer};
14use std::{
15    collections::BTreeSet,
16    fs,
17    io::{BufWriter, Write},
18    path::{Path, PathBuf},
19    sync::Arc,
20};
21use url::Url;
22
23pub type StorageInfo = HashMap<U256, U256>;
24
25/// A shareable Block database
26#[derive(Clone, Debug)]
27pub struct BlockchainDb {
28    /// Contains all the data
29    db: Arc<MemDb>,
30    /// metadata of the current config
31    meta: Arc<RwLock<BlockchainDbMeta>>,
32    /// the cache that can be flushed
33    cache: Arc<JsonBlockCacheDB>,
34}
35
36impl BlockchainDb {
37    /// Creates a new instance of the [BlockchainDb].
38    ///
39    /// If a `cache_path` is provided it attempts to load a previously stored [JsonBlockCacheData]
40    /// and will try to use the cached entries it holds.
41    ///
42    /// This will return a new and empty [MemDb] if
43    ///   - `cache_path` is `None`
44    ///   - the file the `cache_path` points to, does not exist
45    ///   - the file contains malformed data, or if it couldn't be read
46    ///   - the provided `meta` differs from [BlockchainDbMeta] that's stored on disk
47    pub fn new(meta: BlockchainDbMeta, cache_path: Option<PathBuf>) -> Self {
48        Self::new_db(meta, cache_path, false)
49    }
50
51    /// Creates a new instance of the [BlockchainDb] and skips check when comparing meta
52    /// This is useful for offline-start mode when we don't want to fetch metadata of `block`.
53    ///
54    /// if a `cache_path` is provided it attempts to load a previously stored [JsonBlockCacheData]
55    /// and will try to use the cached entries it holds.
56    ///
57    /// This will return a new and empty [MemDb] if
58    ///   - `cache_path` is `None`
59    ///   - the file the `cache_path` points to, does not exist
60    ///   - the file contains malformed data, or if it couldn't be read
61    ///   - the provided `meta` differs from [BlockchainDbMeta] that's stored on disk
62    pub fn new_skip_check(meta: BlockchainDbMeta, cache_path: Option<PathBuf>) -> Self {
63        Self::new_db(meta, cache_path, true)
64    }
65
66    fn new_db(meta: BlockchainDbMeta, cache_path: Option<PathBuf>, skip_check: bool) -> Self {
67        trace!(target: "forge::cache", cache=?cache_path, "initialising blockchain db");
68        // read cache and check if metadata matches
69        let cache = cache_path
70            .as_ref()
71            .and_then(|p| {
72                JsonBlockCacheDB::load(p).ok().filter(|cache| {
73                    if skip_check {
74                        return true;
75                    }
76                    let mut existing = cache.meta().write();
77                    existing.hosts.extend(meta.hosts.clone());
78                    if meta != *existing {
79                        warn!(target: "cache", "non-matching block metadata");
80                        false
81                    } else {
82                        true
83                    }
84                })
85            })
86            .unwrap_or_else(|| JsonBlockCacheDB::new(Arc::new(RwLock::new(meta)), cache_path));
87
88        Self { db: Arc::clone(cache.db()), meta: Arc::clone(cache.meta()), cache: Arc::new(cache) }
89    }
90
91    /// Returns the map that holds the account related info
92    pub fn accounts(&self) -> &RwLock<AddressHashMap<AccountInfo>> {
93        &self.db.accounts
94    }
95
96    /// Returns the map that holds the storage related info
97    pub fn storage(&self) -> &RwLock<AddressHashMap<StorageInfo>> {
98        &self.db.storage
99    }
100
101    /// Returns the map that holds all the block hashes
102    pub fn block_hashes(&self) -> &RwLock<HashMap<U256, B256>> {
103        &self.db.block_hashes
104    }
105
106    /// Returns the Env related metadata
107    pub const fn meta(&self) -> &Arc<RwLock<BlockchainDbMeta>> {
108        &self.meta
109    }
110
111    /// Returns the inner cache
112    pub const fn cache(&self) -> &Arc<JsonBlockCacheDB> {
113        &self.cache
114    }
115
116    /// Returns the underlying storage
117    pub const fn db(&self) -> &Arc<MemDb> {
118        &self.db
119    }
120}
121
122/// relevant identifying markers in the context of [BlockchainDb]
123#[derive(Clone, Debug, Eq, Serialize, Default)]
124pub struct BlockchainDbMeta {
125    pub cfg_env: CfgEnv,
126    pub block_env: BlockEnv,
127    /// all the hosts used to connect to
128    pub hosts: BTreeSet<String>,
129}
130
131impl BlockchainDbMeta {
132    /// Creates a new instance
133    pub fn new(env: revm::primitives::Env, url: String) -> Self {
134        let host = Url::parse(&url)
135            .ok()
136            .and_then(|url| url.host().map(|host| host.to_string()))
137            .unwrap_or(url);
138
139        Self { cfg_env: env.cfg.clone(), block_env: env.block, hosts: BTreeSet::from([host]) }
140    }
141
142    /// Sets the chain_id in the [CfgEnv] of this instance.
143    ///
144    /// Remaining fields of [CfgEnv] are left unchanged.
145    pub const fn with_chain_id(mut self, chain_id: u64) -> Self {
146        self.cfg_env.chain_id = chain_id;
147        self
148    }
149
150    /// Sets the [BlockEnv] of this instance using the provided [alloy_rpc_types::Block]
151    pub fn with_block<T: TransactionResponse, H: BlockHeader>(
152        mut self,
153        block: &alloy_rpc_types::Block<T, H>,
154    ) -> Self {
155        self.block_env = BlockEnv {
156            number: U256::from(block.header.number()),
157            coinbase: block.header.beneficiary(),
158            timestamp: U256::from(block.header.timestamp()),
159            difficulty: U256::from(block.header.difficulty()),
160            basefee: block.header.base_fee_per_gas().map(U256::from).unwrap_or_default(),
161            gas_limit: U256::from(block.header.gas_limit()),
162            prevrandao: block.header.mix_hash(),
163            blob_excess_gas_and_price: Some(BlobExcessGasAndPrice::new(
164                block.header.excess_blob_gas().unwrap_or_default(),
165                false,
166            )),
167        };
168
169        self
170    }
171
172    /// Infers the host from the provided url and adds it to the set of hosts
173    pub fn with_url(mut self, url: &str) -> Self {
174        let host = Url::parse(url)
175            .ok()
176            .and_then(|url| url.host().map(|host| host.to_string()))
177            .unwrap_or(url.to_string());
178        self.hosts.insert(host);
179        self
180    }
181
182    /// Sets [CfgEnv] of this instance
183    pub fn set_cfg_env(mut self, cfg_env: revm::primitives::CfgEnv) {
184        self.cfg_env = cfg_env;
185    }
186
187    /// Sets the [BlockEnv] of this instance
188    pub fn set_block_env(mut self, block_env: revm::primitives::BlockEnv) {
189        self.block_env = block_env;
190    }
191}
192
193// ignore hosts to not invalidate the cache when different endpoints are used, as it's commonly the
194// case for http vs ws endpoints
195impl PartialEq for BlockchainDbMeta {
196    fn eq(&self, other: &Self) -> bool {
197        self.cfg_env == other.cfg_env && self.block_env == other.block_env
198    }
199}
200
201impl<'de> Deserialize<'de> for BlockchainDbMeta {
202    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
203    where
204        D: Deserializer<'de>,
205    {
206        /// A backwards compatible representation of [revm::primitives::CfgEnv]
207        ///
208        /// This prevents deserialization errors of cache files caused by breaking changes to the
209        /// default [revm::primitives::CfgEnv], for example enabling an optional feature.
210        /// By hand rolling deserialize impl we can prevent cache file issues
211        struct CfgEnvBackwardsCompat {
212            inner: revm::primitives::CfgEnv,
213        }
214
215        impl<'de> Deserialize<'de> for CfgEnvBackwardsCompat {
216            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
217            where
218                D: Deserializer<'de>,
219            {
220                let mut value = serde_json::Value::deserialize(deserializer)?;
221
222                // we check for breaking changes here
223                if let Some(obj) = value.as_object_mut() {
224                    let default_value =
225                        serde_json::to_value(revm::primitives::CfgEnv::default()).unwrap();
226                    for (key, value) in default_value.as_object().unwrap() {
227                        if !obj.contains_key(key) {
228                            obj.insert(key.to_string(), value.clone());
229                        }
230                    }
231                }
232
233                let cfg_env: revm::primitives::CfgEnv =
234                    serde_json::from_value(value).map_err(serde::de::Error::custom)?;
235                Ok(Self { inner: cfg_env })
236            }
237        }
238
239        /// A backwards compatible representation of [revm::primitives::BlockEnv]
240        ///
241        /// This prevents deserialization errors of cache files caused by breaking changes to the
242        /// default [revm::primitives::BlockEnv], for example enabling an optional feature.
243        /// By hand rolling deserialize impl we can prevent cache file issues
244        struct BlockEnvBackwardsCompat {
245            inner: revm::primitives::BlockEnv,
246        }
247
248        impl<'de> Deserialize<'de> for BlockEnvBackwardsCompat {
249            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
250            where
251                D: Deserializer<'de>,
252            {
253                let mut value = serde_json::Value::deserialize(deserializer)?;
254
255                // we check for any missing fields here
256                if let Some(obj) = value.as_object_mut() {
257                    let default_value =
258                        serde_json::to_value(revm::primitives::BlockEnv::default()).unwrap();
259                    for (key, value) in default_value.as_object().unwrap() {
260                        if !obj.contains_key(key) {
261                            obj.insert(key.to_string(), value.clone());
262                        }
263                    }
264                }
265
266                let cfg_env: revm::primitives::BlockEnv =
267                    serde_json::from_value(value).map_err(serde::de::Error::custom)?;
268                Ok(Self { inner: cfg_env })
269            }
270        }
271
272        // custom deserialize impl to not break existing cache files
273        #[derive(Deserialize)]
274        struct Meta {
275            cfg_env: CfgEnvBackwardsCompat,
276            block_env: BlockEnvBackwardsCompat,
277            /// all the hosts used to connect to
278            #[serde(alias = "host")]
279            hosts: Hosts,
280        }
281
282        #[derive(Deserialize)]
283        #[serde(untagged)]
284        enum Hosts {
285            Multi(BTreeSet<String>),
286            Single(String),
287        }
288
289        let Meta { cfg_env, block_env, hosts } = Meta::deserialize(deserializer)?;
290        Ok(Self {
291            cfg_env: cfg_env.inner,
292            block_env: block_env.inner,
293            hosts: match hosts {
294                Hosts::Multi(hosts) => hosts,
295                Hosts::Single(host) => BTreeSet::from([host]),
296            },
297        })
298    }
299}
300
301/// In Memory cache containing all fetched accounts and storage slots
302/// and their values from RPC
303#[derive(Debug, Default)]
304pub struct MemDb {
305    /// Account related data
306    pub accounts: RwLock<AddressHashMap<AccountInfo>>,
307    /// Storage related data
308    pub storage: RwLock<AddressHashMap<StorageInfo>>,
309    /// All retrieved block hashes
310    pub block_hashes: RwLock<HashMap<U256, B256>>,
311}
312
313impl MemDb {
314    /// Clears all data stored in this db
315    pub fn clear(&self) {
316        self.accounts.write().clear();
317        self.storage.write().clear();
318        self.block_hashes.write().clear();
319    }
320
321    // Inserts the account, replacing it if it exists already
322    pub fn do_insert_account(&self, address: Address, account: AccountInfo) {
323        self.accounts.write().insert(address, account);
324    }
325
326    /// The implementation of [DatabaseCommit::commit()]
327    pub fn do_commit(&self, changes: HashMap<Address, Account>) {
328        let mut storage = self.storage.write();
329        let mut accounts = self.accounts.write();
330        for (add, mut acc) in changes {
331            if acc.is_empty() || acc.is_selfdestructed() {
332                accounts.remove(&add);
333                storage.remove(&add);
334            } else {
335                // insert account
336                if let Some(code_hash) = acc
337                    .info
338                    .code
339                    .as_ref()
340                    .filter(|code| !code.is_empty())
341                    .map(|code| code.hash_slow())
342                {
343                    acc.info.code_hash = code_hash;
344                } else if acc.info.code_hash.is_zero() {
345                    acc.info.code_hash = KECCAK_EMPTY;
346                }
347                accounts.insert(add, acc.info);
348
349                let acc_storage = storage.entry(add).or_default();
350                if acc.status.contains(AccountStatus::Created) {
351                    acc_storage.clear();
352                }
353                for (index, value) in acc.storage {
354                    if value.present_value().is_zero() {
355                        acc_storage.remove(&index);
356                    } else {
357                        acc_storage.insert(index, value.present_value());
358                    }
359                }
360                if acc_storage.is_empty() {
361                    storage.remove(&add);
362                }
363            }
364        }
365    }
366}
367
368impl Clone for MemDb {
369    fn clone(&self) -> Self {
370        Self {
371            storage: RwLock::new(self.storage.read().clone()),
372            accounts: RwLock::new(self.accounts.read().clone()),
373            block_hashes: RwLock::new(self.block_hashes.read().clone()),
374        }
375    }
376}
377
378impl DatabaseCommit for MemDb {
379    fn commit(&mut self, changes: HashMap<Address, Account>) {
380        self.do_commit(changes)
381    }
382}
383
384/// A DB that stores the cached content in a json file
385#[derive(Debug)]
386pub struct JsonBlockCacheDB {
387    /// Where this cache file is stored.
388    ///
389    /// If this is a [None] then caching is disabled
390    cache_path: Option<PathBuf>,
391    /// Object that's stored in a json file
392    data: JsonBlockCacheData,
393}
394
395impl JsonBlockCacheDB {
396    /// Creates a new instance.
397    fn new(meta: Arc<RwLock<BlockchainDbMeta>>, cache_path: Option<PathBuf>) -> Self {
398        Self { cache_path, data: JsonBlockCacheData { meta, data: Arc::new(Default::default()) } }
399    }
400
401    /// Loads the contents of the diskmap file and returns the read object
402    ///
403    /// # Errors
404    /// This will fail if
405    ///   - the `path` does not exist
406    ///   - the format does not match [JsonBlockCacheData]
407    pub fn load(path: impl Into<PathBuf>) -> eyre::Result<Self> {
408        let path = path.into();
409        trace!(target: "cache", ?path, "reading json cache");
410        let contents = std::fs::read_to_string(&path).map_err(|err| {
411            warn!(?err, ?path, "Failed to read cache file");
412            err
413        })?;
414        let data = serde_json::from_str(&contents).map_err(|err| {
415            warn!(target: "cache", ?err, ?path, "Failed to deserialize cache data");
416            err
417        })?;
418        Ok(Self { cache_path: Some(path), data })
419    }
420
421    /// Returns the [MemDb] it holds access to
422    pub const fn db(&self) -> &Arc<MemDb> {
423        &self.data.data
424    }
425
426    /// Metadata stored alongside the data
427    pub const fn meta(&self) -> &Arc<RwLock<BlockchainDbMeta>> {
428        &self.data.meta
429    }
430
431    /// Returns `true` if this is a transient cache and nothing will be flushed
432    pub const fn is_transient(&self) -> bool {
433        self.cache_path.is_none()
434    }
435
436    /// Flushes the DB to disk if caching is enabled.
437    #[instrument(level = "warn", skip_all, fields(path = ?self.cache_path))]
438    pub fn flush(&self) {
439        let Some(path) = &self.cache_path else { return };
440        self.flush_to(path.as_path());
441    }
442
443    /// Flushes the DB to a specific file
444    pub fn flush_to(&self, cache_path: &Path) {
445        let path: &Path = cache_path;
446
447        trace!(target: "cache", "saving json cache");
448
449        if let Some(parent) = path.parent() {
450            let _ = fs::create_dir_all(parent);
451        }
452
453        let file = match fs::File::create(path) {
454            Ok(file) => file,
455            Err(e) => return warn!(target: "cache", %e, "Failed to open json cache for writing"),
456        };
457
458        let mut writer = BufWriter::new(file);
459        if let Err(e) = serde_json::to_writer(&mut writer, &self.data) {
460            return warn!(target: "cache", %e, "Failed to write to json cache");
461        }
462        if let Err(e) = writer.flush() {
463            return warn!(target: "cache", %e, "Failed to flush to json cache");
464        }
465
466        trace!(target: "cache", "saved json cache");
467    }
468
469    /// Returns the cache path.
470    pub fn cache_path(&self) -> Option<&Path> {
471        self.cache_path.as_deref()
472    }
473}
474
475/// The Data the [JsonBlockCacheDB] can read and flush
476///
477/// This will be deserialized in a JSON object with the keys:
478/// `["meta", "accounts", "storage", "block_hashes"]`
479#[derive(Debug)]
480pub struct JsonBlockCacheData {
481    pub meta: Arc<RwLock<BlockchainDbMeta>>,
482    pub data: Arc<MemDb>,
483}
484
485impl Serialize for JsonBlockCacheData {
486    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
487    where
488        S: Serializer,
489    {
490        let mut map = serializer.serialize_map(Some(4))?;
491
492        map.serialize_entry("meta", &*self.meta.read())?;
493        map.serialize_entry("accounts", &*self.data.accounts.read())?;
494        map.serialize_entry("storage", &*self.data.storage.read())?;
495        map.serialize_entry("block_hashes", &*self.data.block_hashes.read())?;
496
497        map.end()
498    }
499}
500
501impl<'de> Deserialize<'de> for JsonBlockCacheData {
502    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
503    where
504        D: Deserializer<'de>,
505    {
506        #[derive(Deserialize)]
507        struct Data {
508            meta: BlockchainDbMeta,
509            accounts: AddressHashMap<AccountInfo>,
510            storage: AddressHashMap<HashMap<U256, U256>>,
511            block_hashes: HashMap<U256, B256>,
512        }
513
514        let Data { meta, accounts, storage, block_hashes } = Data::deserialize(deserializer)?;
515
516        Ok(Self {
517            meta: Arc::new(RwLock::new(meta)),
518            data: Arc::new(MemDb {
519                accounts: RwLock::new(accounts),
520                storage: RwLock::new(storage),
521                block_hashes: RwLock::new(block_hashes),
522            }),
523        })
524    }
525}
526
527/// A type that flushes a `JsonBlockCacheDB` on drop
528///
529/// This type intentionally does not implement `Clone` since it's intended that there's only once
530/// instance that will flush the cache.
531#[derive(Debug)]
532pub struct FlushJsonBlockCacheDB(pub Arc<JsonBlockCacheDB>);
533
534impl Drop for FlushJsonBlockCacheDB {
535    fn drop(&mut self) {
536        trace!(target: "fork::cache", "flushing cache");
537        self.0.flush();
538        trace!(target: "fork::cache", "flushed cache");
539    }
540}
541
542#[cfg(test)]
543mod tests {
544    use super::*;
545
546    #[test]
547    fn can_deserialize_cache() {
548        let s = r#"{
549    "meta": {
550        "cfg_env": {
551            "chain_id": 1337,
552            "perf_analyse_created_bytecodes": "Analyse",
553            "limit_contract_code_size": 18446744073709551615,
554            "memory_limit": 4294967295,
555            "disable_block_gas_limit": false,
556            "disable_eip3607": false,
557            "disable_base_fee": false
558        },
559        "block_env": {
560            "number": "0xed3ddf",
561            "coinbase": "0x0000000000000000000000000000000000000000",
562            "timestamp": "0x6324bc3f",
563            "difficulty": "0x0",
564            "basefee": "0x2e5fda223",
565            "gas_limit": "0x1c9c380",
566            "prevrandao": "0x0000000000000000000000000000000000000000000000000000000000000000"
567        },
568        "hosts": [
569            "eth-mainnet.alchemyapi.io"
570        ]
571    },
572    "accounts": {
573        "0xb8ffc3cd6e7cf5a098a1c92f48009765b24088dc": {
574            "balance": "0x0",
575            "nonce": 10,
576            "code_hash": "0x3ac64c95eedf82e5d821696a12daac0e1b22c8ee18a9fd688b00cfaf14550aad",
577            "code": {
578                "LegacyAnalyzed": {
579                    "bytecode": "0x00",
580                    "original_len": 0,
581                    "jump_table": {
582                      "order": "bitvec::order::Lsb0",
583                      "head": {
584                        "width": 8,
585                        "index": 0
586                      },
587                      "bits": 1,
588                      "data": [0]
589                    }
590                }
591            }
592        }
593    },
594    "storage": {
595        "0xa354f35829ae975e850e23e9615b11da1b3dc4de": {
596            "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e564": "0x5553444320795661756c74000000000000000000000000000000000000000000",
597            "0x10": "0x37fd60ff8346",
598            "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563": "0xb",
599            "0x6": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
600            "0x5": "0x36ff5b93162e",
601            "0x14": "0x29d635a8e000",
602            "0x11": "0x63224c73",
603            "0x2": "0x6"
604        }
605    },
606    "block_hashes": {
607        "0xed3deb": "0xbf7be3174b261ea3c377b6aba4a1e05d5fae7eee7aab5691087c20cf353e9877",
608        "0xed3de9": "0xba1c3648e0aee193e7d00dffe4e9a5e420016b4880455641085a4731c1d32eef",
609        "0xed3de8": "0x61d1491c03a9295fb13395cca18b17b4fa5c64c6b8e56ee9cc0a70c3f6cf9855",
610        "0xed3de7": "0xb54560b5baeccd18350d56a3bee4035432294dc9d2b7e02f157813e1dee3a0be",
611        "0xed3dea": "0x816f124480b9661e1631c6ec9ee39350bda79f0cbfc911f925838d88e3d02e4b"
612    }
613}"#;
614
615        let cache: JsonBlockCacheData = serde_json::from_str(s).unwrap();
616        assert_eq!(cache.data.accounts.read().len(), 1);
617        assert_eq!(cache.data.storage.read().len(), 1);
618        assert_eq!(cache.data.block_hashes.read().len(), 5);
619
620        let _s = serde_json::to_string(&cache).unwrap();
621    }
622
623    #[test]
624    fn can_deserialize_cache_post_4844() {
625        let s = r#"{
626    "meta": {
627        "cfg_env": {
628            "chain_id": 1,
629            "kzg_settings": "Default",
630            "perf_analyse_created_bytecodes": "Analyse",
631            "limit_contract_code_size": 18446744073709551615,
632            "memory_limit": 134217728,
633            "disable_block_gas_limit": false,
634            "disable_eip3607": true,
635            "disable_base_fee": false,
636            "optimism": false
637        },
638        "block_env": {
639            "number": "0x11c99bc",
640            "coinbase": "0x4838b106fce9647bdf1e7877bf73ce8b0bad5f97",
641            "timestamp": "0x65627003",
642            "gas_limit": "0x1c9c380",
643            "basefee": "0x64288ff1f",
644            "difficulty": "0xc6b1a299886016dea3865689f8393b9bf4d8f4fe8c0ad25f0058b3569297c057",
645            "prevrandao": "0xc6b1a299886016dea3865689f8393b9bf4d8f4fe8c0ad25f0058b3569297c057",
646            "blob_excess_gas_and_price": {
647                "excess_blob_gas": 0,
648                "blob_gasprice": 1
649            }
650        },
651        "hosts": [
652            "eth-mainnet.alchemyapi.io"
653        ]
654    },
655    "accounts": {
656        "0x4838b106fce9647bdf1e7877bf73ce8b0bad5f97": {
657            "balance": "0x8e0c373cfcdfd0eb",
658            "nonce": 128912,
659            "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
660            "code": {
661                "LegacyAnalyzed": {
662                    "bytecode": "0x00",
663                    "original_len": 0,
664                    "jump_table": {
665                      "order": "bitvec::order::Lsb0",
666                      "head": {
667                        "width": 8,
668                        "index": 0
669                      },
670                      "bits": 1,
671                      "data": [0]
672                    }
673                }
674            }
675        }
676    },
677    "storage": {},
678    "block_hashes": {}
679}"#;
680
681        let cache: JsonBlockCacheData = serde_json::from_str(s).unwrap();
682        assert_eq!(cache.data.accounts.read().len(), 1);
683
684        let _s = serde_json::to_string(&cache).unwrap();
685    }
686
687    #[test]
688    fn can_return_cache_path_if_set() {
689        // set
690        let cache_db = JsonBlockCacheDB::new(
691            Arc::new(RwLock::new(BlockchainDbMeta::default())),
692            Some(PathBuf::from("/tmp/foo")),
693        );
694        assert_eq!(Some(Path::new("/tmp/foo")), cache_db.cache_path());
695
696        // unset
697        let cache_db =
698            JsonBlockCacheDB::new(Arc::new(RwLock::new(BlockchainDbMeta::default())), None);
699        assert_eq!(None, cache_db.cache_path());
700    }
701}