1use alloy_chains::Chain;
4use alloy_primitives::{Address, B256, U256, map::U256Map};
5use parking_lot::RwLock;
6use revm::{
7 DatabaseCommit,
8 context::BlockEnv,
9 primitives::{KECCAK_EMPTY, StorageKeyMap, map::AddressHashMap},
10 state::{Account, AccountInfo, AccountStatus},
11};
12use serde::{
13 Deserialize, Deserializer, Serialize, Serializer, de::DeserializeOwned, ser::SerializeMap,
14};
15use std::{
16 collections::BTreeSet,
17 fs,
18 io::{BufWriter, Write},
19 path::{Path, PathBuf},
20 sync::Arc,
21};
22use url::Url;
23
24pub type StorageInfo = StorageKeyMap<U256>;
25
26#[derive(Clone, Debug)]
28pub struct BlockchainDb<B = BlockEnv> {
29 db: Arc<MemDb>,
31 meta: Arc<RwLock<BlockchainDbMeta<B>>>,
33 cache: Arc<JsonBlockCacheDB<B>>,
35}
36
37impl<B: ForkBlockEnv> BlockchainDb<B> {
38 pub fn new(meta: BlockchainDbMeta<B>, cache_path: Option<PathBuf>) -> Self {
49 Self::new_db(meta, cache_path, false)
50 }
51
52 pub fn new_skip_check(meta: BlockchainDbMeta<B>, cache_path: Option<PathBuf>) -> Self {
64 Self::new_db(meta, cache_path, true)
65 }
66
67 fn new_db(meta: BlockchainDbMeta<B>, cache_path: Option<PathBuf>, skip_check: bool) -> Self {
68 trace!(target: "forge::cache", cache=?cache_path, "initialising blockchain db");
69 let cache = cache_path
71 .as_ref()
72 .and_then(|p| {
73 JsonBlockCacheDB::load(p).ok().filter(|cache| {
74 if skip_check {
75 return true;
76 }
77 let mut existing = cache.meta().write();
78 existing.hosts.extend(meta.hosts.clone());
79 if meta != *existing {
80 warn!(target: "cache", "non-matching block metadata");
81 false
82 } else {
83 true
84 }
85 })
86 })
87 .unwrap_or_else(|| JsonBlockCacheDB::new(Arc::new(RwLock::new(meta)), cache_path));
88
89 Self { db: Arc::clone(cache.db()), meta: Arc::clone(cache.meta()), cache: Arc::new(cache) }
90 }
91
92 pub fn accounts(&self) -> &RwLock<AddressHashMap<AccountInfo>> {
94 &self.db.accounts
95 }
96
97 pub fn storage(&self) -> &RwLock<AddressHashMap<StorageInfo>> {
99 &self.db.storage
100 }
101
102 pub fn block_hashes(&self) -> &RwLock<U256Map<B256>> {
104 &self.db.block_hashes
105 }
106
107 pub const fn meta(&self) -> &Arc<RwLock<BlockchainDbMeta<B>>> {
109 &self.meta
110 }
111
112 pub const fn cache(&self) -> &Arc<JsonBlockCacheDB<B>> {
114 &self.cache
115 }
116
117 pub const fn db(&self) -> &Arc<MemDb> {
119 &self.db
120 }
121}
122
123pub trait ForkBlockEnv:
128 Serialize + DeserializeOwned + Default + Clone + PartialEq + Send + Sync + 'static
129{
130}
131
132impl<B: Serialize + DeserializeOwned + Default + Clone + PartialEq + Send + Sync + 'static>
133 ForkBlockEnv for B
134{
135}
136
137#[derive(Clone, Debug, Default, Eq)]
139pub struct BlockchainDbMeta<B> {
140 pub chain: Option<Chain>,
142 pub block_env: B,
144 pub hosts: BTreeSet<String>,
146}
147
148impl<B> BlockchainDbMeta<B> {
149 pub fn new(block_env: B, url: String) -> Self {
151 let host = Url::parse(&url)
152 .ok()
153 .and_then(|url| url.host().map(|host| host.to_string()))
154 .unwrap_or(url);
155
156 Self { chain: None, block_env, hosts: BTreeSet::from([host]) }
157 }
158
159 pub fn with_url(mut self, url: &str) -> Self {
161 let host = Url::parse(url)
162 .ok()
163 .and_then(|url| url.host().map(|host| host.to_string()))
164 .unwrap_or(url.to_string());
165 self.hosts.insert(host);
166 self
167 }
168
169 pub const fn set_chain(mut self, chain: Chain) -> Self {
171 self.chain = Some(chain);
172 self
173 }
174
175 pub fn set_block_env(mut self, block_env: B) -> Self {
177 self.block_env = block_env;
178 self
179 }
180}
181
182impl<B: PartialEq> PartialEq for BlockchainDbMeta<B> {
185 fn eq(&self, other: &Self) -> bool {
186 self.block_env == other.block_env
187 }
188}
189
190impl<B: Serialize> Serialize for BlockchainDbMeta<B> {
191 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
192 use serde::ser::SerializeStruct;
193
194 let field_count = if self.chain.is_some() { 3 } else { 2 };
195 let mut s = serializer.serialize_struct("BlockchainDbMeta", field_count)?;
196 if let Some(chain) = &self.chain {
197 s.serialize_field("chain", chain)?;
198 }
199 s.serialize_field("block_env", &self.block_env)?;
200 s.serialize_field("hosts", &self.hosts)?;
201 s.end()
202 }
203}
204
205struct BlockEnvBackwardsCompat<B> {
211 inner: B,
212}
213
214impl<'de, B: DeserializeOwned + Default + Serialize> Deserialize<'de>
215 for BlockEnvBackwardsCompat<B>
216{
217 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
218 let mut value = serde_json::Value::deserialize(deserializer)?;
219
220 if let Some(obj) = value.as_object_mut() {
222 let default_value = serde_json::to_value(B::default()).unwrap();
223 for (key, value) in default_value.as_object().unwrap() {
224 if !obj.contains_key(key) {
225 obj.insert(key.to_string(), value.clone());
226 }
227 }
228 }
229
230 let inner: B = serde_json::from_value(value).map_err(serde::de::Error::custom)?;
231 Ok(Self { inner })
232 }
233}
234
235#[derive(Deserialize)]
236#[serde(untagged)]
237enum Hosts {
238 Multi(BTreeSet<String>),
239 Single(String),
240}
241
242impl From<Hosts> for BTreeSet<String> {
243 fn from(hosts: Hosts) -> Self {
244 match hosts {
245 Hosts::Multi(hosts) => hosts,
246 Hosts::Single(host) => Self::from([host]),
247 }
248 }
249}
250
251impl<'de, B: DeserializeOwned + Default + Serialize> Deserialize<'de> for BlockchainDbMeta<B> {
252 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
253 #[derive(Deserialize)]
254 #[serde(bound = "B: DeserializeOwned + Default + Serialize")]
255 struct Meta<B> {
256 chain: Option<Chain>,
257 block_env: BlockEnvBackwardsCompat<B>,
258 #[serde(alias = "host")]
259 hosts: Hosts,
260 }
261
262 let Meta { chain, block_env, hosts } = Meta::deserialize(deserializer)?;
263 Ok(Self { chain, block_env: block_env.inner, hosts: hosts.into() })
264 }
265}
266
267#[derive(Debug, Default)]
270pub struct MemDb {
271 pub accounts: RwLock<AddressHashMap<AccountInfo>>,
273 pub storage: RwLock<AddressHashMap<StorageInfo>>,
275 pub block_hashes: RwLock<U256Map<B256>>,
277}
278
279impl MemDb {
280 pub fn clear(&self) {
282 self.accounts.write().clear();
283 self.storage.write().clear();
284 self.block_hashes.write().clear();
285 }
286
287 pub fn do_insert_account(&self, address: Address, account: AccountInfo) {
289 self.accounts.write().insert(address, account);
290 }
291
292 pub fn do_commit(&self, changes: AddressHashMap<Account>) {
294 let mut storage = self.storage.write();
295 let mut accounts = self.accounts.write();
296 for (add, mut acc) in changes {
297 if acc.is_empty() || acc.is_selfdestructed() {
298 accounts.remove(&add);
299 storage.remove(&add);
300 } else {
301 if let Some(code_hash) = acc
303 .info
304 .code
305 .as_ref()
306 .filter(|code| !code.is_empty())
307 .map(|code| code.hash_slow())
308 {
309 acc.info.code_hash = code_hash;
310 } else if acc.info.code_hash.is_zero() {
311 acc.info.code_hash = KECCAK_EMPTY;
312 }
313 accounts.insert(add, acc.info);
314
315 let acc_storage = storage.entry(add).or_default();
316 if acc.status.contains(AccountStatus::Created) {
317 acc_storage.clear();
318 }
319 for (index, value) in acc.storage {
320 if value.present_value().is_zero() {
321 acc_storage.remove(&index);
322 } else {
323 acc_storage.insert(index, value.present_value());
324 }
325 }
326 if acc_storage.is_empty() {
327 storage.remove(&add);
328 }
329 }
330 }
331 }
332}
333
334impl Clone for MemDb {
335 fn clone(&self) -> Self {
336 Self {
337 storage: RwLock::new(self.storage.read().clone()),
338 accounts: RwLock::new(self.accounts.read().clone()),
339 block_hashes: RwLock::new(self.block_hashes.read().clone()),
340 }
341 }
342}
343
344impl DatabaseCommit for MemDb {
345 fn commit(&mut self, changes: AddressHashMap<Account>) {
346 self.do_commit(changes)
347 }
348}
349
350#[derive(Debug)]
352pub struct JsonBlockCacheDB<B> {
353 cache_path: Option<PathBuf>,
357 data: JsonBlockCacheData<B>,
359}
360
361impl<B> JsonBlockCacheDB<B> {
362 fn new(meta: Arc<RwLock<BlockchainDbMeta<B>>>, cache_path: Option<PathBuf>) -> Self {
364 Self { cache_path, data: JsonBlockCacheData { meta, data: Arc::new(Default::default()) } }
365 }
366
367 pub const fn db(&self) -> &Arc<MemDb> {
369 &self.data.data
370 }
371
372 pub const fn meta(&self) -> &Arc<RwLock<BlockchainDbMeta<B>>> {
374 &self.data.meta
375 }
376
377 pub const fn is_transient(&self) -> bool {
379 self.cache_path.is_none()
380 }
381
382 pub fn cache_path(&self) -> Option<&Path> {
384 self.cache_path.as_deref()
385 }
386}
387
388impl<B: ForkBlockEnv> JsonBlockCacheDB<B> {
389 pub fn load(path: impl Into<PathBuf>) -> eyre::Result<Self> {
396 let path = path.into();
397 trace!(target: "cache", ?path, "reading json cache");
398 let contents = std::fs::read_to_string(&path).inspect_err(|err| {
399 warn!(?err, ?path, "Failed to read cache file");
400 })?;
401 let data = serde_json::from_str(&contents).inspect_err(|err| {
402 warn!(target: "cache", ?err, ?path, "Failed to deserialize cache data");
403 })?;
404 trace!(target: "cache", ?path, "read json cache");
405 Ok(Self { cache_path: Some(path), data })
406 }
407}
408
409impl<B: Serialize + Clone> JsonBlockCacheDB<B> {
410 #[instrument(level = "warn", skip_all, fields(path = ?self.cache_path))]
412 pub fn flush(&self) {
413 let Some(path) = &self.cache_path else { return };
414 self.flush_to(path.as_path());
415 }
416
417 pub fn flush_to(&self, cache_path: &Path) {
419 let path: &Path = cache_path;
420
421 trace!(target: "cache", "saving json cache");
422
423 if let Some(parent) = path.parent() {
424 let _ = fs::create_dir_all(parent);
425 }
426
427 let file = match fs::File::create(path) {
428 Ok(file) => file,
429 Err(e) => return warn!(target: "cache", %e, "Failed to open json cache for writing"),
430 };
431
432 let mut writer = BufWriter::new(file);
433 if let Err(e) = serde_json::to_writer(&mut writer, &self.data) {
434 return warn!(target: "cache", %e, "Failed to write to json cache");
435 }
436 if let Err(e) = writer.flush() {
437 return warn!(target: "cache", %e, "Failed to flush to json cache");
438 }
439
440 trace!(target: "cache", "saved json cache");
441 }
442}
443
444#[derive(Debug)]
449pub struct JsonBlockCacheData<B> {
450 pub meta: Arc<RwLock<BlockchainDbMeta<B>>>,
451 pub data: Arc<MemDb>,
452}
453
454impl<B: Serialize + Clone> Serialize for JsonBlockCacheData<B> {
455 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
456 let mut map = serializer.serialize_map(Some(4))?;
457
458 map.serialize_entry("meta", &self.meta.read().clone())?;
459 map.serialize_entry("accounts", &self.data.accounts.read().clone())?;
460 map.serialize_entry("storage", &self.data.storage.read().clone())?;
461 map.serialize_entry("block_hashes", &self.data.block_hashes.read().clone())?;
462
463 map.end()
464 }
465}
466
467impl<'de, B: DeserializeOwned + Default + Serialize> Deserialize<'de> for JsonBlockCacheData<B> {
468 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
469 #[derive(Deserialize)]
470 struct Data<B> {
471 meta: B,
472 accounts: AddressHashMap<AccountInfo>,
473 storage: AddressHashMap<StorageInfo>,
474 block_hashes: U256Map<B256>,
475 }
476
477 let Data { meta, accounts, storage, block_hashes } =
478 Data::<BlockchainDbMeta<B>>::deserialize(deserializer)?;
479
480 Ok(Self {
481 meta: Arc::new(RwLock::new(meta)),
482 data: Arc::new(MemDb {
483 accounts: RwLock::new(accounts),
484 storage: RwLock::new(storage),
485 block_hashes: RwLock::new(block_hashes),
486 }),
487 })
488 }
489}
490
491#[derive(Debug)]
496pub struct FlushJsonBlockCacheDB<B: Serialize + Clone>(pub Arc<JsonBlockCacheDB<B>>);
497
498impl<B: Serialize + Clone> Drop for FlushJsonBlockCacheDB<B> {
499 fn drop(&mut self) {
500 trace!(target: "fork::cache", "flushing cache");
501 self.0.flush();
502 trace!(target: "fork::cache", "flushed cache");
503 }
504}
505
506#[cfg(test)]
507mod tests {
508 use super::*;
509
510 #[test]
511 fn can_deserialize_cache() {
512 let s = r#"{
513 "meta": {
514 "cfg_env": {
515 "chain_id": 1337,
516 "perf_analyse_created_bytecodes": "Analyse",
517 "limit_contract_code_size": 18446744073709551615,
518 "memory_limit": 4294967295,
519 "disable_block_gas_limit": false,
520 "disable_eip3607": false,
521 "disable_base_fee": false
522 },
523 "block_env": {
524 "number": 15547871,
525 "coinbase": "0x0000000000000000000000000000000000000000",
526 "timestamp": 1663351871,
527 "difficulty": "0x0",
528 "basefee": 12448539171,
529 "gas_limit": 30000000,
530 "prevrandao": "0x0000000000000000000000000000000000000000000000000000000000000000"
531 },
532 "hosts": [
533 "eth-mainnet.alchemyapi.io"
534 ]
535 },
536 "accounts": {
537 "0xb8ffc3cd6e7cf5a098a1c92f48009765b24088dc": {
538 "balance": "0x0",
539 "nonce": 10,
540 "code_hash": "0x3ac64c95eedf82e5d821696a12daac0e1b22c8ee18a9fd688b00cfaf14550aad",
541 "code": {
542 "LegacyAnalyzed": {
543 "bytecode": "0x00",
544 "original_len": 0,
545 "jump_table": {
546 "order": "bitvec::order::Lsb0",
547 "head": {
548 "width": 8,
549 "index": 0
550 },
551 "bits": 1,
552 "data": [0]
553 }
554 }
555 }
556 }
557 },
558 "storage": {
559 "0xa354f35829ae975e850e23e9615b11da1b3dc4de": {
560 "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e564": "0x5553444320795661756c74000000000000000000000000000000000000000000",
561 "0x10": "0x37fd60ff8346",
562 "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563": "0xb",
563 "0x6": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
564 "0x5": "0x36ff5b93162e",
565 "0x14": "0x29d635a8e000",
566 "0x11": "0x63224c73",
567 "0x2": "0x6"
568 }
569 },
570 "block_hashes": {
571 "0xed3deb": "0xbf7be3174b261ea3c377b6aba4a1e05d5fae7eee7aab5691087c20cf353e9877",
572 "0xed3de9": "0xba1c3648e0aee193e7d00dffe4e9a5e420016b4880455641085a4731c1d32eef",
573 "0xed3de8": "0x61d1491c03a9295fb13395cca18b17b4fa5c64c6b8e56ee9cc0a70c3f6cf9855",
574 "0xed3de7": "0xb54560b5baeccd18350d56a3bee4035432294dc9d2b7e02f157813e1dee3a0be",
575 "0xed3dea": "0x816f124480b9661e1631c6ec9ee39350bda79f0cbfc911f925838d88e3d02e4b"
576 }
577}"#;
578
579 let cache: JsonBlockCacheData<BlockEnv> = serde_json::from_str(s).unwrap();
580 assert_eq!(cache.data.accounts.read().len(), 1);
581 assert_eq!(cache.data.storage.read().len(), 1);
582 assert_eq!(cache.data.block_hashes.read().len(), 5);
583
584 let _s = serde_json::to_string(&cache).unwrap();
585 }
586
587 #[test]
588 fn can_deserialize_cache_post_4844() {
589 let s = r#"{
590 "meta": {
591 "cfg_env": {
592 "chain_id": 1,
593 "kzg_settings": "Default",
594 "perf_analyse_created_bytecodes": "Analyse",
595 "limit_contract_code_size": 18446744073709551615,
596 "memory_limit": 134217728,
597 "disable_block_gas_limit": false,
598 "disable_eip3607": true,
599 "disable_base_fee": false,
600 "optimism": false
601 },
602 "block_env": {
603 "number": 18651580,
604 "coinbase": "0x4838b106fce9647bdf1e7877bf73ce8b0bad5f97",
605 "timestamp": 1700950019,
606 "gas_limit": 30000000,
607 "basefee": 26886078239,
608 "difficulty": "0xc6b1a299886016dea3865689f8393b9bf4d8f4fe8c0ad25f0058b3569297c057",
609 "prevrandao": "0xc6b1a299886016dea3865689f8393b9bf4d8f4fe8c0ad25f0058b3569297c057",
610 "blob_excess_gas_and_price": {
611 "excess_blob_gas": 0,
612 "blob_gasprice": 1
613 }
614 },
615 "hosts": [
616 "eth-mainnet.alchemyapi.io"
617 ]
618 },
619 "accounts": {
620 "0x4838b106fce9647bdf1e7877bf73ce8b0bad5f97": {
621 "balance": "0x8e0c373cfcdfd0eb",
622 "nonce": 128912,
623 "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
624 "code": {
625 "LegacyAnalyzed": {
626 "bytecode": "0x00",
627 "original_len": 0,
628 "jump_table": {
629 "order": "bitvec::order::Lsb0",
630 "head": {
631 "width": 8,
632 "index": 0
633 },
634 "bits": 1,
635 "data": [0]
636 }
637 }
638 }
639 }
640 },
641 "storage": {},
642 "block_hashes": {}
643}"#;
644
645 let cache: JsonBlockCacheData<BlockEnv> = serde_json::from_str(s).unwrap();
646 assert_eq!(cache.data.accounts.read().len(), 1);
647
648 let _s = serde_json::to_string(&cache).unwrap();
649 }
650
651 #[test]
652 fn roundtrip_meta_block_env() {
653 let meta = BlockchainDbMeta {
654 chain: Some(Chain::mainnet()),
655 block_env: BlockEnv { number: U256::from(1u64), ..Default::default() },
656 hosts: BTreeSet::from(["eth-mainnet.alchemyapi.io".to_string()]),
657 };
658 let json = serde_json::to_string(&meta).unwrap();
659 let recovered: BlockchainDbMeta<BlockEnv> = serde_json::from_str(&json).unwrap();
660 assert_eq!(meta, recovered);
661 }
662
663 #[test]
664 fn can_return_cache_path_if_set() {
665 let cache_db = JsonBlockCacheDB::<BlockEnv>::new(
667 Arc::new(RwLock::new(BlockchainDbMeta::default())),
668 Some(PathBuf::from("/tmp/foo")),
669 );
670 assert_eq!(Some(Path::new("/tmp/foo")), cache_db.cache_path());
671
672 let cache_db = JsonBlockCacheDB::<BlockEnv>::new(
674 Arc::new(RwLock::new(BlockchainDbMeta::default())),
675 None,
676 );
677 assert_eq!(None, cache_db.cache_path());
678 }
679}