Skip to content

Commit

Permalink
ConcurrentState (#66)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
prestwich authored Dec 16, 2024
1 parent 41ef795 commit b0c340d
Show file tree
Hide file tree
Showing 7 changed files with 792 additions and 6 deletions.
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ revm = { version = "18.0.0", default-features = false }

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

dashmap = { version = "6.1.0", optional = true }

[dev-dependencies]
alloy-rlp = { version = "0.3", default-features = false }
revm = { version = "18.0.0", features = [
Expand All @@ -56,6 +58,7 @@ tokio = { version = "1.39", features = ["macros", "rt-multi-thread"] }
[features]
default = [
"std",
"concurrent-db",
"revm/std",
"revm/c-kzg",
"revm/blst",
Expand All @@ -65,6 +68,8 @@ default = [

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

concurrent-db = ["std", "dep:dashmap"]

test-utils = ["revm/test-utils", "revm/std", "revm/serde-json", "revm/alloydb"]

secp256k1 = ["revm/secp256k1"]
Expand Down
181 changes: 181 additions & 0 deletions src/db/builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
use crate::db::ConcurrentState;
use revm::{
db::{
states::{BundleState, TransitionState},
EmptyDB,
},
primitives::{db::DatabaseRef, B256},
};
use std::collections::BTreeMap;

use super::{ConcurrentCacheState, ConcurrentStateInfo};

/// Allows building of State and initializing it with different options.
#[derive(Clone, Debug)]
pub struct ConcurrentStateBuilder<Db> {
/// Database that we use to fetch data from.
database: Db,
/// Enabled state clear flag that is introduced in Spurious Dragon hardfork.
/// Default is true as spurious dragon happened long time ago.
with_state_clear: bool,
/// if there is prestate that we want to use.
/// This would mean that we have additional state layer between evm and disk/database.
with_bundle_prestate: Option<BundleState>,
/// This will initialize cache to this state.
with_cache_prestate: Option<ConcurrentCacheState>,
/// Do we want to create reverts and update bundle state.
/// Default is false.
with_bundle_update: bool,
/// Do we want to merge transitions in background.
/// This will allows evm to continue executing.
/// Default is false.
with_background_transition_merge: bool,
/// If we want to set different block hashes
with_block_hashes: BTreeMap<u64, B256>,
}

impl ConcurrentStateBuilder<EmptyDB> {
/// Create a new builder with an empty database.
///
/// If you want to instantiate it with a specific database, use
/// [`new_with_database`](Self::new_with_database).
pub fn new() -> Self {
Self::default()
}
}

impl<Db: DatabaseRef + Sync + Default> Default for ConcurrentStateBuilder<Db> {
fn default() -> Self {
Self::new_with_database(Db::default())
}
}

impl<Db: DatabaseRef + Sync> ConcurrentStateBuilder<Db> {
/// Create a new builder with the given database.
pub const fn new_with_database(database: Db) -> Self {
Self {
database,
with_state_clear: true,
with_cache_prestate: None,
with_bundle_prestate: None,
with_bundle_update: false,
with_background_transition_merge: false,
with_block_hashes: BTreeMap::new(),
}
}

/// Set the database.
pub fn with_database<ODb: DatabaseRef + Sync>(
self,
database: ODb,
) -> ConcurrentStateBuilder<ODb> {
// cast to the different database,
// Note that we return different type depending of the database NewDBError.
ConcurrentStateBuilder {
with_state_clear: self.with_state_clear,
database,
with_cache_prestate: self.with_cache_prestate,
with_bundle_prestate: self.with_bundle_prestate,
with_bundle_update: self.with_bundle_update,
with_background_transition_merge: self.with_background_transition_merge,
with_block_hashes: self.with_block_hashes,
}
}

/// Alias for [`Self::with_database`], for revm compat reasons.
pub fn with_database_ref<ODb: DatabaseRef + Sync>(
self,
database: ODb,
) -> ConcurrentStateBuilder<ODb> {
self.with_database(database)
}

/// By default state clear flag is enabled but for initial sync on mainnet
/// we want to disable it so proper consensus changes are in place.
pub fn without_state_clear(self) -> Self {
Self { with_state_clear: false, ..self }
}

/// Allows setting prestate that is going to be used for execution.
/// This bundle state will act as additional layer of cache.
/// and State after not finding data inside StateCache will try to find it inside BundleState.
///
/// On update Bundle state will be changed and updated.
pub fn with_bundle_prestate(self, bundle: BundleState) -> Self {
Self { with_bundle_prestate: Some(bundle), ..self }
}

/// Make transitions and update bundle state.
///
/// This is needed option if we want to create reverts
/// and getting output of changed states.
pub fn with_bundle_update(self) -> Self {
Self { with_bundle_update: true, ..self }
}

/// It will use different cache for the state. If set, it will ignore bundle prestate.
/// and will ignore `without_state_clear` flag as cache contains its own state_clear flag.
///
/// This is useful for testing.
pub fn with_cached_prestate(self, cache: impl Into<ConcurrentCacheState>) -> Self {
Self { with_cache_prestate: Some(cache.into()), ..self }
}

/// Starts the thread that will take transitions and do merge to the bundle state
/// in the background.
pub fn with_background_transition_merge(self) -> Self {
Self { with_background_transition_merge: true, ..self }
}

/// Add block hashes to the state.
pub fn with_block_hashes(self, block_hashes: BTreeMap<u64, B256>) -> Self {
Self { with_block_hashes: block_hashes, ..self }
}

/// Build the concurrent state.
pub fn build(mut self) -> ConcurrentState<Db> {
let use_preloaded_bundle = if self.with_cache_prestate.is_some() {
self.with_bundle_prestate = None;
false
} else {
self.with_bundle_prestate.is_some()
};
ConcurrentState::new(
self.database,
ConcurrentStateInfo {
cache: self
.with_cache_prestate
.unwrap_or_else(|| ConcurrentCacheState::new(self.with_state_clear)),
transition_state: self.with_bundle_update.then(TransitionState::default),
bundle_state: self.with_bundle_prestate.unwrap_or_default(),
use_preloaded_bundle,
block_hashes: self.with_block_hashes.into(),
},
)
}
}

// Some code above and documentation is adapted from the revm crate, and is
// reproduced here under the terms of the MIT license.
//
// MIT License
//
// Copyright (c) 2021-2024 draganrakita
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
175 changes: 175 additions & 0 deletions src/db/cache_state.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
//! The majority of this code has been reproduced from revm.
use alloy_primitives::{Address, B256};
use dashmap::DashMap;
use revm::{
db::states::{plain_account::PlainStorage, CacheAccount},
primitives::{Account, AccountInfo, Bytecode, EvmState},
CacheState, TransitionAccount,
};

/// A concurrent version of [`revm::db::CacheState`].
///
/// Most of the code for this has been reproduced from revm.
#[derive(Debug, Clone)]
pub struct ConcurrentCacheState {
/// Block state account with account state.
pub accounts: DashMap<Address, CacheAccount>,
/// Created contracts.
// TODO add bytecode counter for number of bytecodes added/removed.
pub contracts: DashMap<B256, Bytecode>,
/// Has EIP-161 state clear enabled (Spurious Dragon hardfork).
pub has_state_clear: bool,
}

impl From<CacheState> for ConcurrentCacheState {
fn from(other: CacheState) -> Self {
Self {
accounts: other.accounts.into_iter().collect(),
contracts: other.contracts.into_iter().collect(),
has_state_clear: other.has_state_clear,
}
}
}

impl Default for ConcurrentCacheState {
fn default() -> Self {
Self::new(true)
}
}

impl ConcurrentCacheState {
/// New default state.
pub fn new(has_state_clear: bool) -> Self {
Self { accounts: DashMap::default(), contracts: DashMap::default(), has_state_clear }
}

/// Set state clear flag. EIP-161.
pub fn set_state_clear_flag(&mut self, has_state_clear: bool) {
self.has_state_clear = has_state_clear;
}

/// Insert not existing account.
pub fn insert_not_existing(&self, address: Address) {
self.accounts.insert(address, CacheAccount::new_loaded_not_existing());
}

/// Insert Loaded (Or LoadedEmptyEip161 if account is empty) account.
pub fn insert_account(&self, address: Address, info: AccountInfo) {
let account = if !info.is_empty() {
CacheAccount::new_loaded(info, PlainStorage::default())
} else {
CacheAccount::new_loaded_empty_eip161(PlainStorage::default())
};
self.accounts.insert(address, account);
}

/// Similar to `insert_account` but with storage.
pub fn insert_account_with_storage(
&self,
address: Address,
info: AccountInfo,
storage: PlainStorage,
) {
let account = if !info.is_empty() {
CacheAccount::new_loaded(info, storage)
} else {
CacheAccount::new_loaded_empty_eip161(storage)
};
self.accounts.insert(address, account);
}

/// Apply output of revm execution and create account transitions that are used to build BundleState.
pub fn apply_evm_state(&self, evm_state: EvmState) -> Vec<(Address, TransitionAccount)> {
let mut transitions = Vec::with_capacity(evm_state.len());
for (address, account) in evm_state {
if let Some(transition) = self.apply_account_state(address, account) {
transitions.push((address, transition));
}
}
transitions
}

/// Apply updated account state to the cached account.
/// Returns account transition if applicable.
fn apply_account_state(&self, address: Address, account: Account) -> Option<TransitionAccount> {
// not touched account are never changed.
if !account.is_touched() {
return None;
}

let mut this_account =
self.accounts.get_mut(&address).expect("All accounts should be present inside cache");

// If it is marked as selfdestructed inside revm
// we need to changed state to destroyed.
if account.is_selfdestructed() {
return this_account.selfdestruct();
}

let is_created = account.is_created();
let is_empty = account.is_empty();

// transform evm storage to storage with previous value.
let changed_storage = account
.storage
.into_iter()
.filter(|(_, slot)| slot.is_changed())
.map(|(key, slot)| (key, slot.into()))
.collect();

// Note: it can happen that created contract get selfdestructed in same block
// that is why is_created is checked after selfdestructed
//
// Note: Create2 opcode (Petersburg) was after state clear EIP (Spurious Dragon)
//
// Note: It is possibility to create KECCAK_EMPTY contract with some storage
// by just setting storage inside CRATE constructor. Overlap of those contracts
// is not possible because CREATE2 is introduced later.
if is_created {
return Some(this_account.newly_created(account.info, changed_storage));
}

// Account is touched, but not selfdestructed or newly created.
// Account can be touched and not changed.
// And when empty account is touched it needs to be removed from database.
// EIP-161 state clear
if is_empty {
if self.has_state_clear {
// touch empty account.
this_account.touch_empty_eip161()
} else {
// if account is empty and state clear is not enabled we should save
// empty account.
this_account.touch_create_pre_eip161(changed_storage)
}
} else {
Some(this_account.change(account.info, changed_storage))
}
}
}

// Some code above and documentation is adapted from the revm crate, and is
// reproduced here under the terms of the MIT license.
//
// MIT License
//
// Copyright (c) 2021-2024 draganrakita
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
Loading

0 comments on commit b0c340d

Please sign in to comment.