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

[wip]: BlockExecutor #1

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
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
113 changes: 113 additions & 0 deletions crates/evm/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,116 @@
// complex unless we operate on something that yields `Tx`, and not blocks, this iterator style
// type must be aware of things like `gas_used`, invalid tx, etc... so that we can properly build
// a block.

use crate::evm::Evm;

/// Abstraction over block execution input.
///
/// This type must contain all of the context required to configure EVM and a way to obtain
/// transactions for the block.
pub trait BlockExecutionInput {
/// Transaction type.
type Transaction;
}

/// Abstraction over block execution outcome.
pub trait BlockExecutionOutcome {
/// Receipt type.
type Receipt;

fn receipts(&self) -> &[Self::Receipt];
}

/// Abstraction over type that is capable of executing a block.
///
/// This type knows how to configure an underlying EVM and execute transactions on top of it along
/// with any additional pre/post execution actions.
pub trait BlockExecutor {
/// Input for the block execution.
type Input: BlockExecutionInput;

/// Outcome of the block execution.
type Output: BlockExecutionOutcome;

/// Errors that can occur during block execution.
type Error;

fn execute(&mut self, input: Self::Input) -> Result<Self::Output, Self::Error>;
}
Comment on lines +42 to +53
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cool, yep I think this is it.
pretty simple and should allow for any kind of restrictions that may be necessary


#[cfg(test)]
mod tests {
use crate::evm::{self, EvmFactory};

use super::*;

trait ReceiptBuilder<Tx, EvmRes> {
type Receipt: Default;

fn build_receipt<'a>(
&self,
ctx: &'a BlockExecutorContext,
tx: &'a Tx,
evm_res: &'a EvmRes,
) -> Self::Receipt;
}

#[derive(Default)]
struct BlockExecutorContext {
gas_used: u64,
blob_gas_used: u64,
}

struct BlockExecutorOutput<T> {
receipts: Vec<T>,
}

struct EthBlockExecutor<EvmF, ReceiptB, T> {
evm_factory: EvmF,
receipt_builder: ReceiptB,
_pd: core::marker::PhantomData<T>,
}

impl<EvmF, ReceiptB, T> BlockExecutor for EthBlockExecutor<EvmF, ReceiptB, T>
where
EvmF: EvmFactory<Evm: Evm<Tx: From<T>>>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this could be problematic where tx is like TxEnvelope because we still need to do recovery

ReceiptB: ReceiptBuilder<T, <EvmF::Evm as Evm>::Outcome>,
{
type Input = alloy_consensus::Block<T>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should perhaps consider adding some convenience type here so that we can accept any block variant, e.g. with recovered senders.
or we require it recovered as input

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Think API wise you want the block to be either type, and if it's not recovered it should do the recovery for you -- so probably a T: Into RecoveredBlock?

type Output = BlockExecutorOutput<ReceiptB::Receipt>;
type Error = <EvmF::Evm as Evm>::Error;

fn execute(&mut self, input: Self::Input) -> Result<Self::Output, Self::Error> {
// This should use block header and additional context (chainspec?) to create the evm
// config.
let evm_input: EvmF::Input = todo!();
let mut evm = self.evm_factory.create_evm(evm_input);

let mut ctx = BlockExecutorContext::default();
let mut receipts = Vec::new();

for tx in input.body.transactions {
let result = evm.transact(tx.into())?;
// ctx.gas_used += result.gas_used();
// ctx.blob_gas_used += result.blob_gas_used();

let receipt = self.receipt_builder.build_receipt(&ctx, &tx, &result);
receipts.push(receipt);
}

Ok(BlockExecutorOutput { receipts })
}
}

impl<T> BlockExecutionInput for alloy_consensus::Block<T> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: this should still support somebody adding a custom block executor, say, enforcing inclusion lists from the header

type Transaction = T;
}

impl<R> BlockExecutionOutcome for BlockExecutorOutput<R> {
type Receipt = R;

fn receipts(&self) -> &[Self::Receipt] {
&self.receipts
}
}
}
1 change: 1 addition & 0 deletions crates/evm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@

pub mod block;
pub mod evm;
pub use evm::{Evm, EvmFactory};
Loading