Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[pull] main from bluealloy:main #49

Merged
merged 2 commits into from
Mar 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 66 additions & 35 deletions crates/database/src/in_memory_db.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
use core::convert::Infallible;
use database_interface::{Database, DatabaseCommit, DatabaseRef, EmptyDB};
use primitives::{address, hash_map::Entry, Address, HashMap, Log, B256, KECCAK_EMPTY, U256};
use primitives::{
address,
hash_map::{Entry, HashMap},
Address, Log, B256, KECCAK_EMPTY, U256,
};
use state::{Account, AccountInfo, Bytecode};
use std::vec::Vec;

/// A [Database] implementation that stores all state changes in memory.
pub type InMemoryDB = CacheDB<EmptyDB>;

/// A [Database] implementation that stores all state changes in memory.
///
/// This implementation wraps a [DatabaseRef] that is used to load data ([AccountInfo]).
/// A cache used in [CacheDB]. Its kept separate so it can be used independently.
///
/// Accounts and code are stored in two separate maps, the `accounts` map maps addresses to [DbAccount],
/// whereas contracts are identified by their code hash, and are stored in the `contracts` map.
/// The [DbAccount] holds the code hash of the contract, which is used to look up the contract in the `contracts` map.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CacheDB<ExtDB> {
pub struct Cache {
/// Account info where None means it is not existing. Not existing state is needed for Pre TANGERINE forks.
/// `code` is always `None`, and bytecode can be found in `contracts`.
pub accounts: HashMap<Address, DbAccount>,
Expand All @@ -26,6 +28,31 @@ pub struct CacheDB<ExtDB> {
pub logs: Vec<Log>,
/// All cached block hashes from the [DatabaseRef].
pub block_hashes: HashMap<U256, B256>,
}

impl Default for Cache {
fn default() -> Self {
let mut contracts = HashMap::new();
contracts.insert(KECCAK_EMPTY, Bytecode::default());
contracts.insert(B256::ZERO, Bytecode::default());

Cache {
accounts: HashMap::default(),
contracts,
logs: Vec::default(),
block_hashes: HashMap::default(),
}
}
}

/// A [Database] implementation that stores all state changes in memory.
///
/// This implementation wraps a [DatabaseRef] that is used to load data ([AccountInfo]).
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CacheDB<ExtDB> {
/// The cache that stores all state changes.
pub cache: Cache,
/// The underlying database ([DatabaseRef]) that is used to load data.
///
/// Note: This is read-only, data is never written to this database.
Expand All @@ -48,17 +75,20 @@ impl<ExtDb> CacheDB<CacheDB<ExtDb>> {
/// - Block hashes are overridden with outer block hashes
pub fn flatten(self) -> CacheDB<ExtDb> {
let CacheDB {
accounts,
contracts,
logs,
block_hashes,
cache:
Cache {
accounts,
contracts,
logs,
block_hashes,
},
db: mut inner,
} = self;

inner.accounts.extend(accounts);
inner.contracts.extend(contracts);
inner.logs.extend(logs);
inner.block_hashes.extend(block_hashes);
inner.cache.accounts.extend(accounts);
inner.cache.contracts.extend(contracts);
inner.cache.logs.extend(logs);
inner.cache.block_hashes.extend(block_hashes);
inner
}

Expand All @@ -71,14 +101,8 @@ impl<ExtDb> CacheDB<CacheDB<ExtDb>> {
impl<ExtDB> CacheDB<ExtDB> {
/// Creates a new cache with the given external database.
pub fn new(db: ExtDB) -> Self {
let mut contracts = HashMap::default();
contracts.insert(KECCAK_EMPTY, Bytecode::default());
contracts.insert(B256::ZERO, Bytecode::default());
Self {
accounts: HashMap::default(),
contracts,
logs: Vec::default(),
block_hashes: HashMap::default(),
cache: Cache::default(),
db,
}
}
Expand All @@ -94,7 +118,8 @@ impl<ExtDB> CacheDB<ExtDB> {
if account.code_hash == KECCAK_EMPTY {
account.code_hash = code.hash_slow();
}
self.contracts
self.cache
.contracts
.entry(account.code_hash)
.or_insert_with(|| code.clone());
}
Expand All @@ -107,7 +132,7 @@ impl<ExtDB> CacheDB<ExtDB> {
/// Inserts account info but not override storage
pub fn insert_account_info(&mut self, address: Address, mut info: AccountInfo) {
self.insert_contract(&mut info);
self.accounts.entry(address).or_default().info = info;
self.cache.accounts.entry(address).or_default().info = info;
}

/// Wraps the cache in a [CacheDB], creating a nested cache.
Expand All @@ -122,7 +147,7 @@ impl<ExtDB: DatabaseRef> CacheDB<ExtDB> {
/// If the account was not found in the cache, it will be loaded from the underlying database.
pub fn load_account(&mut self, address: Address) -> Result<&mut DbAccount, ExtDB::Error> {
let db = &self.db;
match self.accounts.entry(address) {
match self.cache.accounts.entry(address) {
Entry::Occupied(entry) => Ok(entry.into_mut()),
Entry::Vacant(entry) => Ok(entry.insert(
db.basic_ref(address)?
Expand Down Expand Up @@ -167,7 +192,7 @@ impl<ExtDB> DatabaseCommit for CacheDB<ExtDB> {
continue;
}
if account.is_selfdestructed() {
let db_account = self.accounts.entry(address).or_default();
let db_account = self.cache.accounts.entry(address).or_default();
db_account.storage.clear();
db_account.account_state = AccountState::NotExisting;
db_account.info = AccountInfo::default();
Expand All @@ -176,7 +201,7 @@ impl<ExtDB> DatabaseCommit for CacheDB<ExtDB> {
let is_newly_created = account.is_created();
self.insert_contract(&mut account.info);

let db_account = self.accounts.entry(address).or_default();
let db_account = self.cache.accounts.entry(address).or_default();
db_account.info = account.info;

db_account.account_state = if is_newly_created {
Expand All @@ -202,7 +227,7 @@ impl<ExtDB: DatabaseRef> Database for CacheDB<ExtDB> {
type Error = ExtDB::Error;

fn basic(&mut self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
let basic = match self.accounts.entry(address) {
let basic = match self.cache.accounts.entry(address) {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => entry.insert(
self.db
Expand All @@ -218,7 +243,7 @@ impl<ExtDB: DatabaseRef> Database for CacheDB<ExtDB> {
}

fn code_by_hash(&mut self, code_hash: B256) -> Result<Bytecode, Self::Error> {
match self.contracts.entry(code_hash) {
match self.cache.contracts.entry(code_hash) {
Entry::Occupied(entry) => Ok(entry.get().clone()),
Entry::Vacant(entry) => {
// If you return code bytes when basic fn is called this function is not needed.
Expand All @@ -231,7 +256,7 @@ impl<ExtDB: DatabaseRef> Database for CacheDB<ExtDB> {
///
/// It is assumed that account is already loaded.
fn storage(&mut self, address: Address, index: U256) -> Result<U256, Self::Error> {
match self.accounts.entry(address) {
match self.cache.accounts.entry(address) {
Entry::Occupied(mut acc_entry) => {
let acc_entry = acc_entry.get_mut();
match acc_entry.storage.entry(index) {
Expand Down Expand Up @@ -268,7 +293,7 @@ impl<ExtDB: DatabaseRef> Database for CacheDB<ExtDB> {
}

fn block_hash(&mut self, number: u64) -> Result<B256, Self::Error> {
match self.block_hashes.entry(U256::from(number)) {
match self.cache.block_hashes.entry(U256::from(number)) {
Entry::Occupied(entry) => Ok(*entry.get()),
Entry::Vacant(entry) => {
let hash = self.db.block_hash_ref(number)?;
Expand All @@ -283,21 +308,21 @@ impl<ExtDB: DatabaseRef> DatabaseRef for CacheDB<ExtDB> {
type Error = ExtDB::Error;

fn basic_ref(&self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
match self.accounts.get(&address) {
match self.cache.accounts.get(&address) {
Some(acc) => Ok(acc.info()),
None => self.db.basic_ref(address),
}
}

fn code_by_hash_ref(&self, code_hash: B256) -> Result<Bytecode, Self::Error> {
match self.contracts.get(&code_hash) {
match self.cache.contracts.get(&code_hash) {
Some(entry) => Ok(entry.clone()),
None => self.db.code_by_hash_ref(code_hash),
}
}

fn storage_ref(&self, address: Address, index: U256) -> Result<U256, Self::Error> {
match self.accounts.get(&address) {
match self.cache.accounts.get(&address) {
Some(acc_entry) => match acc_entry.storage.get(&index) {
Some(entry) => Ok(*entry),
None => {
Expand All @@ -316,7 +341,7 @@ impl<ExtDB: DatabaseRef> DatabaseRef for CacheDB<ExtDB> {
}

fn block_hash_ref(&self, number: u64) -> Result<B256, Self::Error> {
match self.block_hashes.get(&U256::from(number)) {
match self.cache.block_hashes.get(&U256::from(number)) {
Some(entry) => Ok(*entry),
None => self.db.block_hash_ref(number),
}
Expand Down Expand Up @@ -524,9 +549,15 @@ mod tests {
let serialized = serde_json::to_string(&init_state).unwrap();
let deserialized: CacheDB<EmptyDB> = serde_json::from_str(&serialized).unwrap();

assert!(deserialized.accounts.contains_key(&account));
assert!(deserialized.cache.accounts.contains_key(&account));
assert_eq!(
deserialized.accounts.get(&account).unwrap().info.nonce,
deserialized
.cache
.accounts
.get(&account)
.unwrap()
.info
.nonce,
nonce
);
}
Expand Down
26 changes: 11 additions & 15 deletions crates/optimism/src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,15 +99,25 @@ where

fn validate_tx_against_state(&self, evm: &mut Self::Evm) -> Result<(), Self::Error> {
let context = evm.ctx();
let spec = context.cfg().spec();
let block_number = context.block().number();
if context.tx().tx_type() == DEPOSIT_TRANSACTION_TYPE {
return Ok(());
} else {
// The L1-cost fee is only computed for Optimism non-deposit transactions.
if context.chain().l2_block != block_number {
// L1 block info is stored in the context for later use.
// and it will be reloaded from the database if it is not for the current block.
*context.chain() = L1BlockInfo::try_fetch(context.db(), block_number, spec)?;
}
}
let spec = context.cfg().spec();

let enveloped_tx = context
.tx()
.enveloped_tx()
.expect("all not deposit tx have enveloped tx")
.clone();

// compute L1 cost
let mut additional_cost = context.chain().calculate_tx_l1_cost(&enveloped_tx, spec);

Expand All @@ -130,20 +140,6 @@ where
Ok(())
}

fn load_accounts(&self, evm: &mut Self::Evm) -> Result<(), Self::Error> {
// The L1-cost fee is only computed for Optimism non-deposit transactions.
let spec = evm.ctx().cfg().spec();
if evm.ctx().tx().tx_type() != DEPOSIT_TRANSACTION_TYPE {
let l1_block_info: crate::L1BlockInfo =
super::L1BlockInfo::try_fetch(evm.ctx().db(), spec)?;

// Storage L1 block info for later use.
*evm.ctx().chain() = l1_block_info;
}

self.mainnet.load_accounts(evm)
}

fn deduct_caller(&self, evm: &mut Self::Evm) -> Result<(), Self::Error> {
let ctx = evm.ctx();
let spec = ctx.cfg().spec();
Expand Down
5 changes: 5 additions & 0 deletions crates/optimism/src/l1block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ use revm::{
/// For now, we only care about the fields necessary for L1 cost calculation.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct L1BlockInfo {
/// The L2 block number. If not same as the one in the context,
/// L1BlockInfo is not valid and will be reloaded from the database.
pub l2_block: u64,
/// The base fee of the L1 origin block.
pub l1_base_fee: U256,
/// The current L1 fee overhead. None if Ecotone is activated.
Expand All @@ -52,6 +55,7 @@ impl L1BlockInfo {
/// Try to fetch the L1 block info from the database.
pub fn try_fetch<DB: Database>(
db: &mut DB,
l2_block: u64,
spec_id: OpSpecId,
) -> Result<L1BlockInfo, DB::Error> {
// Ensure the L1 Block account is loaded into the cache after Ecotone. With EIP-4788, it is no longer the case
Expand Down Expand Up @@ -116,6 +120,7 @@ impl L1BlockInfo {
.as_ref(),
);
Ok(L1BlockInfo {
l2_block,
l1_base_fee,
l1_base_fee_scalar,
l1_blob_base_fee: Some(l1_blob_base_fee),
Expand Down
Loading