Skip to content

Commit

Permalink
feat(wip): Introduce a generic chain spec in the edr_generic crate
Browse files Browse the repository at this point in the history
  • Loading branch information
Xanewok committed Sep 9, 2024
1 parent b694d46 commit aef02e2
Show file tree
Hide file tree
Showing 17 changed files with 876 additions and 3 deletions.
24 changes: 24 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/edr_evm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ tracing = { version = "0.1.37", features = ["attributes", "std"], optional = tru
anyhow = "1.0.75"
criterion = { version = "0.4.0", default-features = false, features = ["cargo_bench_support", "html_reports", "plotters"] }
edr_test_utils = { version = "0.3.5", path = "../edr_test_utils" }
edr_generic = { version = "0.3.5", path = "../edr_generic" }
lazy_static = "1.4.0"
paste = { version = "1.0.14", default-features = false }
serial_test = "2.0.0"
Expand Down
3 changes: 2 additions & 1 deletion crates/edr_evm/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use edr_eth::{
B256, U256,
};
use edr_rpc_eth::spec::RpcSpec;
use revm::EvmWiring;

pub use self::{
builder::{
Expand Down Expand Up @@ -74,7 +75,7 @@ where
}

/// A type containing the relevant data for an Ethereum block.
pub struct EthBlockData<ChainSpecT: ChainSpec> {
pub struct EthBlockData<ChainSpecT: EvmWiring> {
/// The block's header.
pub header: edr_eth::block::Header,
/// The block's transactions.
Expand Down
11 changes: 11 additions & 0 deletions crates/edr_evm/src/hardfork.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,17 @@ impl<ChainSpecT: ChainSpec> Activations<ChainSpecT> {
})
.map(|entry| entry.1)
}

/// Views the activations as for a different chain spec (that shares the
/// underlying hardforks).
pub fn as_chain_spec<OtherChainSpecT: ChainSpec<Hardfork = ChainSpecT::Hardfork>>(
&'static self,
) -> &'static Activations<OtherChainSpecT> {
// SAFETY: The layout is the same as we're using the same struct and the
// Hardfork associated type is the same and we are also converting from
// one static reference to another, so no lifetime hazards here as well.
unsafe { std::mem::transmute(self) }
}
}

impl<ChainSpecT: ChainSpec> From<&[(ForkCondition, ChainSpecT::Hardfork)]>
Expand Down
6 changes: 4 additions & 2 deletions crates/edr_evm/tests/blockchain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,9 +185,11 @@ fn insert_dummy_block_with_transaction(
let state_overrides = BTreeMap::new();
let state = blockchain.state_at_block_number(header.number - 1, &state_overrides)?;

let receipt_builder = receipt::execution::Builder::new_receipt_builder(state, &transaction)?;
let receipt_builder = <receipt::execution::Builder as receipt::ExecutionReceiptBuilder<
L1ChainSpec,
>>::new_receipt_builder(state, &transaction)?;

let execution_result = ExecutionResult::Success {
let execution_result = ExecutionResult::<L1ChainSpec>::Success {
reason: SuccessReason::Stop,
gas_used: GAS_USED,
gas_refunded: 0,
Expand Down
34 changes: 34 additions & 0 deletions crates/edr_generic/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
[package]
name = "edr_generic"
version = "0.3.5"
edition = "2021"
description = "A generic chain specification for Ethereum Layer 1 chains."

[dependencies]
alloy-rlp = { version = "0.3", default-features = false, features = ["derive"] }
edr_eth = { path = "../edr_eth" }
edr_evm = { path = "../edr_evm" }
edr_rpc_eth = { path = "../edr_rpc_eth" }
log = { version = "0.4.17", default-features = false }
revm = { git = "https://github.com/Wodann/revm", rev = "a500675", version = "12.1", default-features = false, features = ["c-kzg", "dev", "serde"] }
revm-primitives = { git = "https://github.com/Wodann/revm", rev = "a500675", version = "7.1", default-features = false, features = ["c-kzg", "hashbrown"] }
serde = { version = "1.0.209", default-features = false, features = ["derive"] }
thiserror = { version = "1.0.37", default-features = false }

[dev-dependencies]
anyhow = "1.0.75"
edr_defaults = { path = "../edr_defaults" }
edr_evm = { path = "../edr_evm", features = ["test-utils"] }
edr_rpc_eth = { path = "../edr_rpc_eth", features = ["test-utils"] }
edr_test_utils = { path = "../edr_test_utils" }
parking_lot = { version = "0.12.1", default-features = false }
paste = { version = "1.0.14", default-features = false }
serde_json = { version = "1.0.127" }
serial_test = "2.0.0"
tokio = { version = "1.21.2", default-features = false, features = ["macros", "rt-multi-thread", "sync"] }

[features]
test-remote = []

[lints]
workspace = true
144 changes: 144 additions & 0 deletions crates/edr_generic/src/eip2718.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
use alloy_rlp::Buf as _;
use edr_eth::{
receipt::{MapReceiptLogs, Receipt, RootOrStatus},
transaction::TransactionType,
Bloom,
};

use crate::transaction;

/// An compile-time typed EIP-2718 envelope for L1 Ethereum.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum TypedEnvelope<DataT> {
/// Legacy transaction.
Legacy(DataT),
/// EIP-2930 transaction.
Eip2930(DataT),
/// EIP-1559 transaction.
Eip1559(DataT),
/// EIP-4844 transaction.
Eip4844(DataT),
/// Unrecognized transaction type.
Unrecognized(DataT),
}

impl<DataT> TypedEnvelope<DataT> {
/// Constructs a typed envelope around the given data.
pub fn new(data: DataT, transaction_type: transaction::Type) -> Self {
match transaction_type {
transaction::Type::Legacy => Self::Legacy(data),
transaction::Type::Eip2930 => Self::Eip2930(data),
transaction::Type::Eip1559 => Self::Eip1559(data),
transaction::Type::Eip4844 => Self::Eip4844(data),
transaction::Type::Unrecognized(_) => Self::Unrecognized(data),
}
}

/// Returns a reference to the data inside the envelope.
pub fn data(&self) -> &DataT {
match self {
TypedEnvelope::Legacy(data)
| TypedEnvelope::Eip2930(data)
| TypedEnvelope::Eip1559(data)
| TypedEnvelope::Eip4844(data)
| TypedEnvelope::Unrecognized(data) => data,
}
}

/// Maps the data inside the envelope to a new type.
pub fn map<NewDataT, F>(self, f: F) -> TypedEnvelope<NewDataT>
where
F: FnOnce(DataT) -> NewDataT,
{
match self {
TypedEnvelope::Legacy(data) => TypedEnvelope::Legacy(f(data)),
TypedEnvelope::Eip2930(data) => TypedEnvelope::Eip2930(f(data)),
TypedEnvelope::Eip1559(data) => TypedEnvelope::Eip1559(f(data)),
TypedEnvelope::Eip4844(data) => TypedEnvelope::Eip4844(f(data)),
TypedEnvelope::Unrecognized(data) => TypedEnvelope::Unrecognized(f(data)),
}
}
}

impl<DataT> TransactionType for TypedEnvelope<DataT> {
type Type = transaction::Type;

fn transaction_type(&self) -> Self::Type {
match self {
TypedEnvelope::Legacy(_) => transaction::Type::Legacy,
TypedEnvelope::Eip2930(_) => transaction::Type::Eip2930,
TypedEnvelope::Eip1559(_) => transaction::Type::Eip1559,
TypedEnvelope::Eip4844(_) => transaction::Type::Eip4844,
TypedEnvelope::Unrecognized(_) => todo!(),
}
}
}

impl<DataT> alloy_rlp::Decodable for TypedEnvelope<DataT>
where
DataT: alloy_rlp::Decodable,
{
fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
fn is_list(byte: u8) -> bool {
byte >= 0xc0
}

let first = *buf.first().ok_or(alloy_rlp::Error::InputTooShort)?;
let transaction_type = if is_list(first) {
transaction::Type::Legacy
} else {
// Consume the first byte
buf.advance(1);

crate::transaction::Type::from(first)
};

let data = DataT::decode(buf)?;
Ok(TypedEnvelope::new(data, transaction_type))
}
}

impl<DataT> alloy_rlp::Encodable for TypedEnvelope<DataT>
where
DataT: alloy_rlp::Encodable,
{
fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
let transaction_type: u8 = self.transaction_type().into();
if transaction_type > 0 {
out.put_u8(transaction_type);
}

self.data().encode(out);
}

fn length(&self) -> usize {
let type_length = usize::from(u8::from(self.transaction_type()) > 0u8);
type_length + self.data().length()
}
}

impl<DataT: Receipt<LogT>, LogT> Receipt<LogT> for TypedEnvelope<DataT> {
fn cumulative_gas_used(&self) -> u64 {
self.data().cumulative_gas_used()
}

fn logs_bloom(&self) -> &Bloom {
self.data().logs_bloom()
}

fn transaction_logs(&self) -> &[LogT] {
self.data().transaction_logs()
}

fn root_or_status(&self) -> RootOrStatus<'_> {
self.data().root_or_status()
}
}

impl<OldDataT: MapReceiptLogs<OldLogT, NewLogT, NewDataT>, OldLogT, NewLogT, NewDataT>
MapReceiptLogs<OldLogT, NewLogT, TypedEnvelope<NewDataT>> for TypedEnvelope<OldDataT>
{
fn map_logs(self, map_fn: impl FnMut(OldLogT) -> NewLogT) -> TypedEnvelope<NewDataT> {
self.map(|data| data.map_logs(map_fn))
}
}
19 changes: 19 additions & 0 deletions crates/edr_generic/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//! A slightly more flexible chain specification for Ethereum Layer 1 chain.
mod eip2718;
mod receipt;
mod rpc;
mod spec;
mod transaction;

/// The chain specification for Ethereum Layer 1 that is a bit more lenient
/// and allows for more flexibility in contrast to
/// [`L1ChainSpec`](edr_eth::chain_spec::L1ChainSpec).
///
/// Specifically:
/// - it allows unknown transaction types (treates them as legacy
/// [`Eip155`](edr_eth::transaction::signed::Eip155) transactions)
/// - it allows remote blocks with missing `nonce` and `mix_hash` fields (**Not
/// implemented yet**)
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, alloy_rlp::RlpEncodable)]
pub struct GenericChainSpec;
3 changes: 3 additions & 0 deletions crates/edr_generic/src/receipt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub use edr_eth::receipt::BlockReceipt;

pub mod execution;
55 changes: 55 additions & 0 deletions crates/edr_generic/src/receipt/execution.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use revm_primitives::{EvmWiring, SpecId};

use edr_eth::{
log::ExecutionLog,
receipt::{
execution::{Eip658, Legacy},
Execution, ExecutionReceiptBuilder,
},
transaction::TransactionType,
};

use crate::eip2718::TypedEnvelope;
use crate::GenericChainSpec;

pub struct Builder;

impl ExecutionReceiptBuilder<GenericChainSpec> for Builder {
type Receipt = TypedEnvelope<Execution<ExecutionLog>>;

fn new_receipt_builder<StateT: revm::db::StateRef>(
_pre_execution_state: StateT,
_transaction: &<GenericChainSpec as EvmWiring>::Transaction,
) -> Result<Self, StateT::Error> {
Ok(Self)
}

fn build_receipt(
self,
header: &edr_eth::block::PartialHeader,
transaction: &crate::transaction::SignedFallbackToPostEip155,
result: &revm_primitives::ExecutionResult<GenericChainSpec>,
hardfork: SpecId,
) -> Self::Receipt {
let logs = result.logs().to_vec();
let logs_bloom = edr_eth::log::logs_to_bloom(&logs);

let receipt = if hardfork >= SpecId::BYZANTIUM {
Execution::Eip658(Eip658 {
status: result.is_success(),
cumulative_gas_used: header.gas_used,
logs_bloom,
logs,
})
} else {
Execution::Legacy(Legacy {
root: header.state_root,
cumulative_gas_used: header.gas_used,
logs_bloom,
logs,
})
};

TypedEnvelope::new(receipt, transaction.transaction_type())
}
}
17 changes: 17 additions & 0 deletions crates/edr_generic/src/rpc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use edr_rpc_eth::RpcSpec;
use serde::{de::DeserializeOwned, Serialize};

use crate::eip2718::TypedEnvelope;
use crate::GenericChainSpec;

pub mod receipt;
pub mod transaction;

impl RpcSpec for GenericChainSpec {
type ExecutionReceipt<Log> = TypedEnvelope<edr_eth::receipt::Execution<Log>>;
type RpcBlock<Data> = edr_rpc_eth::Block<Data> where Data: Default + DeserializeOwned + Serialize;
type RpcCallRequest = edr_rpc_eth::CallRequest;
type RpcReceipt = self::receipt::BlockReceipt;
type RpcTransaction = self::transaction::TransactionWithSignature;
type RpcTransactionRequest = edr_rpc_eth::TransactionRequest;
}
Loading

0 comments on commit aef02e2

Please sign in to comment.