Skip to content

Commit b0c340d

Browse files
authored
ConcurrentState (#66)
* feat: concurrentstate * doc: add a line * chore: integrate into trevm * chore: make it a feature * lint: clipppppppy * fix: add some missing muts and update docs * feat: builder port * refactor: rename * feat: clean up sync requirement
1 parent 41ef795 commit b0c340d

File tree

7 files changed

+792
-6
lines changed

7 files changed

+792
-6
lines changed

Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ revm = { version = "18.0.0", default-features = false }
3838

3939
zenith-types = { version = "0.10", optional = true }
4040

41+
dashmap = { version = "6.1.0", optional = true }
42+
4143
[dev-dependencies]
4244
alloy-rlp = { version = "0.3", default-features = false }
4345
revm = { version = "18.0.0", features = [
@@ -56,6 +58,7 @@ tokio = { version = "1.39", features = ["macros", "rt-multi-thread"] }
5658
[features]
5759
default = [
5860
"std",
61+
"concurrent-db",
5962
"revm/std",
6063
"revm/c-kzg",
6164
"revm/blst",
@@ -65,6 +68,8 @@ default = [
6568

6669
std = ["revm/std", "alloy/std", "alloy-rlp/std", "alloy-primitives/std", "alloy-sol-types/std", "dep:zenith-types"]
6770

71+
concurrent-db = ["std", "dep:dashmap"]
72+
6873
test-utils = ["revm/test-utils", "revm/std", "revm/serde-json", "revm/alloydb"]
6974

7075
secp256k1 = ["revm/secp256k1"]

src/db/builder.rs

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
use crate::db::ConcurrentState;
2+
use revm::{
3+
db::{
4+
states::{BundleState, TransitionState},
5+
EmptyDB,
6+
},
7+
primitives::{db::DatabaseRef, B256},
8+
};
9+
use std::collections::BTreeMap;
10+
11+
use super::{ConcurrentCacheState, ConcurrentStateInfo};
12+
13+
/// Allows building of State and initializing it with different options.
14+
#[derive(Clone, Debug)]
15+
pub struct ConcurrentStateBuilder<Db> {
16+
/// Database that we use to fetch data from.
17+
database: Db,
18+
/// Enabled state clear flag that is introduced in Spurious Dragon hardfork.
19+
/// Default is true as spurious dragon happened long time ago.
20+
with_state_clear: bool,
21+
/// if there is prestate that we want to use.
22+
/// This would mean that we have additional state layer between evm and disk/database.
23+
with_bundle_prestate: Option<BundleState>,
24+
/// This will initialize cache to this state.
25+
with_cache_prestate: Option<ConcurrentCacheState>,
26+
/// Do we want to create reverts and update bundle state.
27+
/// Default is false.
28+
with_bundle_update: bool,
29+
/// Do we want to merge transitions in background.
30+
/// This will allows evm to continue executing.
31+
/// Default is false.
32+
with_background_transition_merge: bool,
33+
/// If we want to set different block hashes
34+
with_block_hashes: BTreeMap<u64, B256>,
35+
}
36+
37+
impl ConcurrentStateBuilder<EmptyDB> {
38+
/// Create a new builder with an empty database.
39+
///
40+
/// If you want to instantiate it with a specific database, use
41+
/// [`new_with_database`](Self::new_with_database).
42+
pub fn new() -> Self {
43+
Self::default()
44+
}
45+
}
46+
47+
impl<Db: DatabaseRef + Sync + Default> Default for ConcurrentStateBuilder<Db> {
48+
fn default() -> Self {
49+
Self::new_with_database(Db::default())
50+
}
51+
}
52+
53+
impl<Db: DatabaseRef + Sync> ConcurrentStateBuilder<Db> {
54+
/// Create a new builder with the given database.
55+
pub const fn new_with_database(database: Db) -> Self {
56+
Self {
57+
database,
58+
with_state_clear: true,
59+
with_cache_prestate: None,
60+
with_bundle_prestate: None,
61+
with_bundle_update: false,
62+
with_background_transition_merge: false,
63+
with_block_hashes: BTreeMap::new(),
64+
}
65+
}
66+
67+
/// Set the database.
68+
pub fn with_database<ODb: DatabaseRef + Sync>(
69+
self,
70+
database: ODb,
71+
) -> ConcurrentStateBuilder<ODb> {
72+
// cast to the different database,
73+
// Note that we return different type depending of the database NewDBError.
74+
ConcurrentStateBuilder {
75+
with_state_clear: self.with_state_clear,
76+
database,
77+
with_cache_prestate: self.with_cache_prestate,
78+
with_bundle_prestate: self.with_bundle_prestate,
79+
with_bundle_update: self.with_bundle_update,
80+
with_background_transition_merge: self.with_background_transition_merge,
81+
with_block_hashes: self.with_block_hashes,
82+
}
83+
}
84+
85+
/// Alias for [`Self::with_database`], for revm compat reasons.
86+
pub fn with_database_ref<ODb: DatabaseRef + Sync>(
87+
self,
88+
database: ODb,
89+
) -> ConcurrentStateBuilder<ODb> {
90+
self.with_database(database)
91+
}
92+
93+
/// By default state clear flag is enabled but for initial sync on mainnet
94+
/// we want to disable it so proper consensus changes are in place.
95+
pub fn without_state_clear(self) -> Self {
96+
Self { with_state_clear: false, ..self }
97+
}
98+
99+
/// Allows setting prestate that is going to be used for execution.
100+
/// This bundle state will act as additional layer of cache.
101+
/// and State after not finding data inside StateCache will try to find it inside BundleState.
102+
///
103+
/// On update Bundle state will be changed and updated.
104+
pub fn with_bundle_prestate(self, bundle: BundleState) -> Self {
105+
Self { with_bundle_prestate: Some(bundle), ..self }
106+
}
107+
108+
/// Make transitions and update bundle state.
109+
///
110+
/// This is needed option if we want to create reverts
111+
/// and getting output of changed states.
112+
pub fn with_bundle_update(self) -> Self {
113+
Self { with_bundle_update: true, ..self }
114+
}
115+
116+
/// It will use different cache for the state. If set, it will ignore bundle prestate.
117+
/// and will ignore `without_state_clear` flag as cache contains its own state_clear flag.
118+
///
119+
/// This is useful for testing.
120+
pub fn with_cached_prestate(self, cache: impl Into<ConcurrentCacheState>) -> Self {
121+
Self { with_cache_prestate: Some(cache.into()), ..self }
122+
}
123+
124+
/// Starts the thread that will take transitions and do merge to the bundle state
125+
/// in the background.
126+
pub fn with_background_transition_merge(self) -> Self {
127+
Self { with_background_transition_merge: true, ..self }
128+
}
129+
130+
/// Add block hashes to the state.
131+
pub fn with_block_hashes(self, block_hashes: BTreeMap<u64, B256>) -> Self {
132+
Self { with_block_hashes: block_hashes, ..self }
133+
}
134+
135+
/// Build the concurrent state.
136+
pub fn build(mut self) -> ConcurrentState<Db> {
137+
let use_preloaded_bundle = if self.with_cache_prestate.is_some() {
138+
self.with_bundle_prestate = None;
139+
false
140+
} else {
141+
self.with_bundle_prestate.is_some()
142+
};
143+
ConcurrentState::new(
144+
self.database,
145+
ConcurrentStateInfo {
146+
cache: self
147+
.with_cache_prestate
148+
.unwrap_or_else(|| ConcurrentCacheState::new(self.with_state_clear)),
149+
transition_state: self.with_bundle_update.then(TransitionState::default),
150+
bundle_state: self.with_bundle_prestate.unwrap_or_default(),
151+
use_preloaded_bundle,
152+
block_hashes: self.with_block_hashes.into(),
153+
},
154+
)
155+
}
156+
}
157+
158+
// Some code above and documentation is adapted from the revm crate, and is
159+
// reproduced here under the terms of the MIT license.
160+
//
161+
// MIT License
162+
//
163+
// Copyright (c) 2021-2024 draganrakita
164+
//
165+
// Permission is hereby granted, free of charge, to any person obtaining a copy
166+
// of this software and associated documentation files (the "Software"), to deal
167+
// in the Software without restriction, including without limitation the rights
168+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
169+
// copies of the Software, and to permit persons to whom the Software is
170+
// furnished to do so, subject to the following conditions:
171+
//
172+
// The above copyright notice and this permission notice shall be included in all
173+
// copies or substantial portions of the Software.
174+
//
175+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
176+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
177+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
178+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
179+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
180+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
181+
// SOFTWARE.

src/db/cache_state.rs

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
//! The majority of this code has been reproduced from revm.
2+
3+
use alloy_primitives::{Address, B256};
4+
use dashmap::DashMap;
5+
use revm::{
6+
db::states::{plain_account::PlainStorage, CacheAccount},
7+
primitives::{Account, AccountInfo, Bytecode, EvmState},
8+
CacheState, TransitionAccount,
9+
};
10+
11+
/// A concurrent version of [`revm::db::CacheState`].
12+
///
13+
/// Most of the code for this has been reproduced from revm.
14+
#[derive(Debug, Clone)]
15+
pub struct ConcurrentCacheState {
16+
/// Block state account with account state.
17+
pub accounts: DashMap<Address, CacheAccount>,
18+
/// Created contracts.
19+
// TODO add bytecode counter for number of bytecodes added/removed.
20+
pub contracts: DashMap<B256, Bytecode>,
21+
/// Has EIP-161 state clear enabled (Spurious Dragon hardfork).
22+
pub has_state_clear: bool,
23+
}
24+
25+
impl From<CacheState> for ConcurrentCacheState {
26+
fn from(other: CacheState) -> Self {
27+
Self {
28+
accounts: other.accounts.into_iter().collect(),
29+
contracts: other.contracts.into_iter().collect(),
30+
has_state_clear: other.has_state_clear,
31+
}
32+
}
33+
}
34+
35+
impl Default for ConcurrentCacheState {
36+
fn default() -> Self {
37+
Self::new(true)
38+
}
39+
}
40+
41+
impl ConcurrentCacheState {
42+
/// New default state.
43+
pub fn new(has_state_clear: bool) -> Self {
44+
Self { accounts: DashMap::default(), contracts: DashMap::default(), has_state_clear }
45+
}
46+
47+
/// Set state clear flag. EIP-161.
48+
pub fn set_state_clear_flag(&mut self, has_state_clear: bool) {
49+
self.has_state_clear = has_state_clear;
50+
}
51+
52+
/// Insert not existing account.
53+
pub fn insert_not_existing(&self, address: Address) {
54+
self.accounts.insert(address, CacheAccount::new_loaded_not_existing());
55+
}
56+
57+
/// Insert Loaded (Or LoadedEmptyEip161 if account is empty) account.
58+
pub fn insert_account(&self, address: Address, info: AccountInfo) {
59+
let account = if !info.is_empty() {
60+
CacheAccount::new_loaded(info, PlainStorage::default())
61+
} else {
62+
CacheAccount::new_loaded_empty_eip161(PlainStorage::default())
63+
};
64+
self.accounts.insert(address, account);
65+
}
66+
67+
/// Similar to `insert_account` but with storage.
68+
pub fn insert_account_with_storage(
69+
&self,
70+
address: Address,
71+
info: AccountInfo,
72+
storage: PlainStorage,
73+
) {
74+
let account = if !info.is_empty() {
75+
CacheAccount::new_loaded(info, storage)
76+
} else {
77+
CacheAccount::new_loaded_empty_eip161(storage)
78+
};
79+
self.accounts.insert(address, account);
80+
}
81+
82+
/// Apply output of revm execution and create account transitions that are used to build BundleState.
83+
pub fn apply_evm_state(&self, evm_state: EvmState) -> Vec<(Address, TransitionAccount)> {
84+
let mut transitions = Vec::with_capacity(evm_state.len());
85+
for (address, account) in evm_state {
86+
if let Some(transition) = self.apply_account_state(address, account) {
87+
transitions.push((address, transition));
88+
}
89+
}
90+
transitions
91+
}
92+
93+
/// Apply updated account state to the cached account.
94+
/// Returns account transition if applicable.
95+
fn apply_account_state(&self, address: Address, account: Account) -> Option<TransitionAccount> {
96+
// not touched account are never changed.
97+
if !account.is_touched() {
98+
return None;
99+
}
100+
101+
let mut this_account =
102+
self.accounts.get_mut(&address).expect("All accounts should be present inside cache");
103+
104+
// If it is marked as selfdestructed inside revm
105+
// we need to changed state to destroyed.
106+
if account.is_selfdestructed() {
107+
return this_account.selfdestruct();
108+
}
109+
110+
let is_created = account.is_created();
111+
let is_empty = account.is_empty();
112+
113+
// transform evm storage to storage with previous value.
114+
let changed_storage = account
115+
.storage
116+
.into_iter()
117+
.filter(|(_, slot)| slot.is_changed())
118+
.map(|(key, slot)| (key, slot.into()))
119+
.collect();
120+
121+
// Note: it can happen that created contract get selfdestructed in same block
122+
// that is why is_created is checked after selfdestructed
123+
//
124+
// Note: Create2 opcode (Petersburg) was after state clear EIP (Spurious Dragon)
125+
//
126+
// Note: It is possibility to create KECCAK_EMPTY contract with some storage
127+
// by just setting storage inside CRATE constructor. Overlap of those contracts
128+
// is not possible because CREATE2 is introduced later.
129+
if is_created {
130+
return Some(this_account.newly_created(account.info, changed_storage));
131+
}
132+
133+
// Account is touched, but not selfdestructed or newly created.
134+
// Account can be touched and not changed.
135+
// And when empty account is touched it needs to be removed from database.
136+
// EIP-161 state clear
137+
if is_empty {
138+
if self.has_state_clear {
139+
// touch empty account.
140+
this_account.touch_empty_eip161()
141+
} else {
142+
// if account is empty and state clear is not enabled we should save
143+
// empty account.
144+
this_account.touch_create_pre_eip161(changed_storage)
145+
}
146+
} else {
147+
Some(this_account.change(account.info, changed_storage))
148+
}
149+
}
150+
}
151+
152+
// Some code above and documentation is adapted from the revm crate, and is
153+
// reproduced here under the terms of the MIT license.
154+
//
155+
// MIT License
156+
//
157+
// Copyright (c) 2021-2024 draganrakita
158+
//
159+
// Permission is hereby granted, free of charge, to any person obtaining a copy
160+
// of this software and associated documentation files (the "Software"), to deal
161+
// in the Software without restriction, including without limitation the rights
162+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
163+
// copies of the Software, and to permit persons to whom the Software is
164+
// furnished to do so, subject to the following conditions:
165+
//
166+
// The above copyright notice and this permission notice shall be included in all
167+
// copies or substantial portions of the Software.
168+
//
169+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
170+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
171+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
172+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
173+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
174+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
175+
// SOFTWARE.

0 commit comments

Comments
 (0)