feat: op-reth (#4377)

Co-authored-by: Roberto Bayardo <bayardo@alum.mit.edu>
Co-authored-by: refcell.eth <abigger87@gmail.com>
Co-authored-by: Roman Krasiuk <rokrassyuk@gmail.com>
Co-authored-by: refcell <refcell@oplabs.co>
Co-authored-by: nicolas <48695862+merklefruit@users.noreply.github.com>
This commit is contained in:
clabby
2023-11-05 18:33:42 +01:00
committed by GitHub
parent 390abf3a44
commit 52670a8b24
105 changed files with 33415 additions and 408 deletions

View File

@@ -18,12 +18,13 @@ concurrency:
jobs:
test:
name: test (${{ matrix.partition }}/${{ strategy.job-total }})
name: test (${{ matrix.network }} | ${{ matrix.partition }}/${{ strategy.job-total }})
runs-on:
group: Reth
strategy:
matrix:
partition: [1, 2]
network: ["ethereum", "optimism"]
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
@@ -34,10 +35,20 @@ jobs:
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
- name: Set binary value
id: binary
run: |
if [[ "${{ matrix.network }}" == "ethereum" ]]; then
echo "binary=reth" >> $GITHUB_OUTPUT
else
echo "binary=op-reth" >> $GITHUB_OUTPUT
fi
- name: Run tests
run: |
cargo nextest run \
--locked --all-features --workspace --exclude examples --exclude ef-tests \
--locked --features "${{ matrix.network }}" \
--bin ${{ steps.binary.outputs.binary }} \
--workspace --exclude examples --exclude ef-tests \
--partition hash:${{ matrix.partition }}/${{ strategy.job-total }} \
-E 'kind(test)'

View File

@@ -14,6 +14,13 @@ jobs:
name: clippy
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
matrix:
include:
- binary: "reth"
network: "ethereum"
- binary: "op-reth"
network: "optimism"
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@clippy
@@ -22,7 +29,7 @@ jobs:
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
- run: cargo clippy --workspace --all-targets --all-features
- run: cargo clippy --bin "${{ matrix.binary }}" --workspace --features "${{ matrix.network }}"
env:
RUSTFLAGS: -D warnings

View File

@@ -14,12 +14,15 @@ env:
jobs:
unused-dependencies:
runs-on: ubuntu-latest
strategy:
matrix:
network: ["ethereum", "optimism"]
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
- uses: taiki-e/install-action@cargo-udeps
- name: Check for unused dependencies
run: cargo udeps --all-features --all-targets
run: cargo udeps --features "jemalloc,${{ matrix.network }}"
- uses: JasonEtco/create-an-issue@v2
if: ${{ failure() }}
env:

View File

@@ -18,12 +18,13 @@ concurrency:
jobs:
test:
name: tests (${{ matrix.partition }}/${{ strategy.job-total }})
name: tests (${{ matrix.network }} | ${{ matrix.partition }}/${{ strategy.job-total }})
runs-on:
group: Reth
strategy:
matrix:
partition: [1, 2]
network: ["ethereum", "optimism"]
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
@@ -32,10 +33,20 @@ jobs:
with:
cache-on-failure: true
- uses: taiki-e/install-action@nextest
- name: Set binary value
id: binary
run: |
if [[ "${{ matrix.network }}" == "ethereum" ]]; then
echo "binary=reth" >> $GITHUB_OUTPUT
else
echo "binary=op-reth" >> $GITHUB_OUTPUT
fi
- name: Run tests
run: |
cargo nextest run \
--locked --all-features --workspace --exclude examples --exclude ef-tests \
--locked --features "${{ matrix.network }}" \
--bin ${{ steps.binary.outputs.binary }} \
--workspace --exclude examples --exclude ef-tests \
--partition hash:${{ matrix.partition }}/${{ strategy.job-total }} \
-E "kind(lib) | kind(bin) | kind(proc-macro)"
@@ -63,17 +74,21 @@ jobs:
- run: cargo nextest run --release -p ef-tests --features ef-tests
doc:
name: doc tests
name: doc tests (${{ matrix.network }})
runs-on:
group: Reth
timeout-minutes: 30
strategy:
matrix:
network: ["ethereum", "optimism"]
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
- run: cargo test --doc --workspace --all-features
- name: Run doctests
run: cargo test --doc --workspace --features "${{ matrix.network }}"
unit-success:
name: unit success

5
Cargo.lock generated
View File

@@ -5705,6 +5705,7 @@ name = "reth-beacon-consensus"
version = "0.1.0-alpha.10"
dependencies = [
"assert_matches",
"cfg-if",
"futures",
"metrics",
"reth-blockchain-tree",
@@ -5789,6 +5790,7 @@ name = "reth-consensus-common"
version = "0.1.0-alpha.10"
dependencies = [
"assert_matches",
"cfg-if",
"mockall",
"reth-interfaces",
"reth-primitives",
@@ -6232,7 +6234,6 @@ dependencies = [
"rand 0.8.5",
"rayon",
"reth-codecs",
"reth-primitives",
"reth-rpc-types",
"revm",
"revm-primitives",
@@ -6354,6 +6355,7 @@ dependencies = [
"pin-project",
"rand 0.8.5",
"rayon",
"reqwest",
"reth-consensus-common",
"reth-interfaces",
"reth-metrics",
@@ -6609,6 +6611,7 @@ dependencies = [
"reth-metrics",
"reth-primitives",
"reth-provider",
"reth-revm",
"reth-tasks",
"revm",
"serde",

View File

@@ -47,10 +47,20 @@ install: ## Build and install the reth binary under `~/.cargo/bin`.
--profile "$(PROFILE)" \
$(CARGO_INSTALL_EXTRA_FLAGS)
.PHONY: install-op
install-op: ## Build and install the op-reth binary under `~/.cargo/bin`.
cargo install --path bin/reth --bin op-reth --force --locked \
--features "optimism,$(FEATURES)" \
--profile "$(PROFILE)" \
$(CARGO_INSTALL_EXTRA_FLAGS)
# Builds the reth binary natively.
build-native-%:
cargo build --bin reth --target $* --features "$(FEATURES)" --profile "$(PROFILE)"
op-build-native-%:
cargo build --bin op-reth --target $* --features "optimism,$(FEATURES)" --profile "$(PROFILE)"
# The following commands use `cross` to build a cross-compile.
#
# These commands require that:
@@ -70,6 +80,10 @@ build-%:
RUSTFLAGS="-C link-arg=-lgcc -Clink-arg=-static-libgcc" \
cross build --bin reth --target $* --features "$(FEATURES)" --profile "$(PROFILE)"
op-build-%:
RUSTFLAGS="-C link-arg=-lgcc -Clink-arg=-static-libgcc" \
cross build --bin op-reth --target $* --features "optimism,$(FEATURES)" --profile "$(PROFILE)"
# Unfortunately we can't easily use cross to build for Darwin because of licensing issues.
# If we wanted to, we would need to build a custom Docker image with the SDK available.
#
@@ -105,7 +119,8 @@ build-release-tarballs: ## Create a series of `.tar.gz` files in the BIN_DIR dir
##@ Test
UNIT_TEST_ARGS := --locked --workspace --all-features -E 'kind(lib)' -E 'kind(bin)' -E 'kind(proc-macro)'
UNIT_TEST_ARGS := --locked --workspace --features 'jemalloc-prof' -E 'kind(lib)' -E 'kind(bin)' -E 'kind(proc-macro)'
UNIT_TEST_ARGS_OP := --locked --workspace --features 'jemalloc-prof,optimism' -E 'kind(lib)' -E 'kind(bin)' -E 'kind(proc-macro)'
COV_FILE := lcov.info
.PHONY: test-unit
@@ -113,11 +128,21 @@ test-unit: ## Run unit tests.
cargo install cargo-nextest --locked
cargo nextest run $(UNIT_TEST_ARGS)
.PHONY: test-unit-op
test-unit-op: ## Run unit tests (with optimism feature flag enabled).
cargo install cargo-nextest --locked
cargo nextest run $(UNIT_TEST_ARGS_OP)
.PHONY: cov-unit
cov-unit: ## Run unit tests with coverage.
rm -f $(COV_FILE)
cargo llvm-cov nextest --lcov --output-path $(COV_FILE) $(UNIT_TEST_ARGS)
.PHONY: cov-unit-op
cov-unit-op: ## Run unit tests with coverage (with optimism feature flag enabled).
rm -f $(COV_FILE)
cargo llvm-cov nextest --lcov --output-path $(COV_FILE) $(UNIT_TEST_ARGS_OP)
.PHONY: cov-report-html
cov-report-html: cov-unit ## Generate a HTML coverage report and open it in the browser.
cargo llvm-cov report --html

View File

@@ -119,6 +119,30 @@ min-warn-logs = ["tracing/release_max_level_warn"]
min-info-logs = ["tracing/release_max_level_info"]
min-debug-logs = ["tracing/release_max_level_debug"]
min-trace-logs = ["tracing/release_max_level_trace"]
optimism = [
"reth-primitives/optimism",
"reth-revm/optimism",
"reth-interfaces/optimism",
"reth-rpc/optimism",
"reth-rpc-engine-api/optimism",
"reth-transaction-pool/optimism",
"reth-provider/optimism",
"reth-beacon-consensus/optimism",
"reth-basic-payload-builder/optimism",
"reth-network/optimism",
"reth-network-api/optimism"
]
# no-op feature flag for switching between the `optimism` and default functionality in CI matrices
ethereum = []
[build-dependencies]
vergen = { version = "8.0.0", features = ["build", "cargo", "git", "gitcl"] }
[[bin]]
name = "reth"
path = "src/main.rs"
[[bin]]
name = "op-reth"
path = "src/optimism.rs"
required-features = ["optimism"]

View File

@@ -43,6 +43,12 @@ pub use dev_args::DevArgs;
mod pruning_args;
pub use pruning_args::PruningArgs;
/// RollupArgs for configuring the op-reth rollup
#[cfg(feature = "optimism")]
mod rollup_args;
#[cfg(feature = "optimism")]
pub use rollup_args::RollupArgs;
pub mod utils;
pub mod types;

View File

@@ -36,6 +36,18 @@ pub struct PayloadBuilderArgs {
/// Maximum number of tasks to spawn for building a payload.
#[arg(long = "builder.max-tasks", help_heading = "Builder", default_value = "3", value_parser = RangedU64ValueParser::<usize>::new().range(1..))]
pub max_payload_tasks: usize,
/// By default the pending block equals the latest block
/// to save resources and not leak txs from the tx-pool,
/// this flag enables computing of the pending block
/// from the tx-pool instead.
///
/// If `compute_pending_block` is not enabled, the payload builder
/// will use the payload attributes from the latest block. Note
/// that this flag is not yet functional.
#[cfg(feature = "optimism")]
#[arg(long = "rollup.compute-pending-block")]
pub compute_pending_block: bool,
}
impl PayloadBuilderConfig for PayloadBuilderArgs {
@@ -58,6 +70,11 @@ impl PayloadBuilderConfig for PayloadBuilderArgs {
fn max_payload_tasks(&self) -> usize {
self.max_payload_tasks
}
#[cfg(feature = "optimism")]
fn compute_pending_block(&self) -> bool {
self.compute_pending_block
}
}
#[derive(Clone, Debug, Default)]

View File

@@ -0,0 +1,19 @@
//! clap [Args](clap::Args) for op-reth rollup configuration
/// Parameters for rollup configuration
#[derive(Debug, clap::Args)]
#[command(next_help_heading = "Rollup")]
pub struct RollupArgs {
/// HTTP endpoint for the sequencer mempool
#[arg(long = "rollup.sequencer-http", value_name = "HTTP_URL")]
pub sequencer_http: Option<String>,
/// Disable transaction pool gossip
#[arg(long = "rollup.disable-tx-pool-gossip")]
pub disable_txpool_gossip: bool,
/// Enable walkback to genesis on startup. This is useful for re-validating the existing DB
/// prior to beginning normal syncing.
#[arg(long = "rollup.enable-genesis-walkback")]
pub enable_genesis_walkback: bool,
}

View File

@@ -12,6 +12,9 @@ use std::{
time::Duration,
};
#[cfg(feature = "optimism")]
use reth_primitives::{BASE_GOERLI, BASE_MAINNET};
/// Helper to parse a [Duration] from seconds
pub fn parse_duration_from_secs(arg: &str) -> eyre::Result<Duration, std::num::ParseIntError> {
let seconds = arg.parse()?;
@@ -27,6 +30,10 @@ pub fn chain_spec_value_parser(s: &str) -> eyre::Result<Arc<ChainSpec>, eyre::Er
"sepolia" => SEPOLIA.clone(),
"holesky" => HOLESKY.clone(),
"dev" => DEV.clone(),
#[cfg(feature = "optimism")]
"base-goerli" => BASE_GOERLI.clone(),
#[cfg(feature = "optimism")]
"base" => BASE_MAINNET.clone(),
_ => {
let raw = fs::read_to_string(PathBuf::from(shellexpand::full(s)?.into_owned()))?;
serde_json::from_str(&raw)?
@@ -46,6 +53,10 @@ pub fn genesis_value_parser(s: &str) -> eyre::Result<Arc<ChainSpec>, eyre::Error
"sepolia" => SEPOLIA.clone(),
"holesky" => HOLESKY.clone(),
"dev" => DEV.clone(),
#[cfg(feature = "optimism")]
"base-goerli" => BASE_GOERLI.clone(),
#[cfg(feature = "optimism")]
"base" => BASE_MAINNET.clone(),
_ => {
// try to read json from path first
let mut raw =
@@ -156,6 +167,15 @@ mod tests {
use secp256k1::rand::thread_rng;
use std::collections::HashMap;
#[cfg(feature = "optimism")]
#[test]
fn parse_optimism_chain_spec() {
for chain in ["base-goerli", "base"] {
chain_spec_value_parser(chain).unwrap();
genesis_value_parser(chain).unwrap();
}
}
#[test]
fn parse_known_chain_spec() {
for chain in ["mainnet", "sepolia", "goerli", "holesky"] {

View File

@@ -97,4 +97,8 @@ pub trait PayloadBuilderConfig {
/// Maximum number of tasks to spawn for building a payload.
fn max_payload_tasks(&self) -> usize;
/// Returns whether or not to construct the pending block.
#[cfg(feature = "optimism")]
fn compute_pending_block(&self) -> bool;
}

View File

@@ -101,17 +101,33 @@ pub trait RethNodeCommandConfig: fmt::Debug {
Conf: PayloadBuilderConfig,
Reth: RethNodeComponents,
{
let payload_generator = BasicPayloadJobGenerator::new(
let payload_job_config = BasicPayloadJobGeneratorConfig::default()
.interval(conf.interval())
.deadline(conf.deadline())
.max_payload_tasks(conf.max_payload_tasks())
.extradata(conf.extradata_rlp_bytes())
.max_gas_limit(conf.max_gas_limit());
#[cfg(feature = "optimism")]
let payload_job_config =
payload_job_config.compute_pending_block(conf.compute_pending_block());
// The default payload builder is implemented on the unit type.
#[cfg(not(feature = "optimism"))]
#[allow(clippy::let_unit_value)]
let payload_builder = ();
// Optimism's payload builder is implemented on the OptimismPayloadBuilder type.
#[cfg(feature = "optimism")]
let payload_builder = reth_basic_payload_builder::OptimismPayloadBuilder;
let payload_generator = BasicPayloadJobGenerator::with_builder(
components.provider(),
components.pool(),
components.task_executor(),
BasicPayloadJobGeneratorConfig::default()
.interval(conf.interval())
.deadline(conf.deadline())
.max_payload_tasks(conf.max_payload_tasks())
.extradata(conf.extradata_rlp_bytes())
.max_gas_limit(conf.max_gas_limit()),
payload_job_config,
components.chain_spec(),
payload_builder,
);
let (payload_service, payload_builder) = PayloadBuilderService::new(payload_generator);

View File

@@ -245,12 +245,17 @@ impl Command {
suggested_fee_recipient: self.suggested_fee_recipient,
// TODO: add support for withdrawals
withdrawals: None,
#[cfg(feature = "optimism")]
optimism_payload_attributes: reth_rpc_types::engine::OptimismPayloadAttributes::default(
),
};
let payload_config = PayloadConfig::new(
Arc::clone(&best_block),
Bytes::default(),
PayloadBuilderAttributes::new(best_block.hash, payload_attrs),
PayloadBuilderAttributes::try_new(best_block.hash, payload_attrs)?,
self.chain.clone(),
#[cfg(feature = "optimism")]
true,
);
let args = BuildArguments::new(
blockchain_db.clone(),

View File

@@ -15,6 +15,9 @@
//! calls to the logging component is made.
//! - `min-debug-logs`: Disables all logs below `debug` level.
//! - `min-trace-logs`: Disables all logs below `trace` level.
//! - `optimism`: Enables [OP-Stack](https://stack.optimism.io/) support for the node. Note that
//! this breaks compatibility with the Ethereum mainnet as a new deposit transaction type is
//! introduced as well as gas cost changes.
#![doc(
html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png",

View File

@@ -3,10 +3,11 @@
#[global_allocator]
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
fn main() {
// ensure we have the correct features enabled
reth::primitives::ensure_ethereum!();
#[cfg(feature = "optimism")]
compile_error!("Cannot build the `reth` binary with the `optimism` feature flag enabled. Did you mean to build `op-reth`?");
#[cfg(not(feature = "optimism"))]
fn main() {
if let Err(err) = reth::cli::run() {
eprintln!("Error: {err:?}");
std::process::exit(1);

View File

@@ -186,6 +186,11 @@ pub struct NodeCommand<Ext: RethCliExt = ()> {
#[clap(flatten)]
pub pruning: PruningArgs,
/// Rollup related arguments
#[cfg(feature = "optimism")]
#[clap(flatten)]
pub rollup: crate::args::RollupArgs,
/// Additional cli arguments
#[clap(flatten)]
pub ext: Ext::Node,
@@ -209,6 +214,8 @@ impl<Ext: RethCliExt> NodeCommand<Ext> {
db,
dev,
pruning,
#[cfg(feature = "optimism")]
rollup,
..
} = self;
NodeCommand {
@@ -226,6 +233,8 @@ impl<Ext: RethCliExt> NodeCommand<Ext> {
db,
dev,
pruning,
#[cfg(feature = "optimism")]
rollup,
ext,
}
}
@@ -540,6 +549,27 @@ impl<Ext: RethCliExt> NodeCommand<Ext> {
self.ext.on_node_started(&components)?;
// If `enable_genesis_walkback` is set to true, the rollup client will need to
// perform the derivation pipeline from genesis, validating the data dir.
// When set to false, set the finalized, safe, and unsafe head block hashes
// on the rollup client using a fork choice update. This prevents the rollup
// client from performing the derivation pipeline from genesis, and instead
// starts syncing from the current tip in the DB.
#[cfg(feature = "optimism")]
if self.chain.is_optimism() && !self.rollup.enable_genesis_walkback {
let client = _rpc_server_handles.auth.http_client();
reth_rpc_api::EngineApiClient::fork_choice_updated_v2(
&client,
reth_rpc_types::engine::ForkchoiceState {
head_block_hash: head.hash,
safe_block_hash: head.hash,
finalized_block_hash: head.hash,
},
None,
)
.await?;
}
rx.await??;
info!(target: "reth::cli", "Consensus engine has exited.");
@@ -785,7 +815,8 @@ impl<Ext: RethCliExt> NodeCommand<Ext> {
secret_key: SecretKey,
default_peers_path: PathBuf,
) -> NetworkConfig<ProviderFactory<Arc<DatabaseEnv>>> {
self.network
let cfg_builder = self
.network
.network_config(config, self.chain.clone(), secret_key, default_peers_path)
.with_task_executor(Box::new(executor))
.set_head(head)
@@ -798,8 +829,17 @@ impl<Ext: RethCliExt> NodeCommand<Ext> {
self.network.addr,
// set discovery port based on instance number
self.network.port + self.instance - 1,
)))
.build(ProviderFactory::new(db, self.chain.clone()))
)));
// When `sequencer_endpoint` is configured, the node will forward all transactions to a
// Sequencer node for execution and inclusion on L1, and disable its own txpool
// gossip to prevent other parties in the network from learning about them.
#[cfg(feature = "optimism")]
let cfg_builder = cfg_builder
.sequencer_endpoint(self.rollup.sequencer_http.clone())
.disable_tx_gossip(self.rollup.disable_txpool_gossip);
cfg_builder.build(ProviderFactory::new(db, self.chain.clone()))
}
#[allow(clippy::too_many_arguments)]

15
bin/reth/src/optimism.rs Normal file
View File

@@ -0,0 +1,15 @@
// We use jemalloc for performance reasons
#[cfg(all(feature = "jemalloc", unix))]
#[global_allocator]
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
#[cfg(not(feature = "optimism"))]
compile_error!("Cannot build the `op-reth` binary with the `optimism` feature flag disabled. Did you mean to build `reth`?");
#[cfg(feature = "optimism")]
fn main() {
if let Err(err) = reth::cli::run() {
eprintln!("Error: {err:?}");
std::process::exit(1);
}
}

View File

@@ -35,6 +35,7 @@ metrics.workspace = true
tracing.workspace = true
thiserror.workspace = true
schnellru = "0.2"
cfg-if = "1.0.0"
[dev-dependencies]
# reth
@@ -49,3 +50,12 @@ reth-revm = { path = "../../revm" }
reth-downloaders = { path = "../../net/downloaders" }
assert_matches.workspace = true
[features]
optimism = [
"reth-consensus-common/optimism",
"reth-primitives/optimism",
"reth-interfaces/optimism",
"reth-provider/optimism",
"reth-rpc-types/optimism",
]

View File

@@ -666,12 +666,40 @@ where
Ok(outcome) => {
match outcome {
CanonicalOutcome::AlreadyCanonical { ref header } => {
debug!(
target: "consensus::engine",
fcu_head_num=?header.number,
current_head_num=?self.blockchain.canonical_tip().number,
"Ignoring beacon update to old head"
);
// On Optimism, the proposers are allowed to reorg their own chain at will.
cfg_if::cfg_if! {
if #[cfg(feature = "optimism")] {
if self.chain_spec().is_optimism() {
debug!(
target: "consensus::engine",
fcu_head_num=?header.number,
current_head_num=?self.blockchain.canonical_tip().number,
"[Optimism] Allowing beacon reorg to old head"
);
let _ = self.update_head(header.clone());
self.listeners.notify(
BeaconConsensusEngineEvent::CanonicalChainCommitted(
header.clone(),
elapsed,
),
);
} else {
debug!(
target: "consensus::engine",
fcu_head_num=?header.number,
current_head_num=?self.blockchain.canonical_tip().number,
"Ignoring beacon update to old head"
);
}
} else {
debug!(
target: "consensus::engine",
fcu_head_num=?header.number,
current_head_num=?self.blockchain.canonical_tip().number,
"Ignoring beacon update to old head"
);
}
}
}
CanonicalOutcome::Committed { ref head } => {
debug!(
@@ -1045,27 +1073,30 @@ where
// forkchoiceState.headBlockHash and identified via buildProcessId value if
// payloadAttributes is not null and the forkchoice state has been updated successfully.
// The build process is specified in the Payload building section.
let attributes = PayloadBuilderAttributes::new(state.head_block_hash, attrs);
match PayloadBuilderAttributes::try_new(state.head_block_hash, attrs) {
Ok(attributes) => {
// send the payload to the builder and return the receiver for the pending payload
// id, initiating payload job is handled asynchronously
let pending_payload_id = self.payload_builder.send_new_payload(attributes);
// send the payload to the builder and return the receiver for the pending payload id,
// initiating payload job is handled asynchronously
let pending_payload_id = self.payload_builder.send_new_payload(attributes);
// Client software MUST respond to this method call in the following way:
// {
// payloadStatus: {
// status: VALID,
// latestValidHash: forkchoiceState.headBlockHash,
// validationError: null
// },
// payloadId: buildProcessId
// }
//
// if the payload is deemed VALID and the build process has begun.
OnForkChoiceUpdated::updated_with_pending_payload_id(
PayloadStatus::new(PayloadStatusEnum::Valid, Some(state.head_block_hash)),
pending_payload_id,
)
// Client software MUST respond to this method call in the following way:
// {
// payloadStatus: {
// status: VALID,
// latestValidHash: forkchoiceState.headBlockHash,
// validationError: null
// },
// payloadId: buildProcessId
// }
//
// if the payload is deemed VALID and the build process has begun.
OnForkChoiceUpdated::updated_with_pending_payload_id(
PayloadStatus::new(PayloadStatusEnum::Valid, Some(state.head_block_hash)),
pending_payload_id,
)
}
Err(_) => OnForkChoiceUpdated::invalid_payload_attributes(),
}
}
/// When the Consensus layer receives a new block via the consensus gossip protocol,

View File

@@ -23,7 +23,7 @@ use reth_interfaces::{
test_utils::{NoopFullBlockClient, TestConsensus},
};
use reth_payload_builder::test_utils::spawn_test_payload_service;
use reth_primitives::{BlockNumber, ChainSpec, PruneModes, B256, U256};
use reth_primitives::{BlockNumber, ChainSpec, PruneModes, Receipt, B256, U256};
use reth_provider::{
providers::BlockchainProvider, test_utils::TestExecutorFactory, BlockExecutor,
BundleStateWithReceipts, ExecutorFactory, ProviderFactory, PrunableBlockExecutor,
@@ -210,6 +210,22 @@ where
}
}
fn execute_transactions(
&mut self,
block: &reth_primitives::Block,
total_difficulty: U256,
senders: Option<Vec<reth_primitives::Address>>,
) -> Result<(Vec<Receipt>, u64), BlockExecutionError> {
match self {
EitherBlockExecutor::Left(a) => {
a.execute_transactions(block, total_difficulty, senders)
}
EitherBlockExecutor::Right(b) => {
b.execute_transactions(block, total_difficulty, senders)
}
}
}
fn take_output_state(&mut self) -> BundleStateWithReceipts {
match self {
EitherBlockExecutor::Left(a) => a.take_output_state(),

View File

@@ -13,8 +13,14 @@ reth-primitives.workspace = true
reth-interfaces.workspace = true
reth-provider.workspace = true
# misc
cfg-if = "1.0.0"
[dev-dependencies]
reth-interfaces = { workspace = true, features = ["test-utils"] }
reth-provider = { workspace = true, features = ["test-utils"] }
assert_matches.workspace = true
mockall = "0.11.3"
[features]
optimism = ["reth-primitives/optimism"]

View File

@@ -32,9 +32,10 @@ pub fn validate_header_standalone(
return Err(ConsensusError::BaseFeeMissing)
}
let wd_root_missing = header.withdrawals_root.is_none() && !chain_spec.is_optimism();
// EIP-4895: Beacon chain push withdrawals as operations
if chain_spec.fork(Hardfork::Shanghai).active_at_timestamp(header.timestamp) &&
header.withdrawals_root.is_none()
if chain_spec.fork(Hardfork::Shanghai).active_at_timestamp(header.timestamp) && wd_root_missing
{
return Err(ConsensusError::WithdrawalsRootMissing)
} else if !chain_spec.fork(Hardfork::Shanghai).active_at_timestamp(header.timestamp) &&
@@ -116,6 +117,8 @@ pub fn validate_transaction_regarding_header(
Some(*chain_id)
}
#[cfg(feature = "optimism")]
Transaction::Deposit(_) => None,
};
if let Some(chain_id) = chain_id {
if chain_id != chain_spec.chain().id() {
@@ -248,6 +251,40 @@ pub fn validate_block_standalone(
Ok(())
}
// Check gas limit, max diff between child/parent gas_limit should be max_diff=parent_gas/1024
// On Optimism, the gas limit can adjust instantly, so we skip this check if the optimism
// flag is enabled in the chain spec.
#[inline(always)]
fn check_gas_limit(
parent: &SealedHeader,
child: &SealedHeader,
chain_spec: &ChainSpec,
) -> Result<(), ConsensusError> {
let mut parent_gas_limit = parent.gas_limit;
// By consensus, gas_limit is multiplied by elasticity (*2) on
// on exact block that hardfork happens.
if chain_spec.fork(Hardfork::London).transitions_at_block(child.number) {
parent_gas_limit = parent.gas_limit * chain_spec.base_fee_params.elasticity_multiplier;
}
if child.gas_limit > parent_gas_limit {
if child.gas_limit - parent_gas_limit >= parent_gas_limit / 1024 {
return Err(ConsensusError::GasLimitInvalidIncrease {
parent_gas_limit,
child_gas_limit: child.gas_limit,
})
}
} else if parent_gas_limit - child.gas_limit >= parent_gas_limit / 1024 {
return Err(ConsensusError::GasLimitInvalidDecrease {
parent_gas_limit,
child_gas_limit: child.gas_limit,
})
}
Ok(())
}
/// Validate block in regards to parent
pub fn validate_header_regarding_parent(
parent: &SealedHeader,
@@ -280,27 +317,16 @@ pub fn validate_header_regarding_parent(
// TODO Check difficulty increment between parent and child
// Ace age did increment it by some formula that we need to follow.
let mut parent_gas_limit = parent.gas_limit;
// By consensus, gas_limit is multiplied by elasticity (*2) on
// on exact block that hardfork happens.
if chain_spec.fork(Hardfork::London).transitions_at_block(child.number) {
parent_gas_limit = parent.gas_limit * chain_spec.base_fee_params.elasticity_multiplier;
}
// Check gas limit, max diff between child/parent gas_limit should be max_diff=parent_gas/1024
if child.gas_limit > parent_gas_limit {
if child.gas_limit - parent_gas_limit >= parent_gas_limit / 1024 {
return Err(ConsensusError::GasLimitInvalidIncrease {
parent_gas_limit,
child_gas_limit: child.gas_limit,
})
cfg_if::cfg_if! {
if #[cfg(feature = "optimism")] {
// On Optimism, the gas limit can adjust instantly, so we skip this check
// if the optimism feature is enabled in the chain spec.
if !chain_spec.is_optimism() {
check_gas_limit(parent, child, chain_spec)?;
}
} else {
check_gas_limit(parent, child, chain_spec)?;
}
} else if parent_gas_limit - child.gas_limit >= parent_gas_limit / 1024 {
return Err(ConsensusError::GasLimitInvalidDecrease {
parent_gas_limit,
child_gas_limit: child.gas_limit,
})
}
// EIP-1559 check base fee

View File

@@ -50,3 +50,4 @@ secp256k1 = { workspace = true, features = ["alloc", "recovery", "rand"] }
[features]
test-utils = ["tokio-stream/sync", "secp256k1", "rand/std_rng"]
cli = ["clap"]
optimism = ["reth-eth-wire/optimism"]

View File

@@ -243,6 +243,8 @@ impl InsertBlockErrorKind {
BlockExecutionError::CanonicalCommit { .. } |
BlockExecutionError::AppendChainDoesntConnect { .. } |
BlockExecutionError::UnavailableForTest => false,
#[cfg(feature = "optimism")]
BlockExecutionError::OptimismBlockExecution(_) => false,
}
}
InsertBlockErrorKind::Tree(err) => {

View File

@@ -128,6 +128,23 @@ pub enum BlockExecutionError {
/// Note: this is not feature gated for convenience.
#[error("execution unavailable for tests")]
UnavailableForTest,
/// Optimism Block Executor Errors
#[cfg(feature = "optimism")]
#[error(transparent)]
OptimismBlockExecution(#[from] OptimismBlockExecutionError),
}
/// Optimism Block Executor Errors
#[cfg(feature = "optimism")]
#[derive(Error, Debug, Clone, PartialEq, Eq)]
pub enum OptimismBlockExecutionError {
/// Error when trying to parse L1 block info
#[error("Could not get L1 block info from L2 block: {message:?}")]
L1BlockInfoError {
/// The inner error message
message: String,
},
}
impl BlockExecutionError {

View File

@@ -366,6 +366,8 @@ pub fn random_receipt<R: Rng>(
} else {
vec![]
},
#[cfg(feature = "optimism")]
deposit_nonce: None,
}
}

View File

@@ -55,6 +55,7 @@ proptest-derive.workspace = true
default = ["serde"]
serde = ["dep:serde"]
arbitrary = ["reth-primitives/arbitrary", "dep:arbitrary", "dep:proptest", "dep:proptest-derive"]
optimism = ["reth-primitives/optimism"]
[[test]]
name = "fuzz_roundtrip"

View File

@@ -48,6 +48,8 @@ mod test {
success: false,
cumulative_gas_used: 0,
logs: vec![],
#[cfg(feature = "optimism")]
deposit_nonce: None,
},
bloom: Default::default(),
}]]);
@@ -101,10 +103,10 @@ mod test {
let mut data = vec![];
let request = RequestPair::<Receipts> {
request_id: 1111,
message: Receipts(vec![
vec![
ReceiptWithBloom {
receipt: Receipt {tx_type: TxType::Legacy,
message: Receipts(vec![vec![
ReceiptWithBloom {
receipt: Receipt {
tx_type: TxType::Legacy,
cumulative_gas_used: 0x1u64,
logs: vec![
Log {
@@ -117,10 +119,12 @@ mod test {
},
],
success: false,
},bloom: hex!("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").into(),
}
],
]),
#[cfg(feature = "optimism")]
deposit_nonce: None,
},
bloom: hex!("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").into(),
},
]]),
};
request.encode(&mut data);
assert_eq!(data, expected);
@@ -152,6 +156,8 @@ mod test {
},
],
success: false,
#[cfg(feature = "optimism")]
deposit_nonce: None,
},
bloom: hex!("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").into(),
},

View File

@@ -26,3 +26,4 @@ tokio = { workspace = true, features = ["sync"] }
[features]
default = ["serde"]
serde = ["dep:serde"]
optimism = []

View File

@@ -50,6 +50,10 @@ pub trait NetworkInfo: Send + Sync {
/// Returns `true` when the node is undergoing the very first Pipeline sync.
fn is_initially_syncing(&self) -> bool;
/// Returns the sequencer HTTP endpoint, if set.
#[cfg(feature = "optimism")]
fn sequencer_endpoint(&self) -> Option<&str>;
}
/// Provides general purpose information about Peers in the network.

View File

@@ -51,6 +51,11 @@ impl NetworkInfo for NoopNetwork {
fn is_initially_syncing(&self) -> bool {
false
}
#[cfg(feature = "optimism")]
fn sequencer_endpoint(&self) -> Option<&str> {
None
}
}
impl PeersInfo for NoopNetwork {

View File

@@ -96,3 +96,4 @@ default = ["serde"]
serde = ["dep:serde", "dep:humantime-serde", "secp256k1/serde", "enr?/serde", "dep:serde_json"]
test-utils = ["reth-provider/test-utils", "dep:enr", "dep:tempfile"]
geth-tests = []
optimism = ["reth-primitives/optimism", "reth-transaction-pool/optimism", "reth-provider/optimism", "reth-network-api/optimism", "reth-rpc-types/optimism"]

View File

@@ -69,6 +69,19 @@ pub struct NetworkConfig<C> {
pub status: Status,
/// Sets the hello message for the p2p handshake in RLPx
pub hello_message: HelloMessage,
/// Whether to disable transaction gossip
pub tx_gossip_disabled: bool,
/// Optimism Network Config
#[cfg(feature = "optimism")]
pub optimism_network_config: OptimismNetworkConfig,
}
/// Optimmism Network Config
#[cfg(feature = "optimism")]
#[derive(Debug, Clone, Default)]
pub struct OptimismNetworkConfig {
/// The sequencer HTTP endpoint, if provided via CLI flag
pub sequencer_endpoint: Option<String>,
}
// === impl NetworkConfig ===
@@ -148,6 +161,20 @@ pub struct NetworkConfigBuilder {
hello_message: Option<HelloMessage>,
/// Head used to start set for the fork filter and status.
head: Option<Head>,
/// Whether tx gossip is disabled
tx_gossip_disabled: bool,
/// Optimism Network Config Builder
#[cfg(feature = "optimism")]
optimism_network_config: OptimismNetworkConfigBuilder,
}
/// Optimism Network Config Builder
#[cfg(feature = "optimism")]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Default)]
pub struct OptimismNetworkConfigBuilder {
/// The sequencer HTTP endpoint, if provided via CLI flag
sequencer_endpoint: Option<String>,
}
// === impl NetworkConfigBuilder ===
@@ -169,6 +196,9 @@ impl NetworkConfigBuilder {
executor: None,
hello_message: None,
head: None,
tx_gossip_disabled: false,
#[cfg(feature = "optimism")]
optimism_network_config: OptimismNetworkConfigBuilder::default(),
}
}
@@ -347,6 +377,19 @@ impl NetworkConfigBuilder {
}
}
/// Sets whether tx gossip is disabled.
pub fn disable_tx_gossip(mut self, disable_tx_gossip: bool) -> Self {
self.tx_gossip_disabled = disable_tx_gossip;
self
}
/// Sets the sequencer HTTP endpoint.
#[cfg(feature = "optimism")]
pub fn sequencer_endpoint(mut self, endpoint: Option<String>) -> Self {
self.optimism_network_config.sequencer_endpoint = endpoint;
self
}
/// Consumes the type and creates the actual [`NetworkConfig`]
/// for the given client type that can interact with the chain.
///
@@ -369,6 +412,9 @@ impl NetworkConfigBuilder {
executor,
hello_message,
head,
tx_gossip_disabled,
#[cfg(feature = "optimism")]
optimism_network_config: OptimismNetworkConfigBuilder { sequencer_endpoint },
} = self;
let listener_addr = listener_addr.unwrap_or(DEFAULT_DISCOVERY_ADDRESS);
@@ -419,6 +465,9 @@ impl NetworkConfigBuilder {
status,
hello_message,
fork_filter,
tx_gossip_disabled,
#[cfg(feature = "optimism")]
optimism_network_config: OptimismNetworkConfig { sequencer_endpoint },
}
}
}

View File

@@ -182,7 +182,9 @@ where
status,
fork_filter,
dns_discovery_config,
..
tx_gossip_disabled,
#[cfg(feature = "optimism")]
optimism_network_config: crate::config::OptimismNetworkConfig { sequencer_endpoint },
} = config;
let peers_manager = PeersManager::new(peers_config);
@@ -240,6 +242,9 @@ where
network_mode,
bandwidth_meter,
Arc::new(AtomicU64::new(chain_spec.chain.id())),
tx_gossip_disabled,
#[cfg(feature = "optimism")]
sequencer_endpoint,
);
Ok(Self {

View File

@@ -46,6 +46,8 @@ impl NetworkHandle {
network_mode: NetworkMode,
bandwidth_meter: BandwidthMeter,
chain_id: Arc<AtomicU64>,
tx_gossip_disabled: bool,
#[cfg(feature = "optimism")] sequencer_endpoint: Option<String>,
) -> Self {
let inner = NetworkInner {
num_active_peers,
@@ -58,6 +60,9 @@ impl NetworkHandle {
is_syncing: Arc::new(AtomicBool::new(false)),
initial_sync_done: Arc::new(AtomicBool::new(false)),
chain_id,
tx_gossip_disabled,
#[cfg(feature = "optimism")]
sequencer_endpoint,
};
Self { inner: Arc::new(inner) }
}
@@ -171,6 +176,11 @@ impl NetworkHandle {
self.send_message(NetworkHandleMessage::Shutdown(tx));
rx.await
}
/// Whether tx gossip is disabled
pub fn tx_gossip_disabled(&self) -> bool {
self.inner.tx_gossip_disabled
}
}
// === API Implementations ===
@@ -264,6 +274,11 @@ impl NetworkInfo for NetworkHandle {
fn is_initially_syncing(&self) -> bool {
SyncStateProvider::is_initially_syncing(self)
}
#[cfg(feature = "optimism")]
fn sequencer_endpoint(&self) -> Option<&str> {
self.inner.sequencer_endpoint.as_deref()
}
}
impl SyncStateProvider for NetworkHandle {
@@ -317,6 +332,11 @@ struct NetworkInner {
initial_sync_done: Arc<AtomicBool>,
/// The chain id
chain_id: Arc<AtomicU64>,
/// Whether to disable transaction gossip
tx_gossip_disabled: bool,
/// The sequencer HTTP Endpoint
#[cfg(feature = "optimism")]
sequencer_endpoint: Option<String>,
}
/// Internal messages that can be passed to the [`NetworkManager`](crate::NetworkManager).

View File

@@ -12,7 +12,9 @@ use pin_project::pin_project;
use reth_eth_wire::{capability::Capability, DisconnectReason, HelloBuilder};
use reth_network_api::{NetworkInfo, Peers};
use reth_primitives::{PeerId, MAINNET};
use reth_provider::{test_utils::NoopProvider, BlockReader, HeaderProvider, StateProviderFactory};
use reth_provider::{
test_utils::NoopProvider, BlockReader, BlockReaderIdExt, HeaderProvider, StateProviderFactory,
};
use reth_tasks::TokioTaskExecutor;
use reth_transaction_pool::{
blobstore::InMemoryBlobStore,
@@ -156,7 +158,7 @@ where
impl<C, Pool> Testnet<C, Pool>
where
C: StateProviderFactory + BlockReader + HeaderProvider + Clone + 'static,
C: StateProviderFactory + BlockReaderIdExt + HeaderProvider + Clone + 'static,
Pool: TransactionPool,
{
/// Installs an eth pool on each peer

View File

@@ -247,6 +247,10 @@ where
response: oneshot::Sender<RequestResult<PooledTransactions>>,
) {
if let Some(peer) = self.peers.get_mut(&peer_id) {
if self.network.tx_gossip_disabled() {
let _ = response.send(Ok(PooledTransactions::default()));
return
}
let transactions = self
.pool
.get_pooled_transaction_elements(request.0, GET_POOLED_TRANSACTION_SOFT_LIMIT_SIZE);
@@ -276,6 +280,9 @@ where
if self.network.is_initially_syncing() {
return
}
if self.network.tx_gossip_disabled() {
return
}
trace!(target: "net::tx", num_hashes=?hashes.len(), "Start propagating transactions");
@@ -300,6 +307,9 @@ where
to_propagate: Vec<PropagateTransaction>,
) -> PropagatedTransactions {
let mut propagated = PropagatedTransactions::default();
if self.network.tx_gossip_disabled() {
return propagated
}
// send full transactions to a fraction fo the connected peers (square root of the total
// number of connected peers)
@@ -484,6 +494,9 @@ where
if self.network.is_initially_syncing() {
return
}
if self.network.tx_gossip_disabled() {
return
}
let mut num_already_seen = 0;
@@ -620,6 +633,9 @@ where
// `NEW_POOLED_TRANSACTION_HASHES_SOFT_LIMIT` transactions in the
// pool
if !self.network.is_initially_syncing() {
if self.network.tx_gossip_disabled() {
return
}
let peer = self.peers.get_mut(&peer_id).expect("is present; qed");
let mut msg_builder = PooledTransactionsHashesBuilder::new(version);
@@ -655,6 +671,9 @@ where
if self.network.is_initially_syncing() {
return
}
if self.network.tx_gossip_disabled() {
return
}
// tracks the quality of the given transactions
let mut has_bad_transactions = false;

View File

@@ -5,6 +5,7 @@ mod geth;
mod requests;
mod session;
mod startup;
#[cfg(not(feature = "optimism"))]
mod txgossip;
fn main() {}

View File

@@ -33,3 +33,12 @@ metrics.workspace = true
## misc
tracing.workspace = true
[features]
optimism = [
"reth-primitives/optimism",
"reth-revm/optimism",
"reth-transaction-pool/optimism",
"reth-provider/optimism",
"reth-payload-builder/optimism"
]

View File

@@ -57,6 +57,11 @@ use tracing::{debug, trace};
mod metrics;
#[cfg(feature = "optimism")]
mod optimism;
#[cfg(feature = "optimism")]
pub use optimism::OptimismPayloadBuilder;
/// The [`PayloadJobGenerator`] that creates [`BasicPayloadJob`]s.
#[derive(Debug)]
pub struct BasicPayloadJobGenerator<Client, Pool, Tasks, Builder = ()> {
@@ -152,6 +157,8 @@ where
self.config.extradata.clone(),
attributes,
Arc::clone(&self.chain_spec),
#[cfg(feature = "optimism")]
self.config.compute_pending_block,
);
let until = tokio::time::Instant::now() + self.config.deadline;
@@ -199,6 +206,9 @@ pub struct BasicPayloadJobGeneratorConfig {
deadline: Duration,
/// Maximum number of tasks to spawn for building a payload.
max_payload_tasks: usize,
/// The rollup's compute pending block configuration option.
#[cfg(feature = "optimism")]
compute_pending_block: bool,
}
// === impl BasicPayloadJobGeneratorConfig ===
@@ -242,6 +252,15 @@ impl BasicPayloadJobGeneratorConfig {
self.max_gas_limit = max_gas_limit;
self
}
/// Sets the compute pending block configuration option.
///
/// Defaults to `false`.
#[cfg(feature = "optimism")]
pub fn compute_pending_block(mut self, compute_pending_block: bool) -> Self {
self.compute_pending_block = compute_pending_block;
self
}
}
impl Default for BasicPayloadJobGeneratorConfig {
@@ -255,6 +274,8 @@ impl Default for BasicPayloadJobGeneratorConfig {
// 12s slot time
deadline: SLOT_DURATION,
max_payload_tasks: 3,
#[cfg(feature = "optimism")]
compute_pending_block: false,
}
}
}
@@ -341,6 +362,7 @@ where
let result = builder.try_build(args);
let _ = tx.send(result);
}));
this.pending_block = Some(PendingPayload { _cancel, payload: rx });
}
}
@@ -425,6 +447,39 @@ where
let _ = tx.send(res);
}));
// In Optimism, the PayloadAttributes can specify a `no_tx_pool` option that implies we
// should not pull transactions from the tx pool. In this case, we build the payload
// upfront with the list of transactions sent in the attributes without caring about
// the results of the polling job, if a best payload has not already been built.
#[cfg(feature = "optimism")]
if self.config.chain_spec.is_optimism() &&
self.config.attributes.optimism_payload_attributes.no_tx_pool
{
let args = BuildArguments {
client: self.client.clone(),
pool: self.pool.clone(),
cached_reads: self.cached_reads.take().unwrap_or_default(),
config: self.config.clone(),
cancel: Cancelled::default(),
best_payload: None,
};
if let Ok(BuildOutcome::Better { payload, cached_reads }) =
self.builder.try_build(args)
{
self.cached_reads = Some(cached_reads);
trace!(target: "payload_builder", "[OPTIMISM] Forced best payload");
let payload = Arc::new(payload);
return (
ResolveBestPayload {
best_payload: Some(payload),
maybe_better,
empty_payload,
},
KeepPayloadJobAlive::Yes,
)
}
}
empty_payload = Some(rx);
}
@@ -539,6 +594,22 @@ pub struct PayloadConfig {
attributes: PayloadBuilderAttributes,
/// The chain spec.
chain_spec: Arc<ChainSpec>,
/// The rollup's compute pending block configuration option.
/// TODO(clabby): Implement this feature.
#[cfg(feature = "optimism")]
#[allow(dead_code)]
compute_pending_block: bool,
}
impl PayloadConfig {
/// Returns an owned instance of the [PayloadConfig]'s extra_data bytes.
pub(crate) fn extra_data(&self) -> reth_primitives::Bytes {
#[cfg(feature = "optimism")]
if self.chain_spec.is_optimism() {
return Default::default()
}
self.extra_data.clone()
}
}
impl PayloadConfig {
@@ -548,6 +619,7 @@ impl PayloadConfig {
extra_data: Bytes,
attributes: PayloadBuilderAttributes,
chain_spec: Arc<ChainSpec>,
#[cfg(feature = "optimism")] compute_pending_block: bool,
) -> Self {
// configure evm env based on parent block
let (initialized_cfg, initialized_block_env) =
@@ -560,6 +632,8 @@ impl PayloadConfig {
extra_data,
attributes,
chain_spec,
#[cfg(feature = "optimism")]
compute_pending_block,
}
}
}
@@ -674,13 +748,14 @@ where
let state = StateProviderDatabase::new(&state_provider);
let mut db =
State::builder().with_database_ref(cached_reads.as_db(&state)).with_bundle_update().build();
let extra_data = config.extra_data();
let PayloadConfig {
initialized_block_env,
initialized_cfg,
parent_block,
extra_data,
attributes,
chain_spec,
..
} = config;
debug!(target: "payload_builder", parent_hash = ?parent_block.hash, parent_number = parent_block.number, "building new payload");
@@ -799,6 +874,8 @@ where
success: result.is_success(),
cumulative_gas_used,
logs: result.logs().into_iter().map(into_reth_log).collect(),
#[cfg(feature = "optimism")]
deposit_nonce: None,
}));
// update add to total fees
@@ -908,13 +985,14 @@ fn build_empty_payload<Client>(
where
Client: StateProviderFactory,
{
let extra_data = config.extra_data();
let PayloadConfig {
initialized_block_env,
parent_block,
extra_data,
attributes,
chain_spec,
initialized_cfg,
..
} = config;
debug!(target: "payload_builder", parent_hash = ?parent_block.hash, parent_number = parent_block.number, "building empty payload");
@@ -968,9 +1046,9 @@ where
gas_limit: block_gas_limit,
difficulty: U256::ZERO,
gas_used: 0,
extra_data,
blob_gas_used: None,
excess_blob_gas: None,
extra_data,
parent_beacon_block_root: attributes.parent_beacon_block_root,
};

View File

@@ -0,0 +1,305 @@
//! Optimism's [PayloadBuilder] implementation.
use super::*;
use reth_payload_builder::error::OptimismPayloadBuilderError;
use reth_primitives::Hardfork;
/// Constructs an Ethereum transaction payload from the transactions sent through the
/// Payload attributes by the sequencer. If the `no_tx_pool` argument is passed in
/// the payload attributes, the transaction pool will be ignored and the only transactions
/// included in the payload will be those sent through the attributes.
///
/// Given build arguments including an Ethereum client, transaction pool,
/// and configuration, this function creates a transaction payload. Returns
/// a result indicating success with the payload or an error in case of failure.
#[inline]
pub(crate) fn optimism_payload_builder<Pool, Client>(
args: BuildArguments<Pool, Client>,
) -> Result<BuildOutcome, PayloadBuilderError>
where
Client: StateProviderFactory,
Pool: TransactionPool,
{
let BuildArguments { client, pool, mut cached_reads, config, cancel, best_payload } = args;
let state_provider = client.state_by_block_hash(config.parent_block.hash)?;
let state = StateProviderDatabase::new(&state_provider);
let mut db =
State::builder().with_database_ref(cached_reads.as_db(&state)).with_bundle_update().build();
let extra_data = config.extra_data();
let PayloadConfig {
initialized_block_env,
initialized_cfg,
parent_block,
attributes,
chain_spec,
..
} = config;
debug!(target: "payload_builder", parent_hash = ?parent_block.hash, parent_number = parent_block.number, "building new payload");
let mut cumulative_gas_used = 0;
let block_gas_limit: u64 = attributes
.optimism_payload_attributes
.gas_limit
.unwrap_or(initialized_block_env.gas_limit.try_into().unwrap_or(u64::MAX));
let base_fee = initialized_block_env.basefee.to::<u64>();
let mut executed_txs = Vec::new();
let mut best_txs = pool.best_transactions_with_base_fee(base_fee);
let mut total_fees = U256::ZERO;
let block_number = initialized_block_env.number.to::<u64>();
let is_regolith =
chain_spec.is_fork_active_at_timestamp(Hardfork::Regolith, attributes.timestamp);
let mut receipts = Vec::new();
for sequencer_tx in attributes.optimism_payload_attributes.transactions {
// Check if the job was cancelled, if so we can exit early.
if cancel.is_cancelled() {
return Ok(BuildOutcome::Cancelled)
}
// Convert the transaction to a [TransactionSignedEcRecovered]. This is
// purely for the purposes of utilizing the [tx_env_with_recovered] function.
// Deposit transactions do not have signatures, so if the tx is a deposit, this
// will just pull in its `from` address.
let sequencer_tx = sequencer_tx.clone().try_into_ecrecovered().map_err(|_| {
PayloadBuilderError::Optimism(OptimismPayloadBuilderError::TransactionEcRecoverFailed)
})?;
// Cache the depositor account prior to the state transition for the deposit nonce.
//
// Note that this *only* needs to be done post-regolith hardfork, as deposit nonces
// were not introduced in Bedrock. In addition, regular transactions don't have deposit
// nonces, so we don't need to touch the DB for those.
let depositor = (is_regolith && sequencer_tx.is_deposit())
.then(|| {
db.load_cache_account(sequencer_tx.signer())
.map(|acc| acc.account_info().unwrap_or_default())
})
.transpose()
.map_err(|_| {
PayloadBuilderError::Optimism(OptimismPayloadBuilderError::AccountLoadFailed(
sequencer_tx.signer(),
))
})?;
// Configure the environment for the block.
let env = Env {
cfg: initialized_cfg.clone(),
block: initialized_block_env.clone(),
tx: tx_env_with_recovered(&sequencer_tx),
};
let mut evm = revm::EVM::with_env(env);
evm.database(&mut db);
let ResultAndState { result, state } = match evm.transact() {
Ok(res) => res,
Err(err) => {
match err {
EVMError::Transaction(err) => {
trace!(target: "optimism_payload_builder", ?err, ?sequencer_tx, "Error in sequencer transaction, skipping.");
continue
}
err => {
// this is an error that we should treat as fatal for this attempt
return Err(PayloadBuilderError::EvmExecutionError(err))
}
}
}
};
// commit changes
db.commit(state);
let gas_used = result.gas_used();
// add gas used by the transaction to cumulative gas used, before creating the receipt
cumulative_gas_used += gas_used;
// Push transaction changeset and calculate header bloom filter for receipt.
receipts.push(Some(Receipt {
tx_type: sequencer_tx.tx_type(),
success: result.is_success(),
cumulative_gas_used,
logs: result.logs().into_iter().map(into_reth_log).collect(),
#[cfg(feature = "optimism")]
deposit_nonce: depositor.map(|account| account.nonce),
}));
// append transaction to the list of executed transactions
executed_txs.push(sequencer_tx.into_signed());
}
if !attributes.optimism_payload_attributes.no_tx_pool {
while let Some(pool_tx) = best_txs.next() {
// ensure we still have capacity for this transaction
if cumulative_gas_used + pool_tx.gas_limit() > block_gas_limit {
// we can't fit this transaction into the block, so we need to mark it as invalid
// which also removes all dependent transaction from the iterator before we can
// continue
best_txs.mark_invalid(&pool_tx);
continue
}
// check if the job was cancelled, if so we can exit early
if cancel.is_cancelled() {
return Ok(BuildOutcome::Cancelled)
}
// convert tx to a signed transaction
let tx = pool_tx.to_recovered_transaction();
// Configure the environment for the block.
let env = Env {
cfg: initialized_cfg.clone(),
block: initialized_block_env.clone(),
tx: tx_env_with_recovered(&tx),
};
let mut evm = revm::EVM::with_env(env);
evm.database(&mut db);
let ResultAndState { result, state } = match evm.transact() {
Ok(res) => res,
Err(err) => {
match err {
EVMError::Transaction(err) => {
if matches!(err, InvalidTransaction::NonceTooLow { .. }) {
// if the nonce is too low, we can skip this transaction
trace!(target: "payload_builder", ?err, ?tx, "skipping nonce too low transaction");
} else {
// if the transaction is invalid, we can skip it and all of its
// descendants
trace!(target: "payload_builder", ?err, ?tx, "skipping invalid transaction and its descendants");
best_txs.mark_invalid(&pool_tx);
}
continue
}
err => {
// this is an error that we should treat as fatal for this attempt
return Err(PayloadBuilderError::EvmExecutionError(err))
}
}
}
};
// commit changes
db.commit(state);
let gas_used = result.gas_used();
// add gas used by the transaction to cumulative gas used, before creating the receipt
cumulative_gas_used += gas_used;
// Push transaction changeset and calculate header bloom filter for receipt.
receipts.push(Some(Receipt {
tx_type: tx.tx_type(),
success: result.is_success(),
cumulative_gas_used,
logs: result.logs().into_iter().map(into_reth_log).collect(),
#[cfg(feature = "optimism")]
deposit_nonce: None,
}));
// update add to total fees
let miner_fee = tx
.effective_tip_per_gas(Some(base_fee))
.expect("fee is always valid; execution succeeded");
total_fees += U256::from(miner_fee) * U256::from(gas_used);
// append transaction to the list of executed transactions
executed_txs.push(tx.into_signed());
}
}
// check if we have a better block
if !is_better_payload(best_payload.as_deref(), total_fees) {
// can skip building the block
return Ok(BuildOutcome::Aborted { fees: total_fees, cached_reads })
}
let WithdrawalsOutcome { withdrawals_root, withdrawals } =
commit_withdrawals(&mut db, &chain_spec, attributes.timestamp, attributes.withdrawals)?;
// merge all transitions into bundle state, this would apply the withdrawal balance changes
// and 4788 contract call
db.merge_transitions(BundleRetention::PlainState);
let bundle = BundleStateWithReceipts::new(
db.take_bundle(),
Receipts::from_vec(vec![receipts]),
block_number,
);
let receipts_root = bundle.receipts_root_slow(block_number).expect("Number is in range");
let logs_bloom = bundle.block_logs_bloom(block_number).expect("Number is in range");
// calculate the state root
let state_root = state_provider.state_root(&bundle)?;
// create the block header
let transactions_root = proofs::calculate_transaction_root(&executed_txs);
// Cancun is not yet active on Optimism chains.
let blob_sidecars = Vec::new();
let excess_blob_gas = None;
let blob_gas_used = None;
let header = Header {
parent_hash: parent_block.hash,
ommers_hash: EMPTY_OMMER_ROOT_HASH,
beneficiary: initialized_block_env.coinbase,
state_root,
transactions_root,
receipts_root,
withdrawals_root,
logs_bloom,
timestamp: attributes.timestamp,
mix_hash: attributes.prev_randao,
nonce: BEACON_NONCE,
base_fee_per_gas: Some(base_fee),
number: parent_block.number + 1,
gas_limit: block_gas_limit,
difficulty: U256::ZERO,
gas_used: cumulative_gas_used,
extra_data,
parent_beacon_block_root: attributes.parent_beacon_block_root,
blob_gas_used,
excess_blob_gas,
};
// seal the block
let block = Block { header, body: executed_txs, ommers: vec![], withdrawals };
let sealed_block = block.seal_slow();
debug!(target: "payload_builder", ?sealed_block, "sealed built block");
let mut payload = BuiltPayload::new(attributes.id, sealed_block, total_fees);
// extend the payload with the blob sidecars from the executed txs
payload.extend_sidecars(blob_sidecars);
Ok(BuildOutcome::Better { payload, cached_reads })
}
/// Optimism's payload builder
#[derive(Debug, Clone)]
pub struct OptimismPayloadBuilder;
/// Implementation of the [PayloadBuilder] trait for [OptimismPayloadBuilder].
impl<Pool, Client> PayloadBuilder<Pool, Client> for OptimismPayloadBuilder
where
Client: StateProviderFactory,
Pool: TransactionPool,
{
fn try_build(
&self,
args: BuildArguments<Pool, Client>,
) -> Result<BuildOutcome, PayloadBuilderError> {
optimism_payload_builder(args)
}
}

View File

@@ -39,3 +39,8 @@ revm.workspace = true
[features]
test-utils = []
optimism = [
"reth-primitives/optimism",
"reth-rpc-types/optimism",
"reth-interfaces/optimism"
]

View File

@@ -27,6 +27,29 @@ pub enum PayloadBuilderError {
/// Thrown if the payload requests withdrawals before Shanghai activation.
#[error("withdrawals set before Shanghai activation")]
WithdrawalsBeforeShanghai,
/// Optimism specific payload building errors.
#[cfg(feature = "optimism")]
#[error(transparent)]
Optimism(#[from] OptimismPayloadBuilderError),
}
/// Optimism specific payload building errors.
#[derive(Debug, thiserror::Error)]
pub enum OptimismPayloadBuilderError {
/// Thrown when a transaction fails to convert to a
/// [reth_primitives::TransactionSignedEcRecovered].
#[cfg(feature = "optimism")]
#[error("failed to convert deposit transaction to TransactionSignedEcRecovered")]
TransactionEcRecoverFailed,
/// Thrown when the L1 block info could not be parsed from the calldata of the
/// first transaction supplied in the payload attributes.
#[cfg(feature = "optimism")]
#[error("failed to parse L1 block info from L1 info tx calldata")]
L1BlockInfoParseFailed,
/// Thrown when a database account could not be loaded.
#[error("failed to load account {0:?}")]
#[cfg(feature = "optimism")]
AccountLoadFailed(revm_primitives::Address),
}
impl From<oneshot::error::RecvError> for PayloadBuilderError {

View File

@@ -1,6 +1,6 @@
//! Contains types required for building a payload.
use alloy_rlp::Encodable;
use alloy_rlp::{Encodable, Error as DecodeError};
use reth_primitives::{
revm::config::revm_spec_by_timestamp_after_merge, Address, BlobTransactionSidecar, ChainSpec,
Header, SealedBlock, Withdrawal, B256, U256,
@@ -9,11 +9,16 @@ use reth_rpc_types::engine::{
ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3, ExecutionPayloadV1, PayloadAttributes,
PayloadId,
};
use reth_rpc_types_compat::engine::payload::{
block_to_payload_v3, convert_block_to_payload_field_v2,
convert_standalone_withdraw_to_withdrawal, from_primitive_sidecar, try_block_to_payload_v1,
};
use revm_primitives::{BlobExcessGasAndPrice, BlockEnv, CfgEnv, SpecId};
#[cfg(feature = "optimism")]
use reth_primitives::TransactionSigned;
/// Contains the built payload.
///
/// According to the [engine API specification](https://github.com/ethereum/execution-apis/blob/main/src/engine/README.md) the execution layer should build the initial version of the payload with an empty transaction set and then keep update it in order to maximize the revenue.
@@ -137,6 +142,21 @@ pub struct PayloadBuilderAttributes {
pub withdrawals: Vec<Withdrawal>,
/// Root of the parent beacon block
pub parent_beacon_block_root: Option<B256>,
/// Optimism Payload Builder Attributes
#[cfg(feature = "optimism")]
pub optimism_payload_attributes: OptimismPayloadBuilderAttributes,
}
/// Optimism Payload Builder Attributes
#[cfg(feature = "optimism")]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OptimismPayloadBuilderAttributes {
/// NoTxPool option for the generated payload
pub no_tx_pool: bool,
/// Transactions for the generated payload
pub transactions: Vec<TransactionSigned>,
/// The gas limit for the generated payload
pub gas_limit: Option<u64>,
}
// === impl PayloadBuilderAttributes ===
@@ -145,9 +165,23 @@ impl PayloadBuilderAttributes {
/// Creates a new payload builder for the given parent block and the attributes.
///
/// Derives the unique [PayloadId] for the given parent and attributes
pub fn new(parent: B256, attributes: PayloadAttributes) -> Self {
pub fn try_new(parent: B256, attributes: PayloadAttributes) -> Result<Self, DecodeError> {
#[cfg(not(feature = "optimism"))]
let id = payload_id(&parent, &attributes);
#[cfg(feature = "optimism")]
let (id, transactions) = {
let transactions = attributes
.optimism_payload_attributes
.transactions
.as_deref()
.unwrap_or(&[])
.iter()
.map(|tx| TransactionSigned::decode_enveloped(tx.clone()))
.collect::<Result<_, _>>()?;
(payload_id(&parent, &attributes, &transactions), transactions)
};
let withdraw = attributes.withdrawals.map(
|withdrawals: Vec<reth_rpc_types::engine::payload::Withdrawal>| {
withdrawals
@@ -157,7 +191,7 @@ impl PayloadBuilderAttributes {
},
);
Self {
Ok(Self {
id,
parent,
timestamp: attributes.timestamp.to(),
@@ -165,9 +199,14 @@ impl PayloadBuilderAttributes {
prev_randao: attributes.prev_randao,
withdrawals: withdraw.unwrap_or_default(),
parent_beacon_block_root: attributes.parent_beacon_block_root,
}
#[cfg(feature = "optimism")]
optimism_payload_attributes: OptimismPayloadBuilderAttributes {
no_tx_pool: attributes.optimism_payload_attributes.no_tx_pool.unwrap_or_default(),
transactions,
gas_limit: attributes.optimism_payload_attributes.gas_limit,
},
})
}
/// Returns the configured [CfgEnv] and [BlockEnv] for the targeted payload (that has the
/// `parent` as its parent).
///
@@ -182,6 +221,11 @@ impl PayloadBuilderAttributes {
let mut cfg = CfgEnv::default();
cfg.chain_id = chain_spec.chain().id();
#[cfg(feature = "optimism")]
{
cfg.optimism = chain_spec.is_optimism();
}
// ensure we're not missing any timestamp based hardforks
cfg.spec_id = revm_spec_by_timestamp_after_merge(chain_spec, self.timestamp);
@@ -229,7 +273,11 @@ impl PayloadBuilderAttributes {
/// Generates the payload id for the configured payload
///
/// Returns an 8-byte identifier by hashing the payload components with sha256 hash.
pub(crate) fn payload_id(parent: &B256, attributes: &PayloadAttributes) -> PayloadId {
pub(crate) fn payload_id(
parent: &B256,
attributes: &PayloadAttributes,
#[cfg(feature = "optimism")] txs: &Vec<TransactionSigned>,
) -> PayloadId {
use sha2::Digest;
let mut hasher = sha2::Sha256::new();
hasher.update(parent.as_slice());
@@ -241,9 +289,25 @@ pub(crate) fn payload_id(parent: &B256, attributes: &PayloadAttributes) -> Paylo
withdrawals.encode(&mut buf);
hasher.update(buf);
}
if let Some(parent_beacon_block) = attributes.parent_beacon_block_root {
hasher.update(parent_beacon_block);
}
#[cfg(feature = "optimism")]
{
let no_tx_pool = attributes.optimism_payload_attributes.no_tx_pool.unwrap_or_default();
if no_tx_pool || !txs.is_empty() {
hasher.update([no_tx_pool as u8]);
hasher.update(txs.len().to_be_bytes());
txs.iter().for_each(|tx| hasher.update(tx.hash()));
}
if let Some(gas_limit) = attributes.optimism_payload_attributes.gas_limit {
hasher.update(gas_limit.to_be_bytes());
}
}
let out = hasher.finalize();
PayloadId::new(out.as_slice()[..8].try_into().expect("sufficient length"))
}

View File

@@ -77,9 +77,6 @@ triehash = "0.8"
plain_hasher = "0.2"
hash-db = "~0.15"
# value-256 is needed for the main_codec proptests to pass
reth-primitives = { path = ".", features = ["value-256"] }
# necessary so we don't hit a "undeclared 'std'":
# https://github.com/paradigmxyz/reth/pull/177#discussion_r1021172198
@@ -92,10 +89,8 @@ default = ["c-kzg"]
arbitrary = ["revm-primitives/arbitrary", "reth-rpc-types/arbitrary", "dep:arbitrary", "dep:proptest", "dep:proptest-derive"]
c-kzg = ["revm-primitives/c-kzg", "dep:c-kzg"]
test-utils = ["dep:plain_hasher", "dep:hash-db", "dep:ethers-core"]
# value-256 controls whether transaction Value fields are DB-encoded as 256 bits instead of the
# default of 128 bits.
value-256 = ["reth-codecs/value-256"]
clap = ["dep:clap"]
optimism = ["reth-codecs/optimism"]
[[bench]]
name = "recover_ecdsa_crit"

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -96,4 +96,70 @@ mod tests {
);
}
}
#[cfg(feature = "optimism")]
#[test]
fn calculate_optimism_base_fee_success() {
let base_fee = [
1000000000, 1000000000, 1000000000, 1072671875, 1059263476, 1049238967, 1049238967, 0,
1, 2,
];
let gas_used = [
10000000, 10000000, 10000000, 9000000, 10001000, 0, 10000000, 10000000, 10000000,
10000000,
];
let gas_limit = [
10000000, 12000000, 14000000, 10000000, 14000000, 2000000, 18000000, 18000000,
18000000, 18000000,
];
let next_base_fee = [
1100000048, 1080000000, 1065714297, 1167067046, 1128881311, 1028254188, 1098203452, 1,
2, 3,
];
for i in 0..base_fee.len() {
assert_eq!(
next_base_fee[i],
calculate_next_block_base_fee(
gas_used[i],
gas_limit[i],
base_fee[i],
crate::BaseFeeParams::optimism(),
)
);
}
}
#[cfg(feature = "optimism")]
#[test]
fn calculate_optimism_goerli_base_fee_success() {
let base_fee = [
1000000000, 1000000000, 1000000000, 1072671875, 1059263476, 1049238967, 1049238967, 0,
1, 2,
];
let gas_used = [
10000000, 10000000, 10000000, 9000000, 10001000, 0, 10000000, 10000000, 10000000,
10000000,
];
let gas_limit = [
10000000, 12000000, 14000000, 10000000, 14000000, 2000000, 18000000, 18000000,
18000000, 18000000,
];
let next_base_fee = [
1180000000, 1146666666, 1122857142, 1244299375, 1189416692, 1028254188, 1144836295, 1,
2, 3,
];
for i in 0..base_fee.len() {
assert_eq!(
next_base_fee[i],
calculate_next_block_base_fee(
gas_used[i],
gas_limit[i],
base_fee[i],
crate::BaseFeeParams::optimism_goerli(),
)
);
}
}
}

View File

@@ -17,6 +17,9 @@ pub use spec::{
ForkTimestamps, DEV, GOERLI, HOLESKY, MAINNET, SEPOLIA,
};
#[cfg(feature = "optimism")]
pub use spec::{BASE_GOERLI, BASE_MAINNET, OP_GOERLI};
// The chain info module.
mod info;
pub use info::ChainInfo;
@@ -58,6 +61,9 @@ pub enum NamedChain {
OptimismKovan = 69,
OptimismGoerli = 420,
Base = 8453,
BaseGoerli = 84531,
Arbitrum = 42161,
ArbitrumTestnet = 421611,
ArbitrumGoerli = 421613,
@@ -116,11 +122,53 @@ impl Chain {
Chain::Named(NamedChain::Holesky)
}
/// Returns the optimism goerli chain.
pub const fn optimism_goerli() -> Self {
Chain::Named(NamedChain::OptimismGoerli)
}
/// Returns the optimism mainnet chain.
pub const fn optimism_mainnet() -> Self {
Chain::Named(NamedChain::Optimism)
}
/// Returns the base goerli chain.
pub const fn base_goerli() -> Self {
Chain::Named(NamedChain::BaseGoerli)
}
/// Returns the base mainnet chain.
pub const fn base_mainnet() -> Self {
Chain::Named(NamedChain::Base)
}
/// Returns the dev chain.
pub const fn dev() -> Self {
Chain::Named(NamedChain::Dev)
}
/// Returns true if the chain contains Optimism configuration.
pub fn is_optimism(self) -> bool {
self.named().map_or(false, |c| {
matches!(
c,
NamedChain::Optimism |
NamedChain::OptimismGoerli |
NamedChain::OptimismKovan |
NamedChain::Base |
NamedChain::BaseGoerli
)
})
}
/// Attempts to convert the chain into a named chain.
pub fn named(&self) -> Option<NamedChain> {
match self {
Chain::Named(chain) => Some(*chain),
Chain::Id(id) => NamedChain::try_from(*id).ok(),
}
}
/// The id of the chain
pub fn id(&self) -> u64 {
match self {

View File

@@ -153,6 +153,7 @@ pub static SEPOLIA: Lazy<Arc<ChainSpec>> = Lazy::new(|| {
1273020,
b256!("649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5"),
)),
base_fee_params: BaseFeeParams::ethereum(),
prune_delete_limit: 1700,
snapshot_block_interval: 1_000_000,
@@ -241,6 +242,129 @@ pub static DEV: Lazy<Arc<ChainSpec>> = Lazy::new(|| {
.into()
});
/// The Optimism Goerli spec
#[cfg(feature = "optimism")]
pub static OP_GOERLI: Lazy<Arc<ChainSpec>> = Lazy::new(|| {
ChainSpec {
chain: Chain::optimism_goerli(),
genesis: serde_json::from_str(include_str!("../../res/genesis/goerli_op.json"))
.expect("Can't deserialize Optimism Goerli genesis json"),
genesis_hash: Some(b256!(
"c1fc15cd51159b1f1e5cbc4b82e85c1447ddfa33c52cf1d98d14fba0d6354be1"
)),
fork_timestamps: ForkTimestamps::default(),
paris_block_and_final_difficulty: Some((0, U256::from(0))),
hardforks: BTreeMap::from([
(Hardfork::Frontier, ForkCondition::Block(0)),
(Hardfork::Homestead, ForkCondition::Block(0)),
(Hardfork::Tangerine, ForkCondition::Block(0)),
(Hardfork::SpuriousDragon, ForkCondition::Block(0)),
(Hardfork::Byzantium, ForkCondition::Block(0)),
(Hardfork::Constantinople, ForkCondition::Block(0)),
(Hardfork::Petersburg, ForkCondition::Block(0)),
(Hardfork::Istanbul, ForkCondition::Block(0)),
(Hardfork::MuirGlacier, ForkCondition::Block(0)),
(Hardfork::Berlin, ForkCondition::Block(0)),
(Hardfork::London, ForkCondition::Block(0)),
(Hardfork::ArrowGlacier, ForkCondition::Block(0)),
(Hardfork::GrayGlacier, ForkCondition::Block(0)),
(
Hardfork::Paris,
ForkCondition::TTD { fork_block: Some(0), total_difficulty: U256::from(0) },
),
(Hardfork::Bedrock, ForkCondition::Block(4061224)),
(Hardfork::Regolith, ForkCondition::Timestamp(1679079600)),
]),
base_fee_params: BaseFeeParams::optimism(),
prune_delete_limit: 1700,
snapshot_block_interval: 1_000_000,
..Default::default()
}
.into()
});
/// The Base Goerli spec
#[cfg(feature = "optimism")]
pub static BASE_GOERLI: Lazy<Arc<ChainSpec>> = Lazy::new(|| {
ChainSpec {
chain: Chain::base_goerli(),
genesis: serde_json::from_str(include_str!("../../res/genesis/goerli_base.json"))
.expect("Can't deserialize Base Goerli genesis json"),
genesis_hash: Some(b256!(
"a3ab140f15ea7f7443a4702da64c10314eb04d488e72974e02e2d728096b4f76"
)),
fork_timestamps: ForkTimestamps::default(),
paris_block_and_final_difficulty: Some((0, U256::from(0))),
hardforks: BTreeMap::from([
(Hardfork::Frontier, ForkCondition::Block(0)),
(Hardfork::Homestead, ForkCondition::Block(0)),
(Hardfork::Tangerine, ForkCondition::Block(0)),
(Hardfork::SpuriousDragon, ForkCondition::Block(0)),
(Hardfork::Byzantium, ForkCondition::Block(0)),
(Hardfork::Constantinople, ForkCondition::Block(0)),
(Hardfork::Petersburg, ForkCondition::Block(0)),
(Hardfork::Istanbul, ForkCondition::Block(0)),
(Hardfork::MuirGlacier, ForkCondition::Block(0)),
(Hardfork::Berlin, ForkCondition::Block(0)),
(Hardfork::London, ForkCondition::Block(0)),
(Hardfork::ArrowGlacier, ForkCondition::Block(0)),
(Hardfork::GrayGlacier, ForkCondition::Block(0)),
(
Hardfork::Paris,
ForkCondition::TTD { fork_block: Some(0), total_difficulty: U256::from(0) },
),
(Hardfork::Bedrock, ForkCondition::Block(0)),
(Hardfork::Regolith, ForkCondition::Timestamp(1683219600)),
]),
base_fee_params: BaseFeeParams::optimism_goerli(),
prune_delete_limit: 1700,
snapshot_block_interval: 1_000_000,
..Default::default()
}
.into()
});
/// The Base mainnet spec
#[cfg(feature = "optimism")]
pub static BASE_MAINNET: Lazy<Arc<ChainSpec>> = Lazy::new(|| {
ChainSpec {
chain: Chain::base_mainnet(),
genesis: serde_json::from_str(include_str!("../../res/genesis/base.json"))
.expect("Can't deserialize Base genesis json"),
genesis_hash: Some(b256!(
"f712aa9241cc24369b143cf6dce85f0902a9731e70d66818a3a5845b296c73dd"
)),
fork_timestamps: ForkTimestamps::default(),
paris_block_and_final_difficulty: Some((0, U256::from(0))),
hardforks: BTreeMap::from([
(Hardfork::Frontier, ForkCondition::Block(0)),
(Hardfork::Homestead, ForkCondition::Block(0)),
(Hardfork::Tangerine, ForkCondition::Block(0)),
(Hardfork::SpuriousDragon, ForkCondition::Block(0)),
(Hardfork::Byzantium, ForkCondition::Block(0)),
(Hardfork::Constantinople, ForkCondition::Block(0)),
(Hardfork::Petersburg, ForkCondition::Block(0)),
(Hardfork::Istanbul, ForkCondition::Block(0)),
(Hardfork::MuirGlacier, ForkCondition::Block(0)),
(Hardfork::Berlin, ForkCondition::Block(0)),
(Hardfork::London, ForkCondition::Block(0)),
(Hardfork::ArrowGlacier, ForkCondition::Block(0)),
(Hardfork::GrayGlacier, ForkCondition::Block(0)),
(
Hardfork::Paris,
ForkCondition::TTD { fork_block: Some(0), total_difficulty: U256::from(0) },
),
(Hardfork::Bedrock, ForkCondition::Block(0)),
(Hardfork::Regolith, ForkCondition::Timestamp(0)),
]),
base_fee_params: BaseFeeParams::optimism(),
prune_delete_limit: 1700,
snapshot_block_interval: 1_000_000,
..Default::default()
}
.into()
});
/// BaseFeeParams contains the config parameters that control block base fee computation
#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq)]
pub struct BaseFeeParams {
@@ -258,6 +382,28 @@ impl BaseFeeParams {
elasticity_multiplier: EIP1559_DEFAULT_ELASTICITY_MULTIPLIER,
}
}
/// Get the base fee parameters for optimism goerli
#[cfg(feature = "optimism")]
pub const fn optimism_goerli() -> BaseFeeParams {
BaseFeeParams {
max_change_denominator:
crate::constants::OP_GOERLI_EIP1559_DEFAULT_BASE_FEE_MAX_CHANGE_DENOMINATOR,
elasticity_multiplier:
crate::constants::OP_GOERLI_EIP1559_DEFAULT_ELASTICITY_MULTIPLIER,
}
}
/// Get the base fee parameters for optimism mainnet
#[cfg(feature = "optimism")]
pub const fn optimism() -> BaseFeeParams {
BaseFeeParams {
max_change_denominator:
crate::constants::OP_MAINNET_EIP1559_DEFAULT_BASE_FEE_MAX_CHANGE_DENOMINATOR,
elasticity_multiplier:
crate::constants::OP_MAINNET_EIP1559_DEFAULT_ELASTICITY_MULTIPLIER,
}
}
}
/// An Ethereum chain specification.
@@ -335,6 +481,11 @@ impl ChainSpec {
self.chain
}
/// Returns `true` if this chain contains Optimism configuration.
pub fn is_optimism(&self) -> bool {
self.chain.is_optimism()
}
/// Get the genesis block specification.
///
/// To get the header for the genesis block, use [`Self::genesis_header`] instead.
@@ -873,6 +1024,24 @@ impl ChainSpecBuilder {
self
}
/// Enable Bedrock at genesis
#[cfg(feature = "optimism")]
pub fn bedrock_activated(mut self) -> Self {
self = self.paris_activated();
self.hardforks.insert(Hardfork::Bedrock, ForkCondition::Block(0));
self
}
/// Enable Regolith at the timestamp of activation.
/// For post-bedrock op-stack chains, this will be at genesis.
/// For pre-bedrock op-stack chains, this will be at the timestamp of regolith activation.
#[cfg(feature = "optimism")]
pub fn regolith_activated(mut self) -> Self {
self = self.bedrock_activated();
self.hardforks.insert(Hardfork::Regolith, ForkCondition::Timestamp(0));
self
}
/// Build the resulting [`ChainSpec`].
///
/// # Panics
@@ -1197,6 +1366,9 @@ mod tests {
use bytes::BytesMut;
use std::{collections::HashMap, str::FromStr};
#[cfg(feature = "optimism")]
use crate::OP_GOERLI;
fn test_fork_ids(spec: &ChainSpec, cases: &[(Head, ForkId)]) {
for (block, expected_id) in cases {
let computed_id = spec.fork_id(block);
@@ -1798,6 +1970,28 @@ Post-merge hard forks (timestamp based):
)
}
#[cfg(feature = "optimism")]
#[test]
fn optimism_goerli_forkids() {
test_fork_ids(
&OP_GOERLI,
&[
(
Head { number: 0, ..Default::default() },
ForkId { hash: ForkHash([0x6d, 0x63, 0x76, 0xbe]), next: 4061224 },
),
(
Head { number: 4061224, timestamp: 1679079599, ..Default::default() },
ForkId { hash: ForkHash([0x03, 0x47, 0x85, 0x69]), next: 1679079600 },
),
(
Head { number: 4061224, timestamp: 1679079600, ..Default::default() },
ForkId { hash: ForkHash([0x6d, 0x43, 0x1d, 0x6c]), next: 0 },
),
],
);
}
/// Checks that time-based forks work
///
/// This is based off of the test vectors here: https://github.com/ethereum/go-ethereum/blob/5c8cc10d1e05c23ff1108022f4150749e73c0ca1/core/forkid/forkid_test.go#L155-L188

View File

@@ -60,6 +60,26 @@ pub const EIP1559_DEFAULT_BASE_FEE_MAX_CHANGE_DENOMINATOR: u64 = 8;
/// Elasticity multiplier as defined in [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559)
pub const EIP1559_DEFAULT_ELASTICITY_MULTIPLIER: u64 = 2;
/// Base fee max change denominator for Optimism Mainnet as defined in the Optimism
/// [transaction costs](https://community.optimism.io/docs/developers/build/differences/#transaction-costs) doc.
#[cfg(feature = "optimism")]
pub const OP_MAINNET_EIP1559_DEFAULT_BASE_FEE_MAX_CHANGE_DENOMINATOR: u64 = 50;
/// Base fee max change denominator for Optimism Mainnet as defined in the Optimism
/// [transaction costs](https://community.optimism.io/docs/developers/build/differences/#transaction-costs) doc.
#[cfg(feature = "optimism")]
pub const OP_MAINNET_EIP1559_DEFAULT_ELASTICITY_MULTIPLIER: u64 = 6;
/// Base fee max change denominator for Optimism Goerli as defined in the Optimism
/// [transaction costs](https://community.optimism.io/docs/developers/build/differences/#transaction-costs) doc.
#[cfg(feature = "optimism")]
pub const OP_GOERLI_EIP1559_DEFAULT_BASE_FEE_MAX_CHANGE_DENOMINATOR: u64 = 50;
/// Base fee max change denominator for Optimism Goerli as defined in the Optimism
/// [transaction costs](https://community.optimism.io/docs/developers/build/differences/#transaction-costs) doc.
#[cfg(feature = "optimism")]
pub const OP_GOERLI_EIP1559_DEFAULT_ELASTICITY_MULTIPLIER: u64 = 10;
/// Multiplier for converting gwei to wei.
pub const GWEI_TO_WEI: u64 = 1_000_000_000;
@@ -92,6 +112,14 @@ pub const HOLESKY_GENESIS_HASH: B256 =
pub const DEV_GENESIS_HASH: B256 =
b256!("2f980576711e3617a5e4d83dd539548ec0f7792007d505a3d2e9674833af2d7c");
/// Optimism goerli genesis hash.
pub const GOERLI_OP_GENESIS: B256 =
b256!("c1fc15cd51159b1f1e5cbc4b82e85c1447ddfa33c52cf1d98d14fba0d6354be1");
/// Base goerli genesis hash.
pub const GOERLI_BASE_GENESIS: B256 =
b256!("a3ab140f15ea7f7443a4702da64c10314eb04d488e72974e02e2d728096b4f76");
/// Keccak256 over empty array.
pub const KECCAK_EMPTY: B256 =
b256!("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470");

View File

@@ -41,6 +41,12 @@ pub enum Hardfork {
Shanghai,
/// Cancun.
Cancun,
/// Bedrock.
#[cfg(feature = "optimism")]
Bedrock,
/// Regolith
#[cfg(feature = "optimism")]
Regolith,
}
impl Hardfork {
@@ -85,6 +91,10 @@ impl FromStr for Hardfork {
"paris" => Hardfork::Paris,
"shanghai" => Hardfork::Shanghai,
"cancun" => Hardfork::Cancun,
#[cfg(feature = "optimism")]
"bedrock" => Hardfork::Bedrock,
#[cfg(feature = "optimism")]
"regolith" => Hardfork::Regolith,
_ => return Err(format!("Unknown hardfork: {s}")),
};
Ok(hardfork)
@@ -150,6 +160,18 @@ mod tests {
assert_eq!(hardforks, expected_hardforks);
}
#[test]
#[cfg(feature = "optimism")]
fn check_op_hardfork_from_str() {
let hardfork_str = ["beDrOck", "rEgOlITH"];
let expected_hardforks = [Hardfork::Bedrock, Hardfork::Regolith];
let hardforks: Vec<Hardfork> =
hardfork_str.iter().map(|h| Hardfork::from_str(h).unwrap()).collect();
assert_eq!(hardforks, expected_hardforks);
}
#[test]
fn check_nonexistent_hardfork_from_str() {
assert!(Hardfork::from_str("not a hardfork").is_err());

View File

@@ -33,7 +33,6 @@ mod integer_list;
mod log;
mod net;
mod peer;
mod precaution;
pub mod proofs;
mod prune;
mod receipt;
@@ -59,6 +58,8 @@ pub use chain::{
DisplayHardforks, ForkCondition, ForkTimestamps, NamedChain, DEV, GOERLI, HOLESKY, MAINNET,
SEPOLIA,
};
#[cfg(feature = "optimism")]
pub use chain::{BASE_GOERLI, BASE_MAINNET, OP_GOERLI};
pub use compression::*;
pub use constants::{
DEV_GENESIS_HASH, EMPTY_OMMER_ROOT_HASH, GOERLI_GENESIS_HASH, HOLESKY_GENESIS_HASH,
@@ -101,6 +102,8 @@ pub use transaction::{
TxEip4844, TxHashOrNumber, TxLegacy, TxType, TxValue, EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID,
EIP4844_TX_TYPE_ID, LEGACY_TX_TYPE_ID,
};
#[cfg(feature = "optimism")]
pub use transaction::{TxDeposit, DEPOSIT_TX_TYPE_ID};
pub use withdrawal::Withdrawal;
// Re-exports

View File

@@ -1,24 +0,0 @@
//! Helpers to ensure certain features are enabled or disabled.
//!
//! The motivation for this is to prevent that a binary is accidentally built with a feature that is
//! not intended to be used.
//!
//! Currently conflicting features are: `value-u256` which is required by optimism.
/// A macro to ensure that the crate's features are compatible with ethereum
#[macro_export]
macro_rules! ensure_ethereum {
() => {
#[cfg(feature = "value-256")]
compile_error!("The `value-256` feature is enabled but for `ethereum` it must be disabled: https://github.com/paradigmxyz/reth/issues/4891");
};
}
/// A macro to ensure that the crate's features are compatible with optimism
#[macro_export]
macro_rules! ensure_optimism {
() => {
#[cfg(not(feature = "value-256"))]
compile_error!("The `value-256` feature is disabled but for `optimism` it must be enabled: https://github.com/paradigmxyz/reth/issues/4891");
};
}

View File

@@ -4,8 +4,8 @@ use crate::{
constants::EMPTY_OMMER_ROOT_HASH,
keccak256,
trie::{HashBuilder, Nibbles},
Address, GenesisAccount, Header, ReceiptWithBloom, ReceiptWithBloomRef, TransactionSigned,
Withdrawal, B256,
Address, GenesisAccount, Header, Receipt, ReceiptWithBloom, ReceiptWithBloomRef,
TransactionSigned, Withdrawal, B256,
};
use alloy_rlp::Encodable;
use bytes::{BufMut, BytesMut};
@@ -70,18 +70,55 @@ pub fn calculate_withdrawals_root(withdrawals: &[Withdrawal]) -> B256 {
/// Calculates the receipt root for a header.
pub fn calculate_receipt_root(receipts: &[ReceiptWithBloom]) -> B256 {
#[cfg(feature = "optimism")]
{
// There is a minor bug in op-geth and op-erigon where in the Regolith hardfork,
// the receipt root calculation does not include the deposit nonce in the receipt
// encoding. This will be fixd in the next hardfork, however for now, we must strip
// the deposit nonce from the receipts before calculating the receipt root.
let receipts = receipts
.iter()
.cloned()
.map(|mut r| {
r.receipt.deposit_nonce = None;
r
})
.collect::<Vec<_>>();
ordered_trie_root_with_encoder(receipts.as_slice(), |r, buf| r.encode_inner(buf, false))
}
#[cfg(not(feature = "optimism"))]
ordered_trie_root_with_encoder(receipts, |r, buf| r.encode_inner(buf, false))
}
/// Calculates the receipt root for a header for the reference type of [ReceiptWithBloom].
/// Calculates the receipt root for a header for the reference type of [Receipt].
///
/// NOTE: Prefer [calculate_receipt_root] if you have log blooms memoized.
pub fn calculate_receipt_root_ref<T>(receipts: &[&T]) -> B256
where
for<'a> ReceiptWithBloomRef<'a>: From<&'a T>,
{
pub fn calculate_receipt_root_ref(receipts: &[&Receipt]) -> B256 {
#[cfg(feature = "optimism")]
{
// There is a minor bug in op-geth and op-erigon where in the Regolith hardfork,
// the receipt root calculation does not include the deposit nonce in the receipt
// encoding. This will be fixd in the next hardfork, however for now, we must strip
// the deposit nonce from the receipts before calculating the receipt root.
let receipts = receipts
.iter()
.map(|r| {
let mut r = (*r).clone();
r.deposit_nonce = None;
r
})
.collect::<Vec<_>>();
ordered_trie_root_with_encoder(&receipts, |r, buf| {
ReceiptWithBloomRef::from(r).encode_inner(buf, false)
})
}
#[cfg(not(feature = "optimism"))]
ordered_trie_root_with_encoder(receipts, |r, buf| {
ReceiptWithBloomRef::from(r).encode_inner(buf, false)
ReceiptWithBloomRef::from(*r).encode_inner(buf, false)
})
}
@@ -146,9 +183,14 @@ pub mod triehash {
mod tests {
use super::*;
use crate::{
b256, bloom, constants::EMPTY_ROOT_HASH, hex, Block, Log, Receipt, TxType, B256, GOERLI,
bloom,
constants::EMPTY_ROOT_HASH,
hex_literal::hex,
proofs::{calculate_receipt_root, calculate_transaction_root, genesis_state_root},
Address, Block, GenesisAccount, Log, Receipt, ReceiptWithBloom, TxType, B256, GOERLI,
HOLESKY, MAINNET, SEPOLIA, U256,
};
use alloy_primitives::b256;
use alloy_rlp::Decodable;
#[test]
@@ -161,6 +203,233 @@ mod tests {
assert_eq!(block.transactions_root, tx_root, "Must be the same");
}
/// Tests that the receipt root is computed correctly for the regolith block.
/// This was implemented due to a minor bug in op-geth and op-erigon where in
/// the Regolith hardfork, the receipt root calculation does not include the
/// deposit nonce in the receipt encoding.
/// To fix this an op-reth patch was applied to the receipt root calculation
/// to strip the deposit nonce from each receipt before calculating the root.
#[cfg(feature = "optimism")]
#[test]
fn check_optimism_receipt_root() {
use crate::{Bloom, Bytes};
let receipts = vec![
// 0xb0d6ee650637911394396d81172bd1c637d568ed1fbddab0daddfca399c58b53
ReceiptWithBloom {
receipt: Receipt {
tx_type: TxType::DEPOSIT,
success: true,
cumulative_gas_used: 46913,
logs: vec![],
#[cfg(feature = "optimism")]
deposit_nonce: Some(4012991u64),
},
bloom: Bloom(hex!("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").into()),
},
// 0x2f433586bae30573c393adfa02bc81d2a1888a3d6c9869f473fb57245166bd9a
ReceiptWithBloom {
receipt: Receipt {
tx_type: TxType::EIP1559,
success: true,
cumulative_gas_used: 118083,
logs: vec![
Log {
address: hex!("ddb6dcce6b794415145eb5caa6cd335aeda9c272").into(),
topics: vec![
b256!("c3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62"),
b256!("000000000000000000000000c498902843af527e674846bb7edefa8ad62b8fb9"),
b256!("000000000000000000000000c498902843af527e674846bb7edefa8ad62b8fb9"),
b256!("0000000000000000000000000000000000000000000000000000000000000000"),
],
data: Bytes::from_static(&hex!("00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001")),
},
Log {
address: hex!("ddb6dcce6b794415145eb5caa6cd335aeda9c272").into(),
topics: vec![
b256!("c3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62"),
b256!("000000000000000000000000c498902843af527e674846bb7edefa8ad62b8fb9"),
b256!("0000000000000000000000000000000000000000000000000000000000000000"),
b256!("000000000000000000000000c498902843af527e674846bb7edefa8ad62b8fb9"),
],
data: Bytes::from_static(&hex!("00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000001")),
},
Log {
address: hex!("ddb6dcce6b794415145eb5caa6cd335aeda9c272").into(),
topics: vec![
b256!("0eb774bb9698a73583fe07b6972cf2dcc08d1d97581a22861f45feb86b395820"),
b256!("000000000000000000000000c498902843af527e674846bb7edefa8ad62b8fb9"),
b256!("000000000000000000000000c498902843af527e674846bb7edefa8ad62b8fb9"),
],
data: Bytes::from_static(&hex!("0000000000000000000000000000000000000000000000000000000000000003")),
},
],
#[cfg(feature = "optimism")]
deposit_nonce: None,
},
bloom: Bloom(hex!("00001000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000800000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000040000000000004000000000080000000000000000000000000000000000000000000000000000008000000000000080020000000000000000000000000002000000000000000000000000000080000010000").into()),
},
// 0x6c33676e8f6077f46a62eabab70bc6d1b1b18a624b0739086d77093a1ecf8266
ReceiptWithBloom {
receipt: Receipt {
tx_type: TxType::EIP1559,
success: true,
cumulative_gas_used: 189253,
logs: vec![
Log {
address: hex!("ddb6dcce6b794415145eb5caa6cd335aeda9c272").into(),
topics: vec![
b256!("c3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62"),
b256!("0000000000000000000000009d521a04bee134ff8136d2ec957e5bc8c50394ec"),
b256!("0000000000000000000000009d521a04bee134ff8136d2ec957e5bc8c50394ec"),
b256!("0000000000000000000000000000000000000000000000000000000000000000"),
],
data: Bytes::from_static(&hex!("00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001")),
},
Log {
address: hex!("ddb6dcce6b794415145eb5caa6cd335aeda9c272").into(),
topics: vec![
b256!("c3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62"),
b256!("0000000000000000000000009d521a04bee134ff8136d2ec957e5bc8c50394ec"),
b256!("0000000000000000000000000000000000000000000000000000000000000000"),
b256!("0000000000000000000000009d521a04bee134ff8136d2ec957e5bc8c50394ec"),
],
data: Bytes::from_static(&hex!("00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000001")),
},
Log {
address: hex!("ddb6dcce6b794415145eb5caa6cd335aeda9c272").into(),
topics: vec![
b256!("0eb774bb9698a73583fe07b6972cf2dcc08d1d97581a22861f45feb86b395820"),
b256!("0000000000000000000000009d521a04bee134ff8136d2ec957e5bc8c50394ec"),
b256!("0000000000000000000000009d521a04bee134ff8136d2ec957e5bc8c50394ec"),
],
data: Bytes::from_static(&hex!("0000000000000000000000000000000000000000000000000000000000000003")),
},
],
#[cfg(feature = "optimism")]
deposit_nonce: None,
},
bloom: Bloom(hex!("00000000000000000000200000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000002000000000020000000000000000000000000000000000000000000000000000000000000000020000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000040000000000004000000000080000000000000000000000000000000000000000000000000000008000000000000080020000000000000000000000000002000000000000000000000000000080000000000").into()),
},
// 0x4d3ecbef04ba7ce7f5ab55be0c61978ca97c117d7da448ed9771d4ff0c720a3f
ReceiptWithBloom {
receipt: Receipt {
tx_type: TxType::EIP1559,
success: true,
cumulative_gas_used: 346969,
logs: vec![
Log {
address: hex!("4200000000000000000000000000000000000006").into(),
topics: vec![
b256!("ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"),
b256!("000000000000000000000000c3feb4ef4c2a5af77add15c95bd98f6b43640cc8"),
b256!("0000000000000000000000002992607c1614484fe6d865088e5c048f0650afd4"),
],
data: Bytes::from_static(&hex!("0000000000000000000000000000000000000000000000000018de76816d8000")),
},
Log {
address: hex!("cf8e7e6b26f407dee615fc4db18bf829e7aa8c09").into(),
topics: vec![
b256!("ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"),
b256!("0000000000000000000000002992607c1614484fe6d865088e5c048f0650afd4"),
b256!("0000000000000000000000008dbffe4c8bf3caf5deae3a99b50cfcf3648cbc09"),
],
data: Bytes::from_static(&hex!("000000000000000000000000000000000000000000000002d24d8e9ac1aa79e2")),
},
Log {
address: hex!("2992607c1614484fe6d865088e5c048f0650afd4").into(),
topics: vec![
b256!("1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1"),
],
data: Bytes::from_static(&hex!("000000000000000000000000000000000000000000000009bd50642785c15736000000000000000000000000000000000000000000011bb7ac324f724a29bbbf")),
},
Log {
address: hex!("2992607c1614484fe6d865088e5c048f0650afd4").into(),
topics: vec![
b256!("d78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822"),
b256!("00000000000000000000000029843613c7211d014f5dd5718cf32bcd314914cb"),
b256!("0000000000000000000000008dbffe4c8bf3caf5deae3a99b50cfcf3648cbc09"),
],
data: Bytes::from_static(&hex!("0000000000000000000000000000000000000000000000000018de76816d800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002d24d8e9ac1aa79e2")),
},
Log {
address: hex!("6d0f8d488b669aa9ba2d0f0b7b75a88bf5051cd3").into(),
topics: vec![
b256!("ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"),
b256!("0000000000000000000000008dbffe4c8bf3caf5deae3a99b50cfcf3648cbc09"),
b256!("000000000000000000000000c3feb4ef4c2a5af77add15c95bd98f6b43640cc8"),
],
data: Bytes::from_static(&hex!("00000000000000000000000000000000000000000000000014bc73062aea8093")),
},
Log {
address: hex!("8dbffe4c8bf3caf5deae3a99b50cfcf3648cbc09").into(),
topics: vec![
b256!("1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1"),
],
data: Bytes::from_static(&hex!("00000000000000000000000000000000000000000000002f122cfadc1ca82a35000000000000000000000000000000000000000000000665879dc0609945d6d1")),
},
Log {
address: hex!("8dbffe4c8bf3caf5deae3a99b50cfcf3648cbc09").into(),
topics: vec![
b256!("d78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822"),
b256!("00000000000000000000000029843613c7211d014f5dd5718cf32bcd314914cb"),
b256!("000000000000000000000000c3feb4ef4c2a5af77add15c95bd98f6b43640cc8"),
],
data: Bytes::from_static(&hex!("0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002d24d8e9ac1aa79e200000000000000000000000000000000000000000000000014bc73062aea80930000000000000000000000000000000000000000000000000000000000000000")),
},
],
#[cfg(feature = "optimism")]
deposit_nonce: None,
},
bloom: Bloom(hex!("00200000000000000000000080000000000000000000000000040000100004000000000000000000000000100000000000000000000000000000100000000000000000000000000002000008000000200000000200000000020000000000000040000000000000000400000200000000000000000000000000000010000000000400000000010400000000000000000000000000002000c80000004080002000000000000000400200000000800000000000000000000000000000000000000000000002000000000000000000000000000000000100001000000000000000000000002000000000000000000000010000000000000000000000800000800000").into()),
},
// 0xf738af5eb00ba23dbc1be2dbce41dbc0180f0085b7fb46646e90bf737af90351
ReceiptWithBloom {
receipt: Receipt {
tx_type: TxType::EIP1559,
success: true,
cumulative_gas_used: 623249,
logs: vec![
Log {
address: hex!("ac6564f3718837caadd42eed742d75c12b90a052").into(),
topics: vec![
b256!("ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"),
b256!("0000000000000000000000000000000000000000000000000000000000000000"),
b256!("000000000000000000000000a4fa7f3fbf0677f254ebdb1646146864c305b76e"),
b256!("000000000000000000000000000000000000000000000000000000000011a1d3"),
],
data: Default::default(),
},
Log {
address: hex!("ac6564f3718837caadd42eed742d75c12b90a052").into(),
topics: vec![
b256!("9d89e36eadf856db0ad9ffb5a569e07f95634dddd9501141ecf04820484ad0dc"),
b256!("000000000000000000000000a4fa7f3fbf0677f254ebdb1646146864c305b76e"),
b256!("000000000000000000000000000000000000000000000000000000000011a1d3"),
],
data: Bytes::from_static(&hex!("00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000037697066733a2f2f516d515141646b33736538396b47716577395256567a316b68643548375562476d4d4a485a62566f386a6d346f4a2f30000000000000000000")),
},
Log {
address: hex!("ac6564f3718837caadd42eed742d75c12b90a052").into(),
topics: vec![
b256!("110d160a1bedeea919a88fbc4b2a9fb61b7e664084391b6ca2740db66fef80fe"),
b256!("00000000000000000000000084d47f6eea8f8d87910448325519d1bb45c2972a"),
b256!("000000000000000000000000a4fa7f3fbf0677f254ebdb1646146864c305b76e"),
b256!("000000000000000000000000000000000000000000000000000000000011a1d3"),
],
data: Bytes::from_static(&hex!("0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000a4fa7f3fbf0677f254ebdb1646146864c305b76e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007717500762343034303661353035646234633961386163316433306335633332303265370000000000000000000000000000000000000000000000000000000000000037697066733a2f2f516d515141646b33736538396b47716577395256567a316b68643548375562476d4d4a485a62566f386a6d346f4a2f30000000000000000000")),
},
],
#[cfg(feature = "optimism")]
deposit_nonce: None,
},
bloom: Bloom(hex!("00000000000000000000000000000000400000000000000000000000000000000000004000000000000001000000000000000002000000000100000000000000000000000000000000000008000000000000000000000000000000000000000004000000020000000000000000000800000000000000000000000010200100200008000002000000000000000000800000000000000000000002000000000000000000000000000000080000000000000000000000004000000000000000000000000002000000000000000000000000000000000000200000000000000020002000000000000000002000000000000000000000000000000000000000000000").into()),
},
];
let root = calculate_receipt_root(&receipts);
assert_eq!(root, b256!("e255fed45eae7ede0556fe4fabc77b0d294d18781a5a581cab09127bc4cd9ffb"))
}
#[test]
fn check_receipt_root() {
let logs = vec![Log { address: Address::ZERO, topics: vec![], data: Default::default() }];
@@ -171,6 +440,8 @@ mod tests {
success: true,
cumulative_gas_used: 102068,
logs,
#[cfg(feature = "optimism")]
deposit_nonce: None,
},
bloom,
};

View File

@@ -6,14 +6,18 @@ use crate::{
};
use alloy_rlp::{length_of_length, Decodable, Encodable};
use bytes::{Buf, BufMut, BytesMut};
use reth_codecs::{main_codec, Compact, CompactZstd};
use reth_codecs::{add_arbitrary_tests, main_codec, Compact, CompactZstd};
use std::{
cmp::Ordering,
ops::{Deref, DerefMut},
};
#[cfg(any(test, feature = "arbitrary"))]
use proptest::strategy::Strategy;
/// Receipt containing result of transaction execution.
#[main_codec(zstd)]
#[main_codec(no_arbitrary, zstd)]
#[add_arbitrary_tests]
#[derive(Clone, Debug, PartialEq, Eq, Default)]
pub struct Receipt {
/// Receipt type.
@@ -25,13 +29,10 @@ pub struct Receipt {
/// Gas used
pub cumulative_gas_used: u64,
/// Log send from contracts.
#[cfg_attr(
any(test, feature = "arbitrary"),
proptest(
strategy = "proptest::collection::vec(proptest::arbitrary::any::<Log>(), 0..=20)"
)
)]
pub logs: Vec<Log>,
/// Deposit nonce for Optimism deposited transactions
#[cfg(feature = "optimism")]
pub deposit_nonce: Option<u64>,
}
impl Receipt {
@@ -181,6 +182,60 @@ impl ReceiptWithBloom {
}
}
#[cfg(any(test, feature = "arbitrary"))]
impl proptest::arbitrary::Arbitrary for Receipt {
type Parameters = ();
fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
use proptest::prelude::{any, prop_compose};
prop_compose! {
fn arbitrary_receipt()(tx_type in any::<TxType>(),
success in any::<bool>(),
cumulative_gas_used in any::<u64>(),
logs in proptest::collection::vec(proptest::arbitrary::any::<Log>(), 0..=20),
_deposit_nonce in any::<Option<u64>>()) -> Receipt
{
Receipt { tx_type,
success,
cumulative_gas_used,
logs,
// Only receipts for deposit transactions may contain a deposit nonce
#[cfg(feature = "optimism")]
deposit_nonce: (tx_type == TxType::DEPOSIT).then_some(_deposit_nonce).flatten()
}
}
};
arbitrary_receipt().boxed()
}
type Strategy = proptest::strategy::BoxedStrategy<Receipt>;
}
#[cfg(any(test, feature = "arbitrary"))]
impl<'a> arbitrary::Arbitrary<'a> for Receipt {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let tx_type = TxType::arbitrary(u)?;
let success = bool::arbitrary(u)?;
let cumulative_gas_used = u64::arbitrary(u)?;
let logs = Vec::<Log>::arbitrary(u)?;
// Only receipts for deposit transactions may contain a deposit nonce
#[cfg(feature = "optimism")]
let deposit_nonce =
if tx_type == TxType::DEPOSIT { Option::<u64>::arbitrary(u)? } else { None };
Ok(Self {
tx_type,
success,
cumulative_gas_used,
logs,
#[cfg(feature = "optimism")]
deposit_nonce,
})
}
}
impl ReceiptWithBloom {
/// Encode receipt with or without the header data.
pub fn encode_inner(&self, out: &mut dyn BufMut, with_header: bool) {
@@ -201,7 +256,27 @@ impl ReceiptWithBloom {
let bloom = Decodable::decode(b)?;
let logs = alloy_rlp::Decodable::decode(b)?;
let this = Self { receipt: Receipt { tx_type, success, cumulative_gas_used, logs }, bloom };
let receipt = match tx_type {
#[cfg(feature = "optimism")]
TxType::DEPOSIT => {
let consumed = started_len - b.len();
let has_nonce = rlp_head.payload_length - consumed > 0;
let deposit_nonce =
if has_nonce { Some(alloy_rlp::Decodable::decode(b)?) } else { None };
Receipt { tx_type, success, cumulative_gas_used, logs, deposit_nonce }
}
_ => Receipt {
tx_type,
success,
cumulative_gas_used,
logs,
#[cfg(feature = "optimism")]
deposit_nonce: None,
},
};
let this = Self { receipt, bloom };
let consumed = started_len - b.len();
if consumed != rlp_head.payload_length {
return Err(alloy_rlp::Error::ListLengthMismatch {
@@ -239,17 +314,25 @@ impl Decodable for ReceiptWithBloom {
let receipt_type = *buf.first().ok_or(alloy_rlp::Error::Custom(
"typed receipt cannot be decoded from an empty slice",
))?;
if receipt_type == 0x01 {
buf.advance(1);
Self::decode_receipt(buf, TxType::EIP2930)
} else if receipt_type == 0x02 {
buf.advance(1);
Self::decode_receipt(buf, TxType::EIP1559)
} else if receipt_type == 0x03 {
buf.advance(1);
Self::decode_receipt(buf, TxType::EIP4844)
} else {
Err(alloy_rlp::Error::Custom("invalid receipt type"))
match receipt_type {
0x01 => {
buf.advance(1);
Self::decode_receipt(buf, TxType::EIP2930)
}
0x02 => {
buf.advance(1);
Self::decode_receipt(buf, TxType::EIP1559)
}
0x03 => {
buf.advance(1);
Self::decode_receipt(buf, TxType::EIP4844)
}
#[cfg(feature = "optimism")]
0x7E => {
buf.advance(1);
Self::decode_receipt(buf, TxType::DEPOSIT)
}
_ => Err(alloy_rlp::Error::Custom("invalid receipt type")),
}
}
Ordering::Equal => {
@@ -317,6 +400,13 @@ impl<'a> ReceiptWithBloomEncoder<'a> {
rlp_head.payload_length += self.bloom.length();
rlp_head.payload_length += self.receipt.logs.length();
#[cfg(feature = "optimism")]
if self.receipt.tx_type == TxType::DEPOSIT {
if let Some(deposit_nonce) = self.receipt.deposit_nonce {
rlp_head.payload_length += deposit_nonce.length();
}
}
rlp_head
}
@@ -327,6 +417,12 @@ impl<'a> ReceiptWithBloomEncoder<'a> {
self.receipt.cumulative_gas_used.encode(out);
self.bloom.encode(out);
self.receipt.logs.encode(out);
#[cfg(feature = "optimism")]
if self.receipt.tx_type == TxType::DEPOSIT {
if let Some(deposit_nonce) = self.receipt.deposit_nonce {
deposit_nonce.encode(out)
}
}
}
/// Encode receipt with or without the header data.
@@ -355,6 +451,10 @@ impl<'a> ReceiptWithBloomEncoder<'a> {
TxType::EIP4844 => {
out.put_u8(0x03);
}
#[cfg(feature = "optimism")]
TxType::DEPOSIT => {
out.put_u8(0x7E);
}
_ => unreachable!("legacy handled; qed."),
}
out.put_slice(payload.as_ref());
@@ -374,7 +474,7 @@ impl<'a> Encodable for ReceiptWithBloomEncoder<'a> {
fn length(&self) -> usize {
let mut payload_len = self.receipt_length();
// account for eip-2718 type prefix and set the list
if matches!(self.receipt.tx_type, TxType::EIP1559 | TxType::EIP2930 | TxType::EIP4844) {
if !matches!(self.receipt.tx_type, TxType::Legacy) {
payload_len += 1;
// we include a string header for typed receipts, so include the length here
payload_len += length_of_length(payload_len);
@@ -410,6 +510,8 @@ mod tests {
data: bytes!("0100ff"),
}],
success: false,
#[cfg(feature = "optimism")]
deposit_nonce: None,
},
bloom: [0; 256].into(),
};
@@ -440,6 +542,8 @@ mod tests {
data: bytes!("0100ff"),
}],
success: false,
#[cfg(feature = "optimism")]
deposit_nonce: None,
},
bloom: [0; 256].into(),
};
@@ -448,6 +552,31 @@ mod tests {
assert_eq!(receipt, expected);
}
#[cfg(feature = "optimism")]
#[test]
fn decode_deposit_receipt_regolith_roundtrip() {
let data = hex!("7ef9010c0182b741b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0833d3bbf");
// Deposit Receipt (post-regolith)
let expected = ReceiptWithBloom {
receipt: Receipt {
tx_type: TxType::DEPOSIT,
cumulative_gas_used: 46913,
logs: vec![],
success: true,
deposit_nonce: Some(4012991),
},
bloom: [0; 256].into(),
};
let receipt = ReceiptWithBloom::decode(&mut &data[..]).unwrap();
assert_eq!(receipt, expected);
let mut buf = BytesMut::default();
receipt.encode_inner(&mut buf, false);
assert_eq!(buf.freeze(), &data[..]);
}
#[test]
fn gigantic_receipt() {
let receipt = Receipt {
@@ -470,6 +599,8 @@ mod tests {
data: Bytes::from(vec![1; 0xffffff]),
},
],
#[cfg(feature = "optimism")]
deposit_nonce: None,
};
let mut data = vec![];

View File

@@ -8,6 +8,15 @@ pub fn revm_spec_by_timestamp_after_merge(
chain_spec: &ChainSpec,
timestamp: u64,
) -> revm_primitives::SpecId {
#[cfg(feature = "optimism")]
if chain_spec.is_optimism() {
if chain_spec.fork(Hardfork::Regolith).active_at_timestamp(timestamp) {
return revm_primitives::REGOLITH
} else {
return revm_primitives::BEDROCK
}
}
if chain_spec.is_cancun_active_at_timestamp(timestamp) {
revm_primitives::CANCUN
} else if chain_spec.is_shanghai_active_at_timestamp(timestamp) {
@@ -19,6 +28,15 @@ pub fn revm_spec_by_timestamp_after_merge(
/// return revm_spec from spec configuration.
pub fn revm_spec(chain_spec: &ChainSpec, block: Head) -> revm_primitives::SpecId {
#[cfg(feature = "optimism")]
if chain_spec.is_optimism() {
if chain_spec.fork(Hardfork::Regolith).active_at_head(&block) {
return revm_primitives::REGOLITH
} else if chain_spec.fork(Hardfork::Bedrock).active_at_head(&block) {
return revm_primitives::BEDROCK
}
}
if chain_spec.fork(Hardfork::Cancun).active_at_head(&block) {
revm_primitives::CANCUN
} else if chain_spec.fork(Hardfork::Shanghai).active_at_head(&block) {
@@ -112,6 +130,23 @@ mod tests {
revm_spec(&ChainSpecBuilder::mainnet().frontier_activated().build(), Head::default()),
revm_primitives::FRONTIER
);
#[cfg(feature = "optimism")]
{
#[inline(always)]
fn op_cs(f: impl FnOnce(ChainSpecBuilder) -> ChainSpecBuilder) -> ChainSpec {
let cs = ChainSpecBuilder::mainnet().chain(crate::Chain::Id(10));
f(cs).build()
}
assert_eq!(
revm_spec(&op_cs(|cs| cs.bedrock_activated()), Head::default()),
revm_primitives::BEDROCK
);
assert_eq!(
revm_spec(&op_cs(|cs| cs.regolith_activated()), Head::default()),
revm_primitives::REGOLITH
);
}
}
#[test]

View File

@@ -4,9 +4,12 @@ use crate::{
revm::config::revm_spec,
revm_primitives::{AnalysisKind, BlockEnv, CfgEnv, Env, SpecId, TransactTo, TxEnv},
Address, Bytes, Chain, ChainSpec, Head, Header, Transaction, TransactionKind,
TransactionSignedEcRecovered, TxEip1559, TxEip2930, TxEip4844, TxLegacy, B256, U256,
TransactionSignedEcRecovered, B256, U256,
};
#[cfg(feature = "optimism")]
use revm_primitives::OptimismFields;
/// Convenience function to call both [fill_cfg_env] and [fill_block_env]
pub fn fill_cfg_and_block_env(
cfg: &mut CfgEnv,
@@ -41,6 +44,11 @@ pub fn fill_cfg_env(
cfg_env.chain_id = chain_spec.chain().id();
cfg_env.spec_id = spec_id;
cfg_env.perf_analyse_created_bytecodes = AnalysisKind::Analyse;
#[cfg(feature = "optimism")]
{
cfg_env.optimism = chain_spec.is_optimism();
}
}
/// Fill block environment from Block.
@@ -109,7 +117,17 @@ pub fn recover_header_signer(header: &Header) -> Option<Address> {
/// Returns a new [TxEnv] filled with the transaction's data.
pub fn tx_env_with_recovered(transaction: &TransactionSignedEcRecovered) -> TxEnv {
let mut tx_env = TxEnv::default();
#[cfg(not(feature = "optimism"))]
fill_tx_env(&mut tx_env, transaction.as_ref(), transaction.signer());
#[cfg(feature = "optimism")]
{
let mut envelope_buf = Vec::with_capacity(transaction.length_without_header());
transaction.encode_enveloped(&mut envelope_buf);
fill_tx_env(&mut tx_env, transaction.as_ref(), transaction.signer(), envelope_buf.into());
}
tx_env
}
@@ -149,6 +167,13 @@ pub fn fill_tx_env_with_beacon_root_contract_call(env: &mut Env, parent_beacon_b
// blob fields can be None for this tx
blob_hashes: Vec::new(),
max_fee_per_blob_gas: None,
#[cfg(feature = "optimism")]
optimism: OptimismFields {
source_hash: None,
mint: None,
is_system_transaction: Some(false),
enveloped_tx: None,
},
};
// ensure the block gas limit is >= the tx
@@ -159,63 +184,65 @@ pub fn fill_tx_env_with_beacon_root_contract_call(env: &mut Env, parent_beacon_b
}
/// Fill transaction environment from [TransactionSignedEcRecovered].
#[cfg(not(feature = "optimism"))]
pub fn fill_tx_env_with_recovered(tx_env: &mut TxEnv, transaction: &TransactionSignedEcRecovered) {
fill_tx_env(tx_env, transaction.as_ref(), transaction.signer())
fill_tx_env(tx_env, transaction.as_ref(), transaction.signer());
}
/// Fill transaction environment from [TransactionSignedEcRecovered] and the given envelope.
#[cfg(feature = "optimism")]
pub fn fill_tx_env_with_recovered(
tx_env: &mut TxEnv,
transaction: &TransactionSignedEcRecovered,
envelope: Bytes,
) {
fill_tx_env(tx_env, transaction.as_ref(), transaction.signer(), envelope);
}
/// Fill transaction environment from a [Transaction] and the given sender address.
pub fn fill_tx_env<T>(tx_env: &mut TxEnv, transaction: T, sender: Address)
where
pub fn fill_tx_env<T>(
tx_env: &mut TxEnv,
transaction: T,
sender: Address,
#[cfg(feature = "optimism")] envelope: Bytes,
) where
T: AsRef<Transaction>,
{
tx_env.caller = sender;
match transaction.as_ref() {
Transaction::Legacy(TxLegacy {
nonce,
chain_id,
gas_price,
gas_limit,
to,
value,
input,
}) => {
tx_env.gas_limit = *gas_limit;
tx_env.gas_price = U256::from(*gas_price);
Transaction::Legacy(tx) => {
tx_env.gas_limit = tx.gas_limit;
tx_env.gas_price = U256::from(tx.gas_price);
tx_env.gas_priority_fee = None;
tx_env.transact_to = match to {
TransactionKind::Call(to) => TransactTo::Call(*to),
tx_env.transact_to = match tx.to {
TransactionKind::Call(to) => TransactTo::Call(to),
TransactionKind::Create => TransactTo::create(),
};
tx_env.value = (*value).into();
tx_env.data = input.clone();
tx_env.chain_id = *chain_id;
tx_env.nonce = Some(*nonce);
tx_env.value = tx.value.into();
tx_env.data = tx.input.clone();
tx_env.chain_id = tx.chain_id;
tx_env.nonce = Some(tx.nonce);
tx_env.access_list.clear();
tx_env.blob_hashes.clear();
tx_env.max_fee_per_blob_gas.take();
#[cfg(feature = "optimism")]
fill_op_tx_env(tx_env, transaction, envelope);
}
Transaction::Eip2930(TxEip2930 {
nonce,
chain_id,
gas_price,
gas_limit,
to,
value,
input,
access_list,
}) => {
tx_env.gas_limit = *gas_limit;
tx_env.gas_price = U256::from(*gas_price);
Transaction::Eip2930(tx) => {
tx_env.gas_limit = tx.gas_limit;
tx_env.gas_price = U256::from(tx.gas_price);
tx_env.gas_priority_fee = None;
tx_env.transact_to = match to {
TransactionKind::Call(to) => TransactTo::Call(*to),
tx_env.transact_to = match tx.to {
TransactionKind::Call(to) => TransactTo::Call(to),
TransactionKind::Create => TransactTo::create(),
};
tx_env.value = (*value).into();
tx_env.data = input.clone();
tx_env.chain_id = Some(*chain_id);
tx_env.nonce = Some(*nonce);
tx_env.access_list = access_list
tx_env.value = tx.value.into();
tx_env.data = tx.input.clone();
tx_env.chain_id = Some(tx.chain_id);
tx_env.nonce = Some(tx.nonce);
tx_env.access_list = tx
.access_list
.0
.iter()
.map(|l| {
@@ -224,30 +251,24 @@ where
.collect();
tx_env.blob_hashes.clear();
tx_env.max_fee_per_blob_gas.take();
#[cfg(feature = "optimism")]
fill_op_tx_env(tx_env, transaction, envelope);
}
Transaction::Eip1559(TxEip1559 {
nonce,
chain_id,
gas_limit,
max_fee_per_gas,
max_priority_fee_per_gas,
to,
value,
input,
access_list,
}) => {
tx_env.gas_limit = *gas_limit;
tx_env.gas_price = U256::from(*max_fee_per_gas);
tx_env.gas_priority_fee = Some(U256::from(*max_priority_fee_per_gas));
tx_env.transact_to = match to {
TransactionKind::Call(to) => TransactTo::Call(*to),
Transaction::Eip1559(tx) => {
tx_env.gas_limit = tx.gas_limit;
tx_env.gas_price = U256::from(tx.max_fee_per_gas);
tx_env.gas_priority_fee = Some(U256::from(tx.max_priority_fee_per_gas));
tx_env.transact_to = match tx.to {
TransactionKind::Call(to) => TransactTo::Call(to),
TransactionKind::Create => TransactTo::create(),
};
tx_env.value = (*value).into();
tx_env.data = input.clone();
tx_env.chain_id = Some(*chain_id);
tx_env.nonce = Some(*nonce);
tx_env.access_list = access_list
tx_env.value = tx.value.into();
tx_env.data = tx.input.clone();
tx_env.chain_id = Some(tx.chain_id);
tx_env.nonce = Some(tx.nonce);
tx_env.access_list = tx
.access_list
.0
.iter()
.map(|l| {
@@ -256,40 +277,74 @@ where
.collect();
tx_env.blob_hashes.clear();
tx_env.max_fee_per_blob_gas.take();
#[cfg(feature = "optimism")]
fill_op_tx_env(tx_env, transaction, envelope);
}
Transaction::Eip4844(TxEip4844 {
nonce,
chain_id,
gas_limit,
max_fee_per_gas,
max_priority_fee_per_gas,
to,
value,
access_list,
blob_versioned_hashes,
max_fee_per_blob_gas,
input,
}) => {
tx_env.gas_limit = *gas_limit;
tx_env.gas_price = U256::from(*max_fee_per_gas);
tx_env.gas_priority_fee = Some(U256::from(*max_priority_fee_per_gas));
tx_env.transact_to = match to {
TransactionKind::Call(to) => TransactTo::Call(*to),
Transaction::Eip4844(tx) => {
tx_env.gas_limit = tx.gas_limit;
tx_env.gas_price = U256::from(tx.max_fee_per_gas);
tx_env.gas_priority_fee = Some(U256::from(tx.max_priority_fee_per_gas));
tx_env.transact_to = match tx.to {
TransactionKind::Call(to) => TransactTo::Call(to),
TransactionKind::Create => TransactTo::create(),
};
tx_env.value = (*value).into();
tx_env.data = input.clone();
tx_env.chain_id = Some(*chain_id);
tx_env.nonce = Some(*nonce);
tx_env.access_list = access_list
tx_env.value = tx.value.into();
tx_env.data = tx.input.clone();
tx_env.chain_id = Some(tx.chain_id);
tx_env.nonce = Some(tx.nonce);
tx_env.access_list = tx
.access_list
.0
.iter()
.map(|l| {
(l.address, l.storage_keys.iter().map(|k| U256::from_be_bytes(k.0)).collect())
})
.collect();
tx_env.blob_hashes = blob_versioned_hashes.clone();
tx_env.max_fee_per_blob_gas = Some(U256::from(*max_fee_per_blob_gas));
tx_env.blob_hashes = tx.blob_versioned_hashes.clone();
tx_env.max_fee_per_blob_gas = Some(U256::from(tx.max_fee_per_blob_gas));
#[cfg(feature = "optimism")]
fill_op_tx_env(tx_env, transaction, envelope);
}
#[cfg(feature = "optimism")]
Transaction::Deposit(tx) => {
tx_env.gas_limit = tx.gas_limit;
tx_env.gas_price = U256::ZERO;
tx_env.gas_priority_fee = None;
match tx.to {
TransactionKind::Call(to) => tx_env.transact_to = TransactTo::Call(to),
TransactionKind::Create => tx_env.transact_to = TransactTo::create(),
}
tx_env.value = tx.value.into();
tx_env.data = tx.input.clone();
tx_env.chain_id = None;
tx_env.nonce = None;
fill_op_tx_env(tx_env, transaction, envelope);
}
}
}
#[cfg(feature = "optimism")]
#[inline(always)]
fn fill_op_tx_env<T: AsRef<Transaction>>(tx_env: &mut TxEnv, transaction: T, envelope: Bytes) {
match transaction.as_ref() {
Transaction::Deposit(tx) => {
tx_env.optimism = OptimismFields {
source_hash: Some(tx.source_hash),
mint: tx.mint,
is_system_transaction: Some(tx.is_system_transaction),
enveloped_tx: Some(envelope),
};
}
_ => {
tx_env.optimism = OptimismFields {
source_hash: None,
mint: None,
is_system_transaction: Some(false),
enveloped_tx: Some(envelope),
}
}
}
}

View File

@@ -49,6 +49,15 @@ mod tx_value;
pub(crate) mod util;
mod variant;
#[cfg(feature = "optimism")]
mod optimism;
#[cfg(feature = "optimism")]
pub use optimism::TxDeposit;
#[cfg(feature = "optimism")]
use revm_primitives::U256;
#[cfg(feature = "optimism")]
pub use tx_type::DEPOSIT_TX_TYPE_ID;
// Expected number of transactions where we can expect a speed-up by recovering the senders in
// parallel.
pub(crate) static PARALLEL_SENDER_RECOVERY_THRESHOLD: Lazy<usize> =
@@ -103,6 +112,9 @@ pub enum Transaction {
/// EIP-4844, also known as proto-danksharding, implements the framework and logic of
/// danksharding, introducing new transaction formats and verification rules.
Eip4844(TxEip4844),
/// Optimism deposit transaction.
#[cfg(feature = "optimism")]
Deposit(TxDeposit),
}
// === impl Transaction ===
@@ -116,6 +128,8 @@ impl Transaction {
Transaction::Eip2930(tx) => tx.signature_hash(),
Transaction::Eip1559(tx) => tx.signature_hash(),
Transaction::Eip4844(tx) => tx.signature_hash(),
#[cfg(feature = "optimism")]
Transaction::Deposit(_) => B256::ZERO,
}
}
@@ -126,6 +140,8 @@ impl Transaction {
Transaction::Eip2930(TxEip2930 { chain_id, .. }) |
Transaction::Eip1559(TxEip1559 { chain_id, .. }) |
Transaction::Eip4844(TxEip4844 { chain_id, .. }) => Some(*chain_id),
#[cfg(feature = "optimism")]
Transaction::Deposit(_) => None,
}
}
@@ -136,6 +152,8 @@ impl Transaction {
Transaction::Eip2930(TxEip2930 { chain_id: ref mut c, .. }) |
Transaction::Eip1559(TxEip1559 { chain_id: ref mut c, .. }) |
Transaction::Eip4844(TxEip4844 { chain_id: ref mut c, .. }) => *c = chain_id,
#[cfg(feature = "optimism")]
Transaction::Deposit(_) => { /* noop */ }
}
}
@@ -147,6 +165,8 @@ impl Transaction {
Transaction::Eip2930(TxEip2930 { to, .. }) |
Transaction::Eip1559(TxEip1559 { to, .. }) |
Transaction::Eip4844(TxEip4844 { to, .. }) => to,
#[cfg(feature = "optimism")]
Transaction::Deposit(TxDeposit { to, .. }) => to,
}
}
@@ -162,6 +182,8 @@ impl Transaction {
Transaction::Eip2930(access_list_tx) => access_list_tx.tx_type(),
Transaction::Eip1559(dynamic_fee_tx) => dynamic_fee_tx.tx_type(),
Transaction::Eip4844(blob_tx) => blob_tx.tx_type(),
#[cfg(feature = "optimism")]
Transaction::Deposit(deposit_tx) => deposit_tx.tx_type(),
}
}
@@ -172,6 +194,8 @@ impl Transaction {
Transaction::Eip2930(TxEip2930 { value, .. }) |
Transaction::Eip1559(TxEip1559 { value, .. }) |
Transaction::Eip4844(TxEip4844 { value, .. }) => value,
#[cfg(feature = "optimism")]
Transaction::Deposit(TxDeposit { value, .. }) => value,
}
}
@@ -182,6 +206,9 @@ impl Transaction {
Transaction::Eip2930(TxEip2930 { nonce, .. }) |
Transaction::Eip1559(TxEip1559 { nonce, .. }) |
Transaction::Eip4844(TxEip4844 { nonce, .. }) => *nonce,
// Deposit transactions do not have nonces.
#[cfg(feature = "optimism")]
Transaction::Deposit(_) => 0,
}
}
@@ -194,6 +221,8 @@ impl Transaction {
Transaction::Eip2930(tx) => Some(&tx.access_list),
Transaction::Eip1559(tx) => Some(&tx.access_list),
Transaction::Eip4844(tx) => Some(&tx.access_list),
#[cfg(feature = "optimism")]
Transaction::Deposit(_) => None,
}
}
@@ -204,6 +233,8 @@ impl Transaction {
Transaction::Eip2930(TxEip2930 { gas_limit, .. }) |
Transaction::Eip1559(TxEip1559 { gas_limit, .. }) |
Transaction::Eip4844(TxEip4844 { gas_limit, .. }) => *gas_limit,
#[cfg(feature = "optimism")]
Transaction::Deposit(TxDeposit { gas_limit, .. }) => *gas_limit,
}
}
@@ -212,6 +243,8 @@ impl Transaction {
match self {
Transaction::Legacy(_) | Transaction::Eip2930(_) => false,
Transaction::Eip1559(_) | Transaction::Eip4844(_) => true,
#[cfg(feature = "optimism")]
Transaction::Deposit(_) => false,
}
}
@@ -224,6 +257,10 @@ impl Transaction {
Transaction::Eip2930(TxEip2930 { gas_price, .. }) => *gas_price,
Transaction::Eip1559(TxEip1559 { max_fee_per_gas, .. }) |
Transaction::Eip4844(TxEip4844 { max_fee_per_gas, .. }) => *max_fee_per_gas,
// Deposit transactions buy their L2 gas on L1 and, as such, the L2 gas is not
// refundable.
#[cfg(feature = "optimism")]
Transaction::Deposit(_) => 0,
}
}
@@ -238,6 +275,8 @@ impl Transaction {
Transaction::Eip4844(TxEip4844 { max_priority_fee_per_gas, .. }) => {
Some(*max_priority_fee_per_gas)
}
#[cfg(feature = "optimism")]
Transaction::Deposit(_) => None,
}
}
@@ -251,6 +290,8 @@ impl Transaction {
Transaction::Eip4844(TxEip4844 { blob_versioned_hashes, .. }) => {
Some(blob_versioned_hashes.to_vec())
}
#[cfg(feature = "optimism")]
Transaction::Deposit(_) => None,
}
}
@@ -292,6 +333,8 @@ impl Transaction {
Transaction::Eip4844(TxEip4844 { max_priority_fee_per_gas, .. }) => {
*max_priority_fee_per_gas
}
#[cfg(feature = "optimism")]
Transaction::Deposit(_) => 0,
}
}
@@ -304,6 +347,8 @@ impl Transaction {
Transaction::Eip2930(tx) => tx.gas_price,
Transaction::Eip1559(dynamic_tx) => dynamic_tx.effective_gas_price(base_fee),
Transaction::Eip4844(dynamic_tx) => dynamic_tx.effective_gas_price(base_fee),
#[cfg(feature = "optimism")]
Transaction::Deposit(_) => 0,
}
}
@@ -345,9 +390,47 @@ impl Transaction {
Transaction::Eip2930(TxEip2930 { input, .. }) |
Transaction::Eip1559(TxEip1559 { input, .. }) |
Transaction::Eip4844(TxEip4844 { input, .. }) => input,
#[cfg(feature = "optimism")]
Transaction::Deposit(TxDeposit { input, .. }) => input,
}
}
/// Returns the source hash of the transaction, which uniquely identifies its source.
/// If not a deposit transaction, this will always return `None`.
#[cfg(feature = "optimism")]
pub fn source_hash(&self) -> Option<B256> {
match self {
Transaction::Deposit(TxDeposit { source_hash, .. }) => Some(*source_hash),
_ => None,
}
}
/// Returns the amount of ETH locked up on L1 that will be minted on L2. If the transaction
/// is not a deposit transaction, this will always return `None`.
#[cfg(feature = "optimism")]
pub fn mint(&self) -> Option<u128> {
match self {
Transaction::Deposit(TxDeposit { mint, .. }) => *mint,
_ => None,
}
}
/// Returns whether or not the transaction is a system transaction. If the transaction
/// is not a deposit transaction, this will always return `false`.
#[cfg(feature = "optimism")]
pub fn is_system_transaction(&self) -> bool {
match self {
Transaction::Deposit(TxDeposit { is_system_transaction, .. }) => *is_system_transaction,
_ => false,
}
}
/// Returns whether or not the transaction is an Optimism Deposited transaction.
#[cfg(feature = "optimism")]
pub fn is_deposit(&self) -> bool {
matches!(self, Transaction::Deposit(_))
}
/// This encodes the transaction _without_ the signature, and is only suitable for creating a
/// hash intended for signing.
pub fn encode_without_signature(&self, out: &mut dyn bytes::BufMut) {
@@ -376,6 +459,8 @@ impl Transaction {
Transaction::Eip4844(blob_tx) => {
blob_tx.encode_with_signature(signature, out, with_header)
}
#[cfg(feature = "optimism")]
Transaction::Deposit(deposit_tx) => deposit_tx.encode(out, with_header),
}
}
@@ -386,6 +471,8 @@ impl Transaction {
Transaction::Eip2930(tx) => tx.nonce = nonce,
Transaction::Eip1559(tx) => tx.nonce = nonce,
Transaction::Eip4844(tx) => tx.nonce = nonce,
#[cfg(feature = "optimism")]
Transaction::Deposit(_) => { /* noop */ }
}
}
@@ -396,6 +483,8 @@ impl Transaction {
Transaction::Eip2930(tx) => tx.value = value,
Transaction::Eip1559(tx) => tx.value = value,
Transaction::Eip4844(tx) => tx.value = value,
#[cfg(feature = "optimism")]
Transaction::Deposit(tx) => tx.value = value,
}
}
@@ -406,6 +495,8 @@ impl Transaction {
Transaction::Eip2930(tx) => tx.input = input,
Transaction::Eip1559(tx) => tx.input = input,
Transaction::Eip4844(tx) => tx.input = input,
#[cfg(feature = "optimism")]
Transaction::Deposit(tx) => tx.input = input,
}
}
@@ -417,6 +508,8 @@ impl Transaction {
Transaction::Eip2930(tx) => tx.size(),
Transaction::Eip1559(tx) => tx.size(),
Transaction::Eip4844(tx) => tx.size(),
#[cfg(feature = "optimism")]
Transaction::Deposit(tx) => tx.size(),
}
}
@@ -502,31 +595,38 @@ impl From<TxEip4844> for Transaction {
}
impl Compact for Transaction {
// Serializes the TxType to the buffer if necessary, returning 2 bits of the type as an
// identifier instead of the length.
fn to_compact<B>(self, buf: &mut B) -> usize
where
B: bytes::BufMut + AsMut<[u8]>,
{
let identifier = self.tx_type().to_compact(buf);
match self {
Transaction::Legacy(tx) => {
tx.to_compact(buf);
0
}
Transaction::Eip2930(tx) => {
tx.to_compact(buf);
1
}
Transaction::Eip1559(tx) => {
tx.to_compact(buf);
2
}
Transaction::Eip4844(tx) => {
tx.to_compact(buf);
3
}
#[cfg(feature = "optimism")]
Transaction::Deposit(tx) => {
tx.to_compact(buf);
}
}
identifier
}
fn from_compact(buf: &[u8], identifier: usize) -> (Self, &[u8]) {
// For backwards compatibility purposes, only 2 bits of the type are encoded in the identifier
// parameter. In the case of a 3, the full transaction type is read from the buffer as a
// single byte.
fn from_compact(mut buf: &[u8], identifier: usize) -> (Self, &[u8]) {
match identifier {
0 => {
let (tx, buf) = TxLegacy::from_compact(buf, buf.len());
@@ -541,8 +641,24 @@ impl Compact for Transaction {
(Transaction::Eip1559(tx), buf)
}
3 => {
let (tx, buf) = TxEip4844::from_compact(buf, buf.len());
(Transaction::Eip4844(tx), buf)
// An identifier of 3 indicates that the transaction type did not fit into
// the backwards compatible 2 bit identifier, their transaction types are
// larger than 2 bits (eg. 4844 and Deposit Transactions). In this case,
// we need to read the concrete transaction type from the buffer by
// reading the full 8 bits (single byte) and match on this transaction type.
let identifier = buf.get_u8() as usize;
match identifier {
3 => {
let (tx, buf) = TxEip4844::from_compact(buf, buf.len());
(Transaction::Eip4844(tx), buf)
}
#[cfg(feature = "optimism")]
126 => {
let (tx, buf) = TxDeposit::from_compact(buf, buf.len());
(Transaction::Deposit(tx), buf)
}
_ => unreachable!("Junk data in database: unknown Transaction variant"),
}
}
_ => unreachable!("Junk data in database: unknown Transaction variant"),
}
@@ -572,6 +688,10 @@ impl Encodable for Transaction {
Transaction::Eip4844(blob_tx) => {
blob_tx.encode_for_signing(out);
}
#[cfg(feature = "optimism")]
Transaction::Deposit(deposit_tx) => {
deposit_tx.encode(out, true);
}
}
}
@@ -581,6 +701,8 @@ impl Encodable for Transaction {
Transaction::Eip2930(access_list_tx) => access_list_tx.payload_len_for_signature(),
Transaction::Eip1559(dynamic_fee_tx) => dynamic_fee_tx.payload_len_for_signature(),
Transaction::Eip4844(blob_tx) => blob_tx.payload_len_for_signature(),
#[cfg(feature = "optimism")]
Transaction::Deposit(deposit_tx) => deposit_tx.payload_len(),
}
}
}
@@ -861,6 +983,12 @@ impl TransactionSigned {
///
/// Returns `None` if the transaction's signature is invalid, see also [Self::recover_signer].
pub fn recover_signer(&self) -> Option<Address> {
// Optimism's Deposit transaction does not have a signature. Directly return the
// `from` address.
#[cfg(feature = "optimism")]
if let Transaction::Deposit(TxDeposit { from, .. }) = self.transaction {
return Some(from)
}
let signature_hash = self.signature_hash();
self.signature.recover_signer(signature_hash)
}
@@ -942,6 +1070,8 @@ impl TransactionSigned {
dynamic_fee_tx.payload_len_with_signature(&self.signature)
}
Transaction::Eip4844(blob_tx) => blob_tx.payload_len_with_signature(&self.signature),
#[cfg(feature = "optimism")]
Transaction::Deposit(deposit_tx) => deposit_tx.payload_len(),
}
}
@@ -1065,11 +1195,21 @@ impl TransactionSigned {
1 => Transaction::Eip2930(TxEip2930::decode_inner(data)?),
2 => Transaction::Eip1559(TxEip1559::decode_inner(data)?),
3 => Transaction::Eip4844(TxEip4844::decode_inner(data)?),
#[cfg(feature = "optimism")]
0x7E => Transaction::Deposit(TxDeposit::decode_inner(data)?),
_ => return Err(RlpError::Custom("unsupported typed transaction type")),
};
#[cfg(not(feature = "optimism"))]
let signature = Signature::decode(data)?;
#[cfg(feature = "optimism")]
let signature = if tx_type == DEPOSIT_TX_TYPE_ID {
Signature::default()
} else {
Signature::decode(data)?
};
let bytes_consumed = remaining_len - data.len();
if bytes_consumed != header.payload_length {
return Err(RlpError::UnexpectedLength)
@@ -1127,6 +1267,8 @@ impl TransactionSigned {
Transaction::Eip4844(blob_tx) => {
blob_tx.payload_len_with_signature_without_header(&self.signature)
}
#[cfg(feature = "optimism")]
Transaction::Deposit(deposit_tx) => deposit_tx.payload_len_without_header(),
}
}
}
@@ -1212,6 +1354,14 @@ impl proptest::arbitrary::Arbitrary for TransactionSigned {
// Otherwise we might overflow when calculating `v` on `recalculate_hash`
transaction.set_chain_id(chain_id % (u64::MAX / 2 - 36));
}
#[cfg(feature = "optimism")]
let sig = if transaction.is_deposit() {
Signature { r: U256::ZERO, s: U256::ZERO, odd_y_parity: false }
} else {
sig
};
let mut tx =
TransactionSigned { hash: Default::default(), signature: sig, transaction };
tx.hash = tx.recalculate_hash();
@@ -1232,14 +1382,16 @@ impl<'a> arbitrary::Arbitrary<'a> for TransactionSigned {
transaction.set_chain_id(chain_id % (u64::MAX / 2 - 36));
}
let mut tx = TransactionSigned {
hash: Default::default(),
signature: Signature::arbitrary(u)?,
transaction,
};
tx.hash = tx.recalculate_hash();
let signature = Signature::arbitrary(u)?;
Ok(tx)
#[cfg(feature = "optimism")]
let signature = if transaction.is_deposit() {
Signature { r: U256::ZERO, s: U256::ZERO, odd_y_parity: false }
} else {
signature
};
Ok(TransactionSigned::from_transaction_and_signature(transaction, signature))
}
}

View File

@@ -0,0 +1,169 @@
use crate::{Address, Bytes, TransactionKind, TxType, TxValue, B256};
use alloy_rlp::{
length_of_length, Decodable, Encodable, Error as DecodeError, Header, EMPTY_STRING_CODE,
};
use bytes::Buf;
use reth_codecs::{main_codec, Compact};
use std::mem;
/// Deposit transactions, also known as deposits are initiated on L1, and executed on L2.
#[main_codec]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
pub struct TxDeposit {
/// Hash that uniquely identifies the source of the deposit.
pub source_hash: B256,
/// The address of the sender account.
pub from: Address,
/// The address of the recipient account, or the null (zero-length) address if the deposited
/// transaction is a contract creation.
pub to: TransactionKind,
/// The ETH value to mint on L2.
pub mint: Option<u128>,
/// The ETH value to send to the recipient account.
pub value: TxValue,
/// The gas limit for the L2 transaction.
pub gas_limit: u64,
/// Field indicating if this transaction is exempt from the L2 gas limit.
pub is_system_transaction: bool,
/// Input has two uses depending if transaction is Create or Call (if `to` field is None or
/// Some).
pub input: Bytes,
}
impl TxDeposit {
/// Calculates a heuristic for the in-memory size of the [TxDeposit] transaction.
#[inline]
pub fn size(&self) -> usize {
mem::size_of::<B256>() + // source_hash
mem::size_of::<Address>() + // from
self.to.size() + // to
mem::size_of::<Option<u128>>() + // mint
mem::size_of::<TxValue>() + // value
mem::size_of::<u64>() + // gas_limit
mem::size_of::<bool>() + // is_system_transaction
self.input.len() // input
}
/// Decodes the inner [TxDeposit] fields from RLP bytes.
///
/// NOTE: This assumes a RLP header has already been decoded, and _just_ decodes the following
/// RLP fields in the following order:
///
/// - `source_hash`
/// - `from`
/// - `to`
/// - `mint`
/// - `value`
/// - `gas_limit`
/// - `is_system_transaction`
/// - `input`
pub fn decode_inner(buf: &mut &[u8]) -> Result<Self, DecodeError> {
Ok(Self {
source_hash: Decodable::decode(buf)?,
from: Decodable::decode(buf)?,
to: Decodable::decode(buf)?,
mint: if *buf.first().ok_or(DecodeError::InputTooShort)? == EMPTY_STRING_CODE {
buf.advance(1);
None
} else {
Some(Decodable::decode(buf)?)
},
value: Decodable::decode(buf)?,
gas_limit: Decodable::decode(buf)?,
is_system_transaction: Decodable::decode(buf)?,
input: Decodable::decode(buf)?,
})
}
/// Outputs the length of the transaction's fields, without a RLP header or length of the
/// eip155 fields.
pub(crate) fn fields_len(&self) -> usize {
let mut len = 0;
len += self.source_hash.length();
len += self.from.length();
len += self.to.length();
len += self.mint.map_or(1, |mint| mint.length());
len += self.value.length();
len += self.gas_limit.length();
len += self.is_system_transaction.length();
len += self.input.0.length();
len
}
/// Encodes only the transaction's fields into the desired buffer, without a RLP header.
/// <https://github.com/ethereum-optimism/optimism/blob/develop/specs/deposits.md#the-deposited-transaction-type>
pub(crate) fn encode_fields(&self, out: &mut dyn bytes::BufMut) {
self.source_hash.encode(out);
self.from.encode(out);
self.to.encode(out);
if let Some(mint) = self.mint {
mint.encode(out);
} else {
out.put_u8(EMPTY_STRING_CODE);
}
self.value.encode(out);
self.gas_limit.encode(out);
self.is_system_transaction.encode(out);
self.input.encode(out);
}
/// Inner encoding function that is used for both rlp [`Encodable`] trait and for calculating
/// hash that for eip2718 does not require rlp header
pub(crate) fn encode(&self, out: &mut dyn bytes::BufMut, with_header: bool) {
let payload_length = self.fields_len();
if with_header {
Header {
list: false,
payload_length: 1 + length_of_length(payload_length) + payload_length,
}
.encode(out);
}
out.put_u8(self.tx_type() as u8);
let header = Header { list: true, payload_length };
header.encode(out);
self.encode_fields(out);
}
/// Output the length of the RLP signed transaction encoding. This encodes with a RLP header.
pub(crate) fn payload_len(&self) -> usize {
let payload_length = self.fields_len();
// 'tx type' + 'header length' + 'payload length'
let len = 1 + length_of_length(payload_length) + payload_length;
length_of_length(len) + len
}
pub(crate) fn payload_len_without_header(&self) -> usize {
let payload_length = self.fields_len();
// 'transaction type byte length' + 'header length' + 'payload length'
1 + length_of_length(payload_length) + payload_length
}
/// Get the transaction type
pub(crate) fn tx_type(&self) -> TxType {
TxType::DEPOSIT
}
}
#[cfg(test)]
mod tests {
use crate::{Bytes, TransactionSigned};
use alloy_rlp::Decodable;
use bytes::BytesMut;
use revm_primitives::hex_literal::hex;
#[test]
fn test_rlp_roundtrip() {
let bytes = Bytes::from_static(&hex!("7ef9015aa044bae9d41b8380d781187b426c6fe43df5fb2fb57bd4466ef6a701e1f01e015694deaddeaddeaddeaddeaddeaddeaddeaddead000194420000000000000000000000000000000000001580808408f0d18001b90104015d8eb900000000000000000000000000000000000000000000000000000000008057650000000000000000000000000000000000000000000000000000000063d96d10000000000000000000000000000000000000000000000000000000000009f35273d89754a1e0387b89520d989d3be9c37c1f32495a88faf1ea05c61121ab0d1900000000000000000000000000000000000000000000000000000000000000010000000000000000000000002d679b567db6187c0c8323fa982cfb88b74dbcc7000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240"));
let tx_a = TransactionSigned::decode_enveloped(bytes.clone()).unwrap();
let tx_b = TransactionSigned::decode(&mut &bytes[..]).unwrap();
let mut buf_a = BytesMut::default();
tx_a.encode_enveloped(&mut buf_a);
assert_eq!(&buf_a[..], &bytes[..]);
let mut buf_b = BytesMut::default();
tx_b.encode_enveloped(&mut buf_b);
assert_eq!(&buf_b[..], &bytes[..]);
}
}

View File

@@ -47,6 +47,16 @@ pub enum PooledTransactionsElement {
},
/// A blob transaction, which includes the transaction, blob data, commitments, and proofs.
BlobTransaction(BlobTransaction),
/// An Optimism deposit transaction
#[cfg(feature = "optimism")]
Deposit {
/// The inner transaction
transaction: crate::TxDeposit,
/// The signature
signature: Signature,
/// The hash of the transaction
hash: TxHash,
},
}
impl PooledTransactionsElement {
@@ -69,6 +79,8 @@ impl PooledTransactionsElement {
Self::Eip2930 { transaction, .. } => transaction.signature_hash(),
Self::Eip1559 { transaction, .. } => transaction.signature_hash(),
Self::BlobTransaction(blob_tx) => blob_tx.transaction.signature_hash(),
#[cfg(feature = "optimism")]
Self::Deposit { .. } => B256::ZERO,
}
}
@@ -79,6 +91,8 @@ impl PooledTransactionsElement {
PooledTransactionsElement::Eip2930 { hash, .. } => hash,
PooledTransactionsElement::Eip1559 { hash, .. } => hash,
PooledTransactionsElement::BlobTransaction(tx) => &tx.hash,
#[cfg(feature = "optimism")]
PooledTransactionsElement::Deposit { hash, .. } => hash,
}
}
@@ -89,6 +103,10 @@ impl PooledTransactionsElement {
Self::Eip2930 { signature, .. } => signature,
Self::Eip1559 { signature, .. } => signature,
Self::BlobTransaction(blob_tx) => &blob_tx.signature,
#[cfg(feature = "optimism")]
Self::Deposit { .. } => {
panic!("Deposit transactions do not have a signature! This is a bug.")
}
}
}
@@ -99,6 +117,8 @@ impl PooledTransactionsElement {
Self::Eip2930 { transaction, .. } => transaction.nonce,
Self::Eip1559 { transaction, .. } => transaction.nonce,
Self::BlobTransaction(blob_tx) => blob_tx.transaction.nonce,
#[cfg(feature = "optimism")]
Self::Deposit { .. } => 0,
}
}
@@ -204,6 +224,12 @@ impl PooledTransactionsElement {
signature: typed_tx.signature,
hash: typed_tx.hash,
}),
#[cfg(feature = "optimism")]
Transaction::Deposit(tx) => Ok(PooledTransactionsElement::Deposit {
transaction: tx,
signature: typed_tx.signature,
hash: typed_tx.hash,
}),
}
}
}
@@ -232,6 +258,12 @@ impl PooledTransactionsElement {
hash,
},
Self::BlobTransaction(blob_tx) => blob_tx.into_parts().0,
#[cfg(feature = "optimism")]
Self::Deposit { transaction, signature, hash } => TransactionSigned {
transaction: Transaction::Deposit(transaction),
signature,
hash,
},
}
}
@@ -254,6 +286,8 @@ impl PooledTransactionsElement {
// the encoding does not use a header, so we set `with_header` to false
blob_tx.payload_len_with_type(false)
}
#[cfg(feature = "optimism")]
Self::Deposit { transaction, .. } => transaction.payload_len_without_header(),
}
}
}
@@ -279,6 +313,10 @@ impl Encodable for PooledTransactionsElement {
// `rlp(tx_type || rlp([transaction_payload_body, blobs, commitments, proofs]))`
blob_tx.encode_with_type_inner(out, true);
}
#[cfg(feature = "optimism")]
Self::Deposit { transaction, .. } => {
transaction.encode(out, true);
}
}
}
@@ -300,6 +338,11 @@ impl Encodable for PooledTransactionsElement {
// the encoding uses a header, so we set `with_header` to true
blob_tx.payload_len_with_type(true)
}
#[cfg(feature = "optimism")]
Self::Deposit { transaction, .. } => {
// method computes the payload len with a RLP header
transaction.payload_len()
}
}
}
}
@@ -401,6 +444,12 @@ impl Decodable for PooledTransactionsElement {
signature: typed_tx.signature,
hash: typed_tx.hash,
}),
#[cfg(feature = "optimism")]
Transaction::Deposit(tx) => Ok(PooledTransactionsElement::Deposit {
transaction: tx,
signature: typed_tx.signature,
hash: typed_tx.hash,
}),
}
}
}
@@ -432,6 +481,10 @@ impl From<TransactionSigned> for PooledTransactionsElement {
sidecar: Default::default(),
})
}
#[cfg(feature = "optimism")]
Transaction::Deposit(tx) => {
PooledTransactionsElement::Deposit { transaction: tx, signature, hash }
}
}
}
}

View File

@@ -62,6 +62,11 @@ impl Signature {
/// Output the `v` of the signature depends on chain_id
#[inline]
pub fn v(&self, chain_id: Option<u64>) -> u64 {
#[cfg(feature = "optimism")]
if self.r == U256::ZERO && self.s == U256::ZERO {
return 0
}
if let Some(chain_id) = chain_id {
// EIP-155: v = {0, 1} + CHAIN_ID * 2 + 35
self.odd_y_parity as u64 + chain_id * 2 + 35
@@ -158,27 +163,50 @@ mod tests {
#[test]
fn test_payload_len_with_eip155_chain_id() {
let signature = Signature { r: U256::default(), s: U256::default(), odd_y_parity: false };
// Select 1 as an arbitrary nonzero value for R and S, as v() always returns 0 for (0, 0).
let signature = Signature { r: U256::from(1), s: U256::from(1), odd_y_parity: false };
assert_eq!(3, signature.payload_len_with_eip155_chain_id(None));
assert_eq!(3, signature.payload_len_with_eip155_chain_id(Some(1)));
assert_eq!(4, signature.payload_len_with_eip155_chain_id(Some(47)));
}
#[cfg(feature = "optimism")]
#[test]
fn test_zero_signature_payload_len_with_eip155_chain_id() {
let zero_signature = Signature { r: U256::ZERO, s: U256::ZERO, odd_y_parity: false };
assert_eq!(3, zero_signature.payload_len_with_eip155_chain_id(None));
assert_eq!(3, zero_signature.payload_len_with_eip155_chain_id(Some(1)));
assert_eq!(3, zero_signature.payload_len_with_eip155_chain_id(Some(47)));
}
#[test]
fn test_v() {
let signature = Signature { r: U256::default(), s: U256::default(), odd_y_parity: false };
// Select 1 as an arbitrary nonzero value for R and S, as v() always returns 0 for (0, 0).
let signature = Signature { r: U256::from(1), s: U256::from(1), odd_y_parity: false };
assert_eq!(27, signature.v(None));
assert_eq!(37, signature.v(Some(1)));
let signature = Signature { r: U256::default(), s: U256::default(), odd_y_parity: true };
let signature = Signature { r: U256::from(1), s: U256::from(1), odd_y_parity: true };
assert_eq!(28, signature.v(None));
assert_eq!(38, signature.v(Some(1)));
}
#[cfg(feature = "optimism")]
#[test]
fn test_zero_signature_v() {
let signature = Signature { r: U256::ZERO, s: U256::ZERO, odd_y_parity: false };
assert_eq!(0, signature.v(None));
assert_eq!(0, signature.v(Some(1)));
assert_eq!(0, signature.v(Some(47)));
}
#[test]
fn test_encode_and_decode_with_eip155_chain_id() {
let signature = Signature { r: U256::default(), s: U256::default(), odd_y_parity: false };
// Select 1 as an arbitrary nonzero value for R and S, as v() always returns 0 for (0, 0).
let signature = Signature { r: U256::from(1), s: U256::from(1), odd_y_parity: false };
let mut encoded = BytesMut::new();
signature.encode_with_eip155_chain_id(&mut encoded, None);

View File

@@ -1,4 +1,5 @@
use crate::U8;
use bytes::Buf;
use reth_codecs::{derive_arbitrary, Compact};
use serde::{Deserialize, Serialize};
@@ -15,6 +16,10 @@ pub const EIP1559_TX_TYPE_ID: u8 = 2;
/// Identifier for [TxEip4844](crate::TxEip4844) transaction.
pub const EIP4844_TX_TYPE_ID: u8 = 3;
/// Identifier for [TxDeposit](crate::TxDeposit) transaction.
#[cfg(feature = "optimism")]
pub const DEPOSIT_TX_TYPE_ID: u8 = 126;
/// Transaction Type
///
/// Currently being used as 2-bit type when encoding it to [`Compact`] on
@@ -34,6 +39,9 @@ pub enum TxType {
EIP1559 = 2_isize,
/// Shard Blob Transactions - EIP-4844
EIP4844 = 3_isize,
/// Optimism Deposit transaction.
#[cfg(feature = "optimism")]
DEPOSIT = 126_isize,
}
impl From<TxType> for u8 {
@@ -43,6 +51,8 @@ impl From<TxType> for u8 {
TxType::EIP2930 => EIP2930_TX_TYPE_ID,
TxType::EIP1559 => EIP1559_TX_TYPE_ID,
TxType::EIP4844 => EIP4844_TX_TYPE_ID,
#[cfg(feature = "optimism")]
TxType::DEPOSIT => DEPOSIT_TX_TYPE_ID,
}
}
}
@@ -54,7 +64,7 @@ impl From<TxType> for U8 {
}
impl Compact for TxType {
fn to_compact<B>(self, _: &mut B) -> usize
fn to_compact<B>(self, buf: &mut B) -> usize
where
B: bytes::BufMut + AsMut<[u8]>,
{
@@ -62,17 +72,30 @@ impl Compact for TxType {
TxType::Legacy => 0,
TxType::EIP2930 => 1,
TxType::EIP1559 => 2,
TxType::EIP4844 => 3,
_ => {
buf.put_u8(self as u8);
3
}
}
}
fn from_compact(buf: &[u8], identifier: usize) -> (Self, &[u8]) {
// For backwards compatibility purposes only 2 bits of the type are encoded in the identifier
// parameter. In the case of a 3, the full transaction type is read from the buffer as a
// single byte.
fn from_compact(mut buf: &[u8], identifier: usize) -> (Self, &[u8]) {
(
match identifier {
0 => TxType::Legacy,
1 => TxType::EIP2930,
2 => TxType::EIP1559,
_ => TxType::EIP4844,
_ => {
let identifier = buf.get_u8() as usize;
match identifier {
#[cfg(feature = "optimism")]
126 => TxType::DEPOSIT,
_ => TxType::EIP4844,
}
}
},
buf,
)

View File

@@ -1,5 +1,5 @@
#[allow(unused_imports)]
// suppress warning for UIntTryTo, which is required only when value-256 feature is disabled
// suppress warning for UIntTryTo, which is required only when optimism feature is disabled
use crate::{
ruint::{ToUintError, UintTryFrom, UintTryTo},
U256,
@@ -88,11 +88,11 @@ impl Compact for TxValue {
where
B: bytes::BufMut + AsMut<[u8]>,
{
#[cfg(feature = "value-256")]
#[cfg(feature = "optimism")]
{
self.0.to_compact(buf)
}
#[cfg(not(feature = "value-256"))]
#[cfg(not(feature = "optimism"))]
{
// SAFETY: For ethereum mainnet this is safe as the max value is
// 120000000000000000000000000 wei
@@ -103,12 +103,12 @@ impl Compact for TxValue {
#[allow(unreachable_code)]
fn from_compact(buf: &[u8], identifier: usize) -> (Self, &[u8]) {
#[cfg(feature = "value-256")]
#[cfg(feature = "optimism")]
{
let (i, buf) = U256::from_compact(buf, identifier);
(TxValue(i), buf)
}
#[cfg(not(feature = "value-256"))]
#[cfg(not(feature = "optimism"))]
{
let (i, buf) = u128::from_compact(buf, identifier);
(TxValue::from(i), buf)
@@ -119,12 +119,12 @@ impl Compact for TxValue {
#[cfg(any(test, feature = "arbitrary"))]
impl<'a> arbitrary::Arbitrary<'a> for TxValue {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
#[cfg(feature = "value-256")]
#[cfg(feature = "optimism")]
{
Ok(Self(U256::arbitrary(u)?))
}
#[cfg(not(feature = "value-256"))]
#[cfg(not(feature = "optimism"))]
{
Ok(Self::try_from(u128::arbitrary(u)?).expect("to fit"))
}
@@ -135,12 +135,12 @@ impl<'a> arbitrary::Arbitrary<'a> for TxValue {
impl proptest::arbitrary::Arbitrary for TxValue {
type Parameters = ParamsFor<()>;
fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
#[cfg(feature = "value-256")]
#[cfg(feature = "optimism")]
{
proptest::prelude::any::<U256>().prop_map(Self).boxed()
}
#[cfg(not(feature = "value-256"))]
#[cfg(not(feature = "optimism"))]
{
proptest::prelude::any::<u128>()
.prop_map(|num| Self::try_from(num).expect("to fit"))

View File

@@ -22,3 +22,10 @@ revm.workspace = true
# common
tracing.workspace = true
[features]
optimism = [
"revm/optimism",
"reth-primitives/optimism",
"reth-consensus-common/optimism",
"reth-interfaces/optimism",
]

View File

@@ -32,3 +32,7 @@ pub use revm::{self, *};
/// Ethereum DAO hardfork state change data.
pub mod eth_dao_fork;
/// Optimism-specific implementation and utilities for the executor
#[cfg(feature = "optimism")]
pub mod optimism;

View File

@@ -0,0 +1,175 @@
use reth_interfaces::executor::{self as reth_executor, BlockExecutionError};
use reth_primitives::{Block, Bytes, ChainSpec, Hardfork, TransactionKind, U256};
use revm::{
primitives::{BedrockSpec, RegolithSpec},
L1BlockInfo,
};
/// Optimism-specific processor implementation for the `EVMProcessor`
pub mod processor;
/// Extracts the [L1BlockInfo] from the L2 block. The L1 info transaction is always the first
/// transaction in the L2 block.
pub fn extract_l1_info(block: &Block) -> Result<L1BlockInfo, BlockExecutionError> {
let l1_info_tx_data = block
.body
.iter()
.find(|tx| matches!(tx.kind(), TransactionKind::Call(to) if to == &revm::optimism::L1_BLOCK_CONTRACT))
.ok_or(reth_executor::BlockExecutionError::OptimismBlockExecution(
reth_executor::OptimismBlockExecutionError::L1BlockInfoError {
message: "could not find l1 block info tx in the L2 block".to_string(),
}))
.and_then(|tx| {
tx.input().get(4..).ok_or(reth_executor::BlockExecutionError::OptimismBlockExecution(
reth_executor::OptimismBlockExecutionError::L1BlockInfoError {
message: "could not get l1 block info tx calldata bytes".to_string(),
}))
})?;
parse_l1_info_tx(l1_info_tx_data)
}
/// Parses the calldata of the [L1BlockInfo] transaction.
pub fn parse_l1_info_tx(data: &[u8]) -> Result<L1BlockInfo, BlockExecutionError> {
// The setL1BlockValues tx calldata must be exactly 260 bytes long, considering that
// we already removed the first 4 bytes (the function selector). Detailed breakdown:
// 32 bytes for the block number
// + 32 bytes for the block timestamp
// + 32 bytes for the base fee
// + 32 bytes for the block hash
// + 32 bytes for the block sequence number
// + 32 bytes for the batcher hash
// + 32 bytes for the fee overhead
// + 32 bytes for the fee scalar
if data.len() != 256 {
return Err(reth_executor::BlockExecutionError::OptimismBlockExecution(
reth_executor::OptimismBlockExecutionError::L1BlockInfoError {
message: "unexpected l1 block info tx calldata length found".to_string(),
},
))
}
let l1_base_fee = U256::try_from_be_slice(&data[64..96]).ok_or(
reth_executor::BlockExecutionError::OptimismBlockExecution(
reth_executor::OptimismBlockExecutionError::L1BlockInfoError {
message: "could not convert l1 base fee".to_string(),
},
),
)?;
let l1_fee_overhead = U256::try_from_be_slice(&data[192..224]).ok_or(
reth_executor::BlockExecutionError::OptimismBlockExecution(
reth_executor::OptimismBlockExecutionError::L1BlockInfoError {
message: "could not convert l1 fee overhead".to_string(),
},
),
)?;
let l1_fee_scalar = U256::try_from_be_slice(&data[224..256]).ok_or(
reth_executor::BlockExecutionError::OptimismBlockExecution(
reth_executor::OptimismBlockExecutionError::L1BlockInfoError {
message: "could not convert l1 fee scalar".to_string(),
},
),
)?;
Ok(L1BlockInfo { l1_base_fee, l1_fee_overhead, l1_fee_scalar })
}
/// An extension trait for [L1BlockInfo] that allows us to calculate the L1 cost of a transaction
/// based off of the [ChainSpec]'s activated hardfork.
pub trait RethL1BlockInfo {
/// Forwards an L1 transaction calculation to revm and returns the gas cost.
///
/// ### Takes
/// - `chain_spec`: The [ChainSpec] for the node.
/// - `timestamp`: The timestamp of the current block.
/// - `input`: The calldata of the transaction.
/// - `is_deposit`: Whether or not the transaction is a deposit.
fn l1_tx_data_fee(
&self,
chain_spec: &ChainSpec,
timestamp: u64,
input: &Bytes,
is_deposit: bool,
) -> Result<U256, BlockExecutionError>;
/// Computes the data gas cost for an L2 transaction.
///
/// ### Takes
/// - `chain_spec`: The [ChainSpec] for the node.
/// - `timestamp`: The timestamp of the current block.
/// - `input`: The calldata of the transaction.
fn l1_data_gas(
&self,
chain_spec: &ChainSpec,
timestamp: u64,
input: &Bytes,
) -> Result<U256, BlockExecutionError>;
}
impl RethL1BlockInfo for L1BlockInfo {
fn l1_tx_data_fee(
&self,
chain_spec: &ChainSpec,
timestamp: u64,
input: &Bytes,
is_deposit: bool,
) -> Result<U256, BlockExecutionError> {
if is_deposit {
return Ok(U256::ZERO)
}
if chain_spec.is_fork_active_at_timestamp(Hardfork::Regolith, timestamp) {
Ok(self.calculate_tx_l1_cost::<RegolithSpec>(input))
} else if chain_spec.is_fork_active_at_timestamp(Hardfork::Bedrock, timestamp) {
Ok(self.calculate_tx_l1_cost::<BedrockSpec>(input))
} else {
Err(reth_executor::BlockExecutionError::OptimismBlockExecution(
reth_executor::OptimismBlockExecutionError::L1BlockInfoError {
message: "Optimism hardforks are not active".to_string(),
},
))
}
}
fn l1_data_gas(
&self,
chain_spec: &ChainSpec,
timestamp: u64,
input: &Bytes,
) -> Result<U256, BlockExecutionError> {
if chain_spec.is_fork_active_at_timestamp(Hardfork::Regolith, timestamp) {
Ok(self.data_gas::<RegolithSpec>(input))
} else if chain_spec.is_fork_active_at_timestamp(Hardfork::Bedrock, timestamp) {
Ok(self.data_gas::<BedrockSpec>(input))
} else {
Err(reth_executor::BlockExecutionError::OptimismBlockExecution(
reth_executor::OptimismBlockExecutionError::L1BlockInfoError {
message: "Optimism hardforks are not active".to_string(),
},
))
}
}
}
#[cfg(test)]
mod test_l1_fee {
#[test]
fn sanity_l1_block() {
use super::*;
use reth_primitives::{hex_literal::hex, Bytes, Header, TransactionSigned};
let bytes = Bytes::from_static(&hex!("7ef9015aa044bae9d41b8380d781187b426c6fe43df5fb2fb57bd4466ef6a701e1f01e015694deaddeaddeaddeaddeaddeaddeaddeaddead000194420000000000000000000000000000000000001580808408f0d18001b90104015d8eb900000000000000000000000000000000000000000000000000000000008057650000000000000000000000000000000000000000000000000000000063d96d10000000000000000000000000000000000000000000000000000000000009f35273d89754a1e0387b89520d989d3be9c37c1f32495a88faf1ea05c61121ab0d1900000000000000000000000000000000000000000000000000000000000000010000000000000000000000002d679b567db6187c0c8323fa982cfb88b74dbcc7000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240"));
let l1_info_tx = TransactionSigned::decode_enveloped(bytes).unwrap();
let mock_block = Block {
header: Header::default(),
body: vec![l1_info_tx],
ommers: Vec::default(),
withdrawals: None,
};
let l1_info: L1BlockInfo = super::extract_l1_info(&mock_block).unwrap();
assert_eq!(l1_info.l1_base_fee, U256::from(652_114));
assert_eq!(l1_info.l1_fee_overhead, U256::from(2100));
assert_eq!(l1_info.l1_fee_scalar, U256::from(1_000_000));
}
}

View File

@@ -0,0 +1,150 @@
use reth_interfaces::executor::{BlockExecutionError, BlockValidationError};
use reth_primitives::{
revm::compat::into_reth_log, revm_primitives::ResultAndState, Address, Block, Hardfork,
Receipt, U256,
};
use reth_provider::{BlockExecutor, BlockExecutorStats, BundleStateWithReceipts};
use revm::DatabaseCommit;
use std::time::Instant;
use tracing::{debug, trace};
use crate::processor::{verify_receipt, EVMProcessor};
impl<'a> BlockExecutor for EVMProcessor<'a> {
fn execute(
&mut self,
block: &Block,
total_difficulty: U256,
senders: Option<Vec<Address>>,
) -> Result<(), BlockExecutionError> {
let receipts = self.execute_inner(block, total_difficulty, senders)?;
self.save_receipts(receipts)
}
fn execute_and_verify_receipt(
&mut self,
block: &Block,
total_difficulty: U256,
senders: Option<Vec<Address>>,
) -> Result<(), BlockExecutionError> {
// execute block
let receipts = self.execute_inner(block, total_difficulty, senders)?;
// TODO Before Byzantium, receipts contained state root that would mean that expensive
// operation as hashing that is needed for state root got calculated in every
// transaction This was replaced with is_success flag.
// See more about EIP here: https://eips.ethereum.org/EIPS/eip-658
if self.chain_spec.fork(Hardfork::Byzantium).active_at_block(block.header.number) {
let time = Instant::now();
if let Err(error) =
verify_receipt(block.header.receipts_root, block.header.logs_bloom, receipts.iter())
{
debug!(target: "evm", ?error, ?receipts, "receipts verification failed");
return Err(error)
};
self.stats.receipt_root_duration += time.elapsed();
}
self.save_receipts(receipts)
}
fn execute_transactions(
&mut self,
block: &Block,
total_difficulty: U256,
senders: Option<Vec<Address>>,
) -> Result<(Vec<Receipt>, u64), BlockExecutionError> {
self.init_env(&block.header, total_difficulty);
// perf: do not execute empty blocks
if block.body.is_empty() {
return Ok((Vec::new(), 0))
}
let senders = self.recover_senders(&block.body, senders)?;
let is_regolith =
self.chain_spec.fork(Hardfork::Regolith).active_at_timestamp(block.timestamp);
let mut cumulative_gas_used = 0;
let mut receipts = Vec::with_capacity(block.body.len());
for (transaction, sender) in block.body.iter().zip(senders) {
let time = Instant::now();
// The sum of the transactions gas limit, Tg, and the gas utilized in this block prior,
// must be no greater than the blocks gasLimit.
let block_available_gas = block.header.gas_limit - cumulative_gas_used;
if transaction.gas_limit() > block_available_gas &&
(is_regolith || !transaction.is_system_transaction())
{
return Err(BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas {
transaction_gas_limit: transaction.gas_limit(),
block_available_gas,
}
.into())
}
// Cache the depositor account prior to the state transition for the deposit nonce.
//
// Note that this *only* needs to be done post-regolith hardfork, as deposit nonces
// were not introduced in Bedrock. In addition, regular transactions don't have deposit
// nonces, so we don't need to touch the DB for those.
let depositor = (is_regolith && transaction.is_deposit())
.then(|| {
self.db_mut()
.load_cache_account(sender)
.map(|acc| acc.account_info().unwrap_or_default())
})
.transpose()
.map_err(|_| BlockExecutionError::ProviderError)?;
// Execute transaction.
let ResultAndState { result, state } = self.transact(transaction, sender)?;
trace!(
target: "evm",
?transaction, ?result, ?state,
"Executed transaction"
);
self.stats.execution_duration += time.elapsed();
let time = Instant::now();
self.db_mut().commit(state);
self.stats.apply_state_duration += time.elapsed();
// append gas used
cumulative_gas_used += result.gas_used();
// Push transaction changeset and calculate header bloom filter for receipt.
receipts.push(Receipt {
tx_type: transaction.tx_type(),
// Success flag was added in `EIP-658: Embedding transaction status code in
// receipts`.
success: result.is_success(),
cumulative_gas_used,
// convert to reth log
logs: result.into_logs().into_iter().map(into_reth_log).collect(),
#[cfg(feature = "optimism")]
deposit_nonce: depositor.map(|account| account.nonce),
});
}
Ok((receipts, cumulative_gas_used))
}
fn take_output_state(&mut self) -> BundleStateWithReceipts {
let receipts = std::mem::take(&mut self.receipts);
BundleStateWithReceipts::new(
self.evm.db().unwrap().take_bundle(),
receipts,
self.first_block.unwrap_or_default(),
)
}
fn stats(&self) -> BlockExecutorStats {
self.stats.clone()
}
fn size_hint(&self) -> Option<usize> {
self.evm.db.as_ref().map(|db| db.bundle_size_hint())
}
}

View File

@@ -9,24 +9,26 @@ use reth_interfaces::{
RethError,
};
use reth_primitives::{
revm::{
compat::into_reth_log,
env::{fill_cfg_and_block_env, fill_tx_env},
},
revm::env::{fill_cfg_and_block_env, fill_tx_env},
Address, Block, BlockNumber, Bloom, ChainSpec, Hardfork, Header, PruneMode, PruneModes,
PruneSegmentError, Receipt, ReceiptWithBloom, Receipts, TransactionSigned, B256,
MINIMUM_PRUNING_DISTANCE, U256,
};
use reth_provider::{
BlockExecutor, BlockExecutorStats, BundleStateWithReceipts, PrunableBlockExecutor,
StateProvider,
};
use reth_provider::{BlockExecutor, BlockExecutorStats, PrunableBlockExecutor, StateProvider};
use revm::{
db::{states::bundle_state::BundleRetention, StateDBBox},
primitives::ResultAndState,
DatabaseCommit, State, EVM,
State, EVM,
};
use std::{sync::Arc, time::Instant};
#[cfg(not(feature = "optimism"))]
use reth_primitives::revm::compat::into_reth_log;
#[cfg(not(feature = "optimism"))]
use reth_provider::BundleStateWithReceipts;
#[cfg(not(feature = "optimism"))]
use revm::DatabaseCommit;
#[cfg(not(feature = "optimism"))]
use tracing::{debug, trace};
/// EVMProcessor is a block executor that uses revm to execute blocks or multiple blocks.
@@ -49,9 +51,9 @@ use tracing::{debug, trace};
#[allow(missing_debug_implementations)]
pub struct EVMProcessor<'a> {
/// The configured chain-spec
chain_spec: Arc<ChainSpec>,
pub(crate) chain_spec: Arc<ChainSpec>,
/// revm instance that contains database and env environment.
evm: EVM<StateDBBox<'a, RethError>>,
pub(crate) evm: EVM<StateDBBox<'a, RethError>>,
/// Hook and inspector stack that we want to invoke on that hook.
stack: InspectorStack,
/// The collection of receipts.
@@ -59,10 +61,10 @@ pub struct EVMProcessor<'a> {
/// The inner vector stores receipts ordered by transaction number.
///
/// If receipt is None it means it is pruned.
receipts: Receipts,
pub(crate) receipts: Receipts,
/// First block will be initialized to `None`
/// and be set to the block number of first block executed.
first_block: Option<BlockNumber>,
pub(crate) first_block: Option<BlockNumber>,
/// The maximum known block.
tip: Option<BlockNumber>,
/// Pruning configuration.
@@ -72,7 +74,7 @@ pub struct EVMProcessor<'a> {
/// block. None means there isn't any kind of configuration.
pruning_address_filter: Option<(u64, Vec<Address>)>,
/// Execution stats
stats: BlockExecutorStats,
pub(crate) stats: BlockExecutorStats,
}
impl<'a> EVMProcessor<'a> {
@@ -148,7 +150,7 @@ impl<'a> EVMProcessor<'a> {
self.evm.db().expect("Database inside EVM is always set")
}
fn recover_senders(
pub(crate) fn recover_senders(
&mut self,
body: &[TransactionSigned],
senders: Option<Vec<Address>>,
@@ -169,7 +171,7 @@ impl<'a> EVMProcessor<'a> {
}
/// Initializes the config and block env.
fn init_env(&mut self, header: &Header, total_difficulty: U256) {
pub(crate) fn init_env(&mut self, header: &Header, total_difficulty: U256) {
// Set state clear flag.
let state_clear_flag =
self.chain_spec.fork(Hardfork::SpuriousDragon).active_at_block(header.number);
@@ -252,8 +254,16 @@ impl<'a> EVMProcessor<'a> {
sender: Address,
) -> Result<ResultAndState, BlockExecutionError> {
// Fill revm structure.
#[cfg(not(feature = "optimism"))]
fill_tx_env(&mut self.evm.env.tx, transaction, sender);
#[cfg(feature = "optimism")]
{
let mut envelope_buf = Vec::with_capacity(transaction.length_without_header());
transaction.encode_enveloped(&mut envelope_buf);
fill_tx_env(&mut self.evm.env.tx, transaction, sender, envelope_buf.into());
}
let hash = transaction.hash();
let out = if self.stack.should_inspect(&self.evm.env, hash) {
// execution with inspector.
@@ -271,79 +281,8 @@ impl<'a> EVMProcessor<'a> {
out.map_err(|e| BlockValidationError::EVM { hash, error: e.into() }.into())
}
/// Runs the provided transactions and commits their state to the run-time database.
///
/// The returned [BundleStateWithReceipts] can be used to persist the changes to disk, and
/// contains the changes made by each transaction.
///
/// The changes in [BundleStateWithReceipts] have a transition ID associated with them: there is
/// one transition ID for each transaction (with the first executed tx having transition ID
/// 0, and so on).
///
/// The second returned value represents the total gas used by this block of transactions.
pub fn execute_transactions(
&mut self,
block: &Block,
total_difficulty: U256,
senders: Option<Vec<Address>>,
) -> Result<(Vec<Receipt>, u64), BlockExecutionError> {
self.init_env(&block.header, total_difficulty);
// perf: do not execute empty blocks
if block.body.is_empty() {
return Ok((Vec::new(), 0))
}
let senders = self.recover_senders(&block.body, senders)?;
let mut cumulative_gas_used = 0;
let mut receipts = Vec::with_capacity(block.body.len());
for (transaction, sender) in block.body.iter().zip(senders) {
let time = Instant::now();
// The sum of the transactions gas limit, Tg, and the gas utilized in this block prior,
// must be no greater than the blocks gasLimit.
let block_available_gas = block.header.gas_limit - cumulative_gas_used;
if transaction.gas_limit() > block_available_gas {
return Err(BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas {
transaction_gas_limit: transaction.gas_limit(),
block_available_gas,
}
.into())
}
// Execute transaction.
let ResultAndState { result, state } = self.transact(transaction, sender)?;
trace!(
target: "evm",
?transaction, ?result, ?state,
"Executed transaction"
);
self.stats.execution_duration += time.elapsed();
let time = Instant::now();
self.db_mut().commit(state);
self.stats.apply_state_duration += time.elapsed();
// append gas used
cumulative_gas_used += result.gas_used();
// Push transaction changeset and calculate header bloom filter for receipt.
receipts.push(Receipt {
tx_type: transaction.tx_type(),
// Success flag was added in `EIP-658: Embedding transaction status code in
// receipts`.
success: result.is_success(),
cumulative_gas_used,
// convert to reth log
logs: result.into_logs().into_iter().map(into_reth_log).collect(),
});
}
Ok((receipts, cumulative_gas_used))
}
/// Execute the block, verify gas usage and apply post-block state changes.
fn execute_inner(
pub(crate) fn execute_inner(
&mut self,
block: &Block,
total_difficulty: U256,
@@ -457,6 +396,8 @@ impl<'a> EVMProcessor<'a> {
}
}
/// Default Ethereum implementation of the [BlockExecutor] trait for the [EVMProcessor].
#[cfg(not(feature = "optimism"))]
impl<'a> BlockExecutor for EVMProcessor<'a> {
fn execute(
&mut self,
@@ -495,6 +436,69 @@ impl<'a> BlockExecutor for EVMProcessor<'a> {
self.save_receipts(receipts)
}
fn execute_transactions(
&mut self,
block: &Block,
total_difficulty: U256,
senders: Option<Vec<Address>>,
) -> Result<(Vec<Receipt>, u64), BlockExecutionError> {
self.init_env(&block.header, total_difficulty);
// perf: do not execute empty blocks
if block.body.is_empty() {
return Ok((Vec::new(), 0))
}
let senders = self.recover_senders(&block.body, senders)?;
let mut cumulative_gas_used = 0;
let mut receipts = Vec::with_capacity(block.body.len());
for (transaction, sender) in block.body.iter().zip(senders) {
let time = Instant::now();
// The sum of the transactions gas limit, Tg, and the gas utilized in this block prior,
// must be no greater than the blocks gasLimit.
let block_available_gas = block.header.gas_limit - cumulative_gas_used;
if transaction.gas_limit() > block_available_gas {
return Err(BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas {
transaction_gas_limit: transaction.gas_limit(),
block_available_gas,
}
.into())
}
// Execute transaction.
let ResultAndState { result, state } = self.transact(transaction, sender)?;
trace!(
target: "evm",
?transaction, ?result, ?state,
"Executed transaction"
);
self.stats.execution_duration += time.elapsed();
let time = Instant::now();
self.db_mut().commit(state);
self.stats.apply_state_duration += time.elapsed();
// append gas used
cumulative_gas_used += result.gas_used();
// Push transaction changeset and calculate header bloom filter for receipt.
receipts.push(Receipt {
tx_type: transaction.tx_type(),
// Success flag was added in `EIP-658: Embedding transaction status code in
// receipts`.
success: result.is_success(),
cumulative_gas_used,
// convert to reth log
logs: result.into_logs().into_iter().map(into_reth_log).collect(),
#[cfg(feature = "optimism")]
deposit_nonce: None,
});
}
Ok((receipts, cumulative_gas_used))
}
fn take_output_state(&mut self) -> BundleStateWithReceipts {
let receipts = std::mem::take(&mut self.receipts);
BundleStateWithReceipts::new(
@@ -564,7 +568,9 @@ mod tests {
trie::AccountProof,
Account, Bytecode, Bytes, ChainSpecBuilder, ForkCondition, StorageKey, MAINNET,
};
use reth_provider::{AccountReader, BlockHashReader, StateRootProvider};
use reth_provider::{
AccountReader, BlockHashReader, BundleStateWithReceipts, StateRootProvider,
};
use revm::{Database, TransitionState};
use std::collections::HashMap;

View File

@@ -40,3 +40,6 @@ reth-interfaces = { workspace = true, features = ["test-utils"] }
reth-provider = { workspace = true, features = ["test-utils"] }
reth-payload-builder = { workspace = true, features = ["test-utils"] }
assert_matches.workspace = true
[features]
optimism = ["reth-primitives/optimism", "reth-rpc-types/optimism"]

View File

@@ -557,6 +557,13 @@ where
if let Some(ref attrs) = payload_attrs {
let attr_validation_res = self.validate_version_specific_fields(version, &attrs.into());
#[cfg(feature = "optimism")]
if attrs.optimism_payload_attributes.gas_limit.is_none() &&
self.inner.chain_spec.is_optimism()
{
return Err(EngineApiError::MissingGasLimitInPayloadAttributes)
}
// From the engine API spec:
//
// Client software MUST ensure that payloadAttributes.timestamp is greater than

View File

@@ -98,6 +98,11 @@ pub enum EngineApiError {
/// Fetching the payload failed
#[error(transparent)]
GetPayloadError(#[from] PayloadBuilderError),
/// If the optimism feature flag is enabled, the payload attributes must have a present
/// gas limit for the forkchoice updated method.
#[cfg(feature = "optimism")]
#[error("Missing gas limit in payload attributes")]
MissingGasLimitInPayloadAttributes,
}
/// Helper type to represent the `error` field in the error response:
@@ -181,6 +186,15 @@ impl From<EngineApiError> for jsonrpsee_types::error::ErrorObject<'static> {
)
}
},
// Optimism errors
#[cfg(feature = "optimism")]
EngineApiError::MissingGasLimitInPayloadAttributes => {
jsonrpsee_types::error::ErrorObject::owned(
INVALID_PARAMS_CODE,
INVALID_PARAMS_MSG,
Some(ErrorData::new(error)),
)
}
// Any other server error
EngineApiError::TerminalTD { .. } |
EngineApiError::TerminalBlockHash { .. } |

View File

@@ -13,5 +13,7 @@ Compatibility layer for reth-primitives and ethereum RPC types
[dependencies]
reth-primitives.workspace = true
reth-rpc-types.workspace = true
alloy-rlp.workspace = true
[features]
optimism = ["reth-primitives/optimism", "reth-rpc-types/optimism"]

View File

@@ -62,6 +62,8 @@ fn fill(
(Some(U128::from(gas_price)), Some(U128::from(signed_tx.max_fee_per_gas())))
}
#[cfg(feature = "optimism")]
TxType::DEPOSIT => (None, None),
};
let chain_id = signed_tx.chain_id().map(U64::from);
@@ -104,6 +106,8 @@ fn fill(
.collect(),
)
}
#[cfg(feature = "optimism")]
PrimitiveTransaction::Deposit(_) => None,
};
let signature =
@@ -129,10 +133,16 @@ fn fill(
block_hash,
block_number: block_number.map(U256::from),
transaction_index,
// EIP-4844 fields
max_fee_per_blob_gas: signed_tx.max_fee_per_blob_gas().map(U128::from),
blob_versioned_hashes,
// Optimism fields
#[cfg(feature = "optimism")]
optimism: reth_rpc_types::OptimismTransactionFields {
source_hash: signed_tx.source_hash(),
mint: signed_tx.mint().map(U128::from),
is_system_tx: signed_tx.is_deposit().then_some(signed_tx.is_system_transaction()),
},
}
}

View File

@@ -36,6 +36,7 @@ proptest-derive = { workspace = true, optional = true }
[features]
default = ["jsonrpsee-types"]
arbitrary = ["dep:arbitrary", "dep:proptest-derive", "dep:proptest", "alloy-primitives/arbitrary"]
optimism = []
[dev-dependencies]
# misc

View File

@@ -334,7 +334,7 @@ impl FromStr for BlockNumberOrTag {
let number = u64::from_str_radix(hex_val, 16);
BlockNumberOrTag::Number(number?)
} else {
return Err(HexStringMissingPrefixError::default().into());
return Err(HexStringMissingPrefixError::default().into())
}
}
};
@@ -486,25 +486,25 @@ impl<'de> Deserialize<'de> for BlockId {
match key.as_str() {
"blockNumber" => {
if number.is_some() || block_hash.is_some() {
return Err(serde::de::Error::duplicate_field("blockNumber"));
return Err(serde::de::Error::duplicate_field("blockNumber"))
}
if require_canonical.is_some() {
return Err(serde::de::Error::custom(
"Non-valid require_canonical field",
));
))
}
number = Some(map.next_value::<BlockNumberOrTag>()?)
}
"blockHash" => {
if number.is_some() || block_hash.is_some() {
return Err(serde::de::Error::duplicate_field("blockHash"));
return Err(serde::de::Error::duplicate_field("blockHash"))
}
block_hash = Some(map.next_value::<B256>()?);
}
"requireCanonical" => {
if number.is_some() || require_canonical.is_some() {
return Err(serde::de::Error::duplicate_field("requireCanonical"));
return Err(serde::de::Error::duplicate_field("requireCanonical"))
}
require_canonical = Some(map.next_value::<bool>()?)

View File

@@ -378,6 +378,31 @@ pub struct PayloadAttributes {
/// See also <https://github.com/ethereum/execution-apis/blob/main/src/engine/cancun.md#payloadattributesv3>
#[serde(default, skip_serializing_if = "Option::is_none")]
pub parent_beacon_block_root: Option<B256>,
/// Optimism Payload Attributes
#[cfg(feature = "optimism")]
#[serde(flatten)]
pub optimism_payload_attributes: OptimismPayloadAttributes,
}
/// Optimism Payload Attributes
#[derive(Clone, Default, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg(feature = "optimism")]
pub struct OptimismPayloadAttributes {
/// Transactions is a field for rollups: the transactions list is forced into the block
#[serde(default, skip_serializing_if = "Option::is_none")]
pub transactions: Option<Vec<Bytes>>,
/// If true, the no transactions are taken out of the tx-pool, only transactions from the above
/// Transactions list will be included.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub no_tx_pool: Option<bool>,
/// If set, this sets the exact gas limit the block produced with.
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "crate::serde_helpers::u64_hex::u64_hex_opt::deserialize"
)]
pub gas_limit: Option<u64>,
}
#[serde_as]
@@ -392,7 +417,11 @@ struct BeaconAPIPayloadAttributes {
withdrawals: Option<Vec<Withdrawal>>,
#[serde(skip_serializing_if = "Option::is_none")]
parent_beacon_block_root: Option<B256>,
#[cfg(feature = "optimism")]
#[serde(flatten)]
optimism_payload_attributes: OptimismPayloadAttributes,
}
/// A helper module for serializing and deserializing the payload attributes for the beacon API.
///
/// The beacon API encoded object has equivalent fields to the [PayloadAttributes] with two
@@ -417,6 +446,8 @@ pub mod beacon_api_payload_attributes {
suggested_fee_recipient: payload_attributes.suggested_fee_recipient,
withdrawals: payload_attributes.withdrawals.clone(),
parent_beacon_block_root: payload_attributes.parent_beacon_block_root,
#[cfg(feature = "optimism")]
optimism_payload_attributes: payload_attributes.optimism_payload_attributes.clone(),
};
beacon_api_payload_attributes.serialize(serializer)
}
@@ -433,6 +464,8 @@ pub mod beacon_api_payload_attributes {
suggested_fee_recipient: beacon_api_payload_attributes.suggested_fee_recipient,
withdrawals: beacon_api_payload_attributes.withdrawals,
parent_beacon_block_root: beacon_api_payload_attributes.parent_beacon_block_root,
#[cfg(feature = "optimism")]
optimism_payload_attributes: beacon_api_payload_attributes.optimism_payload_attributes,
})
}
}

View File

@@ -71,6 +71,27 @@ pub struct Transaction {
/// Some(1) for AccessList transaction, None for Legacy
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub transaction_type: Option<U64>,
/// Optimism specific transaction fields
#[cfg(feature = "optimism")]
#[serde(flatten)]
pub optimism: OptimismTransactionFields,
}
/// Optimism specific transaction fields
#[cfg(feature = "optimism")]
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct OptimismTransactionFields {
/// Hash that uniquely identifies the source of the deposit.
#[serde(rename = "sourceHash", skip_serializing_if = "Option::is_none")]
pub source_hash: Option<B256>,
/// The ETH value to mint on L2
#[serde(rename = "mint", skip_serializing_if = "Option::is_none")]
pub mint: Option<U128>,
/// Field indicating whether the transaction is a system transaction, and therefore
/// exempt from the L2 gas limit.
#[serde(rename = "isSystemTx", skip_serializing_if = "Option::is_none")]
pub is_system_tx: Option<bool>,
}
#[cfg(test)]
@@ -105,6 +126,8 @@ mod tests {
max_fee_per_gas: Some(U128::from(21)),
max_priority_fee_per_gas: Some(U128::from(22)),
max_fee_per_blob_gas: None,
#[cfg(feature = "optimism")]
optimism: Default::default(),
};
let serialized = serde_json::to_string(&transaction).unwrap();
assert_eq!(
@@ -142,6 +165,8 @@ mod tests {
max_fee_per_gas: Some(U128::from(21)),
max_priority_fee_per_gas: Some(U128::from(22)),
max_fee_per_blob_gas: None,
#[cfg(feature = "optimism")]
optimism: Default::default(),
};
let serialized = serde_json::to_string(&transaction).unwrap();
assert_eq!(

View File

@@ -3,7 +3,7 @@ use alloy_primitives::{Address, Bloom, B256, U128, U256, U64, U8};
use serde::{Deserialize, Serialize};
/// Transaction receipt
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[derive(Clone, Default, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TransactionReceipt {
/// Transaction Hash.
@@ -51,4 +51,24 @@ pub struct TransactionReceipt {
/// EIP-2718 Transaction type, Some(1) for AccessList transaction, None for Legacy
#[serde(rename = "type")]
pub transaction_type: U8,
/// Deposit nonce for deposit transactions post-regolith
#[cfg(feature = "optimism")]
#[serde(skip_serializing_if = "Option::is_none")]
pub deposit_nonce: Option<U64>,
/// L1 fee for the transaction
#[cfg(feature = "optimism")]
#[serde(skip_serializing_if = "Option::is_none")]
pub l1_fee: Option<U256>,
/// L1 fee scalar for the transaction
#[cfg(feature = "optimism")]
#[serde(skip_serializing_if = "Option::is_none")]
pub l1_fee_scalar: Option<U256>,
/// L1 gas price for the transaction
#[cfg(feature = "optimism")]
#[serde(skip_serializing_if = "Option::is_none")]
pub l1_gas_price: Option<U256>,
/// L1 gas used for the transaction
#[cfg(feature = "optimism")]
#[serde(skip_serializing_if = "Option::is_none")]
pub l1_gas_used: Option<U256>,
}

View File

@@ -61,7 +61,7 @@ where
D: Deserializer<'de>,
{
if bytes.0.len() > 32 {
return Err(serde::de::Error::custom("input too long to be a B256"));
return Err(serde::de::Error::custom("input too long to be a B256"))
}
// left pad with zeros to 32 bytes

View File

@@ -16,3 +16,18 @@ where
pub fn serialize<S: Serializer>(value: &u64, s: S) -> Result<S::Ok, S::Error> {
U64::from(*value).serialize(s)
}
/// serde functions for handling `Option<u64>` as [U64]
pub mod u64_hex_opt {
use alloy_primitives::U64;
use serde::{Deserialize, Deserializer};
/// Deserializes an `Option` from [U64] accepting a hex quantity string with optional 0x prefix
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error>
where
D: Deserializer<'de>,
{
Ok(U64::deserialize(deserializer)
.map_or(None, |v| Some(u64::from_be_bytes(v.to_be_bytes()))))
}
}

View File

@@ -42,6 +42,7 @@ jsonrpsee.workspace = true
http = "0.2.8"
http-body = "0.4.5"
hyper = "0.14.24"
reqwest = { version = "0.11.20", optional = true }
jsonwebtoken = "8"
# async
@@ -75,3 +76,6 @@ jsonrpsee = { workspace = true, features = ["client"] }
assert_matches.workspace = true
tempfile.workspace = true
reth-interfaces = { workspace = true, features = ["test-utils"] }
[features]
optimism = ["dep:reqwest", "reth-primitives/optimism", "reth-rpc-types-compat/optimism", "reth-network-api/optimism"]

View File

@@ -15,6 +15,7 @@ use reth_rpc_types::{Index, RichBlock, TransactionReceipt};
use reth_rpc_types_compat::block::{from_block, uncle_block_from_header};
use reth_transaction_pool::TransactionPool;
impl<Provider, Pool, Network> EthApi<Provider, Pool, Network>
where
Provider:
@@ -74,6 +75,15 @@ where
let base_fee = block.base_fee_per_gas;
let block_hash = block.hash;
let excess_blob_gas = block.excess_blob_gas;
#[cfg(feature = "optimism")]
let (block_timestamp, l1_block_info) = {
let body = reth_revm::optimism::parse_l1_info_tx(
&block.body.first().ok_or(EthApiError::InternalEthError)?.input()[4..],
);
(block.timestamp, body.ok())
};
let receipts = block
.body
.into_iter()
@@ -88,7 +98,19 @@ where
base_fee,
excess_blob_gas,
};
build_transaction_receipt_with_block_receipts(tx, meta, receipt, &receipts)
#[cfg(feature = "optimism")]
let op_tx_meta =
self.build_op_tx_meta(&tx, l1_block_info.clone(), block_timestamp)?;
build_transaction_receipt_with_block_receipts(
tx,
meta,
receipt,
&receipts,
#[cfg(feature = "optimism")]
op_tx_meta,
)
})
.collect::<EthResult<Vec<_>>>();
return receipts.map(Some)

View File

@@ -33,6 +33,8 @@ use tokio::sync::{oneshot, Mutex};
mod block;
mod call;
mod fees;
#[cfg(feature = "optimism")]
mod optimism;
mod pending_block;
mod server;
mod sign;
@@ -137,6 +139,8 @@ where
task_spawner,
pending_block: Default::default(),
blocking_task_pool,
#[cfg(feature = "optimism")]
http_client: reqwest::Client::new(),
};
Self { inner: Arc::new(inner) }
}
@@ -262,6 +266,12 @@ where
};
let mut cfg = CfgEnv::default();
#[cfg(feature = "optimism")]
{
cfg.optimism = self.provider().chain_spec().is_optimism();
}
let mut block_env = BlockEnv::default();
// Note: for the PENDING block we assume it is past the known merge block and thus this will
// not fail when looking up the total difficulty value for the blockenv.
@@ -438,4 +448,7 @@ struct EthApiInner<Provider, Pool, Network> {
pending_block: Mutex<Option<PendingBlock>>,
/// A pool dedicated to blocking tasks.
blocking_task_pool: BlockingTaskPool,
/// An http client for communicating with sequencers.
#[cfg(feature = "optimism")]
http_client: reqwest::Client,
}

View File

@@ -0,0 +1,30 @@
use reth_primitives::U256;
use revm::L1BlockInfo;
/// Optimism Transaction Metadata
///
/// Includes the L1 fee and data gas for the tx along with the L1
/// block info. In order to pass the [OptimismTxMeta] into the
/// async colored `build_transaction_receipt_with_block_receipts`
/// function, a reference counter for the L1 block info is
/// used so the L1 block info can be shared between receipts.
#[derive(Debug, Default, Clone)]
pub(crate) struct OptimismTxMeta {
/// The L1 block info.
pub(crate) l1_block_info: Option<L1BlockInfo>,
/// The L1 fee for the block.
pub(crate) l1_fee: Option<U256>,
/// The L1 data gas for the block.
pub(crate) l1_data_gas: Option<U256>,
}
impl OptimismTxMeta {
/// Creates a new [OptimismTxMeta].
pub(crate) fn new(
l1_block_info: Option<L1BlockInfo>,
l1_fee: Option<U256>,
l1_data_gas: Option<U256>,
) -> Self {
Self { l1_block_info, l1_fee, l1_data_gas }
}
}

View File

@@ -171,6 +171,8 @@ impl PendingBlockEnv {
success: result.is_success(),
cumulative_gas_used,
logs: result.logs().into_iter().map(into_reth_log).collect(),
#[cfg(feature = "optimism")]
deposit_nonce: None,
}));
// append transaction to the list of executed transactions

View File

@@ -41,6 +41,15 @@ use revm::{
};
use revm_primitives::{db::DatabaseCommit, Env, ExecutionResult, ResultAndState, SpecId, State};
#[cfg(feature = "optimism")]
use crate::eth::api::optimism::OptimismTxMeta;
#[cfg(feature = "optimism")]
use reth_revm::optimism::RethL1BlockInfo;
#[cfg(feature = "optimism")]
use revm::L1BlockInfo;
#[cfg(feature = "optimism")]
use std::ops::Div;
/// Helper alias type for the state's [CacheDB]
pub(crate) type StateCacheDB<'r> = CacheDB<StateProviderDatabase<StateProviderBox<'r>>>;
@@ -480,8 +489,12 @@ where
}
async fn send_raw_transaction(&self, tx: Bytes) -> EthResult<B256> {
let recovered = recover_raw_transaction(tx)?;
// On optimism, transactions are forwarded directly to the sequencer to be included in
// blocks that it builds.
#[cfg(feature = "optimism")]
self.forward_to_sequencer(&tx).await?;
let recovered = recover_raw_transaction(tx)?;
let pool_transaction = <Pool::Transaction>::from_recovered_transaction(recovered);
// submit the transaction to the pool with a `Local` origin
@@ -847,11 +860,12 @@ impl<Provider, Pool, Network> EthApi<Provider, Pool, Network>
where
Provider:
BlockReaderIdExt + ChainSpecProvider + StateProviderFactory + EvmEnvProvider + 'static,
Network: 'static,
Network: NetworkInfo + 'static,
{
/// Helper function for `eth_getTransactionReceipt`
///
/// Returns the receipt
#[cfg(not(feature = "optimism"))]
pub(crate) async fn build_transaction_receipt(
&self,
tx: TransactionSigned,
@@ -865,6 +879,120 @@ where
};
build_transaction_receipt_with_block_receipts(tx, meta, receipt, &all_receipts)
}
/// Helper function for `eth_getTransactionReceipt` (optimism)
///
/// Returns the receipt
#[cfg(feature = "optimism")]
pub(crate) async fn build_transaction_receipt(
&self,
tx: TransactionSigned,
meta: TransactionMeta,
receipt: Receipt,
) -> EthResult<TransactionReceipt> {
let (block, receipts) = self
.cache()
.get_block_and_receipts(meta.block_hash)
.await?
.ok_or(EthApiError::UnknownBlockNumber)?;
let block = block.unseal();
let l1_block_info = reth_revm::optimism::extract_l1_info(&block).ok();
let optimism_tx_meta = self.build_op_tx_meta(&tx, l1_block_info, block.timestamp)?;
build_transaction_receipt_with_block_receipts(
tx,
meta,
receipt,
&receipts,
optimism_tx_meta,
)
}
/// Builds [OptimismTxMeta] object using the provided [TransactionSigned],
/// [L1BlockInfo] and `block_timestamp`. The [L1BlockInfo] is used to calculate
/// the l1 fee and l1 data gas for the transaction.
/// If the [L1BlockInfo] is not provided, the [OptimismTxMeta] will be empty.
#[cfg(feature = "optimism")]
pub(crate) fn build_op_tx_meta(
&self,
tx: &TransactionSigned,
l1_block_info: Option<L1BlockInfo>,
block_timestamp: u64,
) -> EthResult<OptimismTxMeta> {
if let Some(l1_block_info) = l1_block_info {
let envelope_buf: Bytes = {
let mut envelope_buf = bytes::BytesMut::default();
tx.encode_enveloped(&mut envelope_buf);
envelope_buf.freeze().into()
};
let (l1_fee, l1_data_gas) = match (!tx.is_deposit())
.then(|| {
let inner_l1_fee = match l1_block_info.l1_tx_data_fee(
&self.inner.provider.chain_spec(),
block_timestamp,
&envelope_buf,
tx.is_deposit(),
) {
Ok(inner_l1_fee) => inner_l1_fee,
Err(e) => return Err(e),
};
let inner_l1_data_gas = match l1_block_info.l1_data_gas(
&self.inner.provider.chain_spec(),
block_timestamp,
&envelope_buf,
) {
Ok(inner_l1_data_gas) => inner_l1_data_gas,
Err(e) => return Err(e),
};
Ok((inner_l1_fee, inner_l1_data_gas))
})
.transpose()
.map_err(|_| EthApiError::InternalEthError)?
{
Some((l1_fee, l1_data_gas)) => (Some(l1_fee), Some(l1_data_gas)),
None => (None, None),
};
Ok(OptimismTxMeta::new(Some(l1_block_info), l1_fee, l1_data_gas))
} else {
Ok(OptimismTxMeta::default())
}
}
/// Helper function for `eth_sendRawTransaction` for Optimism.
///
/// Forwards the raw transaction bytes to the configured sequencer endpoint.
/// This is a no-op if the sequencer endpoint is not configured.
#[cfg(feature = "optimism")]
pub async fn forward_to_sequencer(&self, tx: &Bytes) -> EthResult<()> {
if let Some(endpoint) = self.network().sequencer_endpoint() {
let body = serde_json::to_string(&serde_json::json!({
"jsonrpc": "2.0",
"method": "eth_sendRawTransaction",
"params": [format!("0x{}", alloy_primitives::hex::encode(tx))],
"id": self.network().chain_id()
}))
.map_err(|_| {
tracing::warn!(
target = "rpc::eth",
"Failed to serialize transaction for forwarding to sequencer"
);
EthApiError::InternalEthError
})?;
self.inner
.http_client
.post(endpoint)
.header(http::header::CONTENT_TYPE, "application/json")
.body(body)
.send()
.await
.map_err(|_| EthApiError::InternalEthError)?;
}
Ok(())
}
}
impl<Provider, Pool, Network> EthApi<Provider, Pool, Network>
@@ -1014,6 +1142,7 @@ pub(crate) fn build_transaction_receipt_with_block_receipts(
meta: TransactionMeta,
receipt: Receipt,
all_receipts: &[Receipt],
#[cfg(feature = "optimism")] optimism_tx_meta: OptimismTxMeta,
) -> EthResult<TransactionReceipt> {
let transaction =
tx.clone().into_ecrecovered().ok_or(EthApiError::InvalidTransactionSignature)?;
@@ -1029,6 +1158,7 @@ pub(crate) fn build_transaction_receipt_with_block_receipts(
.unwrap_or_default()
};
#[allow(clippy::needless_update)]
let mut res_receipt = TransactionReceipt {
transaction_hash: Some(meta.tx_hash),
transaction_index: U64::from(meta.index),
@@ -1046,12 +1176,27 @@ pub(crate) fn build_transaction_receipt_with_block_receipts(
state_root: None,
logs_bloom: receipt.bloom_slow(),
status_code: if receipt.success { Some(U64::from(1)) } else { Some(U64::from(0)) },
// EIP-4844 fields
blob_gas_price: meta.excess_blob_gas.map(calc_blob_gasprice).map(U128::from),
blob_gas_used: transaction.transaction.blob_gas_used().map(U128::from),
// Optimism fields
#[cfg(feature = "optimism")]
deposit_nonce: receipt.deposit_nonce.map(U64::from),
..Default::default()
};
#[cfg(feature = "optimism")]
if let Some(l1_block_info) = optimism_tx_meta.l1_block_info {
if !tx.is_deposit() {
res_receipt.l1_fee = optimism_tx_meta.l1_fee;
res_receipt.l1_gas_used =
optimism_tx_meta.l1_data_gas.map(|dg| dg + l1_block_info.l1_fee_overhead);
res_receipt.l1_fee_scalar =
Some(l1_block_info.l1_fee_scalar.div(U256::from(1_000_000)));
res_receipt.l1_gas_price = Some(l1_block_info.l1_base_fee);
}
}
match tx.transaction.kind() {
Create => {
res_receipt.contract_address =

View File

@@ -101,6 +101,22 @@ pub enum EthApiError {
InternalJsTracerError(String),
#[error(transparent)]
CallInputError(#[from] CallInputError),
/// Optimism related error
#[error(transparent)]
#[cfg(feature = "optimism")]
Optimism(#[from] OptimismEthApiError),
}
/// Eth Optimism Api Error
#[cfg(feature = "optimism")]
#[derive(Debug, thiserror::Error)]
pub enum OptimismEthApiError {
/// Wrapper around a [hyper::Error].
#[error(transparent)]
HyperError(#[from] hyper::Error),
/// Wrapper around an [http::Error].
#[error(transparent)]
HttpError(#[from] http::Error),
}
impl From<EthApiError> for ErrorObject<'static> {
@@ -137,6 +153,11 @@ impl From<EthApiError> for ErrorObject<'static> {
err @ EthApiError::InternalBlockingTaskError => internal_rpc_err(err.to_string()),
err @ EthApiError::InternalEthError => internal_rpc_err(err.to_string()),
err @ EthApiError::CallInputError(_) => invalid_params_rpc_err(err.to_string()),
#[cfg(feature = "optimism")]
EthApiError::Optimism(err) => match err {
OptimismEthApiError::HyperError(err) => internal_rpc_err(err.to_string()),
OptimismEthApiError::HttpError(err) => internal_rpc_err(err.to_string()),
},
}
}
}
@@ -314,6 +335,22 @@ pub enum RpcInvalidTransactionError {
/// Blob transaction is a create transaction
#[error("blob transaction is a create transaction")]
BlobTransactionIsCreate,
/// Optimism related error
#[error(transparent)]
#[cfg(feature = "optimism")]
Optimism(#[from] OptimismInvalidTransactionError),
}
/// Optimism specific invalid transaction errors
#[cfg(feature = "optimism")]
#[derive(thiserror::Error, Debug)]
pub enum OptimismInvalidTransactionError {
/// A deposit transaction was submitted as a system transaction post-regolith.
#[error("no system transactions allowed after regolith")]
DepositSystemTxPostRegolith,
/// A deposit transaction halted post-regolith
#[error("deposit transaction halted after regolith")]
HaltedDepositPostRegolith,
}
impl RpcInvalidTransactionError {
@@ -422,6 +459,16 @@ impl From<revm::primitives::InvalidTransaction> for RpcInvalidTransactionError {
InvalidTransaction::BlobCreateTransaction => {
RpcInvalidTransactionError::BlobTransactionIsCreate
}
#[cfg(feature = "optimism")]
InvalidTransaction::DepositSystemTxPostRegolith => {
RpcInvalidTransactionError::Optimism(
OptimismInvalidTransactionError::DepositSystemTxPostRegolith,
)
}
#[cfg(feature = "optimism")]
InvalidTransaction::HaltedDepositPostRegolith => RpcInvalidTransactionError::Optimism(
OptimismInvalidTransactionError::HaltedDepositPostRegolith,
),
}
}
}

View File

@@ -74,7 +74,15 @@ impl FillableTransaction for TransactionSignedEcRecovered {
}
fn try_fill_tx_env(&self, tx_env: &mut TxEnv) -> EthResult<()> {
#[cfg(not(feature = "optimism"))]
fill_tx_env_with_recovered(tx_env, self);
#[cfg(feature = "optimism")]
{
let mut envelope_buf = Vec::with_capacity(self.length_without_header());
self.encode_enveloped(&mut envelope_buf);
fill_tx_env_with_recovered(tx_env, self, envelope_buf.into());
}
Ok(())
}
}
@@ -86,7 +94,15 @@ impl FillableTransaction for TransactionSigned {
fn try_fill_tx_env(&self, tx_env: &mut TxEnv) -> EthResult<()> {
let signer =
self.recover_signer().ok_or_else(|| EthApiError::InvalidTransactionSignature)?;
#[cfg(not(feature = "optimism"))]
fill_tx_env(tx_env, self, signer);
#[cfg(feature = "optimism")]
{
let mut envelope_buf = Vec::with_capacity(self.length_without_header());
self.encode_enveloped(&mut envelope_buf);
fill_tx_env(tx_env, self, signer, envelope_buf.into());
}
Ok(())
}
}
@@ -315,6 +331,8 @@ pub(crate) fn create_txn_env(block_env: &BlockEnv, request: CallRequest) -> EthR
// EIP-4844 fields
blob_hashes: blob_versioned_hashes.unwrap_or_default(),
max_fee_per_blob_gas,
#[cfg(feature = "optimism")]
optimism: Default::default(),
};
Ok(env)

View File

@@ -14,7 +14,7 @@ scale = ["codecs-derive/scale"]
postcard = ["codecs-derive/postcard"]
no_codec = ["codecs-derive/no_codec"]
arbitrary = ["revm-primitives/arbitrary", "dep:arbitrary", "dep:proptest", "dep:proptest-derive"]
value-256 = ["codecs-derive/value-256"]
optimism = ["codecs-derive/optimism"]
[dependencies]
bytes.workspace = true

View File

@@ -38,4 +38,4 @@ compact = []
scale = []
postcard = []
no_codec = []
value-256 = []
optimism = []

View File

@@ -164,9 +164,9 @@ pub fn get_bit_size(ftype: &str) -> u8 {
"u64" | "BlockNumber" | "TxNumber" | "ChainId" | "NumTransactions" => 4,
"u128" => 5,
"U256" => 6,
#[cfg(not(feature = "value-256"))]
#[cfg(not(feature = "optimism"))]
"TxValue" => 5, // u128 for ethereum chains assuming high order bits are not used
#[cfg(feature = "value-256")]
#[cfg(feature = "optimism")]
// for fuzz/prop testing and chains that may require full 256 bits
"TxValue" => 6,
_ => 0,

View File

@@ -56,3 +56,7 @@ rand.workspace = true
[features]
test-utils = ["alloy-rlp"]
optimism = [
"reth-primitives/optimism",
"reth-interfaces/optimism"
]

View File

@@ -137,6 +137,8 @@ fn block1(number: BlockNumber) -> (SealedBlockWithSenders, BundleStateWithReceip
topics: vec![B256::with_last_byte(1), B256::with_last_byte(2)],
data: Bytes::default(),
}],
#[cfg(feature = "optimism")]
deposit_nonce: None,
})]]),
number,
);
@@ -192,6 +194,8 @@ fn block2(
topics: vec![B256::with_last_byte(3), B256::with_last_byte(4)],
data: Bytes::default(),
}],
#[cfg(feature = "optimism")]
deposit_nonce: None,
})]]),
number,
);

View File

@@ -4,7 +4,7 @@ use crate::{
};
use parking_lot::Mutex;
use reth_interfaces::executor::BlockExecutionError;
use reth_primitives::{Address, Block, BlockNumber, ChainSpec, PruneModes, U256};
use reth_primitives::{Address, Block, BlockNumber, ChainSpec, PruneModes, Receipt, U256};
use std::sync::Arc;
/// Test executor with mocked result.
#[derive(Debug)]
@@ -35,6 +35,15 @@ impl BlockExecutor for TestExecutor {
Ok(())
}
fn execute_transactions(
&mut self,
_block: &Block,
_total_difficulty: U256,
_senders: Option<Vec<Address>>,
) -> Result<(Vec<Receipt>, u64), BlockExecutionError> {
Err(BlockExecutionError::UnavailableForTest)
}
fn take_output_state(&mut self) -> BundleStateWithReceipts {
self.0.clone().unwrap_or_default()
}

View File

@@ -2,7 +2,7 @@
use crate::{bundle_state::BundleStateWithReceipts, StateProvider};
use reth_interfaces::executor::BlockExecutionError;
use reth_primitives::{Address, Block, BlockNumber, ChainSpec, PruneModes, U256};
use reth_primitives::{Address, Block, BlockNumber, ChainSpec, PruneModes, Receipt, U256};
use std::time::Duration;
use tracing::debug;
@@ -45,6 +45,23 @@ pub trait BlockExecutor {
senders: Option<Vec<Address>>,
) -> Result<(), BlockExecutionError>;
/// Runs the provided transactions and commits their state to the run-time database.
///
/// The returned [BundleStateWithReceipts] can be used to persist the changes to disk, and
/// contains the changes made by each transaction.
///
/// The changes in [BundleStateWithReceipts] have a transition ID associated with them: there is
/// one transition ID for each transaction (with the first executed tx having transition ID
/// 0, and so on).
///
/// The second returned value represents the total gas used by this block of transactions.
fn execute_transactions(
&mut self,
block: &Block,
total_difficulty: U256,
senders: Option<Vec<Address>>,
) -> Result<(Vec<Receipt>, u64), BlockExecutionError>;
/// Return bundle state. This is output of executed blocks.
fn take_output_state(&mut self) -> BundleStateWithReceipts;

View File

@@ -24,6 +24,7 @@ reth-interfaces.workspace = true
reth-tasks.workspace = true
revm.workspace = true
alloy-rlp.workspace = true
reth-revm = { path = "../revm", optional = true }
# async/futures
async-trait.workspace = true
@@ -64,6 +65,13 @@ default = ["serde"]
serde = ["dep:serde"]
test-utils = ["rand", "paste", "serde"]
arbitrary = ["proptest", "reth-primitives/arbitrary"]
optimism = [
"dep:reth-revm",
"reth-primitives/optimism",
"reth-provider/test-utils",
"reth-provider/optimism",
"revm/optimism"
]
[[bench]]
name = "reorder"

View File

@@ -80,11 +80,11 @@
//!
//! ```
//! use reth_primitives::MAINNET;
//! use reth_provider::{ChainSpecProvider, StateProviderFactory};
//! use reth_provider::{BlockReaderIdExt, ChainSpecProvider, StateProviderFactory};
//! use reth_tasks::TokioTaskExecutor;
//! use reth_transaction_pool::{TransactionValidationTaskExecutor, Pool, TransactionPool};
//! use reth_transaction_pool::blobstore::InMemoryBlobStore;
//! async fn t<C>(client: C) where C: StateProviderFactory + ChainSpecProvider + Clone + 'static{
//! async fn t<C>(client: C) where C: StateProviderFactory + BlockReaderIdExt + ChainSpecProvider + Clone + 'static{
//! let blob_store = InMemoryBlobStore::default();
//! let pool = Pool::eth_pool(
//! TransactionValidationTaskExecutor::eth(client, MAINNET.clone(), blob_store.clone(), TokioTaskExecutor::default()),
@@ -270,7 +270,7 @@ where
impl<Client, S> EthTransactionPool<Client, S>
where
Client: StateProviderFactory + Clone + 'static,
Client: StateProviderFactory + reth_provider::BlockReaderIdExt + Clone + 'static,
S: BlobStore,
{
/// Returns a new [Pool] that uses the default [TransactionValidationTaskExecutor] when
@@ -280,12 +280,12 @@ where
///
/// ```
/// use reth_primitives::MAINNET;
/// use reth_provider::StateProviderFactory;
/// use reth_provider::{BlockReaderIdExt, StateProviderFactory};
/// use reth_tasks::TokioTaskExecutor;
/// use reth_transaction_pool::{
/// blobstore::InMemoryBlobStore, Pool, TransactionValidationTaskExecutor,
/// };
/// # fn t<C>(client: C) where C: StateProviderFactory + Clone + 'static {
/// # fn t<C>(client: C) where C: StateProviderFactory + BlockReaderIdExt + Clone + 'static {
/// let blob_store = InMemoryBlobStore::default();
/// let pool = Pool::eth_pool(
/// TransactionValidationTaskExecutor::eth(

Some files were not shown because too many files have changed in this diff Show More