Skip to content
Draft
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
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ alloy-op-evm = { version = "0.25.0", path = "crates/op-evm", default-features =

# alloy
alloy-eip2124 = { version = "0.2", default-features = false }
alloy-eip7928 = { git = "https://github.com/alloy-rs/eips/", default-features = false }
alloy-chains = { version = "0.2.0", default-features = false }
alloy-eips = { version = "1.0.34", default-features = false }
alloy-consensus = { version = "1.0.27", default-features = false }
Expand Down
1 change: 1 addition & 0 deletions crates/evm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ alloy-consensus = { workspace = true, features = ["k256"] }
alloy-primitives.workspace = true
alloy-sol-types.workspace = true
alloy-eips.workspace = true
alloy-eip7928.workspace = true
alloy-hardforks.workspace = true
alloy-op-hardforks = { workspace = true, optional = true }
alloy-rpc-types-eth = { workspace = true, optional = true }
Expand Down
137 changes: 137 additions & 0 deletions crates/evm/src/block/bal.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
//! Helper types for [EIP-7928](https://eips.ethereum.org/EIPS/eip-7928#asynchronous-validation) aware execution.

use alloy_eip7928::AccountChanges;
use alloy_primitives::{Address, B256};
use alloy_primitives::map::{HashMap, HashSet};
use revm::bytecode::Bytecode;
use revm::Database;
use revm::primitives::{StorageKey, StorageValue};
use revm::state::AccountInfo;
use crate::block::BlockExecutionError;

/// A Block level access list aware database.
pub trait BalDb {

/// Sets the index of the transaction that's being executed.
fn set_transaction_index(&mut self, index: usize);

}

/// A block level accesslist aware database/state implementation that is diff aware.
///
/// This type is intended to be used for block level execution.
#[derive(Debug)]
pub struct BalState<DB> {
/// The underlying database used to fetch reads from.
inner: DB,
/// The block level
bal : IndexedBal,
/// Tracks the index of the transaction that is currently being executed.
current_tx_index: usize,
}

impl<DB> BalState<DB> {

/// Marks the execution as completed
fn finish(&mut self) {
// TODO: do we need this?
}

}

impl<DB> BalDb for BalState<DB> {
fn set_transaction_index(&mut self, index: usize) {
self.current_tx_index = index;
}
}

impl<DB> Database for BalState<DB>
where DB: Database
{
type Error = <DB as Database>::Error;

fn basic(&mut self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
todo!()
}

fn code_by_hash(&mut self, code_hash: B256) -> Result<Bytecode, Self::Error> {
todo!()
}

fn storage(&mut self, address: Address, index: StorageKey) -> Result<StorageValue, Self::Error> {
todo!()
}

fn block_hash(&mut self, number: u64) -> Result<B256, Self::Error> {
todo!()
}
}


/// A transaction indexed representation of EIP-7928 accesslist.
/// TODO: Arc this
#[derive(Debug, Clone)]
pub struct IndexedBal {
/// All accounts grouped by their address.
accounts: HashMap<Address, AccountChanges>
}

impl IndexedBal {

/// Creates a new instance from the given list of account changes
pub fn new(bal: Vec<AccountChanges>) -> Self {
Self {
accounts: bal.into_iter().map(|change|(change.address, change)).collect(),
}
}

/// Returns the account
fn account_at(&self, address: &Address, idx: usize) -> Option<AccountInfo> {
None
}


}


// /// Required context for executing a BAL transaction.
// #[derive(Debug)]
// pub struct BalTxContext {
// /// The mandatory input for this transaction.
// ///
// /// This is derived from the state changes in previous transactions in the block.
// pub input: TxInput,
// /// The expected output set for this specific transaction
// pub output: BalTxExpectedOutput,
// }
//
// /// The exection output of a BAL aware transaction
// #[derive(Debug)]
// pub struct BalTxOutput {}
//
// /// The transaction specific input set.
// ///
// /// This is similar to state overrides that must be applied before executing the transaction.
// /// For example any account/storage changes from previous transactions in the block.
// #[derive(Debug)]
// pub struct TxInput {}

// /// The expected changes performed by the transaction.
// ///
// /// This is effectively the write portion of the [`EvmState`] in the execution result and must be
// /// complete match.
// #[derive(Debug)]
// pub struct BalTxExpectedOutput {}

/// A BAL aware transaction failed to execute
#[derive(Debug)]
pub enum BalExecutionError {
/// Output of the transaction invalidated the BAL input.
BalDiff(BalDiffError),
/// Executing the transaction failed.
Execution(BlockExecutionError),
}

/// A BAL aware transaction execution resulted in an invalid BAL.
#[derive(Debug)]
pub enum BalDiffError {}
82 changes: 82 additions & 0 deletions crates/evm/src/block/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ use revm::{
Inspector,
};

mod bal;
pub use bal::*;
mod error;
pub use error::*;

Expand Down Expand Up @@ -92,6 +94,56 @@ impl CommitChanges {
///
/// The output of [`BlockExecutor::finish`] is a [`BlockExecutionResult`] which contains all
/// relevant information about the block execution.
///
/// ## Conflict free parallel execution
///
/// With [EIP-7928](https://eips.ethereum.org/EIPS/eip-7928) Execution can be fully parallelized if the BAL is known in advance.
/// The BAL must still be validated by checking if the BAL recorded during validation matches the
/// input:
/// > The BAL MUST be complete and accurate. Missing or spurious entries invalidate the block.
///
/// ### Transaction independence
///
/// The BAL tracks all touched accounts with its accessed storage (read+write) and changes to the
/// account itself (balance,nonce,code). All changes (storage changes, balance, nonce, code) are
/// assigned a tx index (the tx in the block that caused the change). From this input we can make
/// transaction execution operate on:
/// - dependent inputs:
/// * Any accessed account data changed in previous transactions in the block
/// - expected outputs:
/// * The changes recorded for this transaction in the BAL
///
/// Validation must then ensure that:
/// - The state output matches the storage input:
/// * All accessed, but unchanged slots in the _output_ set appear in the provided input set.
/// * All storage writes provided in the input match the output
/// * All account modifications in the input match the output
///
/// If any of those mismatch then the input BAL is invalid and the block is invalid.
///
/// Storage read caveat:
/// Account level storage reads aren't indexed by transactions, the view of storage views is however
/// transaction specific. In total validation must ensure that _all_ reads are present in the block,
/// this condition can only be validation after executing all transactions in the block.
///
/// ### Deriving transaction level input
///
/// The EIP-7928 BAL tracks account level access, all writes are assigned an index (the index of the
/// transaction in the block). Starting from the parent state, the input and output set can be
/// derived from the BAL by sorting and grouping changes for transactions. Each transaction has a
/// unique input and output set, which is derived from the input and output set of previous changes
/// in the block.
/// Because EIP-7928 BAL is just a list of `Vec<AccountChange>` where each `AccountChange` includes
/// indexed changes, we get the transaction specific [`TxInput`] by folding the account changeset
/// with the transaction indexes.
///
/// ### BAL execution/validation abstraction
///
/// If the BAL is valid (transaction specific input and output sets are correct) then transaction
/// changes don't need to be committed across transactions. Parallel execution can then be achieved
/// by using multiple instances of the [`BlockExecutor`], like a pool of executors. Hence this trait
/// does only provide BAL abstractions for executing single transactions with the transaction
/// specific input sets.
pub trait BlockExecutor {
/// Input transaction type.
///
Expand Down Expand Up @@ -233,6 +285,36 @@ pub trait BlockExecutor {
tx: impl ExecutableTx<Self>,
) -> Result<ResultAndState<<Self::Evm as Evm>::HaltReason>, BlockExecutionError>;

/// Executes a single transaction using the transaction specific context without committing
/// state changes.
///
/// This method uses the BAL context's input for applying state changes that happened in
/// previous transactions in the block.
///
/// This returns an error if the transaction's output deviates from the expected output.
///
/// Returns a [`revm::context_interface::result::ResultAndState`] containing the execution
/// result and state changes.
/// TODO: we need the same functions for pre and post sections.
fn execute_transaction_with_index(
&self,
tx: impl ExecutableTx<Self>,
index: usize,
) -> Result<ResultAndState<<Self::Evm as Evm>::HaltReason>, BalExecutionError>
where <Self::Evm as Evm>::DB: BalDb
{
// TODO: we need a away to effectively apply state overrides for previous state changes
// because we don't know the tx specific reads, all state changes carry over, so ideally we
// don't need to apply them for _every_ transaction and instead only apply the new ones, for
// example if this executor was used first to execute tx 5, we need to apply changes from tx
// 0..=4 before, if its then reused to execute tx 9 (because 6,7,8 are executed by different
// block executors in the pool), then we only want to apply missing changes from 5..=8 and
// should reuse changes applied before executing tx5 execute the transaction and
// compare against the expected output for this tx, the BAL context is invalid if
// the expected output is not an exact match.
todo!()
}

/// Commits a previously executed transaction's state changes.
///
/// Takes the output from
Expand Down
Loading