Skip to content

Commit 247e156

Browse files
authored
feat: cow (#93)
* feat: cow * lint: clippy * chore: update for revm@20 * fix: doclinks * fix: copyright notice
1 parent 6928dcb commit 247e156

File tree

2 files changed

+252
-0
lines changed

2 files changed

+252
-0
lines changed

src/db/cow/mod.rs

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
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.

src/db/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,7 @@ pub mod sync;
55
/// Database abstraction traits.
66
mod traits;
77
pub use traits::{ArcUpgradeError, StateAcc, TryDatabaseCommit, TryStateAcc};
8+
9+
/// Cache-on-write database. A memory cache that caches only on write, not on
10+
/// read. Intended to wrap some other caching database.
11+
pub mod cow;

0 commit comments

Comments
 (0)