-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* 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
Showing
7 changed files
with
792 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
Oops, something went wrong.