1use 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#[derive(Clone, Debug)]
27pub struct BlockchainDb {
28 db: Arc<MemDb>,
30 meta: Arc<RwLock<BlockchainDbMeta>>,
32 cache: Arc<JsonBlockCacheDB>,
34}
35
36impl BlockchainDb {
37 pub fn new(meta: BlockchainDbMeta, cache_path: Option<PathBuf>) -> Self {
48 Self::new_db(meta, cache_path, false)
49 }
50
51 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 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 pub fn accounts(&self) -> &RwLock<AddressHashMap<AccountInfo>> {
93 &self.db.accounts
94 }
95
96 pub fn storage(&self) -> &RwLock<AddressHashMap<StorageInfo>> {
98 &self.db.storage
99 }
100
101 pub fn block_hashes(&self) -> &RwLock<HashMap<U256, B256>> {
103 &self.db.block_hashes
104 }
105
106 pub const fn meta(&self) -> &Arc<RwLock<BlockchainDbMeta>> {
108 &self.meta
109 }
110
111 pub const fn cache(&self) -> &Arc<JsonBlockCacheDB> {
113 &self.cache
114 }
115
116 pub const fn db(&self) -> &Arc<MemDb> {
118 &self.db
119 }
120}
121
122#[derive(Clone, Debug, Eq, Serialize, Default)]
124pub struct BlockchainDbMeta {
125 pub cfg_env: CfgEnv,
126 pub block_env: BlockEnv,
127 pub hosts: BTreeSet<String>,
129}
130
131impl BlockchainDbMeta {
132 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 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 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 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 pub fn set_cfg_env(mut self, cfg_env: revm::primitives::CfgEnv) {
184 self.cfg_env = cfg_env;
185 }
186
187 pub fn set_block_env(mut self, block_env: revm::primitives::BlockEnv) {
189 self.block_env = block_env;
190 }
191}
192
193impl 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 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 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 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 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 #[derive(Deserialize)]
274 struct Meta {
275 cfg_env: CfgEnvBackwardsCompat,
276 block_env: BlockEnvBackwardsCompat,
277 #[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#[derive(Debug, Default)]
304pub struct MemDb {
305 pub accounts: RwLock<AddressHashMap<AccountInfo>>,
307 pub storage: RwLock<AddressHashMap<StorageInfo>>,
309 pub block_hashes: RwLock<HashMap<U256, B256>>,
311}
312
313impl MemDb {
314 pub fn clear(&self) {
316 self.accounts.write().clear();
317 self.storage.write().clear();
318 self.block_hashes.write().clear();
319 }
320
321 pub fn do_insert_account(&self, address: Address, account: AccountInfo) {
323 self.accounts.write().insert(address, account);
324 }
325
326 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 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#[derive(Debug)]
386pub struct JsonBlockCacheDB {
387 cache_path: Option<PathBuf>,
391 data: JsonBlockCacheData,
393}
394
395impl JsonBlockCacheDB {
396 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 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 pub const fn db(&self) -> &Arc<MemDb> {
423 &self.data.data
424 }
425
426 pub const fn meta(&self) -> &Arc<RwLock<BlockchainDbMeta>> {
428 &self.data.meta
429 }
430
431 pub const fn is_transient(&self) -> bool {
433 self.cache_path.is_none()
434 }
435
436 #[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 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 pub fn cache_path(&self) -> Option<&Path> {
471 self.cache_path.as_deref()
472 }
473}
474
475#[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#[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 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 let cache_db =
698 JsonBlockCacheDB::new(Arc::new(RwLock::new(BlockchainDbMeta::default())), None);
699 assert_eq!(None, cache_db.cache_path());
700 }
701}