|
| 1 | +use alloy::{ |
| 2 | + consensus::constants::KECCAK_EMPTY, |
| 3 | + primitives::{Address, B256, U256}, |
| 4 | +}; |
| 5 | +use revm::{ |
| 6 | + bytecode::Bytecode, |
| 7 | + database::{in_memory_db::Cache, AccountState, DbAccount}, |
| 8 | + primitives::HashMap, |
| 9 | + state::{Account, AccountInfo}, |
| 10 | + Database, DatabaseCommit, DatabaseRef, |
| 11 | +}; |
| 12 | + |
| 13 | +/// A version of [`CacheDB`] that caches only on write, not on read. |
| 14 | +/// |
| 15 | +/// This saves memory when wrapping some other caching database, like [`State`] |
| 16 | +/// or [`ConcurrentState`]. |
| 17 | +/// |
| 18 | +/// [`CacheDB`]: revm::database::in_memory_db::CacheDB |
| 19 | +/// [`State`]: revm::database::State |
| 20 | +/// [`ConcurrentState`]: crate::db::sync::ConcurrentState |
| 21 | +#[derive(Debug)] |
| 22 | +pub struct CacheOnWrite<Db> { |
| 23 | + cache: Cache, |
| 24 | + inner: Db, |
| 25 | +} |
| 26 | + |
| 27 | +impl<Db> Default for CacheOnWrite<Db> |
| 28 | +where |
| 29 | + Db: Default, |
| 30 | +{ |
| 31 | + fn default() -> Self { |
| 32 | + Self::new(Db::default()) |
| 33 | + } |
| 34 | +} |
| 35 | + |
| 36 | +impl<Db> CacheOnWrite<Db> { |
| 37 | + /// Create a new `CacheOnWrite` with the given inner database. |
| 38 | + pub fn new(inner: Db) -> Self { |
| 39 | + Self { cache: Default::default(), inner } |
| 40 | + } |
| 41 | + |
| 42 | + /// Create a new `CacheOnWrite` with the given inner database and cache. |
| 43 | + pub const fn new_with_cache(inner: Db, cache: Cache) -> Self { |
| 44 | + Self { cache, inner } |
| 45 | + } |
| 46 | + |
| 47 | + /// Get a reference to the inner database. |
| 48 | + pub const fn inner(&self) -> &Db { |
| 49 | + &self.inner |
| 50 | + } |
| 51 | + |
| 52 | + /// Get a refernce to the [`Cache`]. |
| 53 | + pub const fn cache(&self) -> &Cache { |
| 54 | + &self.cache |
| 55 | + } |
| 56 | + |
| 57 | + /// Deconstruct the `CacheOnWrite` into its parts. |
| 58 | + pub fn into_parts(self) -> (Db, Cache) { |
| 59 | + (self.inner, self.cache) |
| 60 | + } |
| 61 | + |
| 62 | + /// Deconstruct the `CacheOnWrite` into its cache, dropping the `Db`. |
| 63 | + pub fn into_cache(self) -> Cache { |
| 64 | + self.cache |
| 65 | + } |
| 66 | + |
| 67 | + /// Nest the `CacheOnWrite` into a double cache. |
| 68 | + pub fn nest(self) -> CacheOnWrite<Self> { |
| 69 | + CacheOnWrite::new(self) |
| 70 | + } |
| 71 | + |
| 72 | + /// Inserts the account's code into the cache. |
| 73 | + /// |
| 74 | + /// Accounts objects and code are stored separately in the cache, this will take the code from the account and instead map it to the code hash. |
| 75 | + /// |
| 76 | + /// Note: This will not insert into the underlying external database. |
| 77 | + fn insert_contract(&mut self, account: &mut AccountInfo) { |
| 78 | + // Reproduced from |
| 79 | + // revm/crates/database/src/in_memory_db.rs |
| 80 | + if let Some(code) = &account.code { |
| 81 | + if !code.is_empty() { |
| 82 | + if account.code_hash == KECCAK_EMPTY { |
| 83 | + account.code_hash = code.hash_slow(); |
| 84 | + } |
| 85 | + self.cache.contracts.entry(account.code_hash).or_insert_with(|| code.clone()); |
| 86 | + } |
| 87 | + } |
| 88 | + if account.code_hash.is_zero() { |
| 89 | + account.code_hash = KECCAK_EMPTY; |
| 90 | + } |
| 91 | + } |
| 92 | +} |
| 93 | + |
| 94 | +impl<Db> CacheOnWrite<CacheOnWrite<Db>> { |
| 95 | + /// Discard the outer cache, returning the inner. |
| 96 | + pub fn discard_outer(self) -> CacheOnWrite<Db> { |
| 97 | + self.inner |
| 98 | + } |
| 99 | + |
| 100 | + /// Flattens a nested cache by applying the outer cache to the inner cache. |
| 101 | + /// |
| 102 | + /// The behavior is as follows: |
| 103 | + /// - Accounts are overridden with outer accounts |
| 104 | + /// - Contracts are overridden with outer contracts |
| 105 | + /// - Block hashes are overridden with outer block hashes |
| 106 | + pub fn flatten(self) -> CacheOnWrite<Db> { |
| 107 | + let Self { cache: Cache { accounts, contracts, logs, block_hashes }, mut inner } = self; |
| 108 | + |
| 109 | + inner.cache.accounts.extend(accounts); |
| 110 | + inner.cache.contracts.extend(contracts); |
| 111 | + inner.cache.logs.extend(logs); |
| 112 | + inner.cache.block_hashes.extend(block_hashes); |
| 113 | + inner |
| 114 | + } |
| 115 | +} |
| 116 | + |
| 117 | +impl<Db: DatabaseRef> Database for CacheOnWrite<Db> { |
| 118 | + type Error = Db::Error; |
| 119 | + |
| 120 | + fn basic(&mut self, address: Address) -> Result<Option<AccountInfo>, Self::Error> { |
| 121 | + if let Some(account) = self.cache.accounts.get(&address).map(DbAccount::info) { |
| 122 | + return Ok(account); |
| 123 | + } |
| 124 | + self.inner.basic_ref(address) |
| 125 | + } |
| 126 | + |
| 127 | + fn code_by_hash(&mut self, code_hash: B256) -> Result<Bytecode, Self::Error> { |
| 128 | + if let Some(code) = self.cache.contracts.get(&code_hash) { |
| 129 | + return Ok(code.clone()); |
| 130 | + } |
| 131 | + self.inner.code_by_hash_ref(code_hash) |
| 132 | + } |
| 133 | + |
| 134 | + fn storage(&mut self, address: Address, index: U256) -> Result<U256, Self::Error> { |
| 135 | + if let Some(storage) = |
| 136 | + self.cache.accounts.get(&address).map(|a| a.storage.get(&index).cloned()) |
| 137 | + { |
| 138 | + return Ok(storage.unwrap_or_default()); |
| 139 | + } |
| 140 | + self.inner.storage_ref(address, index) |
| 141 | + } |
| 142 | + |
| 143 | + fn block_hash(&mut self, number: u64) -> Result<B256, Self::Error> { |
| 144 | + if let Some(hash) = self.cache.block_hashes.get(&U256::from(number)) { |
| 145 | + return Ok(*hash); |
| 146 | + } |
| 147 | + self.inner.block_hash_ref(number) |
| 148 | + } |
| 149 | +} |
| 150 | + |
| 151 | +impl<Db: DatabaseRef> DatabaseRef for CacheOnWrite<Db> { |
| 152 | + type Error = Db::Error; |
| 153 | + |
| 154 | + fn basic_ref(&self, address: Address) -> Result<Option<AccountInfo>, Self::Error> { |
| 155 | + if let Some(account) = self.cache.accounts.get(&address).map(DbAccount::info) { |
| 156 | + return Ok(account); |
| 157 | + } |
| 158 | + self.inner.basic_ref(address) |
| 159 | + } |
| 160 | + |
| 161 | + fn code_by_hash_ref(&self, code_hash: B256) -> Result<Bytecode, Self::Error> { |
| 162 | + if let Some(code) = self.cache.contracts.get(&code_hash) { |
| 163 | + return Ok(code.clone()); |
| 164 | + } |
| 165 | + self.inner.code_by_hash_ref(code_hash) |
| 166 | + } |
| 167 | + |
| 168 | + fn storage_ref(&self, address: Address, index: U256) -> Result<U256, Self::Error> { |
| 169 | + if let Some(storage) = |
| 170 | + self.cache.accounts.get(&address).map(|a| a.storage.get(&index).cloned()) |
| 171 | + { |
| 172 | + return Ok(storage.unwrap_or_default()); |
| 173 | + } |
| 174 | + self.inner.storage_ref(address, index) |
| 175 | + } |
| 176 | + |
| 177 | + fn block_hash_ref(&self, number: u64) -> Result<B256, Self::Error> { |
| 178 | + if let Some(hash) = self.cache.block_hashes.get(&U256::from(number)) { |
| 179 | + return Ok(*hash); |
| 180 | + } |
| 181 | + self.inner.block_hash_ref(number) |
| 182 | + } |
| 183 | +} |
| 184 | + |
| 185 | +impl<Db> DatabaseCommit for CacheOnWrite<Db> { |
| 186 | + fn commit(&mut self, changes: HashMap<Address, Account>) { |
| 187 | + // Reproduced from |
| 188 | + // revm/crates/database/src/in_memory_db.rs |
| 189 | + for (address, mut account) in changes { |
| 190 | + if !account.is_touched() { |
| 191 | + continue; |
| 192 | + } |
| 193 | + |
| 194 | + if account.is_selfdestructed() { |
| 195 | + let db_account = self.cache.accounts.entry(address).or_default(); |
| 196 | + db_account.storage.clear(); |
| 197 | + db_account.account_state = AccountState::NotExisting; |
| 198 | + db_account.info = AccountInfo::default(); |
| 199 | + continue; |
| 200 | + } |
| 201 | + |
| 202 | + let is_newly_created = account.is_created(); |
| 203 | + self.insert_contract(&mut account.info); |
| 204 | + |
| 205 | + let db_account = self.cache.accounts.entry(address).or_default(); |
| 206 | + db_account.info = account.info; |
| 207 | + |
| 208 | + db_account.account_state = if is_newly_created { |
| 209 | + db_account.storage.clear(); |
| 210 | + AccountState::StorageCleared |
| 211 | + } else if db_account.account_state.is_storage_cleared() { |
| 212 | + // Preserve old account state if it already exists |
| 213 | + AccountState::StorageCleared |
| 214 | + } else { |
| 215 | + AccountState::Touched |
| 216 | + }; |
| 217 | + |
| 218 | + db_account.storage.extend( |
| 219 | + account.storage.into_iter().map(|(key, value)| (key, value.present_value())), |
| 220 | + ); |
| 221 | + } |
| 222 | + } |
| 223 | +} |
| 224 | + |
| 225 | +// Some code above and documentation is adapted from the revm crate, and is |
| 226 | +// reproduced here under the terms of the MIT license. |
| 227 | +// |
| 228 | +// MIT License |
| 229 | +// |
| 230 | +// Copyright (c) 2021-2024 draganrakita |
| 231 | +// |
| 232 | +// Permission is hereby granted, free of charge, to any person obtaining a copy |
| 233 | +// of this software and associated documentation files (the "Software"), to deal |
| 234 | +// in the Software without restriction, including without limitation the rights |
| 235 | +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 236 | +// copies of the Software, and to permit persons to whom the Software is |
| 237 | +// furnished to do so, subject to the following conditions: |
| 238 | +// |
| 239 | +// The above copyright notice and this permission notice shall be included in all |
| 240 | +// copies or substantial portions of the Software. |
| 241 | +// |
| 242 | +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 243 | +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 244 | +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 245 | +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 246 | +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 247 | +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| 248 | +// SOFTWARE. |
0 commit comments