From c644ab6a95b9f885ab48e8f1a9ce96eaeab28e80 Mon Sep 17 00:00:00 2001 From: Eric Woolsey Date: Wed, 3 Dec 2025 17:15:20 -0800 Subject: [PATCH 1/8] feat: patch revm --- Cargo.lock | 119 ++++++++++++++++++++++------------------------------- Cargo.toml | 49 +++++++++++++++++----- 2 files changed, 87 insertions(+), 81 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 944143f2f9b..0cfa7667a12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -106,9 +106,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy-chains" -version = "0.2.30" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f374d3c6d729268bbe2d0e0ff992bb97898b2df756691a62ee1d5f0506bc39" +checksum = "1b9ebac8ff9c2f07667e1803dc777304337e160ce5153335beb45e8ec0751808" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -289,9 +289,8 @@ dependencies = [ [[package]] name = "alloy-evm" -version = "0.27.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ccfe6d724ceabd5518350cfb34f17dd3a6c3cc33579eee94d98101d3a511ff" +version = "0.24.2" +source = "git+https://github.com/0xforerunner/evm?rev=30b6df1#30b6df18b2a3c5e23ad190cc96e5bd15ecd53aa1" dependencies = [ "alloy-consensus", "alloy-eips", @@ -418,10 +417,9 @@ dependencies = [ ] [[package]] -name = "alloy-node-bindings" -version = "1.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ce6930ce52e43b0768dc99ceeff5cb9e673e8c9f87d926914cd028b2e3f7233" +name = "alloy-op-evm" +version = "0.24.2" +source = "git+https://github.com/0xforerunner/evm?rev=30b6df1#30b6df18b2a3c5e23ad190cc96e5bd15ecd53aa1" dependencies = [ "alloy-genesis", "alloy-hardforks 0.2.13", @@ -3053,24 +3051,24 @@ dependencies = [ [[package]] name = "derive_more" -version = "2.1.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +checksum = "10b768e943bed7bf2cab53df09f4bc34bfd217cdb57d971e769874c9a6710618" dependencies = [ "derive_more-impl", ] [[package]] name = "derive_more-impl" -version = "2.1.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +checksum = "6d286bfdaf75e988b4a78e013ecd79c581e06399ab53fbacd2d916c2f904f30b" dependencies = [ "convert_case", "proc-macro2", "quote", "rustc_version 0.4.1", - "syn 2.0.114", + "syn 2.0.111", "unicode-xid", ] @@ -4760,9 +4758,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.20" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" dependencies = [ "base64 0.22.1", "bytes", @@ -5504,17 +5502,11 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - [[package]] name = "libc" -version = "0.2.180" +version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "libgit2-sys" @@ -6423,9 +6415,8 @@ dependencies = [ [[package]] name = "op-revm" -version = "15.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79c92b75162c2ed1661849fa51683b11254a5b661798360a2c24be918edafd40" +version = "14.1.0" +source = "git+https://github.com/0xforerunner/revm?rev=195c8e9#195c8e90138c17488dc56aae023903684ec87c73" dependencies = [ "auto_impl", "revm", @@ -10698,9 +10689,8 @@ dependencies = [ [[package]] name = "revm" -version = "34.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2aabdebaa535b3575231a88d72b642897ae8106cf6b0d12eafc6bfdf50abfc7" +version = "33.1.0" +source = "git+https://github.com/0xforerunner/revm?rev=195c8e9#195c8e90138c17488dc56aae023903684ec87c73" dependencies = [ "revm-bytecode", "revm-context", @@ -10717,9 +10707,8 @@ dependencies = [ [[package]] name = "revm-bytecode" -version = "8.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d1e5c1eaa44d39d537f668bc5c3409dc01e5c8be954da6c83370bbdf006457" +version = "7.1.1" +source = "git+https://github.com/0xforerunner/revm?rev=195c8e9#195c8e90138c17488dc56aae023903684ec87c73" dependencies = [ "bitvec", "phf", @@ -10729,9 +10718,8 @@ dependencies = [ [[package]] name = "revm-context" -version = "13.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "892ff3e6a566cf8d72ffb627fdced3becebbd9ba64089c25975b9b028af326a5" +version = "12.1.0" +source = "git+https://github.com/0xforerunner/revm?rev=195c8e9#195c8e90138c17488dc56aae023903684ec87c73" dependencies = [ "bitvec", "cfg-if", @@ -10746,9 +10734,8 @@ dependencies = [ [[package]] name = "revm-context-interface" -version = "14.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57f61cc6d23678c4840af895b19f8acfbbd546142ec8028b6526c53cc1c16c98" +version = "13.1.0" +source = "git+https://github.com/0xforerunner/revm?rev=195c8e9#195c8e90138c17488dc56aae023903684ec87c73" dependencies = [ "alloy-eip2930", "alloy-eip7702", @@ -10762,9 +10749,8 @@ dependencies = [ [[package]] name = "revm-database" -version = "10.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "529528d0b05fe646be86223032c3e77aa8b05caa2a35447d538c55965956a511" +version = "9.0.6" +source = "git+https://github.com/0xforerunner/revm?rev=195c8e9#195c8e90138c17488dc56aae023903684ec87c73" dependencies = [ "alloy-eips", "revm-bytecode", @@ -10776,23 +10762,21 @@ dependencies = [ [[package]] name = "revm-database-interface" -version = "9.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7bf93ac5b91347c057610c0d96e923db8c62807e03f036762d03e981feddc1d" +version = "8.0.5" +source = "git+https://github.com/0xforerunner/revm?rev=195c8e9#195c8e90138c17488dc56aae023903684ec87c73" dependencies = [ "auto_impl", "either", "revm-primitives", "revm-state", "serde", - "thiserror 2.0.18", + "thiserror 2.0.17", ] [[package]] name = "revm-handler" -version = "15.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cd0e43e815a85eded249df886c4badec869195e70cdd808a13cfca2794622d2" +version = "14.1.0" +source = "git+https://github.com/0xforerunner/revm?rev=195c8e9#195c8e90138c17488dc56aae023903684ec87c73" dependencies = [ "auto_impl", "derive-where", @@ -10809,9 +10793,8 @@ dependencies = [ [[package]] name = "revm-inspector" -version = "15.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f3ccad59db91ef93696536a0dbaf2f6f17cfe20d4d8843ae118edb7e97947ef" +version = "14.1.0" +source = "git+https://github.com/0xforerunner/revm?rev=195c8e9#195c8e90138c17488dc56aae023903684ec87c73" dependencies = [ "auto_impl", "either", @@ -10847,9 +10830,8 @@ dependencies = [ [[package]] name = "revm-interpreter" -version = "32.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11406408597bc249392d39295831c4b641b3a6f5c471a7c41104a7a1e3564c07" +version = "31.1.0" +source = "git+https://github.com/0xforerunner/revm?rev=195c8e9#195c8e90138c17488dc56aae023903684ec87c73" dependencies = [ "revm-bytecode", "revm-context-interface", @@ -10860,9 +10842,8 @@ dependencies = [ [[package]] name = "revm-precompile" -version = "32.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c1285c848d240678bf69cb0f6179ff5a4aee6fc8e921d89708087197a0aff3" +version = "31.0.0" +source = "git+https://github.com/0xforerunner/revm?rev=195c8e9#195c8e90138c17488dc56aae023903684ec87c73" dependencies = [ "ark-bls12-381", "ark-bn254", @@ -10884,9 +10865,8 @@ dependencies = [ [[package]] name = "revm-primitives" -version = "22.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba580c56a8ec824a64f8a1683577876c2e1dbe5247044199e9b881421ad5dcf9" +version = "21.0.2" +source = "git+https://github.com/0xforerunner/revm?rev=195c8e9#195c8e90138c17488dc56aae023903684ec87c73" dependencies = [ "alloy-primitives", "num_enum", @@ -10896,9 +10876,8 @@ dependencies = [ [[package]] name = "revm-state" -version = "9.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "311720d4f0f239b041375e7ddafdbd20032a33b7bae718562ea188e188ed9fd3" +version = "8.1.1" +source = "git+https://github.com/0xforerunner/revm?rev=195c8e9#195c8e90138c17488dc56aae023903684ec87c73" dependencies = [ "alloy-eip7928", "bitflags 2.10.0", @@ -12924,9 +12903,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.20.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" +checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" dependencies = [ "getrandom 0.3.4", "js-sys", @@ -13887,18 +13866,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.39" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.39" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 1f3eef32663..1ab17ba2739 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -446,14 +446,18 @@ revm-inspectors = "0.34.2" # eth alloy-dyn-abi = "1.5.5" -alloy-primitives = { version = "1.5.5", default-features = false, features = ["map-foldhash"] } +alloy-primitives = { version = "1.5.5", default-features = false, features = [ + "map-foldhash", +] } alloy-sol-types = { version = "1.5.5", default-features = false } alloy-chains = { version = "0.2.5", default-features = false } alloy-eip2124 = { version = "0.2.0", default-features = false } alloy-eip7928 = { version = "0.3.0", default-features = false } alloy-evm = { version = "0.27.2", default-features = false } -alloy-rlp = { version = "0.3.10", default-features = false, features = ["core-net"] } +alloy-rlp = { version = "0.3.10", default-features = false, features = [ + "core-net", +] } alloy-trie = { version = "0.9.4", default-features = false } alloy-hardforks = "0.4.5" @@ -465,10 +469,15 @@ alloy-genesis = { version = "1.6.3", default-features = false } alloy-json-rpc = { version = "1.6.3", default-features = false } alloy-network = { version = "1.6.3", default-features = false } alloy-network-primitives = { version = "1.6.3", default-features = false } -alloy-provider = { version = "1.6.3", features = ["reqwest", "debug-api"], default-features = false } +alloy-provider = { version = "1.6.3", features = [ + "reqwest", + "debug-api", +], default-features = false } alloy-pubsub = { version = "1.6.3", default-features = false } alloy-rpc-client = { version = "1.6.3", default-features = false } -alloy-rpc-types = { version = "1.6.3", features = ["eth"], default-features = false } +alloy-rpc-types = { version = "1.6.3", features = [ + "eth", +], default-features = false } alloy-rpc-types-admin = { version = "1.6.3", default-features = false } alloy-rpc-types-anvil = { version = "1.6.3", default-features = false } alloy-rpc-types-beacon = { version = "1.6.3", default-features = false } @@ -482,7 +491,9 @@ alloy-serde = { version = "1.6.3", default-features = false } alloy-signer = { version = "1.6.3", default-features = false } alloy-signer-local = { version = "1.6.3", default-features = false } alloy-transport = { version = "1.6.3" } -alloy-transport-http = { version = "1.6.3", features = ["reqwest-rustls-tls"], default-features = false } +alloy-transport-http = { version = "1.6.3", features = [ + "reqwest-rustls-tls", +], default-features = false } alloy-transport-ipc = { version = "1.6.3", default-features = false } alloy-transport-ws = { version = "1.6.3", default-features = false } @@ -501,7 +512,10 @@ either = { version = "1.15.0", default-features = false } arrayvec = { version = "0.7.6", default-features = false } aquamarine = "0.6" auto_impl = "1" -backon = { version = "1.2", default-features = false, features = ["std-blocking-sleep", "tokio-sleep"] } +backon = { version = "1.2", default-features = false, features = [ + "std-blocking-sleep", + "tokio-sleep", +] } bincode = "1.3" bitflags = "2.4" boyer-moore-magiclen = "0.2.16" @@ -523,9 +537,13 @@ itertools = { version = "0.14", default-features = false } linked_hash_set = "0.1" lz4 = "1.28.1" modular-bitfield = "0.13.1" -notify = { version = "8.0.0", default-features = false, features = ["macos_fsevent"] } +notify = { version = "8.0.0", default-features = false, features = [ + "macos_fsevent", +] } nybbles = { version = "0.4.8", default-features = false } -once_cell = { version = "1.19", default-features = false, features = ["critical-section"] } +once_cell = { version = "1.19", default-features = false, features = [ + "critical-section", +] } parking_lot = "0.12" paste = "1.0" rand = "0.9" @@ -544,7 +562,9 @@ strum_macros = "0.27" syn = "2.0" thiserror = { version = "2.0.0", default-features = false } tar = "0.4.44" -tracing = { version = "0.1.0", default-features = false, features = ["attributes"] } +tracing = { version = "0.1.0", default-features = false, features = [ + "attributes", +] } tracing-appender = "0.2" url = { version = "2.3", default-features = false } zstd = "0.13" @@ -582,7 +602,11 @@ futures-util = { version = "0.3", default-features = false } hyper = "1.3" hyper-util = "0.1.5" pin-project = "1.0.12" -reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "rustls-tls-native-roots", "stream"] } +reqwest = { version = "0.12", default-features = false, features = [ + "rustls-tls", + "rustls-tls-native-roots", + "stream", +] } tracing-futures = "0.2" tower = "0.5" tower-http = "0.6" @@ -607,7 +631,10 @@ proptest-arbitrary-interop = "0.1.0" # crypto enr = { version = "0.13", default-features = false } k256 = { version = "0.13", default-features = false, features = ["ecdsa"] } -secp256k1 = { version = "0.30", default-features = false, features = ["global-context", "recovery"] } +secp256k1 = { version = "0.30", default-features = false, features = [ + "global-context", + "recovery", +] } # rand 8 for secp256k1 rand_08 = { package = "rand", version = "0.8" } From 6a0dbcd22b34d4cb661bfd02744fa24394793964 Mon Sep 17 00:00:00 2001 From: Eric Woolsey Date: Wed, 3 Dec 2025 18:13:23 -0800 Subject: [PATCH 2/8] wip: switch over to StateDB --- Cargo.lock | 4 +- Cargo.toml | 2 + crates/evm/evm/src/execute.rs | 12 +- .../custom-beacon-withdrawals/src/main.rs | 7 +- examples/custom-node/src/evm/executor.rs | 122 ++++++++++++++++++ 5 files changed, 135 insertions(+), 12 deletions(-) create mode 100644 examples/custom-node/src/evm/executor.rs diff --git a/Cargo.lock b/Cargo.lock index 0cfa7667a12..61c7d5eea0a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -290,7 +290,7 @@ dependencies = [ [[package]] name = "alloy-evm" version = "0.24.2" -source = "git+https://github.com/0xforerunner/evm?rev=30b6df1#30b6df18b2a3c5e23ad190cc96e5bd15ecd53aa1" +source = "git+https://github.com/0xforerunner/evm?rev=f5a7b28#f5a7b28c7e025de9dd532ab5481bfa306419650b" dependencies = [ "alloy-consensus", "alloy-eips", @@ -419,7 +419,7 @@ dependencies = [ [[package]] name = "alloy-op-evm" version = "0.24.2" -source = "git+https://github.com/0xforerunner/evm?rev=30b6df1#30b6df18b2a3c5e23ad190cc96e5bd15ecd53aa1" +source = "git+https://github.com/0xforerunner/evm?rev=f5a7b28#f5a7b28c7e025de9dd532ab5481bfa306419650b" dependencies = [ "alloy-genesis", "alloy-hardforks 0.2.13", diff --git a/Cargo.toml b/Cargo.toml index 1ab17ba2739..14e52d2551b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -735,6 +735,8 @@ vergen-git2 = "9.1.0" ipnet = "2.11" [patch.crates-io] +alloy-op-evm = { git = "https://github.com/0xforerunner/evm", rev = "0833666" } +alloy-evm = { git = "https://github.com/0xforerunner/evm", rev = "0833666" } # alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } # alloy-contract = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } # alloy-eips = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } diff --git a/crates/evm/evm/src/execute.rs b/crates/evm/evm/src/execute.rs index 00010db95c2..0d669d2b92e 100644 --- a/crates/evm/evm/src/execute.rs +++ b/crates/evm/evm/src/execute.rs @@ -6,7 +6,7 @@ use alloy_consensus::{BlockHeader, Header}; use alloy_eips::eip2718::WithEncoded; pub use alloy_evm::block::{BlockExecutor, BlockExecutorFactory}; use alloy_evm::{ - block::{CommitChanges, ExecutableTxParts}, + block::{CommitChanges, ExecutableTxParts, StateDB}, Evm, EvmEnv, EvmFactory, RecoveredTx, ToTxEnv, }; use alloy_primitives::{Address, B256}; @@ -431,7 +431,7 @@ where } } -impl<'a, F, DB, Executor, Builder, N> BlockBuilder +impl<'a, DB, F, Executor, Builder, N> BlockBuilder for BasicBlockBuilder<'a, F, Executor, Builder, N> where F: BlockExecutorFactory, @@ -440,12 +440,12 @@ where Spec = ::Spec, HaltReason = ::HaltReason, BlockEnv = ::BlockEnv, - DB = &'a mut State, + DB = &'a mut DB, >, Transaction = N::SignedTx, Receipt = N::Receipt, >, - DB: Database + 'a, + DB: StateDB + 'a, Builder: BlockAssembler, N: NodePrimitives, { @@ -485,7 +485,7 @@ where db.merge_transitions(BundleRetention::Reverts); // calculate the state root - let hashed_state = state.hashed_post_state(&db.bundle_state); + let hashed_state = state.hashed_post_state(db.bundle_state()); let (state_root, trie_updates) = state .state_root_with_updates(hashed_state.clone()) .map_err(BlockExecutionError::other)?; @@ -499,7 +499,7 @@ where parent: self.parent, transactions, output: &result, - bundle_state: &db.bundle_state, + bundle_state: db.bundle_state(), state_provider: &state, state_root, })?; diff --git a/examples/custom-beacon-withdrawals/src/main.rs b/examples/custom-beacon-withdrawals/src/main.rs index d6b67572167..f246e1947d7 100644 --- a/examples/custom-beacon-withdrawals/src/main.rs +++ b/examples/custom-beacon-withdrawals/src/main.rs @@ -5,7 +5,7 @@ use alloy_eips::eip4895::Withdrawal; use alloy_evm::{ - block::{BlockExecutorFactory, BlockExecutorFor, ExecutableTx}, + block::{BlockExecutorFactory, BlockExecutorFor, ExecutableTx, StateDB}, eth::{EthBlockExecutionCtx, EthBlockExecutor, EthTxResult}, precompiles::PrecompilesMap, revm::context::Block as _, @@ -187,10 +187,9 @@ pub struct CustomBlockExecutor<'a, Evm> { inner: EthBlockExecutor<'a, Evm, &'a Arc, &'a RethReceiptBuilder>, } -impl<'db, DB, E> BlockExecutor for CustomBlockExecutor<'_, E> +impl BlockExecutor for CustomBlockExecutor<'_, E> where - DB: Database + 'db, - E: Evm, Tx = TxEnv>, + E: Evm, { type Transaction = TransactionSigned; type Receipt = Receipt; diff --git a/examples/custom-node/src/evm/executor.rs b/examples/custom-node/src/evm/executor.rs new file mode 100644 index 00000000000..0206979a519 --- /dev/null +++ b/examples/custom-node/src/evm/executor.rs @@ -0,0 +1,122 @@ +use crate::{ + evm::{ + alloy::{CustomEvm, CustomEvmFactory}, + CustomEvmConfig, CustomTxEnv, + }, + primitives::CustomTransaction, +}; +use alloy_consensus::transaction::Recovered; +use alloy_evm::{ + block::{ + BlockExecutionError, BlockExecutionResult, BlockExecutor, BlockExecutorFactory, + BlockExecutorFor, ExecutableTx, OnStateHook, StateDB, + }, + precompiles::PrecompilesMap, + Database, Evm, +}; +use alloy_op_evm::{OpBlockExecutionCtx, OpBlockExecutor}; +use reth_ethereum::evm::primitives::InspectorFor; +use reth_op::{chainspec::OpChainSpec, node::OpRethReceiptBuilder, OpReceipt}; +use revm::{context::result::ResultAndState, database::State}; +use std::sync::Arc; + +pub struct CustomBlockExecutor { + inner: OpBlockExecutor>, +} + +impl BlockExecutor for CustomBlockExecutor +where + E: Evm, +{ + type Transaction = CustomTransaction; + type Receipt = OpReceipt; + type Evm = E; + + fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError> { + self.inner.apply_pre_execution_changes() + } + + fn execute_transaction_without_commit( + &mut self, + tx: impl ExecutableTx, + ) -> Result::HaltReason>, BlockExecutionError> { + match tx.tx() { + CustomTransaction::Op(op_tx) => self + .inner + .execute_transaction_without_commit(Recovered::new_unchecked(op_tx, *tx.signer())), + CustomTransaction::Payment(..) => todo!(), + } + } + + fn commit_transaction( + &mut self, + output: ResultAndState<::HaltReason>, + tx: impl ExecutableTx, + ) -> Result { + match tx.tx() { + CustomTransaction::Op(op_tx) => { + self.inner.commit_transaction(output, Recovered::new_unchecked(op_tx, *tx.signer())) + } + CustomTransaction::Payment(..) => todo!(), + } + } + + fn finish(self) -> Result<(Self::Evm, BlockExecutionResult), BlockExecutionError> { + self.inner.finish() + } + + fn set_state_hook(&mut self, _hook: Option>) { + self.inner.set_state_hook(_hook) + } + + fn evm_mut(&mut self) -> &mut Self::Evm { + self.inner.evm_mut() + } + + fn evm(&self) -> &Self::Evm { + self.inner.evm() + } +} + +impl BlockExecutorFactory for CustomEvmConfig { + type EvmFactory = CustomEvmFactory; + type ExecutionCtx<'a> = CustomBlockExecutionCtx; + type Transaction = CustomTransaction; + type Receipt = OpReceipt; + + fn evm_factory(&self) -> &Self::EvmFactory { + &self.custom_evm_factory + } + + fn create_executor<'a, DB, I>( + &'a self, + evm: CustomEvm<&'a mut State, I, PrecompilesMap>, + ctx: CustomBlockExecutionCtx, + ) -> impl BlockExecutorFor<'a, Self, DB, I> + where + DB: Database + 'a, + I: InspectorFor> + 'a, + { + CustomBlockExecutor { + inner: OpBlockExecutor::new( + evm, + ctx.inner, + self.inner.chain_spec().clone(), + *self.inner.executor_factory.receipt_builder(), + ), + } + } +} + +/// Additional parameters for executing custom transactions. +#[derive(Debug, Clone)] +pub struct CustomBlockExecutionCtx { + pub inner: OpBlockExecutionCtx, + pub extension: u64, +} + +impl From for OpBlockExecutionCtx { + fn from(value: CustomBlockExecutionCtx) -> Self { + value.inner + } +} From 4c65a656ba5d863bb4174ac6c990b837b986797a Mon Sep 17 00:00:00 2001 From: Eric Woolsey Date: Wed, 3 Dec 2025 18:26:00 -0800 Subject: [PATCH 3/8] fix: lifetime for bundle state --- crates/evm/evm/src/execute.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/evm/evm/src/execute.rs b/crates/evm/evm/src/execute.rs index 0d669d2b92e..bf0c553fb73 100644 --- a/crates/evm/evm/src/execute.rs +++ b/crates/evm/evm/src/execute.rs @@ -499,7 +499,7 @@ where parent: self.parent, transactions, output: &result, - bundle_state: db.bundle_state(), + bundle_state: (*db).bundle_state(), state_provider: &state, state_root, })?; From 78f8915302f3640b63f9cd412c90f8da118cea2a Mon Sep 17 00:00:00 2001 From: Eric Woolsey Date: Wed, 3 Dec 2025 20:14:44 -0800 Subject: [PATCH 4/8] wip: getting everything compiling --- Cargo.lock | 4 +- crates/ethereum/evm/src/test_utils.rs | 22 +- crates/evm/evm/src/execute.rs | 18 +- crates/evm/evm/src/lib.rs | 26 +- crates/optimism/evm/src/build.rs | 154 +++++ crates/optimism/payload/src/builder.rs | 783 +++++++++++++++++++++++++ 6 files changed, 974 insertions(+), 33 deletions(-) create mode 100644 crates/optimism/evm/src/build.rs create mode 100644 crates/optimism/payload/src/builder.rs diff --git a/Cargo.lock b/Cargo.lock index 61c7d5eea0a..8cf7152d571 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -290,7 +290,7 @@ dependencies = [ [[package]] name = "alloy-evm" version = "0.24.2" -source = "git+https://github.com/0xforerunner/evm?rev=f5a7b28#f5a7b28c7e025de9dd532ab5481bfa306419650b" +source = "git+https://github.com/0xforerunner/evm?rev=9de0fb0#9de0fb0c4440b1dfbffa6abca2aad66483d09c07" dependencies = [ "alloy-consensus", "alloy-eips", @@ -419,7 +419,7 @@ dependencies = [ [[package]] name = "alloy-op-evm" version = "0.24.2" -source = "git+https://github.com/0xforerunner/evm?rev=f5a7b28#f5a7b28c7e025de9dd532ab5481bfa306419650b" +source = "git+https://github.com/0xforerunner/evm?rev=9de0fb0#9de0fb0c4440b1dfbffa6abca2aad66483d09c07" dependencies = [ "alloy-genesis", "alloy-hardforks 0.2.13", diff --git a/crates/ethereum/evm/src/test_utils.rs b/crates/ethereum/evm/src/test_utils.rs index ed472c28a4d..f4013a6ebff 100644 --- a/crates/ethereum/evm/src/test_utils.rs +++ b/crates/ethereum/evm/src/test_utils.rs @@ -2,7 +2,7 @@ use crate::EthEvmConfig; use alloc::{boxed::Box, sync::Arc, vec, vec::Vec}; use alloy_consensus::{Header, TxType}; use alloy_eips::eip7685::Requests; -use alloy_evm::precompiles::PrecompilesMap; +use alloy_evm::{block::StateDB, precompiles::PrecompilesMap}; use alloy_primitives::Bytes; use alloy_rpc_types_engine::ExecutionData; use parking_lot::Mutex; @@ -18,7 +18,7 @@ use reth_evm::{ use reth_execution_types::{BlockExecutionResult, ExecutionOutcome}; use reth_primitives_traits::{BlockTy, SealedBlock, SealedHeader}; use revm::{ - context::result::{ExecutionResult, HaltReason, Output, ResultAndState, SuccessReason}, + context::result::{ExecutionResult, Output, ResultAndState, SuccessReason}, database::State, Inspector, }; @@ -58,12 +58,12 @@ impl BlockExecutorFactory for MockEvmConfig { fn create_executor<'a, DB, I>( &'a self, - evm: EthEvm<&'a mut State, I, PrecompilesMap>, + evm: EthEvm, _ctx: Self::ExecutionCtx<'a>, ) -> impl BlockExecutorFor<'a, Self, DB, I> where - DB: Database + 'a, - I: Inspector<::Context<&'a mut State>> + 'a, + DB: StateDB + Database + 'a, + I: Inspector<::Context> + 'a, { MockExecutor { result: self.exec_results.lock().pop().unwrap(), @@ -76,18 +76,18 @@ impl BlockExecutorFactory for MockEvmConfig { /// Mock executor that returns a fixed execution result. #[derive(derive_more::Debug)] -pub struct MockExecutor<'a, DB: Database, I> { +pub struct MockExecutor { result: ExecutionOutcome, - evm: EthEvm<&'a mut State, I, PrecompilesMap>, + evm: EthEvm, #[debug(skip)] hook: Option>, receipts: Vec, } -impl<'a, DB: Database, I: Inspector>>> BlockExecutor - for MockExecutor<'a, DB, I> +impl<'a, DB: StateDB + Database, I: Inspector>> BlockExecutor + for MockExecutor { - type Evm = EthEvm<&'a mut State, I, PrecompilesMap>; + type Evm = EthEvm; type Transaction = TransactionSigned; type Receipt = Receipt; type Result = EthTxResult; @@ -139,7 +139,7 @@ impl<'a, DB: Database, I: Inspector>>> BlockExec blob_gas_used: 0, }; - evm.db_mut().bundle_state = bundle; + *evm.db_mut().bundle_state_mut() = bundle; Ok((evm, result)) } diff --git a/crates/evm/evm/src/execute.rs b/crates/evm/evm/src/execute.rs index bf0c553fb73..ee96c3b6d13 100644 --- a/crates/evm/evm/src/execute.rs +++ b/crates/evm/evm/src/execute.rs @@ -1,5 +1,7 @@ //! Traits for execution. +use std::borrow::Cow; + use crate::{ConfigureEvm, Database, OnStateHook, TxEnvFor}; use alloc::{boxed::Box, sync::Arc, vec::Vec}; use alloy_consensus::{BlockHeader, Header}; @@ -202,7 +204,7 @@ pub struct BlockAssemblerInput<'a, 'b, F: BlockExecutorFactory, H = Header> { /// Output of block execution. pub output: &'b BlockExecutionResult, /// [`BundleState`] after the block execution. - pub bundle_state: &'a BundleState, + pub bundle_state: Cow<'a, BundleState>, /// Provider with access to state. #[debug(skip)] pub state_provider: &'b dyn StateProvider, @@ -222,7 +224,7 @@ impl<'a, 'b, F: BlockExecutorFactory, H> BlockAssemblerInput<'a, 'b, F, H> { parent: &'a SealedHeader, transactions: Vec, output: &'b BlockExecutionResult, - bundle_state: &'a BundleState, + bundle_state: impl Into>, state_provider: &'b dyn StateProvider, state_root: B256, ) -> Self { @@ -232,7 +234,7 @@ impl<'a, 'b, F: BlockExecutorFactory, H> BlockAssemblerInput<'a, 'b, F, H> { parent, transactions, output, - bundle_state, + bundle_state: bundle_state.into(), state_provider, state_root, } @@ -431,8 +433,7 @@ where } } -impl<'a, DB, F, Executor, Builder, N> BlockBuilder - for BasicBlockBuilder<'a, F, Executor, Builder, N> +impl<'a, F, Executor, Builder, N> BlockBuilder for BasicBlockBuilder<'a, F, Executor, Builder, N> where F: BlockExecutorFactory, Executor: BlockExecutor< @@ -440,12 +441,11 @@ where Spec = ::Spec, HaltReason = ::HaltReason, BlockEnv = ::BlockEnv, - DB = &'a mut DB, + DB: StateDB + 'a, >, Transaction = N::SignedTx, Receipt = N::Receipt, >, - DB: StateDB + 'a, Builder: BlockAssembler, N: NodePrimitives, { @@ -479,7 +479,7 @@ where state: impl StateProvider, ) -> Result, BlockExecutionError> { let (evm, result) = self.executor.finish()?; - let (db, evm_env) = evm.finish(); + let (mut db, evm_env) = evm.finish(); // merge all transitions into bundle state db.merge_transitions(BundleRetention::Reverts); @@ -499,7 +499,7 @@ where parent: self.parent, transactions, output: &result, - bundle_state: (*db).bundle_state(), + bundle_state: Cow::Owned(db.take_bundle()), state_provider: &state, state_root, })?; diff --git a/crates/evm/evm/src/lib.rs b/crates/evm/evm/src/lib.rs index bf5ca7ae1de..23f0b6278a3 100644 --- a/crates/evm/evm/src/lib.rs +++ b/crates/evm/evm/src/lib.rs @@ -25,7 +25,7 @@ use alloy_eips::{ eip4895::Withdrawals, }; use alloy_evm::{ - block::{BlockExecutorFactory, BlockExecutorFor}, + block::{BlockExecutorFactory, BlockExecutorFor, StateDB}, precompiles::PrecompilesMap, }; use alloy_primitives::{Address, Bytes, B256}; @@ -35,7 +35,7 @@ use reth_execution_errors::BlockExecutionError; use reth_primitives_traits::{ BlockTy, HeaderTy, NodePrimitives, ReceiptTy, SealedBlock, SealedHeader, TxTy, }; -use revm::{context::TxEnv, database::State, primitives::hardfork::SpecId}; +use revm::{context::TxEnv, DatabaseCommit, database::State, primitives::hardfork::SpecId}; pub mod either; /// EVM environment configuration. @@ -313,20 +313,20 @@ pub trait ConfigureEvm: Clone + Debug + Send + Sync + Unpin { /// Creates a strategy with given EVM and execution context. fn create_executor<'a, DB, I>( &'a self, - evm: EvmFor, I>, + evm: EvmFor, ctx: ::ExecutionCtx<'a>, ) -> impl BlockExecutorFor<'a, Self::BlockExecutorFactory, DB, I> where - DB: Database, - I: InspectorFor> + 'a, + DB: StateDB + DatabaseCommit + Database + 'a, + I: InspectorFor + 'a, { self.block_executor_factory().create_executor(evm, ctx) } /// Creates a strategy for execution of a given block. - fn executor_for_block<'a, DB: Database>( + fn executor_for_block<'a, DB: StateDB + DatabaseCommit + Database + 'a>( &'a self, - db: &'a mut State, + db: DB, block: &'a SealedBlock<::Block>, ) -> Result, Self::Error> { let evm = self.evm_for_block(db, block.header())?; @@ -351,7 +351,7 @@ pub trait ConfigureEvm: Clone + Debug + Send + Sync + Unpin { /// ``` fn create_block_builder<'a, DB, I>( &'a self, - evm: EvmFor, I>, + evm: EvmFor, parent: &'a SealedHeader>, ctx: ::ExecutionCtx<'a>, ) -> impl BlockBuilder< @@ -359,8 +359,8 @@ pub trait ConfigureEvm: Clone + Debug + Send + Sync + Unpin { Executor: BlockExecutorFor<'a, Self::BlockExecutorFactory, DB, I>, > where - DB: Database, - I: InspectorFor> + 'a, + DB: StateDB + DatabaseCommit + Database + 'a, + I: InspectorFor + 'a, { BasicBlockBuilder { executor: self.create_executor(evm, ctx.clone()), @@ -400,9 +400,13 @@ pub trait ConfigureEvm: Clone + Debug + Send + Sync + Unpin { /// // Complete block building /// let outcome = builder.finish(state_provider)?; /// ``` +<<<<<<< HEAD fn builder_for_next_block<'a, DB: Database + 'a>( +======= + fn builder_for_next_block<'a, DB: StateDB + DatabaseCommit + Database + 'a>( +>>>>>>> 71397c6c6b (wip: getting everything compiling) &'a self, - db: &'a mut State, + db: DB, parent: &'a SealedHeader<::BlockHeader>, attributes: Self::NextBlockEnvCtx, ) -> Result< diff --git a/crates/optimism/evm/src/build.rs b/crates/optimism/evm/src/build.rs new file mode 100644 index 00000000000..35fae0981b5 --- /dev/null +++ b/crates/optimism/evm/src/build.rs @@ -0,0 +1,154 @@ +use alloc::sync::Arc; +use alloy_consensus::{ + constants::EMPTY_WITHDRAWALS, proofs, Block, BlockBody, Header, TxReceipt, + EMPTY_OMMER_ROOT_HASH, +}; +use alloy_eips::{eip7685::EMPTY_REQUESTS_HASH, merge::BEACON_NONCE}; +use alloy_evm::block::BlockExecutorFactory; +use alloy_op_evm::OpBlockExecutionCtx; +use alloy_primitives::logs_bloom; +use reth_evm::execute::{BlockAssembler, BlockAssemblerInput}; +use reth_execution_errors::BlockExecutionError; +use reth_execution_types::BlockExecutionResult; +use reth_optimism_consensus::{calculate_receipt_root_no_memo_optimism, isthmus}; +use reth_optimism_forks::OpHardforks; +use reth_optimism_primitives::DepositReceipt; +use reth_primitives_traits::{Receipt, SignedTransaction}; +use revm::context::Block as _; + +/// Block builder for Optimism. +#[derive(Debug)] +pub struct OpBlockAssembler { + chain_spec: Arc, +} + +impl OpBlockAssembler { + /// Creates a new [`OpBlockAssembler`]. + pub const fn new(chain_spec: Arc) -> Self { + Self { chain_spec } + } +} + +impl OpBlockAssembler { + /// Builds a block for `input` without any bounds on header `H`. + pub fn assemble_block< + F: for<'a> BlockExecutorFactory< + ExecutionCtx<'a>: Into, + Transaction: SignedTransaction, + Receipt: Receipt + DepositReceipt, + >, + H, + >( + &self, + input: BlockAssemblerInput<'_, '_, F, H>, + ) -> Result, BlockExecutionError> { + let BlockAssemblerInput { + evm_env, + execution_ctx: ctx, + transactions, + output: BlockExecutionResult { receipts, gas_used, blob_gas_used, requests: _ }, + bundle_state, + state_root, + state_provider, + .. + } = input; + let ctx = ctx.into(); + + let timestamp = evm_env.block_env.timestamp().saturating_to(); + + let transactions_root = proofs::calculate_transaction_root(&transactions); + let receipts_root = + calculate_receipt_root_no_memo_optimism(receipts, &self.chain_spec, timestamp); + let logs_bloom = logs_bloom(receipts.iter().flat_map(|r| r.logs())); + + let mut requests_hash = None; + + let withdrawals_root = if self.chain_spec.is_isthmus_active_at_timestamp(timestamp) { + // always empty requests hash post isthmus + requests_hash = Some(EMPTY_REQUESTS_HASH); + + // withdrawals root field in block header is used for storage root of L2 predeploy + // `l2tol1-message-passer` + Some( + isthmus::withdrawals_root(&bundle_state, state_provider) + .map_err(BlockExecutionError::other)?, + ) + } else if self.chain_spec.is_canyon_active_at_timestamp(timestamp) { + Some(EMPTY_WITHDRAWALS) + } else { + None + }; + + let (excess_blob_gas, blob_gas_used) = + if self.chain_spec.is_jovian_active_at_timestamp(timestamp) { + // In jovian, we're using the blob gas used field to store the current da + // footprint's value. + (Some(0), Some(*blob_gas_used)) + } else if self.chain_spec.is_ecotone_active_at_timestamp(timestamp) { + (Some(0), Some(0)) + } else { + (None, None) + }; + + let header = Header { + parent_hash: ctx.parent_hash, + ommers_hash: EMPTY_OMMER_ROOT_HASH, + beneficiary: evm_env.block_env.beneficiary(), + state_root, + transactions_root, + receipts_root, + withdrawals_root, + logs_bloom, + timestamp, + mix_hash: evm_env.block_env.prevrandao().unwrap_or_default(), + nonce: BEACON_NONCE.into(), + base_fee_per_gas: Some(evm_env.block_env.basefee()), + number: evm_env.block_env.number().saturating_to(), + gas_limit: evm_env.block_env.gas_limit(), + difficulty: evm_env.block_env.difficulty(), + gas_used: *gas_used, + extra_data: ctx.extra_data, + parent_beacon_block_root: ctx.parent_beacon_block_root, + blob_gas_used, + excess_blob_gas, + requests_hash, + }; + + Ok(Block::new( + header, + BlockBody { + transactions, + ommers: Default::default(), + withdrawals: self + .chain_spec + .is_canyon_active_at_timestamp(timestamp) + .then(Default::default), + }, + )) + } +} + +impl Clone for OpBlockAssembler { + fn clone(&self) -> Self { + Self { chain_spec: self.chain_spec.clone() } + } +} + +impl BlockAssembler for OpBlockAssembler +where + ChainSpec: OpHardforks, + F: for<'a> BlockExecutorFactory< + ExecutionCtx<'a> = OpBlockExecutionCtx, + Transaction: SignedTransaction, + Receipt: Receipt + DepositReceipt, + >, +{ + type Block = Block; + + fn assemble_block( + &self, + input: BlockAssemblerInput<'_, '_, F>, + ) -> Result { + self.assemble_block(input) + } +} diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs new file mode 100644 index 00000000000..66647ba0ab9 --- /dev/null +++ b/crates/optimism/payload/src/builder.rs @@ -0,0 +1,783 @@ +//! Optimism payload builder implementation. +use crate::{ + config::OpBuilderConfig, error::OpPayloadBuilderError, payload::OpBuiltPayload, OpAttributes, + OpPayloadBuilderAttributes, OpPayloadPrimitives, +}; +use alloy_consensus::{BlockHeader, Transaction, Typed2718}; +use alloy_evm::Evm as AlloyEvm; +use alloy_primitives::{B256, U256}; +use alloy_rpc_types_debug::ExecutionWitness; +use alloy_rpc_types_engine::PayloadId; +use reth_basic_payload_builder::*; +use reth_chainspec::{ChainSpecProvider, EthChainSpec}; +use reth_evm::{ + block::{BlockExecutorFor, StateDB}, + execute::{ + BlockBuilder, BlockBuilderOutcome, BlockExecutionError, BlockExecutor, BlockValidationError, + }, + op_revm::{constants::L1_BLOCK_CONTRACT, L1BlockInfo}, + ConfigureEvm, Database, +}; +use reth_execution_types::ExecutionOutcome; +use reth_optimism_forks::OpHardforks; +use reth_optimism_primitives::{transaction::OpTransaction, ADDRESS_L2_TO_L1_MESSAGE_PASSER}; +use reth_optimism_txpool::{ + estimated_da_size::DataAvailabilitySized, + interop::{is_valid_interop, MaybeInteropTransaction}, + OpPooledTx, +}; +use reth_payload_builder_primitives::PayloadBuilderError; +use reth_payload_primitives::{BuildNextEnv, BuiltPayloadExecutedBlock, PayloadBuilderAttributes}; +use reth_payload_util::{BestPayloadTransactions, NoopPayloadTransactions, PayloadTransactions}; +use reth_primitives_traits::{ + HeaderTy, NodePrimitives, SealedHeader, SealedHeaderFor, SignedTransaction, TxTy, +}; +use reth_revm::{ + cancelled::CancelOnDrop, database::StateProviderDatabase, db::State, + witness::ExecutionWitnessRecord, +}; +use reth_storage_api::{errors::ProviderError, StateProvider, StateProviderFactory}; +use reth_transaction_pool::{BestTransactionsAttributes, PoolTransaction, TransactionPool}; +use revm::{ + context::{Block, BlockEnv}, + DatabaseCommit, +}; +use std::{marker::PhantomData, sync::Arc}; +use tracing::{debug, trace, warn}; + +/// Optimism's payload builder +#[derive(Debug)] +pub struct OpPayloadBuilder< + Pool, + Client, + Evm, + Txs = (), + Attrs = OpPayloadBuilderAttributes::Primitives>>, +> { + /// The rollup's compute pending block configuration option. + // TODO(clabby): Implement this feature. + pub compute_pending_block: bool, + /// The type responsible for creating the evm. + pub evm_config: Evm, + /// Transaction pool. + pub pool: Pool, + /// Node client. + pub client: Client, + /// Settings for the builder, e.g. DA settings. + pub config: OpBuilderConfig, + /// The type responsible for yielding the best transactions for the payload if mempool + /// transactions are allowed. + pub best_transactions: Txs, + /// Marker for the payload attributes type. + _pd: PhantomData, +} + +impl Clone for OpPayloadBuilder +where + Pool: Clone, + Client: Clone, + Evm: ConfigureEvm, + Txs: Clone, +{ + fn clone(&self) -> Self { + Self { + evm_config: self.evm_config.clone(), + pool: self.pool.clone(), + client: self.client.clone(), + config: self.config.clone(), + best_transactions: self.best_transactions.clone(), + compute_pending_block: self.compute_pending_block, + _pd: PhantomData, + } + } +} + +impl OpPayloadBuilder { + /// `OpPayloadBuilder` constructor. + /// + /// Configures the builder with the default settings. + pub fn new(pool: Pool, client: Client, evm_config: Evm) -> Self { + Self::with_builder_config(pool, client, evm_config, Default::default()) + } + + /// Configures the builder with the given [`OpBuilderConfig`]. + pub const fn with_builder_config( + pool: Pool, + client: Client, + evm_config: Evm, + config: OpBuilderConfig, + ) -> Self { + Self { + pool, + client, + compute_pending_block: true, + evm_config, + config, + best_transactions: (), + _pd: PhantomData, + } + } +} + +impl OpPayloadBuilder { + /// Sets the rollup's compute pending block configuration option. + pub const fn set_compute_pending_block(mut self, compute_pending_block: bool) -> Self { + self.compute_pending_block = compute_pending_block; + self + } + + /// Configures the type responsible for yielding the transactions that should be included in the + /// payload. + pub fn with_transactions( + self, + best_transactions: T, + ) -> OpPayloadBuilder { + let Self { pool, client, compute_pending_block, evm_config, config, .. } = self; + OpPayloadBuilder { + pool, + client, + compute_pending_block, + evm_config, + best_transactions, + config, + _pd: PhantomData, + } + } + + /// Enables the rollup's compute pending block configuration option. + pub const fn compute_pending_block(self) -> Self { + self.set_compute_pending_block(true) + } + + /// Returns the rollup's compute pending block configuration option. + pub const fn is_compute_pending_block(&self) -> bool { + self.compute_pending_block + } +} + +impl OpPayloadBuilder +where + Pool: TransactionPool>, + Client: StateProviderFactory + ChainSpecProvider, + N: OpPayloadPrimitives, + Evm: ConfigureEvm< + Primitives = N, + NextBlockEnvCtx: BuildNextEnv, + >, + Attrs: OpAttributes>, +{ + /// Constructs an Optimism payload from the transactions sent via 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 Optimism 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. + fn build_payload<'a, Txs>( + &self, + args: BuildArguments>, + best: impl FnOnce(BestTransactionsAttributes) -> Txs + Send + Sync + 'a, + ) -> Result>, PayloadBuilderError> + where + Txs: + PayloadTransactions + OpPooledTx>, + { + let BuildArguments { mut cached_reads, config, cancel, best_payload } = args; + + let ctx = OpPayloadBuilderCtx { + evm_config: self.evm_config.clone(), + builder_config: self.config.clone(), + chain_spec: self.client.chain_spec(), + config, + cancel, + best_payload, + }; + + let builder = OpBuilder::new(best); + + let state_provider = self.client.state_by_block_hash(ctx.parent().hash())?; + let state = StateProviderDatabase::new(&state_provider); + + if ctx.attributes().no_tx_pool() { + builder.build(state, &state_provider, ctx) + } else { + // sequencer mode we can reuse cachedreads from previous runs + builder.build(cached_reads.as_db_mut(state), &state_provider, ctx) + } + .map(|out| out.with_cached_reads(cached_reads)) + } + + /// Computes the witness for the payload. + pub fn payload_witness( + &self, + parent: SealedHeader, + attributes: Attrs::RpcPayloadAttributes, + ) -> Result + where + Attrs: PayloadBuilderAttributes, + { + let attributes = + Attrs::try_new(parent.hash(), attributes, 3).map_err(PayloadBuilderError::other)?; + + let config = PayloadConfig { parent_header: Arc::new(parent), attributes }; + let ctx = OpPayloadBuilderCtx { + evm_config: self.evm_config.clone(), + builder_config: self.config.clone(), + chain_spec: self.client.chain_spec(), + config, + cancel: Default::default(), + best_payload: Default::default(), + }; + + let state_provider = self.client.state_by_block_hash(ctx.parent().hash())?; + + let builder = OpBuilder::new(|_| NoopPayloadTransactions::::default()); + builder.witness(state_provider, &ctx) + } +} + +/// Implementation of the [`PayloadBuilder`] trait for [`OpPayloadBuilder`]. +impl PayloadBuilder + for OpPayloadBuilder +where + N: OpPayloadPrimitives, + Client: StateProviderFactory + ChainSpecProvider + Clone, + Pool: TransactionPool>, + Evm: ConfigureEvm< + Primitives = N, + NextBlockEnvCtx: BuildNextEnv, + >, + Txs: OpPayloadTransactions, + Attrs: OpAttributes, +{ + type Attributes = Attrs; + type BuiltPayload = OpBuiltPayload; + + fn try_build( + &self, + args: BuildArguments, + ) -> Result, PayloadBuilderError> { + let pool = self.pool.clone(); + self.build_payload(args, |attrs| self.best_transactions.best_transactions(pool, attrs)) + } + + fn on_missing_payload( + &self, + _args: BuildArguments, + ) -> MissingPayloadBehaviour { + // we want to await the job that's already in progress because that should be returned as + // is, there's no benefit in racing another job + MissingPayloadBehaviour::AwaitInProgress + } + + // NOTE: this should only be used for testing purposes because this doesn't have access to L1 + // system txs, hence on_missing_payload we return [MissingPayloadBehaviour::AwaitInProgress]. + fn build_empty_payload( + &self, + config: PayloadConfig, + ) -> Result { + let args = BuildArguments { + config, + cached_reads: Default::default(), + cancel: Default::default(), + best_payload: None, + }; + self.build_payload(args, |_| NoopPayloadTransactions::::default())? + .into_payload() + .ok_or_else(|| PayloadBuilderError::MissingPayload) + } +} + +/// The type that builds the payload. +/// +/// Payload building for optimism is composed of several steps. +/// The first steps are mandatory and defined by the protocol. +/// +/// 1. first all System calls are applied. +/// 2. After canyon the forced deployed `create2deployer` must be loaded +/// 3. all sequencer transactions are executed (part of the payload attributes) +/// +/// Depending on whether the node acts as a sequencer and is allowed to include additional +/// transactions (`no_tx_pool == false`): +/// 4. include additional transactions +/// +/// And finally +/// 5. build the block: compute all roots (txs, state) +#[derive(derive_more::Debug)] +pub struct OpBuilder<'a, Txs> { + /// Yields the best transaction to include if transactions from the mempool are allowed. + #[debug(skip)] + best: Box Txs + 'a>, +} + +impl<'a, Txs> OpBuilder<'a, Txs> { + /// Creates a new [`OpBuilder`]. + pub fn new(best: impl FnOnce(BestTransactionsAttributes) -> Txs + Send + Sync + 'a) -> Self { + Self { best: Box::new(best) } + } +} + +impl OpBuilder<'_, Txs> { + /// Builds the payload on top of the state. + pub fn build( + self, + db: impl Database, + state_provider: impl StateProvider, + ctx: OpPayloadBuilderCtx, + ) -> Result>, PayloadBuilderError> + where + Evm: ConfigureEvm< + Primitives = N, + NextBlockEnvCtx: BuildNextEnv, + >, + ChainSpec: EthChainSpec + OpHardforks, + N: OpPayloadPrimitives, + Txs: + PayloadTransactions + OpPooledTx>, + Attrs: OpAttributes, + { + let Self { best } = self; + debug!(target: "payload_builder", id=%ctx.payload_id(), parent_header = ?ctx.parent().hash(), parent_number = ctx.parent().number(), "building new payload"); + + let mut db = State::builder().with_database(db).with_bundle_update().build(); + + // Load the L1 block contract into the database cache. If the L1 block contract is not + // pre-loaded the database will panic when trying to fetch the DA footprint gas + // scalar. + db.load_cache_account(L1_BLOCK_CONTRACT).map_err(BlockExecutionError::other)?; + + let mut builder = ctx.block_builder(&mut db)?; + + // 1. apply pre-execution changes + builder.apply_pre_execution_changes().map_err(|err| { + warn!(target: "payload_builder", %err, "failed to apply pre-execution changes"); + PayloadBuilderError::Internal(err.into()) + })?; + + // 2. execute sequencer transactions + let mut info = ctx.execute_sequencer_transactions(&mut builder)?; + + // 3. if mem pool transactions are requested we execute them + if !ctx.attributes().no_tx_pool() { + let best_txs = best(ctx.best_transaction_attributes(builder.evm_mut().block())); + if ctx.execute_best_transactions(&mut info, &mut builder, best_txs)?.is_some() { + return Ok(BuildOutcomeKind::Cancelled) + } + + // check if the new payload is even more valuable + if !ctx.is_better_payload(info.total_fees) { + // can skip building the block + return Ok(BuildOutcomeKind::Aborted { fees: info.total_fees }) + } + } + + let BlockBuilderOutcome { execution_result, hashed_state, trie_updates, block } = + builder.finish(state_provider)?; + + let sealed_block = Arc::new(block.sealed_block().clone()); + debug!(target: "payload_builder", id=%ctx.attributes().payload_id(), sealed_block_header = ?sealed_block.header(), "sealed built block"); + + let execution_outcome = ExecutionOutcome::new( + db.take_bundle(), + vec![execution_result.receipts], + block.number(), + Vec::new(), + ); + + // create the executed block data + let executed: BuiltPayloadExecutedBlock = BuiltPayloadExecutedBlock { + recovered_block: Arc::new(block), + execution_output: Arc::new(execution_outcome), + // Keep unsorted; conversion to sorted happens when needed downstream + hashed_state: either::Either::Left(Arc::new(hashed_state)), + trie_updates: either::Either::Left(Arc::new(trie_updates)), + }; + + let no_tx_pool = ctx.attributes().no_tx_pool(); + + let payload = + OpBuiltPayload::new(ctx.payload_id(), sealed_block, info.total_fees, Some(executed)); + + if no_tx_pool { + // if `no_tx_pool` is set only transactions from the payload attributes will be included + // in the payload. In other words, the payload is deterministic and we can + // freeze it once we've successfully built it. + Ok(BuildOutcomeKind::Freeze(payload)) + } else { + Ok(BuildOutcomeKind::Better { payload }) + } + } + + /// Builds the payload and returns its [`ExecutionWitness`] based on the state after execution. + pub fn witness( + self, + state_provider: impl StateProvider, + ctx: &OpPayloadBuilderCtx, + ) -> Result + where + Evm: ConfigureEvm< + Primitives = N, + NextBlockEnvCtx: BuildNextEnv, + >, + ChainSpec: EthChainSpec + OpHardforks, + N: OpPayloadPrimitives, + Txs: PayloadTransactions>, + Attrs: OpAttributes, + { + let mut db = State::builder() + .with_database(StateProviderDatabase::new(&state_provider)) + .with_bundle_update() + .build(); + let mut builder = ctx.block_builder(&mut db)?; + + builder.apply_pre_execution_changes()?; + ctx.execute_sequencer_transactions(&mut builder)?; + builder.into_executor().apply_post_execution_changes()?; + + if ctx.chain_spec.is_isthmus_active_at_timestamp(ctx.attributes().timestamp()) { + // force load `L2ToL1MessagePasser.sol` so l2 withdrawals root can be computed even if + // no l2 withdrawals in block + _ = db.load_cache_account(ADDRESS_L2_TO_L1_MESSAGE_PASSER)?; + } + + let ExecutionWitnessRecord { hashed_state, codes, keys, lowest_block_number: _ } = + ExecutionWitnessRecord::from_executed_state(&db); + let state = state_provider.witness(Default::default(), hashed_state)?; + Ok(ExecutionWitness { + state: state.into_iter().collect(), + codes, + keys, + ..Default::default() + }) + } +} + +/// A type that returns a the [`PayloadTransactions`] that should be included in the pool. +pub trait OpPayloadTransactions: Clone + Send + Sync + Unpin + 'static { + /// Returns an iterator that yields the transaction in the order they should get included in the + /// new payload. + fn best_transactions>( + &self, + pool: Pool, + attr: BestTransactionsAttributes, + ) -> impl PayloadTransactions; +} + +impl OpPayloadTransactions for () { + fn best_transactions>( + &self, + pool: Pool, + attr: BestTransactionsAttributes, + ) -> impl PayloadTransactions { + BestPayloadTransactions::new(pool.best_transactions_with_attributes(attr)) + } +} + +/// Holds the state after execution +#[derive(Debug)] +pub struct ExecutedPayload { + /// Tracked execution info + pub info: ExecutionInfo, + /// Withdrawal hash. + pub withdrawals_root: Option, + /// The transaction receipts. + pub receipts: Vec, + /// The block env used during execution. + pub block_env: BlockEnv, +} + +/// This acts as the container for executed transactions and its byproducts (receipts, gas used) +#[derive(Default, Debug)] +pub struct ExecutionInfo { + /// All gas used so far + pub cumulative_gas_used: u64, + /// Estimated DA size + pub cumulative_da_bytes_used: u64, + /// Tracks fees from executed mempool transactions + pub total_fees: U256, +} + +impl ExecutionInfo { + /// Create a new instance with allocated slots. + pub const fn new() -> Self { + Self { cumulative_gas_used: 0, cumulative_da_bytes_used: 0, total_fees: U256::ZERO } + } + + /// Returns true if the transaction would exceed the block limits: + /// - block gas limit: ensures the transaction still fits into the block. + /// - tx DA limit: if configured, ensures the tx does not exceed the maximum allowed DA limit + /// per tx. + /// - block DA limit: if configured, ensures the transaction's DA size does not exceed the + /// maximum allowed DA limit per block. + pub fn is_tx_over_limits( + &self, + tx_da_size: u64, + block_gas_limit: u64, + tx_data_limit: Option, + block_data_limit: Option, + tx_gas_limit: u64, + da_footprint_gas_scalar: Option, + ) -> bool { + if tx_data_limit.is_some_and(|da_limit| tx_da_size > da_limit) { + return true; + } + + let total_da_bytes_used = self.cumulative_da_bytes_used.saturating_add(tx_da_size); + + if block_data_limit.is_some_and(|da_limit| total_da_bytes_used > da_limit) { + return true; + } + + // Post Jovian: the tx DA footprint must be less than the block gas limit + if let Some(da_footprint_gas_scalar) = da_footprint_gas_scalar { + let tx_da_footprint = + total_da_bytes_used.saturating_mul(da_footprint_gas_scalar as u64); + if tx_da_footprint > block_gas_limit { + return true; + } + } + + self.cumulative_gas_used + tx_gas_limit > block_gas_limit + } +} + +/// Container type that holds all necessities to build a new payload. +#[derive(derive_more::Debug)] +pub struct OpPayloadBuilderCtx< + Evm: ConfigureEvm, + ChainSpec, + Attrs = OpPayloadBuilderAttributes::Primitives>>, +> { + /// The type that knows how to perform system calls and configure the evm. + pub evm_config: Evm, + /// Additional config for the builder/sequencer, e.g. DA and gas limit + pub builder_config: OpBuilderConfig, + /// The chainspec + pub chain_spec: Arc, + /// How to build the payload. + pub config: PayloadConfig>, + /// Marker to check whether the job has been cancelled. + pub cancel: CancelOnDrop, + /// The currently best payload. + pub best_payload: Option>, +} + +impl OpPayloadBuilderCtx +where + Evm: ConfigureEvm< + Primitives: OpPayloadPrimitives, + NextBlockEnvCtx: BuildNextEnv, ChainSpec>, + >, + ChainSpec: EthChainSpec + OpHardforks, + Attrs: OpAttributes>, +{ + /// Returns the parent block the payload will be build on. + pub fn parent(&self) -> &SealedHeaderFor { + self.config.parent_header.as_ref() + } + + /// Returns the builder attributes. + pub const fn attributes(&self) -> &Attrs { + &self.config.attributes + } + + /// Returns the current fee settings for transactions from the mempool + pub fn best_transaction_attributes(&self, block_env: impl Block) -> BestTransactionsAttributes { + BestTransactionsAttributes::new( + block_env.basefee(), + block_env.blob_gasprice().map(|p| p as u64), + ) + } + + /// Returns the unique id for this payload job. + pub fn payload_id(&self) -> PayloadId { + self.attributes().payload_id() + } + + /// Returns true if the fees are higher than the previous payload. + pub fn is_better_payload(&self, total_fees: U256) -> bool { + is_better_payload(self.best_payload.as_ref(), total_fees) + } + + /// Prepares a [`BlockBuilder`] for the next block. + pub fn block_builder<'a, DB: StateDB + DatabaseCommit + Database + 'a>( + &'a self, + db: DB, + ) -> Result< + impl BlockBuilder< + Primitives = Evm::Primitives, + Executor: BlockExecutorFor<'a, Evm::BlockExecutorFactory, DB>, + > + 'a, + PayloadBuilderError, + > { + self.evm_config + .builder_for_next_block( + db, + self.parent(), + Evm::NextBlockEnvCtx::build_next_env( + self.attributes(), + self.parent(), + self.chain_spec.as_ref(), + ) + .map_err(PayloadBuilderError::other)?, + ) + .map_err(PayloadBuilderError::other) + } + + /// Executes all sequencer transactions that are included in the payload attributes. + pub fn execute_sequencer_transactions( + &self, + builder: &mut impl BlockBuilder, + ) -> Result { + let mut info = ExecutionInfo::new(); + + for sequencer_tx in self.attributes().sequencer_transactions() { + // A sequencer's block should never contain blob transactions. + if sequencer_tx.value().is_eip4844() { + return Err(PayloadBuilderError::other( + OpPayloadBuilderError::BlobTransactionRejected, + )) + } + + // Convert the transaction to a [RecoveredTx]. This is + // purely for the purposes of utilizing the `evm_config.tx_env`` 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.value().try_clone_into_recovered().map_err(|_| { + PayloadBuilderError::other(OpPayloadBuilderError::TransactionEcRecoverFailed) + })?; + + let gas_used = match builder.execute_transaction(sequencer_tx.clone()) { + Ok(gas_used) => gas_used, + Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx { + error, + .. + })) => { + trace!(target: "payload_builder", %error, ?sequencer_tx, "Error in sequencer transaction, skipping."); + continue + } + Err(err) => { + // this is an error that we should treat as fatal for this attempt + return Err(PayloadBuilderError::EvmExecutionError(Box::new(err))) + } + }; + + // add gas used by the transaction to cumulative gas used, before creating the receipt + info.cumulative_gas_used += gas_used; + } + + Ok(info) + } + + /// Executes the given best transactions and updates the execution info. + /// + /// Returns `Ok(Some(())` if the job was cancelled. + pub fn execute_best_transactions( + &self, + info: &mut ExecutionInfo, + builder: &mut Builder, + mut best_txs: impl PayloadTransactions< + Transaction: PoolTransaction> + OpPooledTx, + >, + ) -> Result, PayloadBuilderError> + where + Builder: BlockBuilder, + <::Evm as AlloyEvm>::DB: Database, + { + let mut block_gas_limit = builder.evm_mut().block().gas_limit(); + if let Some(gas_limit_config) = self.builder_config.gas_limit_config.gas_limit() { + // If a gas limit is configured, use that limit as target if it's smaller, otherwise use + // the block's actual gas limit. + block_gas_limit = gas_limit_config.min(block_gas_limit); + }; + let block_da_limit = self.builder_config.da_config.max_da_block_size(); + let tx_da_limit = self.builder_config.da_config.max_da_tx_size(); + let base_fee = builder.evm_mut().block().basefee(); + + while let Some(tx) = best_txs.next(()) { + let interop = tx.interop_deadline(); + let tx_da_size = tx.estimated_da_size(); + let tx = tx.into_consensus(); + + let da_footprint_gas_scalar = self + .chain_spec + .is_jovian_active_at_timestamp(self.attributes().timestamp()) + .then_some( + L1BlockInfo::fetch_da_footprint_gas_scalar(builder.evm_mut().db_mut()).expect( + "DA footprint should always be available from the database post jovian", + ), + ); + + if info.is_tx_over_limits( + tx_da_size, + block_gas_limit, + tx_da_limit, + block_da_limit, + tx.gas_limit(), + da_footprint_gas_scalar, + ) { + // 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(tx.signer(), tx.nonce()); + continue + } + + // A sequencer's block should never contain blob or deposit transactions from the pool. + if tx.is_eip4844() || tx.is_deposit() { + best_txs.mark_invalid(tx.signer(), tx.nonce()); + continue + } + + // We skip invalid cross chain txs, they would be removed on the next block update in + // the maintenance job + if let Some(interop) = interop && + !is_valid_interop(interop, self.config.attributes.timestamp()) + { + best_txs.mark_invalid(tx.signer(), tx.nonce()); + continue + } + // check if the job was cancelled, if so we can exit early + if self.cancel.is_cancelled() { + return Ok(Some(())) + } + + let gas_used = match builder.execute_transaction(tx.clone()) { + Ok(gas_used) => gas_used, + Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx { + error, + .. + })) => { + if error.is_nonce_too_low() { + // if the nonce is too low, we can skip this transaction + trace!(target: "payload_builder", %error, ?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", %error, ?tx, "skipping invalid transaction and its descendants"); + best_txs.mark_invalid(tx.signer(), tx.nonce()); + } + continue + } + Err(err) => { + // this is an error that we should treat as fatal for this attempt + return Err(PayloadBuilderError::EvmExecutionError(Box::new(err))) + } + }; + + // add gas used by the transaction to cumulative gas used, before creating the + // receipt + info.cumulative_gas_used += gas_used; + info.cumulative_da_bytes_used += tx_da_size; + + // update and add to total fees + let miner_fee = tx + .effective_tip_per_gas(base_fee) + .expect("fee is always valid; execution succeeded"); + info.total_fees += U256::from(miner_fee) * U256::from(gas_used); + } + + Ok(None) + } +} From 21e6ea190658b5fdc64fc83cbf7e71ae9514a57e Mon Sep 17 00:00:00 2001 From: Eric Woolsey Date: Wed, 3 Dec 2025 20:22:07 -0800 Subject: [PATCH 5/8] feat: complete transition to StateDB --- crates/ethereum/evm/src/test_utils.rs | 2 +- examples/custom-beacon-withdrawals/src/main.rs | 9 ++++----- examples/custom-node/src/evm/executor.rs | 10 +++++----- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/crates/ethereum/evm/src/test_utils.rs b/crates/ethereum/evm/src/test_utils.rs index f4013a6ebff..044a3b307ef 100644 --- a/crates/ethereum/evm/src/test_utils.rs +++ b/crates/ethereum/evm/src/test_utils.rs @@ -84,7 +84,7 @@ pub struct MockExecutor { receipts: Vec, } -impl<'a, DB: StateDB + Database, I: Inspector>> BlockExecutor +impl>> BlockExecutor for MockExecutor { type Evm = EthEvm; diff --git a/examples/custom-beacon-withdrawals/src/main.rs b/examples/custom-beacon-withdrawals/src/main.rs index f246e1947d7..248eb07fe55 100644 --- a/examples/custom-beacon-withdrawals/src/main.rs +++ b/examples/custom-beacon-withdrawals/src/main.rs @@ -23,7 +23,6 @@ use reth_ethereum::{ }, revm::{ context::TxEnv, - db::State, primitives::{address, hardfork::SpecId, Address}, DatabaseCommit, }, @@ -101,12 +100,12 @@ impl BlockExecutorFactory for CustomEvmConfig { fn create_executor<'a, DB, I>( &'a self, - evm: EthEvm<&'a mut State, I, PrecompilesMap>, + evm: EthEvm, ctx: EthBlockExecutionCtx<'a>, ) -> impl BlockExecutorFor<'a, Self, DB, I> where - DB: Database + 'a, - I: InspectorFor> + 'a, + DB: StateDB + DatabaseCommit + Database + 'a, + I: InspectorFor + 'a, { CustomBlockExecutor { inner: EthBlockExecutor::new( @@ -189,7 +188,7 @@ pub struct CustomBlockExecutor<'a, Evm> { impl BlockExecutor for CustomBlockExecutor<'_, E> where - E: Evm, + E: Evm, { type Transaction = TransactionSigned; type Receipt = Receipt; diff --git a/examples/custom-node/src/evm/executor.rs b/examples/custom-node/src/evm/executor.rs index 0206979a519..1e8535a3464 100644 --- a/examples/custom-node/src/evm/executor.rs +++ b/examples/custom-node/src/evm/executor.rs @@ -17,7 +17,7 @@ use alloy_evm::{ use alloy_op_evm::{OpBlockExecutionCtx, OpBlockExecutor}; use reth_ethereum::evm::primitives::InspectorFor; use reth_op::{chainspec::OpChainSpec, node::OpRethReceiptBuilder, OpReceipt}; -use revm::{context::result::ResultAndState, database::State}; +use revm::{context::result::ResultAndState, DatabaseCommit}; use std::sync::Arc; pub struct CustomBlockExecutor { @@ -26,7 +26,7 @@ pub struct CustomBlockExecutor { impl BlockExecutor for CustomBlockExecutor where - E: Evm, + E: Evm, { type Transaction = CustomTransaction; type Receipt = OpReceipt; @@ -90,12 +90,12 @@ impl BlockExecutorFactory for CustomEvmConfig { fn create_executor<'a, DB, I>( &'a self, - evm: CustomEvm<&'a mut State, I, PrecompilesMap>, + evm: CustomEvm, ctx: CustomBlockExecutionCtx, ) -> impl BlockExecutorFor<'a, Self, DB, I> where - DB: Database + 'a, - I: InspectorFor> + 'a, + DB: StateDB + DatabaseCommit + Database + 'a, + I: InspectorFor + 'a, { CustomBlockExecutor { inner: OpBlockExecutor::new( From de2fb9e9d441016647223daa06ceb4d61bdff3c5 Mon Sep 17 00:00:00 2001 From: Eric Woolsey Date: Wed, 3 Dec 2025 22:51:07 -0800 Subject: [PATCH 6/8] fix: std -> alloc --- crates/evm/evm/src/execute.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/evm/evm/src/execute.rs b/crates/evm/evm/src/execute.rs index ee96c3b6d13..a989b6c0f7a 100644 --- a/crates/evm/evm/src/execute.rs +++ b/crates/evm/evm/src/execute.rs @@ -1,6 +1,6 @@ //! Traits for execution. -use std::borrow::Cow; +use alloc::borrow::Cow; use crate::{ConfigureEvm, Database, OnStateHook, TxEnvFor}; use alloc::{boxed::Box, sync::Arc, vec::Vec}; From 8e21b1e9e3f66d76f43a723c915e7d1bdbdf83e0 Mon Sep 17 00:00:00 2001 From: Eric Woolsey Date: Wed, 11 Feb 2026 14:57:37 -0800 Subject: [PATCH 7/8] chore: satisfy clippy --- Cargo.lock | 91 ++++++++++++++++----------- Cargo.toml | 1 - crates/ethereum/evm/src/test_utils.rs | 3 +- crates/evm/evm/src/lib.rs | 6 +- 4 files changed, 58 insertions(+), 43 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8cf7152d571..d6d7e420a1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -289,8 +289,8 @@ dependencies = [ [[package]] name = "alloy-evm" -version = "0.24.2" -source = "git+https://github.com/0xforerunner/evm?rev=9de0fb0#9de0fb0c4440b1dfbffa6abca2aad66483d09c07" +version = "0.27.2" +source = "git+https://github.com/0xforerunner/evm?rev=0833666#08336663fe05521c820d8e9fdb9b8e040587893e" dependencies = [ "alloy-consensus", "alloy-eips", @@ -417,9 +417,10 @@ dependencies = [ ] [[package]] -name = "alloy-op-evm" -version = "0.24.2" -source = "git+https://github.com/0xforerunner/evm?rev=9de0fb0#9de0fb0c4440b1dfbffa6abca2aad66483d09c07" +name = "alloy-node-bindings" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ce6930ce52e43b0768dc99ceeff5cb9e673e8c9f87d926914cd028b2e3f7233" dependencies = [ "alloy-genesis", "alloy-hardforks 0.2.13", @@ -3068,7 +3069,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version 0.4.1", - "syn 2.0.111", + "syn 2.0.114", "unicode-xid", ] @@ -4765,6 +4766,7 @@ dependencies = [ "base64 0.22.1", "bytes", "futures-channel", + "futures-core", "futures-util", "http", "http-body", @@ -5502,11 +5504,17 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "libc" -version = "0.2.178" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "libgit2-sys" @@ -6415,8 +6423,9 @@ dependencies = [ [[package]] name = "op-revm" -version = "14.1.0" -source = "git+https://github.com/0xforerunner/revm?rev=195c8e9#195c8e90138c17488dc56aae023903684ec87c73" +version = "15.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79c92b75162c2ed1661849fa51683b11254a5b661798360a2c24be918edafd40" dependencies = [ "auto_impl", "revm", @@ -10689,8 +10698,9 @@ dependencies = [ [[package]] name = "revm" -version = "33.1.0" -source = "git+https://github.com/0xforerunner/revm?rev=195c8e9#195c8e90138c17488dc56aae023903684ec87c73" +version = "34.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2aabdebaa535b3575231a88d72b642897ae8106cf6b0d12eafc6bfdf50abfc7" dependencies = [ "revm-bytecode", "revm-context", @@ -10707,8 +10717,9 @@ dependencies = [ [[package]] name = "revm-bytecode" -version = "7.1.1" -source = "git+https://github.com/0xforerunner/revm?rev=195c8e9#195c8e90138c17488dc56aae023903684ec87c73" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d1e5c1eaa44d39d537f668bc5c3409dc01e5c8be954da6c83370bbdf006457" dependencies = [ "bitvec", "phf", @@ -10718,8 +10729,9 @@ dependencies = [ [[package]] name = "revm-context" -version = "12.1.0" -source = "git+https://github.com/0xforerunner/revm?rev=195c8e9#195c8e90138c17488dc56aae023903684ec87c73" +version = "13.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "892ff3e6a566cf8d72ffb627fdced3becebbd9ba64089c25975b9b028af326a5" dependencies = [ "bitvec", "cfg-if", @@ -10734,8 +10746,9 @@ dependencies = [ [[package]] name = "revm-context-interface" -version = "13.1.0" -source = "git+https://github.com/0xforerunner/revm?rev=195c8e9#195c8e90138c17488dc56aae023903684ec87c73" +version = "14.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57f61cc6d23678c4840af895b19f8acfbbd546142ec8028b6526c53cc1c16c98" dependencies = [ "alloy-eip2930", "alloy-eip7702", @@ -10749,8 +10762,9 @@ dependencies = [ [[package]] name = "revm-database" -version = "9.0.6" -source = "git+https://github.com/0xforerunner/revm?rev=195c8e9#195c8e90138c17488dc56aae023903684ec87c73" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "529528d0b05fe646be86223032c3e77aa8b05caa2a35447d538c55965956a511" dependencies = [ "alloy-eips", "revm-bytecode", @@ -10762,21 +10776,23 @@ dependencies = [ [[package]] name = "revm-database-interface" -version = "8.0.5" -source = "git+https://github.com/0xforerunner/revm?rev=195c8e9#195c8e90138c17488dc56aae023903684ec87c73" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7bf93ac5b91347c057610c0d96e923db8c62807e03f036762d03e981feddc1d" dependencies = [ "auto_impl", "either", "revm-primitives", "revm-state", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] name = "revm-handler" -version = "14.1.0" -source = "git+https://github.com/0xforerunner/revm?rev=195c8e9#195c8e90138c17488dc56aae023903684ec87c73" +version = "15.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cd0e43e815a85eded249df886c4badec869195e70cdd808a13cfca2794622d2" dependencies = [ "auto_impl", "derive-where", @@ -10793,8 +10809,9 @@ dependencies = [ [[package]] name = "revm-inspector" -version = "14.1.0" -source = "git+https://github.com/0xforerunner/revm?rev=195c8e9#195c8e90138c17488dc56aae023903684ec87c73" +version = "15.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f3ccad59db91ef93696536a0dbaf2f6f17cfe20d4d8843ae118edb7e97947ef" dependencies = [ "auto_impl", "either", @@ -10830,8 +10847,9 @@ dependencies = [ [[package]] name = "revm-interpreter" -version = "31.1.0" -source = "git+https://github.com/0xforerunner/revm?rev=195c8e9#195c8e90138c17488dc56aae023903684ec87c73" +version = "32.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11406408597bc249392d39295831c4b641b3a6f5c471a7c41104a7a1e3564c07" dependencies = [ "revm-bytecode", "revm-context-interface", @@ -10842,8 +10860,9 @@ dependencies = [ [[package]] name = "revm-precompile" -version = "31.0.0" -source = "git+https://github.com/0xforerunner/revm?rev=195c8e9#195c8e90138c17488dc56aae023903684ec87c73" +version = "32.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50c1285c848d240678bf69cb0f6179ff5a4aee6fc8e921d89708087197a0aff3" dependencies = [ "ark-bls12-381", "ark-bn254", @@ -10865,8 +10884,9 @@ dependencies = [ [[package]] name = "revm-primitives" -version = "21.0.2" -source = "git+https://github.com/0xforerunner/revm?rev=195c8e9#195c8e90138c17488dc56aae023903684ec87c73" +version = "22.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba580c56a8ec824a64f8a1683577876c2e1dbe5247044199e9b881421ad5dcf9" dependencies = [ "alloy-primitives", "num_enum", @@ -10876,8 +10896,9 @@ dependencies = [ [[package]] name = "revm-state" -version = "8.1.1" -source = "git+https://github.com/0xforerunner/revm?rev=195c8e9#195c8e90138c17488dc56aae023903684ec87c73" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "311720d4f0f239b041375e7ddafdbd20032a33b7bae718562ea188e188ed9fd3" dependencies = [ "alloy-eip7928", "bitflags 2.10.0", diff --git a/Cargo.toml b/Cargo.toml index 14e52d2551b..47ae87ef38c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -735,7 +735,6 @@ vergen-git2 = "9.1.0" ipnet = "2.11" [patch.crates-io] -alloy-op-evm = { git = "https://github.com/0xforerunner/evm", rev = "0833666" } alloy-evm = { git = "https://github.com/0xforerunner/evm", rev = "0833666" } # alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } # alloy-contract = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } diff --git a/crates/ethereum/evm/src/test_utils.rs b/crates/ethereum/evm/src/test_utils.rs index 044a3b307ef..6286b046b0d 100644 --- a/crates/ethereum/evm/src/test_utils.rs +++ b/crates/ethereum/evm/src/test_utils.rs @@ -18,8 +18,7 @@ use reth_evm::{ use reth_execution_types::{BlockExecutionResult, ExecutionOutcome}; use reth_primitives_traits::{BlockTy, SealedBlock, SealedHeader}; use revm::{ - context::result::{ExecutionResult, Output, ResultAndState, SuccessReason}, - database::State, + context::result::{ExecutionResult, HaltReason, Output, ResultAndState, SuccessReason}, Inspector, }; diff --git a/crates/evm/evm/src/lib.rs b/crates/evm/evm/src/lib.rs index 23f0b6278a3..2dec8b2611c 100644 --- a/crates/evm/evm/src/lib.rs +++ b/crates/evm/evm/src/lib.rs @@ -35,7 +35,7 @@ use reth_execution_errors::BlockExecutionError; use reth_primitives_traits::{ BlockTy, HeaderTy, NodePrimitives, ReceiptTy, SealedBlock, SealedHeader, TxTy, }; -use revm::{context::TxEnv, DatabaseCommit, database::State, primitives::hardfork::SpecId}; +use revm::{context::TxEnv, primitives::hardfork::SpecId, DatabaseCommit}; pub mod either; /// EVM environment configuration. @@ -400,11 +400,7 @@ pub trait ConfigureEvm: Clone + Debug + Send + Sync + Unpin { /// // Complete block building /// let outcome = builder.finish(state_provider)?; /// ``` -<<<<<<< HEAD - fn builder_for_next_block<'a, DB: Database + 'a>( -======= fn builder_for_next_block<'a, DB: StateDB + DatabaseCommit + Database + 'a>( ->>>>>>> 71397c6c6b (wip: getting everything compiling) &'a self, db: DB, parent: &'a SealedHeader<::BlockHeader>, From f1679f455c2629864b41752f74455a12c03675f4 Mon Sep 17 00:00:00 2001 From: Eric Woolsey Date: Wed, 11 Feb 2026 15:03:32 -0800 Subject: [PATCH 8/8] fix: delete rebase artifacts --- crates/optimism/evm/src/build.rs | 154 ----- crates/optimism/payload/src/builder.rs | 783 ----------------------- examples/custom-node/src/evm/executor.rs | 122 ---- 3 files changed, 1059 deletions(-) delete mode 100644 crates/optimism/evm/src/build.rs delete mode 100644 crates/optimism/payload/src/builder.rs delete mode 100644 examples/custom-node/src/evm/executor.rs diff --git a/crates/optimism/evm/src/build.rs b/crates/optimism/evm/src/build.rs deleted file mode 100644 index 35fae0981b5..00000000000 --- a/crates/optimism/evm/src/build.rs +++ /dev/null @@ -1,154 +0,0 @@ -use alloc::sync::Arc; -use alloy_consensus::{ - constants::EMPTY_WITHDRAWALS, proofs, Block, BlockBody, Header, TxReceipt, - EMPTY_OMMER_ROOT_HASH, -}; -use alloy_eips::{eip7685::EMPTY_REQUESTS_HASH, merge::BEACON_NONCE}; -use alloy_evm::block::BlockExecutorFactory; -use alloy_op_evm::OpBlockExecutionCtx; -use alloy_primitives::logs_bloom; -use reth_evm::execute::{BlockAssembler, BlockAssemblerInput}; -use reth_execution_errors::BlockExecutionError; -use reth_execution_types::BlockExecutionResult; -use reth_optimism_consensus::{calculate_receipt_root_no_memo_optimism, isthmus}; -use reth_optimism_forks::OpHardforks; -use reth_optimism_primitives::DepositReceipt; -use reth_primitives_traits::{Receipt, SignedTransaction}; -use revm::context::Block as _; - -/// Block builder for Optimism. -#[derive(Debug)] -pub struct OpBlockAssembler { - chain_spec: Arc, -} - -impl OpBlockAssembler { - /// Creates a new [`OpBlockAssembler`]. - pub const fn new(chain_spec: Arc) -> Self { - Self { chain_spec } - } -} - -impl OpBlockAssembler { - /// Builds a block for `input` without any bounds on header `H`. - pub fn assemble_block< - F: for<'a> BlockExecutorFactory< - ExecutionCtx<'a>: Into, - Transaction: SignedTransaction, - Receipt: Receipt + DepositReceipt, - >, - H, - >( - &self, - input: BlockAssemblerInput<'_, '_, F, H>, - ) -> Result, BlockExecutionError> { - let BlockAssemblerInput { - evm_env, - execution_ctx: ctx, - transactions, - output: BlockExecutionResult { receipts, gas_used, blob_gas_used, requests: _ }, - bundle_state, - state_root, - state_provider, - .. - } = input; - let ctx = ctx.into(); - - let timestamp = evm_env.block_env.timestamp().saturating_to(); - - let transactions_root = proofs::calculate_transaction_root(&transactions); - let receipts_root = - calculate_receipt_root_no_memo_optimism(receipts, &self.chain_spec, timestamp); - let logs_bloom = logs_bloom(receipts.iter().flat_map(|r| r.logs())); - - let mut requests_hash = None; - - let withdrawals_root = if self.chain_spec.is_isthmus_active_at_timestamp(timestamp) { - // always empty requests hash post isthmus - requests_hash = Some(EMPTY_REQUESTS_HASH); - - // withdrawals root field in block header is used for storage root of L2 predeploy - // `l2tol1-message-passer` - Some( - isthmus::withdrawals_root(&bundle_state, state_provider) - .map_err(BlockExecutionError::other)?, - ) - } else if self.chain_spec.is_canyon_active_at_timestamp(timestamp) { - Some(EMPTY_WITHDRAWALS) - } else { - None - }; - - let (excess_blob_gas, blob_gas_used) = - if self.chain_spec.is_jovian_active_at_timestamp(timestamp) { - // In jovian, we're using the blob gas used field to store the current da - // footprint's value. - (Some(0), Some(*blob_gas_used)) - } else if self.chain_spec.is_ecotone_active_at_timestamp(timestamp) { - (Some(0), Some(0)) - } else { - (None, None) - }; - - let header = Header { - parent_hash: ctx.parent_hash, - ommers_hash: EMPTY_OMMER_ROOT_HASH, - beneficiary: evm_env.block_env.beneficiary(), - state_root, - transactions_root, - receipts_root, - withdrawals_root, - logs_bloom, - timestamp, - mix_hash: evm_env.block_env.prevrandao().unwrap_or_default(), - nonce: BEACON_NONCE.into(), - base_fee_per_gas: Some(evm_env.block_env.basefee()), - number: evm_env.block_env.number().saturating_to(), - gas_limit: evm_env.block_env.gas_limit(), - difficulty: evm_env.block_env.difficulty(), - gas_used: *gas_used, - extra_data: ctx.extra_data, - parent_beacon_block_root: ctx.parent_beacon_block_root, - blob_gas_used, - excess_blob_gas, - requests_hash, - }; - - Ok(Block::new( - header, - BlockBody { - transactions, - ommers: Default::default(), - withdrawals: self - .chain_spec - .is_canyon_active_at_timestamp(timestamp) - .then(Default::default), - }, - )) - } -} - -impl Clone for OpBlockAssembler { - fn clone(&self) -> Self { - Self { chain_spec: self.chain_spec.clone() } - } -} - -impl BlockAssembler for OpBlockAssembler -where - ChainSpec: OpHardforks, - F: for<'a> BlockExecutorFactory< - ExecutionCtx<'a> = OpBlockExecutionCtx, - Transaction: SignedTransaction, - Receipt: Receipt + DepositReceipt, - >, -{ - type Block = Block; - - fn assemble_block( - &self, - input: BlockAssemblerInput<'_, '_, F>, - ) -> Result { - self.assemble_block(input) - } -} diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs deleted file mode 100644 index 66647ba0ab9..00000000000 --- a/crates/optimism/payload/src/builder.rs +++ /dev/null @@ -1,783 +0,0 @@ -//! Optimism payload builder implementation. -use crate::{ - config::OpBuilderConfig, error::OpPayloadBuilderError, payload::OpBuiltPayload, OpAttributes, - OpPayloadBuilderAttributes, OpPayloadPrimitives, -}; -use alloy_consensus::{BlockHeader, Transaction, Typed2718}; -use alloy_evm::Evm as AlloyEvm; -use alloy_primitives::{B256, U256}; -use alloy_rpc_types_debug::ExecutionWitness; -use alloy_rpc_types_engine::PayloadId; -use reth_basic_payload_builder::*; -use reth_chainspec::{ChainSpecProvider, EthChainSpec}; -use reth_evm::{ - block::{BlockExecutorFor, StateDB}, - execute::{ - BlockBuilder, BlockBuilderOutcome, BlockExecutionError, BlockExecutor, BlockValidationError, - }, - op_revm::{constants::L1_BLOCK_CONTRACT, L1BlockInfo}, - ConfigureEvm, Database, -}; -use reth_execution_types::ExecutionOutcome; -use reth_optimism_forks::OpHardforks; -use reth_optimism_primitives::{transaction::OpTransaction, ADDRESS_L2_TO_L1_MESSAGE_PASSER}; -use reth_optimism_txpool::{ - estimated_da_size::DataAvailabilitySized, - interop::{is_valid_interop, MaybeInteropTransaction}, - OpPooledTx, -}; -use reth_payload_builder_primitives::PayloadBuilderError; -use reth_payload_primitives::{BuildNextEnv, BuiltPayloadExecutedBlock, PayloadBuilderAttributes}; -use reth_payload_util::{BestPayloadTransactions, NoopPayloadTransactions, PayloadTransactions}; -use reth_primitives_traits::{ - HeaderTy, NodePrimitives, SealedHeader, SealedHeaderFor, SignedTransaction, TxTy, -}; -use reth_revm::{ - cancelled::CancelOnDrop, database::StateProviderDatabase, db::State, - witness::ExecutionWitnessRecord, -}; -use reth_storage_api::{errors::ProviderError, StateProvider, StateProviderFactory}; -use reth_transaction_pool::{BestTransactionsAttributes, PoolTransaction, TransactionPool}; -use revm::{ - context::{Block, BlockEnv}, - DatabaseCommit, -}; -use std::{marker::PhantomData, sync::Arc}; -use tracing::{debug, trace, warn}; - -/// Optimism's payload builder -#[derive(Debug)] -pub struct OpPayloadBuilder< - Pool, - Client, - Evm, - Txs = (), - Attrs = OpPayloadBuilderAttributes::Primitives>>, -> { - /// The rollup's compute pending block configuration option. - // TODO(clabby): Implement this feature. - pub compute_pending_block: bool, - /// The type responsible for creating the evm. - pub evm_config: Evm, - /// Transaction pool. - pub pool: Pool, - /// Node client. - pub client: Client, - /// Settings for the builder, e.g. DA settings. - pub config: OpBuilderConfig, - /// The type responsible for yielding the best transactions for the payload if mempool - /// transactions are allowed. - pub best_transactions: Txs, - /// Marker for the payload attributes type. - _pd: PhantomData, -} - -impl Clone for OpPayloadBuilder -where - Pool: Clone, - Client: Clone, - Evm: ConfigureEvm, - Txs: Clone, -{ - fn clone(&self) -> Self { - Self { - evm_config: self.evm_config.clone(), - pool: self.pool.clone(), - client: self.client.clone(), - config: self.config.clone(), - best_transactions: self.best_transactions.clone(), - compute_pending_block: self.compute_pending_block, - _pd: PhantomData, - } - } -} - -impl OpPayloadBuilder { - /// `OpPayloadBuilder` constructor. - /// - /// Configures the builder with the default settings. - pub fn new(pool: Pool, client: Client, evm_config: Evm) -> Self { - Self::with_builder_config(pool, client, evm_config, Default::default()) - } - - /// Configures the builder with the given [`OpBuilderConfig`]. - pub const fn with_builder_config( - pool: Pool, - client: Client, - evm_config: Evm, - config: OpBuilderConfig, - ) -> Self { - Self { - pool, - client, - compute_pending_block: true, - evm_config, - config, - best_transactions: (), - _pd: PhantomData, - } - } -} - -impl OpPayloadBuilder { - /// Sets the rollup's compute pending block configuration option. - pub const fn set_compute_pending_block(mut self, compute_pending_block: bool) -> Self { - self.compute_pending_block = compute_pending_block; - self - } - - /// Configures the type responsible for yielding the transactions that should be included in the - /// payload. - pub fn with_transactions( - self, - best_transactions: T, - ) -> OpPayloadBuilder { - let Self { pool, client, compute_pending_block, evm_config, config, .. } = self; - OpPayloadBuilder { - pool, - client, - compute_pending_block, - evm_config, - best_transactions, - config, - _pd: PhantomData, - } - } - - /// Enables the rollup's compute pending block configuration option. - pub const fn compute_pending_block(self) -> Self { - self.set_compute_pending_block(true) - } - - /// Returns the rollup's compute pending block configuration option. - pub const fn is_compute_pending_block(&self) -> bool { - self.compute_pending_block - } -} - -impl OpPayloadBuilder -where - Pool: TransactionPool>, - Client: StateProviderFactory + ChainSpecProvider, - N: OpPayloadPrimitives, - Evm: ConfigureEvm< - Primitives = N, - NextBlockEnvCtx: BuildNextEnv, - >, - Attrs: OpAttributes>, -{ - /// Constructs an Optimism payload from the transactions sent via 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 Optimism 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. - fn build_payload<'a, Txs>( - &self, - args: BuildArguments>, - best: impl FnOnce(BestTransactionsAttributes) -> Txs + Send + Sync + 'a, - ) -> Result>, PayloadBuilderError> - where - Txs: - PayloadTransactions + OpPooledTx>, - { - let BuildArguments { mut cached_reads, config, cancel, best_payload } = args; - - let ctx = OpPayloadBuilderCtx { - evm_config: self.evm_config.clone(), - builder_config: self.config.clone(), - chain_spec: self.client.chain_spec(), - config, - cancel, - best_payload, - }; - - let builder = OpBuilder::new(best); - - let state_provider = self.client.state_by_block_hash(ctx.parent().hash())?; - let state = StateProviderDatabase::new(&state_provider); - - if ctx.attributes().no_tx_pool() { - builder.build(state, &state_provider, ctx) - } else { - // sequencer mode we can reuse cachedreads from previous runs - builder.build(cached_reads.as_db_mut(state), &state_provider, ctx) - } - .map(|out| out.with_cached_reads(cached_reads)) - } - - /// Computes the witness for the payload. - pub fn payload_witness( - &self, - parent: SealedHeader, - attributes: Attrs::RpcPayloadAttributes, - ) -> Result - where - Attrs: PayloadBuilderAttributes, - { - let attributes = - Attrs::try_new(parent.hash(), attributes, 3).map_err(PayloadBuilderError::other)?; - - let config = PayloadConfig { parent_header: Arc::new(parent), attributes }; - let ctx = OpPayloadBuilderCtx { - evm_config: self.evm_config.clone(), - builder_config: self.config.clone(), - chain_spec: self.client.chain_spec(), - config, - cancel: Default::default(), - best_payload: Default::default(), - }; - - let state_provider = self.client.state_by_block_hash(ctx.parent().hash())?; - - let builder = OpBuilder::new(|_| NoopPayloadTransactions::::default()); - builder.witness(state_provider, &ctx) - } -} - -/// Implementation of the [`PayloadBuilder`] trait for [`OpPayloadBuilder`]. -impl PayloadBuilder - for OpPayloadBuilder -where - N: OpPayloadPrimitives, - Client: StateProviderFactory + ChainSpecProvider + Clone, - Pool: TransactionPool>, - Evm: ConfigureEvm< - Primitives = N, - NextBlockEnvCtx: BuildNextEnv, - >, - Txs: OpPayloadTransactions, - Attrs: OpAttributes, -{ - type Attributes = Attrs; - type BuiltPayload = OpBuiltPayload; - - fn try_build( - &self, - args: BuildArguments, - ) -> Result, PayloadBuilderError> { - let pool = self.pool.clone(); - self.build_payload(args, |attrs| self.best_transactions.best_transactions(pool, attrs)) - } - - fn on_missing_payload( - &self, - _args: BuildArguments, - ) -> MissingPayloadBehaviour { - // we want to await the job that's already in progress because that should be returned as - // is, there's no benefit in racing another job - MissingPayloadBehaviour::AwaitInProgress - } - - // NOTE: this should only be used for testing purposes because this doesn't have access to L1 - // system txs, hence on_missing_payload we return [MissingPayloadBehaviour::AwaitInProgress]. - fn build_empty_payload( - &self, - config: PayloadConfig, - ) -> Result { - let args = BuildArguments { - config, - cached_reads: Default::default(), - cancel: Default::default(), - best_payload: None, - }; - self.build_payload(args, |_| NoopPayloadTransactions::::default())? - .into_payload() - .ok_or_else(|| PayloadBuilderError::MissingPayload) - } -} - -/// The type that builds the payload. -/// -/// Payload building for optimism is composed of several steps. -/// The first steps are mandatory and defined by the protocol. -/// -/// 1. first all System calls are applied. -/// 2. After canyon the forced deployed `create2deployer` must be loaded -/// 3. all sequencer transactions are executed (part of the payload attributes) -/// -/// Depending on whether the node acts as a sequencer and is allowed to include additional -/// transactions (`no_tx_pool == false`): -/// 4. include additional transactions -/// -/// And finally -/// 5. build the block: compute all roots (txs, state) -#[derive(derive_more::Debug)] -pub struct OpBuilder<'a, Txs> { - /// Yields the best transaction to include if transactions from the mempool are allowed. - #[debug(skip)] - best: Box Txs + 'a>, -} - -impl<'a, Txs> OpBuilder<'a, Txs> { - /// Creates a new [`OpBuilder`]. - pub fn new(best: impl FnOnce(BestTransactionsAttributes) -> Txs + Send + Sync + 'a) -> Self { - Self { best: Box::new(best) } - } -} - -impl OpBuilder<'_, Txs> { - /// Builds the payload on top of the state. - pub fn build( - self, - db: impl Database, - state_provider: impl StateProvider, - ctx: OpPayloadBuilderCtx, - ) -> Result>, PayloadBuilderError> - where - Evm: ConfigureEvm< - Primitives = N, - NextBlockEnvCtx: BuildNextEnv, - >, - ChainSpec: EthChainSpec + OpHardforks, - N: OpPayloadPrimitives, - Txs: - PayloadTransactions + OpPooledTx>, - Attrs: OpAttributes, - { - let Self { best } = self; - debug!(target: "payload_builder", id=%ctx.payload_id(), parent_header = ?ctx.parent().hash(), parent_number = ctx.parent().number(), "building new payload"); - - let mut db = State::builder().with_database(db).with_bundle_update().build(); - - // Load the L1 block contract into the database cache. If the L1 block contract is not - // pre-loaded the database will panic when trying to fetch the DA footprint gas - // scalar. - db.load_cache_account(L1_BLOCK_CONTRACT).map_err(BlockExecutionError::other)?; - - let mut builder = ctx.block_builder(&mut db)?; - - // 1. apply pre-execution changes - builder.apply_pre_execution_changes().map_err(|err| { - warn!(target: "payload_builder", %err, "failed to apply pre-execution changes"); - PayloadBuilderError::Internal(err.into()) - })?; - - // 2. execute sequencer transactions - let mut info = ctx.execute_sequencer_transactions(&mut builder)?; - - // 3. if mem pool transactions are requested we execute them - if !ctx.attributes().no_tx_pool() { - let best_txs = best(ctx.best_transaction_attributes(builder.evm_mut().block())); - if ctx.execute_best_transactions(&mut info, &mut builder, best_txs)?.is_some() { - return Ok(BuildOutcomeKind::Cancelled) - } - - // check if the new payload is even more valuable - if !ctx.is_better_payload(info.total_fees) { - // can skip building the block - return Ok(BuildOutcomeKind::Aborted { fees: info.total_fees }) - } - } - - let BlockBuilderOutcome { execution_result, hashed_state, trie_updates, block } = - builder.finish(state_provider)?; - - let sealed_block = Arc::new(block.sealed_block().clone()); - debug!(target: "payload_builder", id=%ctx.attributes().payload_id(), sealed_block_header = ?sealed_block.header(), "sealed built block"); - - let execution_outcome = ExecutionOutcome::new( - db.take_bundle(), - vec![execution_result.receipts], - block.number(), - Vec::new(), - ); - - // create the executed block data - let executed: BuiltPayloadExecutedBlock = BuiltPayloadExecutedBlock { - recovered_block: Arc::new(block), - execution_output: Arc::new(execution_outcome), - // Keep unsorted; conversion to sorted happens when needed downstream - hashed_state: either::Either::Left(Arc::new(hashed_state)), - trie_updates: either::Either::Left(Arc::new(trie_updates)), - }; - - let no_tx_pool = ctx.attributes().no_tx_pool(); - - let payload = - OpBuiltPayload::new(ctx.payload_id(), sealed_block, info.total_fees, Some(executed)); - - if no_tx_pool { - // if `no_tx_pool` is set only transactions from the payload attributes will be included - // in the payload. In other words, the payload is deterministic and we can - // freeze it once we've successfully built it. - Ok(BuildOutcomeKind::Freeze(payload)) - } else { - Ok(BuildOutcomeKind::Better { payload }) - } - } - - /// Builds the payload and returns its [`ExecutionWitness`] based on the state after execution. - pub fn witness( - self, - state_provider: impl StateProvider, - ctx: &OpPayloadBuilderCtx, - ) -> Result - where - Evm: ConfigureEvm< - Primitives = N, - NextBlockEnvCtx: BuildNextEnv, - >, - ChainSpec: EthChainSpec + OpHardforks, - N: OpPayloadPrimitives, - Txs: PayloadTransactions>, - Attrs: OpAttributes, - { - let mut db = State::builder() - .with_database(StateProviderDatabase::new(&state_provider)) - .with_bundle_update() - .build(); - let mut builder = ctx.block_builder(&mut db)?; - - builder.apply_pre_execution_changes()?; - ctx.execute_sequencer_transactions(&mut builder)?; - builder.into_executor().apply_post_execution_changes()?; - - if ctx.chain_spec.is_isthmus_active_at_timestamp(ctx.attributes().timestamp()) { - // force load `L2ToL1MessagePasser.sol` so l2 withdrawals root can be computed even if - // no l2 withdrawals in block - _ = db.load_cache_account(ADDRESS_L2_TO_L1_MESSAGE_PASSER)?; - } - - let ExecutionWitnessRecord { hashed_state, codes, keys, lowest_block_number: _ } = - ExecutionWitnessRecord::from_executed_state(&db); - let state = state_provider.witness(Default::default(), hashed_state)?; - Ok(ExecutionWitness { - state: state.into_iter().collect(), - codes, - keys, - ..Default::default() - }) - } -} - -/// A type that returns a the [`PayloadTransactions`] that should be included in the pool. -pub trait OpPayloadTransactions: Clone + Send + Sync + Unpin + 'static { - /// Returns an iterator that yields the transaction in the order they should get included in the - /// new payload. - fn best_transactions>( - &self, - pool: Pool, - attr: BestTransactionsAttributes, - ) -> impl PayloadTransactions; -} - -impl OpPayloadTransactions for () { - fn best_transactions>( - &self, - pool: Pool, - attr: BestTransactionsAttributes, - ) -> impl PayloadTransactions { - BestPayloadTransactions::new(pool.best_transactions_with_attributes(attr)) - } -} - -/// Holds the state after execution -#[derive(Debug)] -pub struct ExecutedPayload { - /// Tracked execution info - pub info: ExecutionInfo, - /// Withdrawal hash. - pub withdrawals_root: Option, - /// The transaction receipts. - pub receipts: Vec, - /// The block env used during execution. - pub block_env: BlockEnv, -} - -/// This acts as the container for executed transactions and its byproducts (receipts, gas used) -#[derive(Default, Debug)] -pub struct ExecutionInfo { - /// All gas used so far - pub cumulative_gas_used: u64, - /// Estimated DA size - pub cumulative_da_bytes_used: u64, - /// Tracks fees from executed mempool transactions - pub total_fees: U256, -} - -impl ExecutionInfo { - /// Create a new instance with allocated slots. - pub const fn new() -> Self { - Self { cumulative_gas_used: 0, cumulative_da_bytes_used: 0, total_fees: U256::ZERO } - } - - /// Returns true if the transaction would exceed the block limits: - /// - block gas limit: ensures the transaction still fits into the block. - /// - tx DA limit: if configured, ensures the tx does not exceed the maximum allowed DA limit - /// per tx. - /// - block DA limit: if configured, ensures the transaction's DA size does not exceed the - /// maximum allowed DA limit per block. - pub fn is_tx_over_limits( - &self, - tx_da_size: u64, - block_gas_limit: u64, - tx_data_limit: Option, - block_data_limit: Option, - tx_gas_limit: u64, - da_footprint_gas_scalar: Option, - ) -> bool { - if tx_data_limit.is_some_and(|da_limit| tx_da_size > da_limit) { - return true; - } - - let total_da_bytes_used = self.cumulative_da_bytes_used.saturating_add(tx_da_size); - - if block_data_limit.is_some_and(|da_limit| total_da_bytes_used > da_limit) { - return true; - } - - // Post Jovian: the tx DA footprint must be less than the block gas limit - if let Some(da_footprint_gas_scalar) = da_footprint_gas_scalar { - let tx_da_footprint = - total_da_bytes_used.saturating_mul(da_footprint_gas_scalar as u64); - if tx_da_footprint > block_gas_limit { - return true; - } - } - - self.cumulative_gas_used + tx_gas_limit > block_gas_limit - } -} - -/// Container type that holds all necessities to build a new payload. -#[derive(derive_more::Debug)] -pub struct OpPayloadBuilderCtx< - Evm: ConfigureEvm, - ChainSpec, - Attrs = OpPayloadBuilderAttributes::Primitives>>, -> { - /// The type that knows how to perform system calls and configure the evm. - pub evm_config: Evm, - /// Additional config for the builder/sequencer, e.g. DA and gas limit - pub builder_config: OpBuilderConfig, - /// The chainspec - pub chain_spec: Arc, - /// How to build the payload. - pub config: PayloadConfig>, - /// Marker to check whether the job has been cancelled. - pub cancel: CancelOnDrop, - /// The currently best payload. - pub best_payload: Option>, -} - -impl OpPayloadBuilderCtx -where - Evm: ConfigureEvm< - Primitives: OpPayloadPrimitives, - NextBlockEnvCtx: BuildNextEnv, ChainSpec>, - >, - ChainSpec: EthChainSpec + OpHardforks, - Attrs: OpAttributes>, -{ - /// Returns the parent block the payload will be build on. - pub fn parent(&self) -> &SealedHeaderFor { - self.config.parent_header.as_ref() - } - - /// Returns the builder attributes. - pub const fn attributes(&self) -> &Attrs { - &self.config.attributes - } - - /// Returns the current fee settings for transactions from the mempool - pub fn best_transaction_attributes(&self, block_env: impl Block) -> BestTransactionsAttributes { - BestTransactionsAttributes::new( - block_env.basefee(), - block_env.blob_gasprice().map(|p| p as u64), - ) - } - - /// Returns the unique id for this payload job. - pub fn payload_id(&self) -> PayloadId { - self.attributes().payload_id() - } - - /// Returns true if the fees are higher than the previous payload. - pub fn is_better_payload(&self, total_fees: U256) -> bool { - is_better_payload(self.best_payload.as_ref(), total_fees) - } - - /// Prepares a [`BlockBuilder`] for the next block. - pub fn block_builder<'a, DB: StateDB + DatabaseCommit + Database + 'a>( - &'a self, - db: DB, - ) -> Result< - impl BlockBuilder< - Primitives = Evm::Primitives, - Executor: BlockExecutorFor<'a, Evm::BlockExecutorFactory, DB>, - > + 'a, - PayloadBuilderError, - > { - self.evm_config - .builder_for_next_block( - db, - self.parent(), - Evm::NextBlockEnvCtx::build_next_env( - self.attributes(), - self.parent(), - self.chain_spec.as_ref(), - ) - .map_err(PayloadBuilderError::other)?, - ) - .map_err(PayloadBuilderError::other) - } - - /// Executes all sequencer transactions that are included in the payload attributes. - pub fn execute_sequencer_transactions( - &self, - builder: &mut impl BlockBuilder, - ) -> Result { - let mut info = ExecutionInfo::new(); - - for sequencer_tx in self.attributes().sequencer_transactions() { - // A sequencer's block should never contain blob transactions. - if sequencer_tx.value().is_eip4844() { - return Err(PayloadBuilderError::other( - OpPayloadBuilderError::BlobTransactionRejected, - )) - } - - // Convert the transaction to a [RecoveredTx]. This is - // purely for the purposes of utilizing the `evm_config.tx_env`` 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.value().try_clone_into_recovered().map_err(|_| { - PayloadBuilderError::other(OpPayloadBuilderError::TransactionEcRecoverFailed) - })?; - - let gas_used = match builder.execute_transaction(sequencer_tx.clone()) { - Ok(gas_used) => gas_used, - Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx { - error, - .. - })) => { - trace!(target: "payload_builder", %error, ?sequencer_tx, "Error in sequencer transaction, skipping."); - continue - } - Err(err) => { - // this is an error that we should treat as fatal for this attempt - return Err(PayloadBuilderError::EvmExecutionError(Box::new(err))) - } - }; - - // add gas used by the transaction to cumulative gas used, before creating the receipt - info.cumulative_gas_used += gas_used; - } - - Ok(info) - } - - /// Executes the given best transactions and updates the execution info. - /// - /// Returns `Ok(Some(())` if the job was cancelled. - pub fn execute_best_transactions( - &self, - info: &mut ExecutionInfo, - builder: &mut Builder, - mut best_txs: impl PayloadTransactions< - Transaction: PoolTransaction> + OpPooledTx, - >, - ) -> Result, PayloadBuilderError> - where - Builder: BlockBuilder, - <::Evm as AlloyEvm>::DB: Database, - { - let mut block_gas_limit = builder.evm_mut().block().gas_limit(); - if let Some(gas_limit_config) = self.builder_config.gas_limit_config.gas_limit() { - // If a gas limit is configured, use that limit as target if it's smaller, otherwise use - // the block's actual gas limit. - block_gas_limit = gas_limit_config.min(block_gas_limit); - }; - let block_da_limit = self.builder_config.da_config.max_da_block_size(); - let tx_da_limit = self.builder_config.da_config.max_da_tx_size(); - let base_fee = builder.evm_mut().block().basefee(); - - while let Some(tx) = best_txs.next(()) { - let interop = tx.interop_deadline(); - let tx_da_size = tx.estimated_da_size(); - let tx = tx.into_consensus(); - - let da_footprint_gas_scalar = self - .chain_spec - .is_jovian_active_at_timestamp(self.attributes().timestamp()) - .then_some( - L1BlockInfo::fetch_da_footprint_gas_scalar(builder.evm_mut().db_mut()).expect( - "DA footprint should always be available from the database post jovian", - ), - ); - - if info.is_tx_over_limits( - tx_da_size, - block_gas_limit, - tx_da_limit, - block_da_limit, - tx.gas_limit(), - da_footprint_gas_scalar, - ) { - // 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(tx.signer(), tx.nonce()); - continue - } - - // A sequencer's block should never contain blob or deposit transactions from the pool. - if tx.is_eip4844() || tx.is_deposit() { - best_txs.mark_invalid(tx.signer(), tx.nonce()); - continue - } - - // We skip invalid cross chain txs, they would be removed on the next block update in - // the maintenance job - if let Some(interop) = interop && - !is_valid_interop(interop, self.config.attributes.timestamp()) - { - best_txs.mark_invalid(tx.signer(), tx.nonce()); - continue - } - // check if the job was cancelled, if so we can exit early - if self.cancel.is_cancelled() { - return Ok(Some(())) - } - - let gas_used = match builder.execute_transaction(tx.clone()) { - Ok(gas_used) => gas_used, - Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx { - error, - .. - })) => { - if error.is_nonce_too_low() { - // if the nonce is too low, we can skip this transaction - trace!(target: "payload_builder", %error, ?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", %error, ?tx, "skipping invalid transaction and its descendants"); - best_txs.mark_invalid(tx.signer(), tx.nonce()); - } - continue - } - Err(err) => { - // this is an error that we should treat as fatal for this attempt - return Err(PayloadBuilderError::EvmExecutionError(Box::new(err))) - } - }; - - // add gas used by the transaction to cumulative gas used, before creating the - // receipt - info.cumulative_gas_used += gas_used; - info.cumulative_da_bytes_used += tx_da_size; - - // update and add to total fees - let miner_fee = tx - .effective_tip_per_gas(base_fee) - .expect("fee is always valid; execution succeeded"); - info.total_fees += U256::from(miner_fee) * U256::from(gas_used); - } - - Ok(None) - } -} diff --git a/examples/custom-node/src/evm/executor.rs b/examples/custom-node/src/evm/executor.rs deleted file mode 100644 index 1e8535a3464..00000000000 --- a/examples/custom-node/src/evm/executor.rs +++ /dev/null @@ -1,122 +0,0 @@ -use crate::{ - evm::{ - alloy::{CustomEvm, CustomEvmFactory}, - CustomEvmConfig, CustomTxEnv, - }, - primitives::CustomTransaction, -}; -use alloy_consensus::transaction::Recovered; -use alloy_evm::{ - block::{ - BlockExecutionError, BlockExecutionResult, BlockExecutor, BlockExecutorFactory, - BlockExecutorFor, ExecutableTx, OnStateHook, StateDB, - }, - precompiles::PrecompilesMap, - Database, Evm, -}; -use alloy_op_evm::{OpBlockExecutionCtx, OpBlockExecutor}; -use reth_ethereum::evm::primitives::InspectorFor; -use reth_op::{chainspec::OpChainSpec, node::OpRethReceiptBuilder, OpReceipt}; -use revm::{context::result::ResultAndState, DatabaseCommit}; -use std::sync::Arc; - -pub struct CustomBlockExecutor { - inner: OpBlockExecutor>, -} - -impl BlockExecutor for CustomBlockExecutor -where - E: Evm, -{ - type Transaction = CustomTransaction; - type Receipt = OpReceipt; - type Evm = E; - - fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError> { - self.inner.apply_pre_execution_changes() - } - - fn execute_transaction_without_commit( - &mut self, - tx: impl ExecutableTx, - ) -> Result::HaltReason>, BlockExecutionError> { - match tx.tx() { - CustomTransaction::Op(op_tx) => self - .inner - .execute_transaction_without_commit(Recovered::new_unchecked(op_tx, *tx.signer())), - CustomTransaction::Payment(..) => todo!(), - } - } - - fn commit_transaction( - &mut self, - output: ResultAndState<::HaltReason>, - tx: impl ExecutableTx, - ) -> Result { - match tx.tx() { - CustomTransaction::Op(op_tx) => { - self.inner.commit_transaction(output, Recovered::new_unchecked(op_tx, *tx.signer())) - } - CustomTransaction::Payment(..) => todo!(), - } - } - - fn finish(self) -> Result<(Self::Evm, BlockExecutionResult), BlockExecutionError> { - self.inner.finish() - } - - fn set_state_hook(&mut self, _hook: Option>) { - self.inner.set_state_hook(_hook) - } - - fn evm_mut(&mut self) -> &mut Self::Evm { - self.inner.evm_mut() - } - - fn evm(&self) -> &Self::Evm { - self.inner.evm() - } -} - -impl BlockExecutorFactory for CustomEvmConfig { - type EvmFactory = CustomEvmFactory; - type ExecutionCtx<'a> = CustomBlockExecutionCtx; - type Transaction = CustomTransaction; - type Receipt = OpReceipt; - - fn evm_factory(&self) -> &Self::EvmFactory { - &self.custom_evm_factory - } - - fn create_executor<'a, DB, I>( - &'a self, - evm: CustomEvm, - ctx: CustomBlockExecutionCtx, - ) -> impl BlockExecutorFor<'a, Self, DB, I> - where - DB: StateDB + DatabaseCommit + Database + 'a, - I: InspectorFor + 'a, - { - CustomBlockExecutor { - inner: OpBlockExecutor::new( - evm, - ctx.inner, - self.inner.chain_spec().clone(), - *self.inner.executor_factory.receipt_builder(), - ), - } - } -} - -/// Additional parameters for executing custom transactions. -#[derive(Debug, Clone)] -pub struct CustomBlockExecutionCtx { - pub inner: OpBlockExecutionCtx, - pub extension: u64, -} - -impl From for OpBlockExecutionCtx { - fn from(value: CustomBlockExecutionCtx) -> Self { - value.inner - } -}