mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-01-09 15:28:01 -05:00
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:
15
.github/workflows/integration.yml
vendored
15
.github/workflows/integration.yml
vendored
@@ -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)'
|
||||
|
||||
|
||||
9
.github/workflows/lint.yml
vendored
9
.github/workflows/lint.yml
vendored
@@ -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
|
||||
|
||||
|
||||
5
.github/workflows/sanity.yml
vendored
5
.github/workflows/sanity.yml
vendored
@@ -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:
|
||||
|
||||
23
.github/workflows/unit.yml
vendored
23
.github/workflows/unit.yml
vendored
@@ -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
5
Cargo.lock
generated
@@ -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",
|
||||
|
||||
27
Makefile
27
Makefile
@@ -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
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)]
|
||||
|
||||
19
bin/reth/src/args/rollup_args.rs
Normal file
19
bin/reth/src/args/rollup_args.rs
Normal 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,
|
||||
}
|
||||
@@ -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"] {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
15
bin/reth/src/optimism.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -243,6 +243,8 @@ impl InsertBlockErrorKind {
|
||||
BlockExecutionError::CanonicalCommit { .. } |
|
||||
BlockExecutionError::AppendChainDoesntConnect { .. } |
|
||||
BlockExecutionError::UnavailableForTest => false,
|
||||
#[cfg(feature = "optimism")]
|
||||
BlockExecutionError::OptimismBlockExecution(_) => false,
|
||||
}
|
||||
}
|
||||
InsertBlockErrorKind::Tree(err) => {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -366,6 +366,8 @@ pub fn random_receipt<R: Rng>(
|
||||
} else {
|
||||
vec![]
|
||||
},
|
||||
#[cfg(feature = "optimism")]
|
||||
deposit_nonce: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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(),
|
||||
},
|
||||
|
||||
@@ -26,3 +26,4 @@ tokio = { workspace = true, features = ["sync"] }
|
||||
[features]
|
||||
default = ["serde"]
|
||||
serde = ["dep:serde"]
|
||||
optimism = []
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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 },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -5,6 +5,7 @@ mod geth;
|
||||
mod requests;
|
||||
mod session;
|
||||
mod startup;
|
||||
#[cfg(not(feature = "optimism"))]
|
||||
mod txgossip;
|
||||
|
||||
fn main() {}
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
305
crates/payload/basic/src/optimism.rs
Normal file
305
crates/payload/basic/src/optimism.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -39,3 +39,8 @@ revm.workspace = true
|
||||
|
||||
[features]
|
||||
test-utils = []
|
||||
optimism = [
|
||||
"reth-primitives/optimism",
|
||||
"reth-rpc-types/optimism",
|
||||
"reth-interfaces/optimism"
|
||||
]
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
14472
crates/primitives/res/genesis/base.json
Normal file
14472
crates/primitives/res/genesis/base.json
Normal file
File diff suppressed because one or more lines are too long
15253
crates/primitives/res/genesis/goerli_base.json
Normal file
15253
crates/primitives/res/genesis/goerli_base.json
Normal file
File diff suppressed because one or more lines are too long
92
crates/primitives/res/genesis/goerli_op.json
Normal file
92
crates/primitives/res/genesis/goerli_op.json
Normal file
File diff suppressed because one or more lines are too long
@@ -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(),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
};
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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![];
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
169
crates/primitives/src/transaction/optimism.rs
Normal file
169
crates/primitives/src/transaction/optimism.rs
Normal 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[..]);
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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;
|
||||
|
||||
175
crates/revm/src/optimism/mod.rs
Normal file
175
crates/revm/src/optimism/mod.rs
Normal 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));
|
||||
}
|
||||
}
|
||||
150
crates/revm/src/optimism/processor.rs
Normal file
150
crates/revm/src/optimism/processor.rs
Normal 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 transaction’s gas limit, Tg, and the gas utilized in this block prior,
|
||||
// must be no greater than the block’s 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())
|
||||
}
|
||||
}
|
||||
@@ -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 transaction’s gas limit, Tg, and the gas utilized in this block prior,
|
||||
// must be no greater than the block’s 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 transaction’s gas limit, Tg, and the gas utilized in this block prior,
|
||||
// must be no greater than the block’s 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;
|
||||
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 { .. } |
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>()?)
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -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>,
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()))))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
30
crates/rpc/rpc/src/eth/api/optimism.rs
Normal file
30
crates/rpc/rpc/src/eth/api/optimism.rs
Normal 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 }
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -38,4 +38,4 @@ compact = []
|
||||
scale = []
|
||||
postcard = []
|
||||
no_codec = []
|
||||
value-256 = []
|
||||
optimism = []
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -56,3 +56,7 @@ rand.workspace = true
|
||||
|
||||
[features]
|
||||
test-utils = ["alloy-rlp"]
|
||||
optimism = [
|
||||
"reth-primitives/optimism",
|
||||
"reth-interfaces/optimism"
|
||||
]
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user