diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 384761ee5bf6..96c4832ae51a 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -158,7 +158,7 @@ jobs: cargo udeps --workspace --lib --examples --tests --benches --all-features --locked \ --exclude "reth-optimism-*" --exclude op-reth --exclude "example-*" --exclude reth \ --exclude reth-e2e-test-utils --exclude reth-ethereum-payload-builder --exclude reth-exex-test-utils \ - --exclude reth-node-ethereum + --exclude reth-node-ethereum --exclude reth-scroll-evm book: name: book diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index fe76e1a4ceb7..539396277c2a 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -42,7 +42,7 @@ jobs: partition: 2 total_partitions: 2 - type: scroll - args: -p reth-scroll-state-commitment --locked --features "scroll" + args: -p "reth-scroll-*" --locked --features "scroll" partition: 1 total_partitions: 1 - type: book diff --git a/Cargo.lock b/Cargo.lock index d512a9ae3645..eb2db392b928 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -179,8 +179,7 @@ dependencies = [ [[package]] name = "alloy-eip2930" version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0069cf0642457f87a01a014f6dc29d5d893cd4fd8fddf0c3cdfad1bb3ebafc41" +source = "git+https://github.com/scroll-tech/alloy-eips?branch=v0.3.2#8d5fc83fc257b09510dc8d76561188218d9c0c32" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -1080,6 +1079,17 @@ dependencies = [ "zstd-safe", ] +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "async-sse" version = "5.1.0" @@ -1354,6 +1364,19 @@ dependencies = [ "zeroize", ] +[[package]] +name = "bn254" +version = "0.1.0" +source = "git+https://github.com/scroll-tech/bn254.git?branch=master#81e1dcc92ee9a2798b13b84b24de182e9c42256e" +dependencies = [ + "ff", + "getrandom 0.2.15", + "rand 0.8.5", + "rand_core 0.6.4", + "sp1-intrinsics", + "subtle", +] + [[package]] name = "bn254" version = "0.1.0" @@ -3177,8 +3200,7 @@ dependencies = [ [[package]] name = "ff" version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +source = "git+https://github.com/scroll-tech/ff?branch=feat/sp1#244b1098f6be1d19c5fd3f0ec60117ac2940e6ca" dependencies = [ "bitvec", "rand_core 0.6.4", @@ -3662,6 +3684,54 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" +[[package]] +name = "hickory-proto" +version = "0.25.0-alpha.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d063c0692ee669aa6d261988aa19ca5510f1cc40e4f211024f50c888499a35d7" +dependencies = [ + "async-recursion", + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna", + "ipnet", + "once_cell", + "rand 0.8.5", + "serde", + "thiserror 2.0.4", + "tinyvec", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "hickory-resolver" +version = "0.25.0-alpha.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42bc352e4412fb657e795f79b4efcf2bd60b59ee5ca0187f3554194cd1107a27" +dependencies = [ + "cfg-if", + "futures-util", + "hickory-proto", + "ipconfig", + "moka", + "once_cell", + "parking_lot", + "rand 0.8.5", + "resolv-conf", + "serde", + "smallvec", + "thiserror 2.0.4", + "tokio", + "tracing", +] + [[package]] name = "hkdf" version = "0.12.4" @@ -4049,16 +4119,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" -[[package]] -name = "idna" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - [[package]] name = "idna" version = "1.0.3" @@ -4649,7 +4709,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -4814,15 +4874,6 @@ dependencies = [ "hashbrown 0.15.2", ] -[[package]] -name = "lru-cache" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" -dependencies = [ - "linked-hash-map", -] - [[package]] name = "lz4_flex" version = "0.11.3" @@ -5067,6 +5118,26 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "moka" +version = "0.12.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32cf62eb4dd975d2dde76432fb1075c49e3ee2331cf36f1f8fd4b66550d32b6f" +dependencies = [ + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "once_cell", + "parking_lot", + "quanta", + "rustc_version 0.4.1", + "smallvec", + "tagptr", + "thiserror 1.0.69", + "triomphe", + "uuid", +] + [[package]] name = "more-asserts" version = "0.3.1" @@ -5798,12 +5869,22 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" +[[package]] +name = "poseidon-bn254" +version = "0.1.0" +source = "git+https://github.com/scroll-tech/poseidon-bn254?branch=master#254baa0e3e85c0975c16fe6f33042b1fa9ae0a44" +dependencies = [ + "bn254 0.1.0 (git+https://github.com/scroll-tech/bn254.git?branch=master)", + "itertools 0.13.0", + "sp1-intrinsics", +] + [[package]] name = "poseidon-bn254" version = "0.1.0" source = "git+https://github.com/scroll-tech/poseidon-bn254?rev=526a64a#526a64a81419bcab6bd8280a8a3f9808189e0373" dependencies = [ - "bn254", + "bn254 0.1.0 (git+https://github.com/scroll-tech/bn254)", "itertools 0.13.0", ] @@ -7098,6 +7179,7 @@ dependencies = [ "alloy-rlp", "data-encoding", "enr", + "hickory-resolver", "linked_hash_set", "parking_lot", "rand 0.8.5", @@ -7114,7 +7196,6 @@ dependencies = [ "tokio", "tokio-stream", "tracing", - "trust-dns-resolver", ] [[package]] @@ -9202,6 +9283,40 @@ dependencies = [ "serde_json", ] +[[package]] +name = "reth-scroll-consensus" +version = "1.1.2" +dependencies = [ + "eyre", + "reth-scroll-revm", +] + +[[package]] +name = "reth-scroll-evm" +version = "1.1.2" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "auto_impl", + "derive_more", + "eyre", + "reth-chainspec", + "reth-consensus", + "reth-ethereum-consensus", + "reth-evm", + "reth-primitives", + "reth-primitives-traits", + "reth-revm", + "reth-scroll-chainspec", + "reth-scroll-consensus", + "reth-scroll-execution", + "reth-scroll-forks", + "reth-scroll-primitives", + "reth-scroll-revm", + "thiserror 1.0.69", + "tracing", +] + [[package]] name = "reth-scroll-execution" version = "1.1.2" @@ -9234,7 +9349,7 @@ dependencies = [ "bincode", "bytes", "modular-bitfield", - "poseidon-bn254", + "poseidon-bn254 0.1.0 (git+https://github.com/scroll-tech/poseidon-bn254?rev=526a64a)", "proptest", "proptest-arbitrary-interop", "rand 0.8.5", @@ -9261,7 +9376,7 @@ dependencies = [ "alloy-primitives", "alloy-rlp", "metrics", - "poseidon-bn254", + "poseidon-bn254 0.1.0 (git+https://github.com/scroll-tech/poseidon-bn254?rev=526a64a)", "proptest", "proptest-arbitrary-interop", "reth-db", @@ -9721,8 +9836,7 @@ dependencies = [ [[package]] name = "revm" version = "18.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15689a3c6a8d14b647b4666f2e236ef47b5a5133cdfd423f545947986fff7013" +source = "git+https://github.com/scroll-tech/revm.git?branch=scroll-evm-executor/reth/v50#a740f3d8086395c00fa77fad5474bc4fde5b195d" dependencies = [ "auto_impl", "cfg-if", @@ -9755,9 +9869,9 @@ dependencies = [ [[package]] name = "revm-interpreter" version = "14.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74e3f11d0fed049a4a10f79820c59113a79b38aed4ebec786a79d5c667bfeb51" +source = "git+https://github.com/scroll-tech/revm.git?branch=scroll-evm-executor/reth/v50#a740f3d8086395c00fa77fad5474bc4fde5b195d" dependencies = [ + "cfg-if", "revm-primitives", "serde", ] @@ -9765,8 +9879,7 @@ dependencies = [ [[package]] name = "revm-precompile" version = "15.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e381060af24b750069a2b2d2c54bba273d84e8f5f9e8026fc9262298e26cc336" +source = "git+https://github.com/scroll-tech/revm.git?branch=scroll-evm-executor/reth/v50#a740f3d8086395c00fa77fad5474bc4fde5b195d" dependencies = [ "aurora-engine-modexp", "blst", @@ -9785,8 +9898,7 @@ dependencies = [ [[package]] name = "revm-primitives" version = "14.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3702f132bb484f4f0d0ca4f6fbde3c82cfd745041abbedd6eda67730e1868ef0" +source = "git+https://github.com/scroll-tech/revm.git?branch=scroll-evm-executor/reth/v50#a740f3d8086395c00fa77fad5474bc4fde5b195d" dependencies = [ "alloy-eip2930", "alloy-eip7702", @@ -9799,6 +9911,7 @@ dependencies = [ "dyn-clone", "enumn", "hex", + "poseidon-bn254 0.1.0 (git+https://github.com/scroll-tech/poseidon-bn254?branch=master)", "serde", ] @@ -10883,6 +10996,12 @@ dependencies = [ "windows 0.57.0", ] +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + [[package]] name = "tap" version = "1.0.1" @@ -11487,7 +11606,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3637e734239e12ab152cd269302500bd063f37624ee210cd04b4936ed671f3b1" dependencies = [ "cc", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -11501,51 +11620,10 @@ dependencies = [ ] [[package]] -name = "trust-dns-proto" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3119112651c157f4488931a01e586aa459736e9d6046d3bd9105ffb69352d374" -dependencies = [ - "async-trait", - "cfg-if", - "data-encoding", - "enum-as-inner", - "futures-channel", - "futures-io", - "futures-util", - "idna 0.4.0", - "ipnet", - "once_cell", - "rand 0.8.5", - "smallvec", - "thiserror 1.0.69", - "tinyvec", - "tokio", - "tracing", - "url", -] - -[[package]] -name = "trust-dns-resolver" -version = "0.23.2" +name = "triomphe" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a3e6c3aff1718b3c73e395d1f35202ba2ffa847c6a62eea0db8fb4cfe30be6" -dependencies = [ - "cfg-if", - "futures-util", - "ipconfig", - "lru-cache", - "once_cell", - "parking_lot", - "rand 0.8.5", - "resolv-conf", - "serde", - "smallvec", - "thiserror 1.0.69", - "tokio", - "tracing", - "trust-dns-proto", -] +checksum = "859eb650cfee7434994602c3a68b25d77ad9e68c8a6cd491616ef86661382eb3" [[package]] name = "try-lock" @@ -11621,27 +11699,12 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" -[[package]] -name = "unicode-bidi" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" - [[package]] name = "unicode-ident" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" -[[package]] -name = "unicode-normalization" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" -dependencies = [ - "tinyvec", -] - [[package]] name = "unicode-segmentation" version = "1.12.0" @@ -11706,7 +11769,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", - "idna 1.0.3", + "idna", "percent-encoding", "serde", ] @@ -11979,7 +12042,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index c66aafc2d71f..532698a2a534 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -101,6 +101,8 @@ members = [ "crates/rpc/rpc-types-compat/", "crates/rpc/rpc/", "crates/scroll/chainspec", + "crates/scroll/consensus", + "crates/scroll/evm", "crates/scroll/execution", "crates/scroll/hardforks", "crates/scroll/primitives", @@ -411,6 +413,9 @@ reth-rpc-eth-types = { path = "crates/rpc/rpc-eth-types", default-features = fal reth-rpc-layer = { path = "crates/rpc/rpc-layer" } reth-rpc-server-types = { path = "crates/rpc/rpc-server-types" } reth-rpc-types-compat = { path = "crates/rpc/rpc-types-compat" } +reth-scroll-chainspec = { path = "crates/scroll/chainspec" } +reth-scroll-consensus = { path = "crates/scroll/consensus" } +reth-scroll-evm = { path = "crates/scroll/evm" } reth-scroll-execution = { path = "crates/scroll/execution" } reth-scroll-primitives = { path = "crates/scroll/primitives" } reth-scroll-state-commitment = { path = "crates/scroll/state-commitment" } @@ -663,3 +668,11 @@ tracy-client = "0.17.3" #op-alloy-network = { git = "https://github.com/alloy-rs/op-alloy", rev = "6a042e7681b1" } #op-alloy-rpc-types = { git = "https://github.com/alloy-rs/op-alloy", rev = "6a042e7681b1" } #op-alloy-rpc-types-engine = { git = "https://github.com/alloy-rs/op-alloy", rev = "6a042e7681b1" } + +[patch.crates-io] +revm = { git = "https://github.com/scroll-tech/revm.git", branch = "scroll-evm-executor/reth/v50" } +revm-primitives = { git = "https://github.com/scroll-tech/revm.git", branch = "scroll-evm-executor/reth/v50" } + +ff = { git = "https://github.com/scroll-tech/ff", branch = "feat/sp1" } + +alloy-eip2930 = { git = "https://github.com/scroll-tech/alloy-eips", branch = "v0.3.2" } diff --git a/crates/chainspec/Cargo.toml b/crates/chainspec/Cargo.toml index 5bac582cd8b6..23b81d79c115 100644 --- a/crates/chainspec/Cargo.toml +++ b/crates/chainspec/Cargo.toml @@ -63,3 +63,7 @@ test-utils = [ "reth-primitives-traits/test-utils", "reth-trie-common/test-utils" ] +scroll = [ + "reth-trie-common/scroll", + "reth-primitives-traits/scroll" +] diff --git a/crates/engine/tree/benches/channel_perf.rs b/crates/engine/tree/benches/channel_perf.rs index c1c65e0a68e1..0103733691b1 100644 --- a/crates/engine/tree/benches/channel_perf.rs +++ b/crates/engine/tree/benches/channel_perf.rs @@ -1,6 +1,7 @@ //! Benchmark comparing `std::sync::mpsc` and `crossbeam` channels for `StateRootTask`. #![allow(missing_docs)] +#![allow(clippy::needless_update)] use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion}; use revm_primitives::{ @@ -23,6 +24,7 @@ fn create_bench_state(num_accounts: usize) -> EvmState { nonce: 10, code_hash: B256::random(), code: Default::default(), + ..Default::default() }, storage, status: AccountStatus::Loaded, diff --git a/crates/engine/tree/src/tree/root.rs b/crates/engine/tree/src/tree/root.rs index 05691a40ae53..c7236b0ba255 100644 --- a/crates/engine/tree/src/tree/root.rs +++ b/crates/engine/tree/src/tree/root.rs @@ -542,6 +542,7 @@ fn update_sparse_trie( #[cfg(test)] mod tests { + #![allow(clippy::needless_update)] use super::*; use reth_primitives::{Account as RethAccount, StorageEntry}; use reth_provider::{ @@ -600,6 +601,7 @@ mod tests { nonce: rng.gen::(), code_hash: KECCAK_EMPTY, code: Some(Default::default()), + ..Default::default() }, storage, status: AccountStatus::Touched, diff --git a/crates/ethereum/consensus/src/lib.rs b/crates/ethereum/consensus/src/lib.rs index 2c260c4a7d1c..26edfe0e443a 100644 --- a/crates/ethereum/consensus/src/lib.rs +++ b/crates/ethereum/consensus/src/lib.rs @@ -26,7 +26,7 @@ use std::{fmt::Debug, sync::Arc, time::SystemTime}; pub const GAS_LIMIT_BOUND_DIVISOR: u64 = 1024; mod validation; -pub use validation::validate_block_post_execution; +pub use validation::{validate_block_post_execution, verify_receipts}; /// Ethereum beacon consensus /// diff --git a/crates/ethereum/consensus/src/validation.rs b/crates/ethereum/consensus/src/validation.rs index f990ecc57d82..b07df409d36e 100644 --- a/crates/ethereum/consensus/src/validation.rs +++ b/crates/ethereum/consensus/src/validation.rs @@ -55,7 +55,7 @@ pub fn validate_block_post_execution( /// Calculate the receipts root, and compare it against against the expected receipts root and logs /// bloom. -fn verify_receipts( +pub fn verify_receipts( expected_receipts_root: B256, expected_logs_bloom: Bloom, receipts: &[Receipt], diff --git a/crates/evm/src/metrics.rs b/crates/evm/src/metrics.rs index 3464bb96f4c7..6f1b0cab47cc 100644 --- a/crates/evm/src/metrics.rs +++ b/crates/evm/src/metrics.rs @@ -143,6 +143,7 @@ impl ExecutorMetrics { #[cfg(test)] mod tests { + #![allow(clippy::needless_update)] use super::*; use alloy_eips::eip7685::Requests; use metrics_util::debugging::{DebugValue, DebuggingRecorder, Snapshotter}; @@ -261,6 +262,7 @@ mod tests { nonce: 10, code_hash: B256::random(), code: Default::default(), + ..Default::default() }, storage, status: AccountStatus::Loaded, diff --git a/crates/net/dns/Cargo.toml b/crates/net/dns/Cargo.toml index a52f65057443..1c3e298413f0 100644 --- a/crates/net/dns/Cargo.toml +++ b/crates/net/dns/Cargo.toml @@ -27,7 +27,7 @@ tokio = { workspace = true, features = ["io-util", "net", "time"] } tokio-stream.workspace = true # trust-dns -trust-dns-resolver = "0.23" +hickory-resolver = { version = "0.25.0-alpha.4" } # misc data-encoding = "2" @@ -58,5 +58,5 @@ serde = [ "parking_lot/serde", "rand/serde", "secp256k1/serde", - "trust-dns-resolver/serde" + "hickory-resolver/serde" ] diff --git a/crates/net/dns/src/resolver.rs b/crates/net/dns/src/resolver.rs index 42c444f89a75..eec3ea2a23d2 100644 --- a/crates/net/dns/src/resolver.rs +++ b/crates/net/dns/src/resolver.rs @@ -1,10 +1,10 @@ //! Perform DNS lookups +use hickory_resolver::name_server::ConnectionProvider; +pub use hickory_resolver::{ResolveError, TokioResolver}; use parking_lot::RwLock; use std::{collections::HashMap, future::Future}; use tracing::trace; -pub use trust_dns_resolver::{error::ResolveError, TokioAsyncResolver}; -use trust_dns_resolver::{name_server::ConnectionProvider, AsyncResolver}; /// A type that can lookup DNS entries pub trait Resolver: Send + Sync + Unpin + 'static { @@ -12,7 +12,7 @@ pub trait Resolver: Send + Sync + Unpin + 'static { fn lookup_txt(&self, query: &str) -> impl Future> + Send; } -impl Resolver for AsyncResolver

{ +impl Resolver for hickory_resolver::Resolver

{ async fn lookup_txt(&self, query: &str) -> Option { // See: [AsyncResolver::txt_lookup] // > *hint* queries that end with a '.' are fully qualified names and are cheaper lookups @@ -33,7 +33,7 @@ impl Resolver for AsyncResolver

{ /// An asynchronous DNS resolver /// -/// See also [`TokioAsyncResolver`] +/// See also [`TokioResolver`] /// /// ``` /// # fn t() { @@ -43,16 +43,16 @@ impl Resolver for AsyncResolver

{ /// ``` /// /// Note: This [Resolver] can send multiple lookup attempts, See also -/// [`ResolverOpts`](trust_dns_resolver::config::ResolverOpts) which configures 2 attempts (1 retry) +/// [`ResolverOpts`](hickory_resolver::config::ResolverOpts) which configures 2 attempts (1 retry) /// by default. #[derive(Clone, Debug)] -pub struct DnsResolver(TokioAsyncResolver); +pub struct DnsResolver(TokioResolver); // === impl DnsResolver === impl DnsResolver { - /// Create a new resolver by wrapping the given [`AsyncResolver`] - pub const fn new(resolver: TokioAsyncResolver) -> Self { + /// Create a new resolver by wrapping the given [`TokioResolver`] + pub const fn new(resolver: TokioResolver) -> Self { Self(resolver) } @@ -60,7 +60,7 @@ impl DnsResolver { /// /// This will use `/etc/resolv.conf` on Unix OSes and the registry on Windows. pub fn from_system_conf() -> Result { - TokioAsyncResolver::tokio_from_system_conf().map(Self::new) + TokioResolver::tokio_from_system_conf().map(Self::new) } } diff --git a/crates/primitives-traits/src/account.rs b/crates/primitives-traits/src/account.rs index d2d6dffb5723..57cfc2a531f5 100644 --- a/crates/primitives-traits/src/account.rs +++ b/crates/primitives-traits/src/account.rs @@ -265,7 +265,15 @@ impl From for revm_primitives::shared::AccountInfo { Self { balance: reth_acc.balance, nonce: reth_acc.nonce, + code_size: reth_acc + .account_extension + .map(|acc| acc.code_size as usize) + .unwrap_or_default(), code_hash: reth_acc.bytecode_hash.unwrap_or(KECCAK_EMPTY), + poseidon_code_hash: reth_acc + .account_extension + .and_then(|acc| acc.poseidon_code_hash) + .unwrap_or(reth_scroll_primitives::poseidon::POSEIDON_EMPTY), code: None, } } diff --git a/crates/revm/Cargo.toml b/crates/revm/Cargo.toml index 7f636474986c..cb7bb9be78ed 100644 --- a/crates/revm/Cargo.toml +++ b/crates/revm/Cargo.toml @@ -68,5 +68,5 @@ serde = [ scroll = [ "reth-scroll-primitives", "reth-primitives-traits/scroll", - "reth-trie/scroll" + "reth-trie?/scroll" ] diff --git a/crates/scroll/chainspec/src/constants.rs b/crates/scroll/chainspec/src/constants.rs new file mode 100644 index 000000000000..98ef129c05a1 --- /dev/null +++ b/crates/scroll/chainspec/src/constants.rs @@ -0,0 +1,47 @@ +use crate::genesis::L1Config; +use alloy_primitives::{address, Address}; + +/// The transaction fee recipient on the L2. +pub const SCROLL_FEE_VAULT_ADDRESS: Address = address!("5300000000000000000000000000000000000005"); + +/// The L1 message queue address for Scroll mainnet. +/// . +pub const SCROLL_MAINNET_L1_MESSAGE_QUEUE_ADDRESS: Address = + address!("0d7E906BD9cAFa154b048cFa766Cc1E54E39AF9B"); + +/// The L1 proxy address for Scroll mainnet. +/// . +pub const SCROLL_MAINNET_L1_PROXY_ADDRESS: Address = + address!("a13BAF47339d63B743e7Da8741db5456DAc1E556"); + +/// The maximum allowed l1 messages per block for Scroll mainnet. +pub const SCROLL_MAINNET_MAX_L1_MESSAGES: u64 = 10; + +/// The L1 configuration for Scroll mainnet. +pub const SCROLL_MAINNET_L1_CONFIG: L1Config = L1Config { + l1_chain_id: alloy_chains::NamedChain::Mainnet as u64, + l1_message_queue_address: SCROLL_MAINNET_L1_MESSAGE_QUEUE_ADDRESS, + scroll_chain_address: SCROLL_MAINNET_L1_PROXY_ADDRESS, + num_l1_messages_per_block: SCROLL_MAINNET_MAX_L1_MESSAGES, +}; + +/// The L1 message queue address for Scroll sepolia. +/// . +pub const SCROLL_SEPOLIA_L1_MESSAGE_QUEUE_ADDRESS: Address = + address!("F0B2293F5D834eAe920c6974D50957A1732de763"); + +/// The L1 proxy address for Scroll sepolia. +/// +pub const SCROLL_SEPOLIA_L1_PROXY_ADDRESS: Address = + address!("2D567EcE699Eabe5afCd141eDB7A4f2D0D6ce8a0"); + +/// The maximum allowed l1 messages per block for Scroll sepolia. +pub const SCROLL_SEPOLIA_MAX_L1_MESSAGES: u64 = 10; + +/// The L1 configuration for Scroll sepolia. +pub const SCROLL_SEPOLIA_L1_CONFIG: L1Config = L1Config { + l1_chain_id: alloy_chains::NamedChain::Sepolia as u64, + l1_message_queue_address: SCROLL_SEPOLIA_L1_MESSAGE_QUEUE_ADDRESS, + scroll_chain_address: SCROLL_SEPOLIA_L1_PROXY_ADDRESS, + num_l1_messages_per_block: SCROLL_SEPOLIA_MAX_L1_MESSAGES, +}; diff --git a/crates/scroll/chainspec/src/genesis.rs b/crates/scroll/chainspec/src/genesis.rs index ceb646a288ba..301343a21dbe 100644 --- a/crates/scroll/chainspec/src/genesis.rs +++ b/crates/scroll/chainspec/src/genesis.rs @@ -1,5 +1,8 @@ //! Scroll types for genesis data. +use crate::constants::{ + SCROLL_FEE_VAULT_ADDRESS, SCROLL_MAINNET_L1_CONFIG, SCROLL_SEPOLIA_L1_CONFIG, +}; use alloy_primitives::Address; use alloy_serde::OtherFields; use serde::de::Error; @@ -80,15 +83,9 @@ pub struct L1Config { pub l1_chain_id: u64, /// The L1 contract address of the contract that handles the message queue targeting the Scroll /// rollup. - /// - /// Scroll mainnet l1 message queue address: . - /// Scroll sepolia l1 message queue address: . pub l1_message_queue_address: Address, /// The L1 contract address of the proxy contract which is responsible for Scroll rollup /// settlement. - /// - /// Scroll mainnet l1 chain proxy address: . - /// Scroll sepolia l1 chain proxy address: pub scroll_chain_address: Address, /// The maximum number of L1 messages to be consumed per L2 rollup block. pub num_l1_messages_per_block: u64, @@ -115,6 +112,21 @@ impl ScrollChainConfig { pub fn extract_from(others: &OtherFields) -> Option { Self::try_from(others).ok() } + + /// Returns the [`ScrollChainConfig`] for Scroll Mainnet. + pub const fn mainnet() -> Self { + Self { + fee_vault_address: Some(SCROLL_FEE_VAULT_ADDRESS), + l1_config: SCROLL_MAINNET_L1_CONFIG, + } + } + /// Returns the [`ScrollChainConfig`] for Scroll Sepolia. + pub const fn sepolia() -> Self { + Self { + fee_vault_address: Some(SCROLL_FEE_VAULT_ADDRESS), + l1_config: SCROLL_SEPOLIA_L1_CONFIG, + } + } } impl TryFrom<&OtherFields> for ScrollChainConfig { diff --git a/crates/scroll/chainspec/src/lib.rs b/crates/scroll/chainspec/src/lib.rs index d628af48e0ea..a9bcfd5fb857 100644 --- a/crates/scroll/chainspec/src/lib.rs +++ b/crates/scroll/chainspec/src/lib.rs @@ -29,11 +29,19 @@ use std::sync::LazyLock; extern crate alloc; +mod constants; +pub use constants::{ + SCROLL_FEE_VAULT_ADDRESS, SCROLL_MAINNET_L1_CONFIG, SCROLL_MAINNET_L1_MESSAGE_QUEUE_ADDRESS, + SCROLL_MAINNET_L1_PROXY_ADDRESS, SCROLL_MAINNET_MAX_L1_MESSAGES, SCROLL_SEPOLIA_L1_CONFIG, + SCROLL_SEPOLIA_L1_MESSAGE_QUEUE_ADDRESS, SCROLL_SEPOLIA_L1_PROXY_ADDRESS, + SCROLL_SEPOLIA_MAX_L1_MESSAGES, +}; + mod dev; pub use dev::SCROLL_DEV; mod genesis; -pub use genesis::ScrollChainInfo; +pub use genesis::{ScrollChainConfig, ScrollChainInfo}; mod scroll; pub use scroll::SCROLL_MAINNET; diff --git a/crates/scroll/consensus/Cargo.toml b/crates/scroll/consensus/Cargo.toml new file mode 100644 index 000000000000..beec5c27120d --- /dev/null +++ b/crates/scroll/consensus/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "reth-scroll-consensus" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +exclude.workspace = true + +[lints] +workspace = true + +[dependencies] +# revm +revm.workspace = true + +[dev-dependencies] +eyre.workspace = true + +[features] +scroll = ["revm/scroll"] diff --git a/crates/scroll/consensus/src/curie.rs b/crates/scroll/consensus/src/curie.rs new file mode 100644 index 000000000000..6824b11027e0 --- /dev/null +++ b/crates/scroll/consensus/src/curie.rs @@ -0,0 +1,203 @@ +//! Curie fork transition for Scroll. +//! +//! On block 7096836, Scroll performed a transition to the Curie fork state, which brought various +//! changes to the protocol: +//! 1. Fee reduction cost thanks to the use of compressed blobs on the L1. +//! 2. Modified [EIP-1559](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md) pricing +//! model along with support for EIP-1559 and EIP-2930 transactions. +//! 3. Support for `MLOAD`, `TLOAD` and `MCOPY` ([EIP-1153](https://eips.ethereum.org/EIPS/eip-1153) +//! and [EIP-5656](https://eips.ethereum.org/EIPS/eip-5656)). +//! 4. Dynamic block time. +//! +//! Compressed blobs allowed for more transactions to be stored in each blob, reducing the DA cost +//! per transactions. Accordingly, the L1 gas oracle contract's bytecode was updated in order to +//! reflect the update in the DA costs: +//! - original formula: `(l1GasUsed(txRlp) + overhead) * l1BaseFee * scalar`. +//! - updated formula: `l1BaseFee * commitScalar + len(txRlp) * l1BlobBaseFee * blobScalar`. +//! +//! More details on the Curie update: + +use revm::{ + db::states::StorageSlot, + primitives::{address, bytes, Address, Bytecode, Bytes, U256}, + shared::AccountInfo, + Database, State, +}; + +/// L1 gas price oracle address. +/// +pub const L1_GAS_PRICE_ORACLE_ADDRESS: Address = + address!("5300000000000000000000000000000000000002"); +/// Bytecode of L1 gas price oracle at Curie transition. +pub const CURIE_L1_GAS_PRICE_ORACLE_BYTECODE: Bytes = bytes!("608060405234801561000f575f80fd5b5060043610610132575f3560e01c8063715018a6116100b4578063a911d77f11610079578063a911d77f1461024c578063bede39b514610254578063de26c4a114610267578063e88a60ad1461027a578063f2fde38b1461028d578063f45e65d8146102a0575f80fd5b8063715018a6146101eb57806384189161146101f35780638da5cb5b146101fc57806393e59dc114610226578063944b247f14610239575f80fd5b80633d0f963e116100fa5780633d0f963e146101a057806349948e0e146101b3578063519b4bd3146101c65780636a5e67e5146101cf57806370465597146101d8575f80fd5b80630c18c1621461013657806313dad5be1461015257806323e524ac1461016f5780633577afc51461017857806339455d3a1461018d575b5f80fd5b61013f60025481565b6040519081526020015b60405180910390f35b60085461015f9060ff1681565b6040519015158152602001610149565b61013f60065481565b61018b6101863660046109b3565b6102a9565b005b61018b61019b3660046109ca565b61033b565b61018b6101ae3660046109ea565b610438565b61013f6101c1366004610a2b565b6104bb565b61013f60015481565b61013f60075481565b61018b6101e63660046109b3565b6104e0565b61018b61056e565b61013f60055481565b5f5461020e906001600160a01b031681565b6040516001600160a01b039091168152602001610149565b60045461020e906001600160a01b031681565b61018b6102473660046109b3565b6105a2565b61018b61062e565b61018b6102623660046109b3565b61068a565b61013f610275366004610a2b565b610747565b61018b6102883660046109b3565b610764565b61018b61029b3660046109ea565b6107f0565b61013f60035481565b5f546001600160a01b031633146102db5760405162461bcd60e51b81526004016102d290610ad6565b60405180910390fd5b621c9c388111156102ff57604051635742c80560e11b815260040160405180910390fd5b60028190556040518181527f32740b35c0ea213650f60d44366b4fb211c9033b50714e4a1d34e65d5beb9bb4906020015b60405180910390a150565b6004805460405163efc7840160e01b815233928101929092526001600160a01b03169063efc7840190602401602060405180830381865afa158015610382573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103a69190610b0d565b6103c3576040516326b3506d60e11b815260040160405180910390fd5b600182905560058190556040518281527f351fb23757bb5ea0546c85b7996ddd7155f96b939ebaa5ff7bc49c75f27f2c449060200160405180910390a16040518181527f9a14bfb5d18c4c3cf14cae19c23d7cf1bcede357ea40ca1f75cd49542c71c214906020015b60405180910390a15050565b5f546001600160a01b031633146104615760405162461bcd60e51b81526004016102d290610ad6565b600480546001600160a01b038381166001600160a01b031983168117909355604080519190921680825260208201939093527f22d1c35fe072d2e42c3c8f9bd4a0d34aa84a0101d020a62517b33fdb3174e5f7910161042c565b6008545f9060ff16156104d7576104d18261087b565b92915050565b6104d1826108c1565b5f546001600160a01b031633146105095760405162461bcd60e51b81526004016102d290610ad6565b610519633b9aca006103e8610b40565b81111561053957604051631e44fdeb60e11b815260040160405180910390fd5b60038190556040518181527f3336cd9708eaf2769a0f0dc0679f30e80f15dcd88d1921b5a16858e8b85c591a90602001610330565b5f546001600160a01b031633146105975760405162461bcd60e51b81526004016102d290610ad6565b6105a05f610904565b565b5f546001600160a01b031633146105cb5760405162461bcd60e51b81526004016102d290610ad6565b6105d9633b9aca0080610b40565b8111156105f95760405163874f603160e01b815260040160405180910390fd5b60068190556040518181527f2ab3f5a4ebbcbf3c24f62f5454f52f10e1a8c9dcc5acac8f19199ce881a6a10890602001610330565b5f546001600160a01b031633146106575760405162461bcd60e51b81526004016102d290610ad6565b60085460ff161561067b576040516379f9c57560e01b815260040160405180910390fd5b6008805460ff19166001179055565b6004805460405163efc7840160e01b815233928101929092526001600160a01b03169063efc7840190602401602060405180830381865afa1580156106d1573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106f59190610b0d565b610712576040516326b3506d60e11b815260040160405180910390fd5b60018190556040518181527f351fb23757bb5ea0546c85b7996ddd7155f96b939ebaa5ff7bc49c75f27f2c4490602001610330565b6008545f9060ff161561075b57505f919050565b6104d182610953565b5f546001600160a01b0316331461078d5760405162461bcd60e51b81526004016102d290610ad6565b61079b633b9aca0080610b40565b8111156107bb5760405163f37ec21560e01b815260040160405180910390fd5b60078190556040518181527f6b332a036d8c3ead57dcb06c87243bd7a2aed015ddf2d0528c2501dae56331aa90602001610330565b5f546001600160a01b031633146108195760405162461bcd60e51b81526004016102d290610ad6565b6001600160a01b03811661086f5760405162461bcd60e51b815260206004820152601d60248201527f6e6577206f776e657220697320746865207a65726f206164647265737300000060448201526064016102d2565b61087881610904565b50565b5f633b9aca0060055483516007546108939190610b40565b61089d9190610b40565b6001546006546108ad9190610b40565b6108b79190610b57565b6104d19190610b6a565b5f806108cc83610953565b90505f600154826108dd9190610b40565b9050633b9aca00600354826108f29190610b40565b6108fc9190610b6a565b949350505050565b5f80546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b80515f908190815b818110156109a45784818151811061097557610975610b89565b01602001516001600160f81b0319165f036109955760048301925061099c565b6010830192505b60010161095b565b50506002540160400192915050565b5f602082840312156109c3575f80fd5b5035919050565b5f80604083850312156109db575f80fd5b50508035926020909101359150565b5f602082840312156109fa575f80fd5b81356001600160a01b0381168114610a10575f80fd5b9392505050565b634e487b7160e01b5f52604160045260245ffd5b5f60208284031215610a3b575f80fd5b813567ffffffffffffffff80821115610a52575f80fd5b818401915084601f830112610a65575f80fd5b813581811115610a7757610a77610a17565b604051601f8201601f19908116603f01168101908382118183101715610a9f57610a9f610a17565b81604052828152876020848701011115610ab7575f80fd5b826020860160208301375f928101602001929092525095945050505050565b60208082526017908201527f63616c6c6572206973206e6f7420746865206f776e6572000000000000000000604082015260600190565b5f60208284031215610b1d575f80fd5b81518015158114610a10575f80fd5b634e487b7160e01b5f52601160045260245ffd5b80820281158282048414176104d1576104d1610b2c565b808201808211156104d1576104d1610b2c565b5f82610b8457634e487b7160e01b5f52601260045260245ffd5b500490565b634e487b7160e01b5f52603260045260245ffdfea26469706673582212200c2ac583f18be4f94ab169ae6f2ea3a708a7c0d4424746b120b177adb39e626064736f6c63430008180033"); +/// Storage update of L1 gas price oracle at Curie transition. +pub const CURIE_L1_GAS_PRICE_ORACLE_STORAGE: [(U256, U256); 4] = [ + (L1_BLOB_BASE_FEE_SLOT, INITIAL_L1_BLOB_BASE_FEE), + (COMMIT_SCALAR_SLOT, INITIAL_COMMIT_SCALAR), + (BLOB_SCALAR_SLOT, INITIAL_BLOB_SCALAR), + (IS_CURIE_SLOT, IS_CURIE), +]; + +/// L1 gas price oracle base fee slot. +pub const L1_BASE_FEE_SLOT: U256 = U256::from_limbs([1, 0, 0, 0]); +/// L1 gas price oracle over head slot. +pub const OVER_HEAD_SLOT: U256 = U256::from_limbs([2, 0, 0, 0]); +/// L1 gas price oracle scalar slot. +pub const SCALAR_SLOT: U256 = U256::from_limbs([3, 0, 0, 0]); + +/// L1 gas price oracle blob base fee slot. Added in the Curie fork. +pub const L1_BLOB_BASE_FEE_SLOT: U256 = U256::from_limbs([5, 0, 0, 0]); +/// L1 gas price oracle commit scalar slot. Added in the Curie fork. +pub const COMMIT_SCALAR_SLOT: U256 = U256::from_limbs([6, 0, 0, 0]); +/// L1 gas price oracle blob scalar slot. Added in the Curie fork. +pub const BLOB_SCALAR_SLOT: U256 = U256::from_limbs([7, 0, 0, 0]); +/// L1 gas price oracle "is Curie" slot. Added in the Curie fork. +pub const IS_CURIE_SLOT: U256 = U256::from_limbs([8, 0, 0, 0]); + +/// The initial blob base fee used by the oracle contract. +const INITIAL_L1_BLOB_BASE_FEE: U256 = U256::from_limbs([1, 0, 0, 0]); +/// The initial commit scalar used by the oracle contract. +const INITIAL_COMMIT_SCALAR: U256 = U256::from_limbs([230759955285, 0, 0, 0]); +/// The initial blob scalar used by the oracle contract. +const INITIAL_BLOB_SCALAR: U256 = U256::from_limbs([417565260, 0, 0, 0]); +/// Curie slot is set to 1 (true) after the Curie block fork. +const IS_CURIE: U256 = U256::from_limbs([1, 0, 0, 0]); + +/// Applies the Scroll Curie hard fork to the state: +/// - Updates the L1 oracle contract bytecode to reflect the DA cost reduction. +/// - Sets the initial blob base fee, commit and blob scalar and sets the `isCurie` slot to 1 +/// (true). +pub fn apply_curie_hard_fork(state: &mut State) -> Result<(), DB::Error> { + let oracle = state.load_cache_account(L1_GAS_PRICE_ORACLE_ADDRESS)?; + + // compute the code hash + let bytecode = Bytecode::new_raw(CURIE_L1_GAS_PRICE_ORACLE_BYTECODE); + let bytecode_len = bytecode.len(); + let code_hash = bytecode.hash_slow(); + let poseidon_code_hash = bytecode.poseidon_hash_slow(); + + // get the old oracle account info + let old_oracle_info = oracle.account_info().unwrap_or_default(); + + // init new oracle account information + let new_oracle_info = AccountInfo { + code_size: bytecode_len, + code_hash, + poseidon_code_hash, + code: Some(bytecode), + ..old_oracle_info + }; + + // init new storage + let new_storage = CURIE_L1_GAS_PRICE_ORACLE_STORAGE + .into_iter() + .map(|(slot, present_value)| { + ( + slot, + StorageSlot { + present_value, + previous_or_original_value: oracle.storage_slot(slot).unwrap_or_default(), + }, + ) + }) + .collect(); + + // create transition for oracle new account info and storage + let transition = oracle.change(new_oracle_info, new_storage); + + // add transition + if let Some(s) = state.transition_state.as_mut() { + s.add_transitions(vec![(L1_GAS_PRICE_ORACLE_ADDRESS, transition)]) + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use crate::{ + apply_curie_hard_fork, + curie::{ + CURIE_L1_GAS_PRICE_ORACLE_BYTECODE, CURIE_L1_GAS_PRICE_ORACLE_STORAGE, + L1_GAS_PRICE_ORACLE_ADDRESS, + }, + }; + use revm::{ + db::states::{bundle_state::BundleRetention, plain_account::PlainStorage, StorageSlot}, + keccak256, + primitives::{bytes, poseidon, U256}, + shared::AccountInfo, + Bytecode, Database, EmptyDB, State, + }; + use std::str::FromStr; + + #[test] + fn test_apply_curie_fork() -> eyre::Result<()> { + // init state + let db = EmptyDB::new(); + let mut state = + State::builder().with_database(db).with_bundle_update().without_state_clear().build(); + + // oracle pre fork state + let bytecode_pre_fork = Bytecode::new_raw( bytes!("608060405234801561001057600080fd5b50600436106100cf5760003560e01c8063715018a61161008c578063bede39b511610066578063bede39b51461018d578063de26c4a1146101a0578063f2fde38b146101b3578063f45e65d8146101c657600080fd5b8063715018a6146101475780638da5cb5b1461014f57806393e59dc11461017a57600080fd5b80630c18c162146100d45780633577afc5146100f05780633d0f963e1461010557806349948e0e14610118578063519b4bd31461012b5780637046559714610134575b600080fd5b6100dd60025481565b6040519081526020015b60405180910390f35b6101036100fe366004610671565b6101cf565b005b61010361011336600461068a565b610291565b6100dd6101263660046106d0565b61031c565b6100dd60015481565b610103610142366004610671565b610361565b610103610416565b600054610162906001600160a01b031681565b6040516001600160a01b0390911681526020016100e7565b600454610162906001600160a01b031681565b61010361019b366004610671565b61044c565b6100dd6101ae3660046106d0565b610533565b6101036101c136600461068a565b610595565b6100dd60035481565b6000546001600160a01b031633146102025760405162461bcd60e51b81526004016101f990610781565b60405180910390fd5b621c9c388111156102555760405162461bcd60e51b815260206004820152601760248201527f657863656564206d6178696d756d206f7665726865616400000000000000000060448201526064016101f9565b60028190556040518181527f32740b35c0ea213650f60d44366b4fb211c9033b50714e4a1d34e65d5beb9bb4906020015b60405180910390a150565b6000546001600160a01b031633146102bb5760405162461bcd60e51b81526004016101f990610781565b600480546001600160a01b038381166001600160a01b031983168117909355604080519190921680825260208201939093527f22d1c35fe072d2e42c3c8f9bd4a0d34aa84a0101d020a62517b33fdb3174e5f7910160405180910390a15050565b60008061032883610533565b905060006001548261033a91906107b8565b9050633b9aca006003548261034f91906107b8565b61035991906107e5565b949350505050565b6000546001600160a01b0316331461038b5760405162461bcd60e51b81526004016101f990610781565b61039b633b9aca006103e86107b8565b8111156103e15760405162461bcd60e51b8152602060048201526014602482015273657863656564206d6178696d756d207363616c6560601b60448201526064016101f9565b60038190556040518181527f3336cd9708eaf2769a0f0dc0679f30e80f15dcd88d1921b5a16858e8b85c591a90602001610286565b6000546001600160a01b031633146104405760405162461bcd60e51b81526004016101f990610781565b61044a6000610621565b565b6004805460405163efc7840160e01b815233928101929092526001600160a01b03169063efc7840190602401602060405180830381865afa158015610495573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104b99190610807565b6104fe5760405162461bcd60e51b81526020600482015260166024820152752737ba103bb434ba32b634b9ba32b21039b2b73232b960511b60448201526064016101f9565b60018190556040518181527f351fb23757bb5ea0546c85b7996ddd7155f96b939ebaa5ff7bc49c75f27f2c4490602001610286565b80516000908190815b818110156105865784818151811061055657610556610829565b01602001516001600160f81b0319166000036105775760048301925061057e565b6010830192505b60010161053c565b50506002540160400192915050565b6000546001600160a01b031633146105bf5760405162461bcd60e51b81526004016101f990610781565b6001600160a01b0381166106155760405162461bcd60e51b815260206004820152601d60248201527f6e6577206f776e657220697320746865207a65726f206164647265737300000060448201526064016101f9565b61061e81610621565b50565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b60006020828403121561068357600080fd5b5035919050565b60006020828403121561069c57600080fd5b81356001600160a01b03811681146106b357600080fd5b9392505050565b634e487b7160e01b600052604160045260246000fd5b6000602082840312156106e257600080fd5b813567ffffffffffffffff808211156106fa57600080fd5b818401915084601f83011261070e57600080fd5b813581811115610720576107206106ba565b604051601f8201601f19908116603f01168101908382118183101715610748576107486106ba565b8160405282815287602084870101111561076157600080fd5b826020860160208301376000928101602001929092525095945050505050565b60208082526017908201527f63616c6c6572206973206e6f7420746865206f776e6572000000000000000000604082015260600190565b60008160001904831182151516156107e057634e487b7160e01b600052601160045260246000fd5b500290565b60008261080257634e487b7160e01b600052601260045260246000fd5b500490565b60006020828403121561081957600080fd5b815180151581146106b357600080fd5b634e487b7160e01b600052603260045260246000fdfea26469706673582212205ea335809638809cf032c794fd966e2439020737b1dcc2218435cb438286efcf64736f6c63430008100033")); + let oracle_pre_fork = AccountInfo { + code_size: bytecode_pre_fork.len(), + code_hash: bytecode_pre_fork.hash_slow(), + poseidon_code_hash: bytecode_pre_fork.poseidon_hash_slow(), + code: Some(bytecode_pre_fork), + ..Default::default() + }; + let oracle_storage_pre_fork = PlainStorage::from_iter([ + (U256::ZERO, U256::from_str("0x13d24a7ff6f5ec5ff0e9c40fc3b8c9c01c65437b")?), + (U256::from(1), U256::from(0x15f50e5e)), + (U256::from(2), U256::from(0x38)), + (U256::from(3), U256::from(0x3e95ba80)), + (U256::from(4), U256::from_str("0x5300000000000000000000000000000000000003")?), + ]); + state.insert_account_with_storage( + L1_GAS_PRICE_ORACLE_ADDRESS, + oracle_pre_fork.clone(), + oracle_storage_pre_fork.clone(), + ); + + // apply curie fork + apply_curie_hard_fork(&mut state)?; + + // merge transitions + state.merge_transitions(BundleRetention::Reverts); + let bundle = state.take_bundle(); + + // check oracle account info + let oracle = bundle.state.get(&L1_GAS_PRICE_ORACLE_ADDRESS).unwrap().clone(); + let code_hash = keccak256(&CURIE_L1_GAS_PRICE_ORACLE_BYTECODE); + let bytecode = Bytecode::new_raw(CURIE_L1_GAS_PRICE_ORACLE_BYTECODE); + let expected_oracle_info = AccountInfo { + code_size: CURIE_L1_GAS_PRICE_ORACLE_BYTECODE.len(), + code_hash, + poseidon_code_hash: poseidon(&CURIE_L1_GAS_PRICE_ORACLE_BYTECODE), + code: Some(bytecode.clone()), + ..Default::default() + }; + + assert_eq!(oracle.original_info.unwrap(), oracle_pre_fork); + assert_eq!(oracle.info.unwrap(), expected_oracle_info); + + // check oracle storage changeset + let mut storage = oracle.storage.into_iter().collect::>(); + storage.sort_by(|(a, _), (b, _)| a.cmp(b)); + for (got, expected) in storage.into_iter().zip(CURIE_L1_GAS_PRICE_ORACLE_STORAGE) { + assert_eq!(got.0, expected.0); + assert_eq!(got.1, StorageSlot { present_value: expected.1, ..Default::default() }); + } + + // check oracle original storage + for (slot, value) in oracle_storage_pre_fork { + assert_eq!(state.storage(L1_GAS_PRICE_ORACLE_ADDRESS, slot)?, value) + } + + // check deployed contract + assert_eq!(bundle.contracts.get(&code_hash).unwrap().clone(), bytecode); + + Ok(()) + } +} diff --git a/crates/scroll/consensus/src/lib.rs b/crates/scroll/consensus/src/lib.rs new file mode 100644 index 000000000000..6d4e4562a5bd --- /dev/null +++ b/crates/scroll/consensus/src/lib.rs @@ -0,0 +1,11 @@ +//! Scroll consensus implementation. + +#![cfg(feature = "scroll")] + +mod curie; +pub use curie::{ + apply_curie_hard_fork, BLOB_SCALAR_SLOT, COMMIT_SCALAR_SLOT, + CURIE_L1_GAS_PRICE_ORACLE_BYTECODE, CURIE_L1_GAS_PRICE_ORACLE_STORAGE, IS_CURIE_SLOT, + L1_BASE_FEE_SLOT, L1_BLOB_BASE_FEE_SLOT, L1_GAS_PRICE_ORACLE_ADDRESS, OVER_HEAD_SLOT, + SCALAR_SLOT, +}; diff --git a/crates/scroll/evm/Cargo.toml b/crates/scroll/evm/Cargo.toml new file mode 100644 index 000000000000..28653851c6d6 --- /dev/null +++ b/crates/scroll/evm/Cargo.toml @@ -0,0 +1,64 @@ +[package] +name = "reth-scroll-evm" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +exclude.workspace = true + +[lints] +workspace = true + +[dependencies] +# reth +reth-chainspec.workspace = true +reth-consensus.workspace = true +reth-ethereum-consensus.workspace = true +reth-evm.workspace = true +reth-primitives.workspace = true +reth-primitives-traits.workspace = true +reth-revm.workspace = true + +# revm +revm = { workspace = true, features = ["optional_no_base_fee"] } + +# scroll +reth-scroll-chainspec.workspace = true +reth-scroll-forks.workspace = true + +# alloy +alloy-consensus.workspace = true +alloy-eips.workspace = true + +# scroll +reth-scroll-consensus.workspace = true +reth-scroll-execution.workspace = true + +# misc +auto_impl.workspace = true +derive_more.workspace = true +thiserror.workspace = true +tracing.workspace = true + +[dev-dependencies] +eyre.workspace = true +reth-scroll-execution = { workspace = true, features = ["test-utils"] } +reth-scroll-primitives.workspace = true + +[features] +optimism = [ + "reth-primitives/optimism", + "revm/optimism" +] +scroll = [ + "reth-chainspec/scroll", + "reth-evm/scroll", + "reth-primitives/scroll", + "reth-primitives-traits/scroll", + "reth-revm/scroll", + "reth-scroll-consensus/scroll", + "reth-scroll-execution/scroll", + "revm/scroll" +] diff --git a/crates/scroll/evm/src/config.rs b/crates/scroll/evm/src/config.rs new file mode 100644 index 000000000000..e2ced0b7c605 --- /dev/null +++ b/crates/scroll/evm/src/config.rs @@ -0,0 +1,329 @@ +use reth_chainspec::Head; +use reth_evm::{ConfigureEvm, ConfigureEvmEnv, NextBlockEnvAttributes}; +use reth_primitives::{transaction::FillTxEnv, TransactionSigned}; +use reth_revm::{inspector_handle_register, Database, Evm, GetInspector, TxEnv}; +use reth_scroll_chainspec::{ScrollChainConfig, ScrollChainSpec}; +use reth_scroll_forks::ScrollHardfork; +use revm::{ + precompile::{Address, Bytes}, + primitives::{ + AnalysisKind, BlockEnv, CfgEnv, CfgEnvWithHandlerCfg, Env, HandlerCfg, SpecId, U256, + }, + EvmBuilder, +}; +use std::{convert::Infallible, sync::Arc}; + +/// Scroll EVM configuration. +#[derive(Clone, Debug)] +pub struct ScrollEvmConfig { + /// The chain spec for Scroll. + chain_spec: Arc, + /// Additional Scroll configuration. + scroll_config: ScrollChainConfig, +} + +impl ScrollEvmConfig { + /// Returns a new instance of [`ScrollEvmConfig`]. + pub const fn new(chain_spec: Arc, scroll_config: ScrollChainConfig) -> Self { + Self { chain_spec, scroll_config } + } + + /// Returns the spec id at the given head. + pub fn spec_id_at_head(&self, head: &Head) -> SpecId { + let chain_spec = &self.chain_spec; + if chain_spec.fork(ScrollHardfork::Curie).active_at_head(head) { + SpecId::CURIE + } else if chain_spec.fork(ScrollHardfork::Bernoulli).active_at_head(head) { + SpecId::BERNOULLI + } else { + SpecId::PRE_BERNOULLI + } + } +} + +impl ConfigureEvm for ScrollEvmConfig { + type DefaultExternalContext<'a> = (); + + fn evm(&self, db: DB) -> Evm<'_, Self::DefaultExternalContext<'_>, DB> { + EvmBuilder::default().with_db(db).scroll().build() + } + + fn evm_with_inspector(&self, db: DB, inspector: I) -> Evm<'_, I, DB> + where + DB: Database, + I: GetInspector, + { + EvmBuilder::default() + .with_db(db) + .with_external_context(inspector) + .scroll() + .append_handler_register(inspector_handle_register) + .build() + } + + fn default_external_context<'a>(&self) -> Self::DefaultExternalContext<'a> {} +} + +impl ConfigureEvmEnv for ScrollEvmConfig { + type Header = alloy_consensus::Header; + type Error = Infallible; + + fn fill_tx_env(&self, tx_env: &mut TxEnv, transaction: &TransactionSigned, sender: Address) { + transaction.fill_tx_env(tx_env, sender); + } + + fn fill_tx_env_system_contract_call( + &self, + _env: &mut Env, + _caller: Address, + _contract: Address, + _data: Bytes, + ) { + /* noop */ + } + + fn fill_cfg_env( + &self, + cfg_env: &mut CfgEnvWithHandlerCfg, + header: &Self::Header, + total_difficulty: U256, + ) { + let spec_id = self.spec_id_at_head(&Head { + number: header.number, + timestamp: header.timestamp, + difficulty: header.difficulty, + total_difficulty, + ..Default::default() + }); + + cfg_env.handler_cfg.spec_id = spec_id; + cfg_env.handler_cfg.is_scroll = true; + + cfg_env.chain_id = self.chain_spec.chain().id(); + cfg_env.perf_analyse_created_bytecodes = AnalysisKind::Analyse; + } + + fn fill_block_env(&self, block_env: &mut BlockEnv, header: &Self::Header, after_merge: bool) { + block_env.number = U256::from(header.number); + + if let Some(vault_address) = self.scroll_config.fee_vault_address { + block_env.coinbase = vault_address; + } else { + block_env.coinbase = header.beneficiary; + } + + block_env.timestamp = U256::from(header.timestamp); + if after_merge { + block_env.prevrandao = Some(header.mix_hash); + block_env.difficulty = U256::ZERO; + } else { + block_env.difficulty = header.difficulty; + block_env.prevrandao = None; + } + block_env.basefee = U256::from(header.base_fee_per_gas.unwrap_or_default()); + block_env.gas_limit = U256::from(header.gas_limit); + } + + fn next_cfg_and_block_env( + &self, + parent: &Self::Header, + attributes: NextBlockEnvAttributes, + ) -> Result<(CfgEnvWithHandlerCfg, BlockEnv), Self::Error> { + // configure evm env based on parent block + let cfg = CfgEnv::default().with_chain_id(self.chain_spec.chain().id()); + + // fetch spec id from next head number and timestamp + let spec_id = self.spec_id_at_head(&Head { + number: parent.number + 1, + timestamp: attributes.timestamp, + ..Default::default() + }); + + let coinbase = if let Some(vault_address) = self.scroll_config.fee_vault_address { + vault_address + } else { + attributes.suggested_fee_recipient + }; + + let block_env = BlockEnv { + number: U256::from(parent.number + 1), + coinbase, + timestamp: U256::from(attributes.timestamp), + difficulty: U256::ZERO, + prevrandao: Some(attributes.prev_randao), + gas_limit: U256::from(parent.gas_limit), + // calculate basefee based on parent block's gas usage + // TODO(scroll): update with correct block fee calculation for block building. + basefee: U256::from(parent.base_fee_per_gas.unwrap_or_default()), + blob_excess_gas_and_price: None, + }; + + let cfg_with_handler_cfg = CfgEnvWithHandlerCfg { + cfg_env: cfg, + handler_cfg: HandlerCfg { spec_id, is_scroll: true }, + }; + + Ok((cfg_with_handler_cfg, block_env)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_consensus::Header; + use reth_chainspec::NamedChain::Scroll; + use reth_scroll_chainspec::ScrollChainSpecBuilder; + use revm::primitives::{SpecId, B256}; + + #[test] + fn test_spec_at_head() { + let config = ScrollEvmConfig::new( + ScrollChainSpecBuilder::scroll_mainnet().build().into(), + ScrollChainConfig::default(), + ); + + // prepare all fork heads + let curie_head = &Head { number: 7096836, ..Default::default() }; + let bernouilli_head = &Head { number: 5220340, ..Default::default() }; + let pre_bernouilli_head = &Head { number: 0, ..Default::default() }; + + // check correct spec id + assert_eq!(config.spec_id_at_head(curie_head), SpecId::CURIE); + assert_eq!(config.spec_id_at_head(bernouilli_head), SpecId::BERNOULLI); + assert_eq!(config.spec_id_at_head(pre_bernouilli_head), SpecId::PRE_BERNOULLI); + } + + #[test] + fn test_fill_cfg_env() { + let config = ScrollEvmConfig::new( + ScrollChainSpecBuilder::scroll_mainnet().build().into(), + ScrollChainConfig::default(), + ); + + // curie + let mut cfg_env = CfgEnvWithHandlerCfg::new(Default::default(), Default::default()); + let curie_header = Header { number: 7096836, ..Default::default() }; + + // fill cfg env + config.fill_cfg_env(&mut cfg_env, &curie_header, U256::ZERO); + + // check correct cfg env + assert_eq!(cfg_env.chain_id, Scroll as u64); + assert_eq!(cfg_env.perf_analyse_created_bytecodes, AnalysisKind::Analyse); + assert_eq!(cfg_env.handler_cfg.spec_id, SpecId::CURIE); + assert!(cfg_env.handler_cfg.is_scroll); + + // bernoulli + let mut cfg_env = CfgEnvWithHandlerCfg::new(Default::default(), Default::default()); + let bernouilli_header = Header { number: 5220340, ..Default::default() }; + + // fill cfg env + config.fill_cfg_env(&mut cfg_env, &bernouilli_header, U256::ZERO); + + // check correct cfg env + assert_eq!(cfg_env.chain_id, Scroll as u64); + assert_eq!(cfg_env.perf_analyse_created_bytecodes, AnalysisKind::Analyse); + assert_eq!(cfg_env.handler_cfg.spec_id, SpecId::BERNOULLI); + assert!(cfg_env.handler_cfg.is_scroll); + + // pre-bernoulli + let mut cfg_env = CfgEnvWithHandlerCfg::new(Default::default(), Default::default()); + let pre_bernouilli_header = Header { number: 0, ..Default::default() }; + + // fill cfg env + config.fill_cfg_env(&mut cfg_env, &pre_bernouilli_header, U256::ZERO); + + // check correct cfg env + assert_eq!(cfg_env.chain_id, Scroll as u64); + assert_eq!(cfg_env.perf_analyse_created_bytecodes, AnalysisKind::Analyse); + assert_eq!(cfg_env.handler_cfg.spec_id, SpecId::PRE_BERNOULLI); + assert!(cfg_env.handler_cfg.is_scroll); + } + + #[test] + fn test_fill_block_env() { + let config = ScrollEvmConfig::new( + ScrollChainSpecBuilder::scroll_mainnet().build().into(), + ScrollChainConfig::mainnet(), + ); + let mut block_env = BlockEnv::default(); + + // curie header + let header = Header { + number: 7096836, + beneficiary: Address::random(), + timestamp: 1719994277, + mix_hash: B256::random(), + base_fee_per_gas: Some(155157341), + gas_limit: 10000000, + ..Default::default() + }; + + // fill block env + config.fill_block_env(&mut block_env, &header, true); + + // verify block env correctly updated + let expected = BlockEnv { + number: U256::from(header.number), + coinbase: config.scroll_config.fee_vault_address.unwrap(), + timestamp: U256::from(header.timestamp), + prevrandao: Some(header.mix_hash), + difficulty: U256::ZERO, + basefee: U256::from(header.base_fee_per_gas.unwrap_or_default()), + gas_limit: U256::from(header.gas_limit), + ..Default::default() + }; + assert_eq!(block_env, expected) + } + + #[test] + fn test_next_cfg_and_block_env() -> eyre::Result<()> { + let config = ScrollEvmConfig::new( + ScrollChainSpecBuilder::scroll_mainnet().build().into(), + ScrollChainConfig::mainnet(), + ); + + // pre curie header + let header = Header { + number: 7096835, + beneficiary: Address::random(), + timestamp: 1719994274, + mix_hash: B256::random(), + base_fee_per_gas: None, + gas_limit: 10000000, + ..Default::default() + }; + + // curie block attributes + let attributes = NextBlockEnvAttributes { + timestamp: 1719994277, + suggested_fee_recipient: Address::random(), + prev_randao: B256::random(), + }; + + // get next cfg env and block env + let (cfg_env, block_env) = config.next_cfg_and_block_env(&header, attributes)?; + + // verify cfg env + assert_eq!(cfg_env.chain_id, Scroll as u64); + assert_eq!(cfg_env.handler_cfg.spec_id, SpecId::CURIE); + assert!(cfg_env.handler_cfg.is_scroll); + + // verify block env + let expected = BlockEnv { + number: U256::from(header.number + 1), + coinbase: config.scroll_config.fee_vault_address.unwrap(), + timestamp: U256::from(attributes.timestamp), + prevrandao: Some(attributes.prev_randao), + difficulty: U256::ZERO, + // TODO(scroll): this shouldn't be 0 at curie fork + basefee: U256::ZERO, + gas_limit: U256::from(header.gas_limit), + blob_excess_gas_and_price: None, + ..Default::default() + }; + assert_eq!(block_env, expected); + + Ok(()) + } +} diff --git a/crates/scroll/evm/src/error.rs b/crates/scroll/evm/src/error.rs new file mode 100644 index 000000000000..3e2f469a4a6e --- /dev/null +++ b/crates/scroll/evm/src/error.rs @@ -0,0 +1,29 @@ +use derive_more::{Display, From}; +use reth_evm::execute::BlockExecutionError; + +/// Execution error for Scroll. +#[derive(thiserror::Error, Display, From, Debug)] +pub enum ScrollBlockExecutionError { + /// Error occurred at fork transition. + #[display("failed to apply fork: {_0}")] + Fork(ForkError), +} + +impl From for BlockExecutionError { + fn from(value: ScrollBlockExecutionError) -> Self { + Self::other(value) + } +} + +/// Scroll fork error. +#[derive(Debug, Display)] +pub enum ForkError { + /// Error occurred at Curie fork. + Curie(String), +} + +impl From for BlockExecutionError { + fn from(value: ForkError) -> Self { + ScrollBlockExecutionError::Fork(value).into() + } +} diff --git a/crates/scroll/evm/src/execute.rs b/crates/scroll/evm/src/execute.rs new file mode 100644 index 000000000000..9642bfecd742 --- /dev/null +++ b/crates/scroll/evm/src/execute.rs @@ -0,0 +1,600 @@ +//! Implementation of the [`BlockExecutionStrategy`] for Scroll. + +use crate::{ForkError, ScrollEvmConfig}; +use alloy_consensus::{Header, Transaction}; +use alloy_eips::{eip2718::Encodable2718, eip7685::Requests}; +use reth_chainspec::EthereumHardforks; +use reth_consensus::ConsensusError; +use reth_evm::{ + execute::{ + BlockExecutionError, BlockExecutionStrategy, BlockExecutionStrategyFactory, + BlockValidationError, ExecuteOutput, ProviderError, + }, + ConfigureEvm, ConfigureEvmEnv, +}; +use reth_primitives::{ + gas_spent_by_transactions, BlockWithSenders, GotExpected, InvalidTransactionError, Receipt, + TxType, +}; +use reth_revm::primitives::{CfgEnvWithHandlerCfg, U256}; +use reth_scroll_chainspec::{ScrollChainConfig, ScrollChainSpec}; +use reth_scroll_consensus::apply_curie_hard_fork; +use reth_scroll_execution::FinalizeExecution; +use reth_scroll_forks::{ScrollHardfork, ScrollHardforks}; +use revm::{ + db::BundleState, + primitives::{bytes::BytesMut, BlockEnv, EnvWithHandlerCfg, ResultAndState}, + Database, DatabaseCommit, State, +}; +use std::{ + fmt::{Debug, Display}, + sync::Arc, +}; + +/// The Scroll block execution strategy. +#[derive(Debug)] +pub struct ScrollExecutionStrategy { + /// Chain specification. + chain_spec: Arc, + /// Evm configuration. + evm_config: EvmConfig, + /// Current state for the execution. + state: State, +} + +impl ScrollExecutionStrategy { + /// Returns an instance of [`ScrollExecutionStrategy`]. + pub const fn new( + chain_spec: Arc, + evm_config: EvmConfig, + state: State, + ) -> Self { + Self { chain_spec, evm_config, state } + } +} + +impl ScrollExecutionStrategy +where + EvmConfig: ConfigureEvmEnv

, +{ + /// Configures a new evm configuration and block environment for the given block. + /// + /// # Caution + /// + /// This does not initialize the tx environment. + fn evm_env_for_block(&self, header: &Header, total_difficulty: U256) -> EnvWithHandlerCfg { + let mut cfg = CfgEnvWithHandlerCfg::new(Default::default(), Default::default()); + let mut block_env = BlockEnv::default(); + self.evm_config.fill_cfg_and_block_env(&mut cfg, &mut block_env, header, total_difficulty); + + EnvWithHandlerCfg::new_with_cfg_env(cfg, block_env, Default::default()) + } +} + +impl BlockExecutionStrategy for ScrollExecutionStrategy +where + DB: Database + Display>, + State: FinalizeExecution, + EvmConfig: ConfigureEvm
, +{ + type Error = BlockExecutionError; + + fn apply_pre_execution_changes( + &mut self, + block: &BlockWithSenders, + _total_difficulty: U256, + ) -> Result<(), Self::Error> { + if self.chain_spec.fork(ScrollHardfork::Curie).transitions_at_block(block.number) { + if let Err(err) = apply_curie_hard_fork(&mut self.state) { + tracing::debug!(%err, "failed to apply curie hardfork"); + return Err(ForkError::Curie(err.to_string()).into()); + }; + } + + Ok(()) + } + + fn execute_transactions( + &mut self, + block: &BlockWithSenders, + total_difficulty: U256, + ) -> Result { + let env = self.evm_env_for_block(&block.header, total_difficulty); + let mut evm = self.evm_config.evm_with_env(&mut self.state, env); + + let mut cumulative_gas_used = 0; + let mut receipts = Vec::with_capacity(block.body.transactions.len()); + + for (sender, transaction) in block.transactions_with_sender() { + // The sum of the transaction’s gas limit and the gas utilized in this block prior, + // must be no greater than the block’s gasLimit. + let block_available_gas = block.header.gas_limit - cumulative_gas_used; + if transaction.gas_limit() > block_available_gas { + return Err(BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas { + transaction_gas_limit: transaction.gas_limit(), + block_available_gas, + } + .into()) + } + + // verify the transaction type is accepted by the current fork. + match transaction.tx_type() { + TxType::Eip2930 if !self.chain_spec.is_curie_active_at_block(block.number) => { + return Err(ConsensusError::InvalidTransaction( + InvalidTransactionError::Eip2930Disabled, + ) + .into()) + } + TxType::Eip1559 if !self.chain_spec.is_curie_active_at_block(block.number) => { + return Err(ConsensusError::InvalidTransaction( + InvalidTransactionError::Eip1559Disabled, + ) + .into()) + } + TxType::Eip4844 => { + return Err(ConsensusError::InvalidTransaction( + InvalidTransactionError::Eip4844Disabled, + ) + .into()) + } + TxType::Eip7702 => { + return Err(ConsensusError::InvalidTransaction( + InvalidTransactionError::Eip7702Disabled, + ) + .into()) + } + _ => (), + }; + + self.evm_config.fill_tx_env(evm.tx_mut(), transaction, *sender); + if transaction.is_l1_message() { + evm.context.evm.env.cfg.disable_base_fee = true; // disable base fee for l1 msg + } else { + // RLP encode the transaction following eip 2718 + let mut buf = BytesMut::with_capacity(transaction.encode_2718_len()); + transaction.encode_2718(&mut buf); + let transaction_rlp_bytes = buf.freeze(); + evm.context.evm.env.tx.scroll.rlp_bytes = Some(transaction_rlp_bytes.into()); + } + evm.context.evm.env.tx.scroll.is_l1_msg = transaction.is_l1_message(); + + // execute the transaction and commit the result to the database + let ResultAndState { result, state } = + evm.transact().map_err(|err| BlockValidationError::EVM { + hash: transaction.recalculate_hash(), + error: Box::new(err.map_db_err(|e| e.into())), + })?; + evm.db_mut().commit(state); + + let l1_fee = if transaction.is_l1_message() { + U256::ZERO + } else { + // compute l1 fee for all non-l1 transaction + let l1_block_info = + evm.context.evm.inner.l1_block_info.as_ref().expect("l1_block_info loaded"); + let transaction_rlp_bytes = + evm.context.evm.env.tx.scroll.rlp_bytes.as_ref().expect("rlp_bytes loaded"); + l1_block_info.calculate_tx_l1_cost(transaction_rlp_bytes, evm.handler.cfg.spec_id) + }; + + cumulative_gas_used += result.gas_used(); + + receipts.push(Receipt { + tx_type: transaction.tx_type(), + success: result.is_success(), + cumulative_gas_used, + logs: result.into_logs(), + l1_fee, + }) + } + + Ok(ExecuteOutput { receipts, gas_used: cumulative_gas_used }) + } + + fn apply_post_execution_changes( + &mut self, + _block: &BlockWithSenders, + _total_difficulty: U256, + _receipts: &[Receipt], + ) -> Result { + Ok(Default::default()) + } + + fn state_ref(&self) -> &State { + &self.state + } + + fn state_mut(&mut self) -> &mut State { + &mut self.state + } + + fn validate_block_post_execution( + &self, + block: &BlockWithSenders, + receipts: &[Receipt], + _requests: &Requests, + ) -> Result<(), ConsensusError> { + // verify the block gas used + let cumulative_gas_used = receipts.last().map(|r| r.cumulative_gas_used).unwrap_or(0); + if block.gas_used != cumulative_gas_used { + return Err(ConsensusError::BlockGasUsed { + gas: GotExpected { got: cumulative_gas_used, expected: block.gas_used }, + gas_spent_by_tx: gas_spent_by_transactions(receipts), + }); + } + + // verify the receipts logs bloom and root + if self.chain_spec.is_byzantium_active_at_block(block.header.number) { + if let Err(error) = reth_ethereum_consensus::verify_receipts( + block.header.receipts_root, + block.header.logs_bloom, + receipts, + ) { + tracing::debug!( + %error, + ?receipts, + header_receipt_root = ?block.header.receipts_root, + header_bloom = ?block.header.logs_bloom, + "failed to verify receipts" + ); + return Err(error); + } + } + + Ok(()) + } +} + +/// The factory for a [`ScrollExecutionStrategy`]. +#[derive(Clone, Debug)] +pub struct ScrollExecutionStrategyFactory { + /// The chain specification for the [`ScrollExecutionStrategy`]. + chain_spec: Arc, + /// The Evm configuration for the [`ScrollExecutionStrategy`]. + evm_config: EvmConfig, +} + +impl ScrollExecutionStrategyFactory { + /// Returns a new instance of the [`ScrollExecutionStrategyFactory`]. + pub fn new(chain_spec: Arc, scroll_config: ScrollChainConfig) -> Self { + let evm_config = ScrollEvmConfig::new(chain_spec.clone(), scroll_config); + Self { chain_spec, evm_config } + } +} + +impl BlockExecutionStrategyFactory for ScrollExecutionStrategyFactory +where + EvmConfig: ConfigureEvm
, +{ + /// Associated strategy type. + type Strategy + Display>> + = ScrollExecutionStrategy + where + State: FinalizeExecution; + + /// Creates a strategy using the give database. + fn create_strategy(&self, db: DB) -> Self::Strategy + where + DB: Database + Display>, + State: FinalizeExecution, + { + let state = + State::builder().with_database(db).without_state_clear().with_bundle_update().build(); + ScrollExecutionStrategy::new(self.chain_spec.clone(), self.evm_config.clone(), state) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ScrollEvmConfig, ScrollExecutionStrategy, ScrollExecutionStrategyFactory}; + use reth_chainspec::MIN_TRANSACTION_GAS; + use reth_evm::execute::ExecuteOutput; + use reth_primitives::{Block, BlockBody, BlockWithSenders, Receipt, TransactionSigned, TxType}; + use reth_primitives_traits::transaction::signed::SignedTransaction; + use reth_scroll_chainspec::ScrollChainSpecBuilder; + use reth_scroll_consensus::{ + BLOB_SCALAR_SLOT, COMMIT_SCALAR_SLOT, CURIE_L1_GAS_PRICE_ORACLE_BYTECODE, + CURIE_L1_GAS_PRICE_ORACLE_STORAGE, IS_CURIE_SLOT, L1_BASE_FEE_SLOT, L1_BLOB_BASE_FEE_SLOT, + L1_GAS_PRICE_ORACLE_ADDRESS, OVER_HEAD_SLOT, SCALAR_SLOT, + }; + use revm::{ + db::states::{bundle_state::BundleRetention, StorageSlot}, + primitives::{Address, B256, U256}, + shared::AccountInfo, + Bytecode, EmptyDBTyped, TxKind, + }; + + const BLOCK_GAS_LIMIT: u64 = 10_000_000; + const SCROLL_CHAIN_ID: u64 = 534352; + const NOT_CURIE_BLOCK_NUMBER: u64 = 7096835; + const CURIE_BLOCK_NUMBER: u64 = 7096837; + + fn strategy() -> ScrollExecutionStrategy, ScrollEvmConfig> { + let chain_spec = Arc::new(ScrollChainSpecBuilder::scroll_mainnet().build()); + let config = ScrollChainConfig::mainnet(); + let factory = ScrollExecutionStrategyFactory::new(chain_spec, config); + let db = EmptyDBTyped::::new(); + + factory.create_strategy(db) + } + + fn block(number: u64, transactions: Vec) -> BlockWithSenders { + let senders = transactions.iter().map(|t| t.recover_signer().unwrap()).collect(); + BlockWithSenders { + block: Block { + header: Header { number, gas_limit: BLOCK_GAS_LIMIT, ..Default::default() }, + body: BlockBody { transactions, ..Default::default() }, + }, + senders, + } + } + + fn transaction(typ: TxType, gas_limit: u64) -> TransactionSigned { + let mut transaction = match typ { + TxType::Legacy => reth_primitives::Transaction::Legacy(alloy_consensus::TxLegacy { + to: TxKind::Call(Address::ZERO), + ..Default::default() + }), + TxType::Eip2930 => reth_primitives::Transaction::Eip2930(alloy_consensus::TxEip2930 { + to: TxKind::Call(Address::ZERO), + ..Default::default() + }), + TxType::Eip1559 => reth_primitives::Transaction::Eip1559(alloy_consensus::TxEip1559 { + to: TxKind::Call(Address::ZERO), + ..Default::default() + }), + TxType::Eip4844 => reth_primitives::Transaction::Eip4844(alloy_consensus::TxEip4844 { + to: Address::ZERO, + ..Default::default() + }), + TxType::Eip7702 => reth_primitives::Transaction::Eip7702(alloy_consensus::TxEip7702 { + to: Address::ZERO, + ..Default::default() + }), + TxType::L1Message => { + reth_primitives::Transaction::L1Message(reth_scroll_primitives::TxL1Message { + sender: Address::random(), + to: Address::ZERO, + ..Default::default() + }) + } + }; + transaction.set_chain_id(SCROLL_CHAIN_ID); + transaction.set_gas_limit(gas_limit); + + let pk = B256::random(); + let signature = reth_primitives::sign_message(pk, transaction.signature_hash()).unwrap(); + TransactionSigned::new_unhashed(transaction, signature) + } + + fn execute_transactions( + tx_type: TxType, + block_number: u64, + expected_l1_fee: U256, + expected_error: Option<&str>, + ) -> eyre::Result<()> { + // init strategy + let mut strategy = strategy(); + + // prepare transaction + let transaction = transaction(tx_type, MIN_TRANSACTION_GAS); + let block = block(block_number, vec![transaction]); + + // determine l1 gas oracle storage + let l1_gas_oracle_storage = if strategy.chain_spec.is_curie_active_at_block(block_number) { + vec![ + (L1_BASE_FEE_SLOT, U256::from(1000)), + (OVER_HEAD_SLOT, U256::from(1000)), + (SCALAR_SLOT, U256::from(1000)), + (L1_BLOB_BASE_FEE_SLOT, U256::from(10000)), + (COMMIT_SCALAR_SLOT, U256::from(1000)), + (BLOB_SCALAR_SLOT, U256::from(10000)), + (IS_CURIE_SLOT, U256::from(1)), + ] + } else { + vec![ + (L1_BASE_FEE_SLOT, U256::from(1000)), + (OVER_HEAD_SLOT, U256::from(1000)), + (SCALAR_SLOT, U256::from(1000)), + ] + } + .into_iter() + .collect(); + + // load accounts in state + strategy.state.insert_account_with_storage( + L1_GAS_PRICE_ORACLE_ADDRESS, + Default::default(), + l1_gas_oracle_storage, + ); + for add in &block.senders { + strategy + .state + .insert_account(*add, AccountInfo { balance: U256::MAX, ..Default::default() }); + } + + // execute and verify output + let res = strategy.execute_transactions(&block, U256::ZERO); + + // check for error or execution outcome + if let Some(error) = expected_error { + assert_eq!(res.unwrap_err().to_string(), error); + } else { + let ExecuteOutput { receipts, .. } = res?; + let expected = vec![Receipt { + tx_type, + cumulative_gas_used: MIN_TRANSACTION_GAS, + success: true, + l1_fee: expected_l1_fee, + ..Default::default() + }]; + + assert_eq!(receipts, expected); + } + + Ok(()) + } + + #[test] + fn test_apply_pre_execution_changes_curie_block() -> eyre::Result<()> { + // init strategy + let mut strategy = strategy(); + + // init curie transition block + let curie_block = block(7096836, vec![]); + + // apply pre execution change + strategy.apply_pre_execution_changes(&curie_block, U256::ZERO)?; + + // take bundle + let mut state = strategy.state; + state.merge_transitions(BundleRetention::Reverts); + let bundle = state.take_bundle(); + + // assert oracle contract contains updated bytecode + let oracle = bundle.state.get(&L1_GAS_PRICE_ORACLE_ADDRESS).unwrap().clone(); + let bytecode = Bytecode::new_raw(CURIE_L1_GAS_PRICE_ORACLE_BYTECODE); + assert_eq!(oracle.info.unwrap().code.unwrap(), bytecode); + + // check oracle contract contains storage changeset + let mut storage = oracle.storage.into_iter().collect::>(); + storage.sort_by(|(a, _), (b, _)| a.cmp(b)); + for (got, expected) in storage.into_iter().zip(CURIE_L1_GAS_PRICE_ORACLE_STORAGE) { + assert_eq!(got.0, expected.0); + assert_eq!(got.1, StorageSlot { present_value: expected.1, ..Default::default() }); + } + + Ok(()) + } + + #[test] + fn test_apply_pre_execution_changes_not_curie_block() -> eyre::Result<()> { + // init strategy + let mut strategy = strategy(); + + // init block + let not_curie_block = block(7096837, vec![]); + + // apply pre execution change + strategy.apply_pre_execution_changes(¬_curie_block, U256::ZERO)?; + + // take bundle + let mut state = strategy.state; + state.merge_transitions(BundleRetention::Reverts); + let bundle = state.take_bundle(); + + // assert oracle contract is empty + let oracle = bundle.state.get(&L1_GAS_PRICE_ORACLE_ADDRESS); + assert!(oracle.is_none()); + + Ok(()) + } + + #[test] + fn test_execute_transactions_exceeds_block_gas_limit() -> eyre::Result<()> { + // init strategy + let mut strategy = strategy(); + + // prepare transaction exceeding block gas limit + let transaction = transaction(TxType::Legacy, BLOCK_GAS_LIMIT + 1); + let block = block(7096837, vec![transaction]); + + // execute and verify error + let res = strategy.execute_transactions(&block, U256::ZERO); + assert_eq!( + res.unwrap_err().to_string(), + "transaction gas limit 10000001 is more than blocks available gas 10000000" + ); + + Ok(()) + } + + #[test] + fn test_execute_transactions_eip4844() -> eyre::Result<()> { + // Execute eip4844 transaction + execute_transactions( + TxType::Eip4844, + CURIE_BLOCK_NUMBER, + U256::ZERO, + Some("EIP-4844 transactions are disabled"), + )?; + Ok(()) + } + + #[test] + fn test_execute_transactions_eip7702() -> eyre::Result<()> { + // Execute eip7702 transaction + execute_transactions( + TxType::Eip7702, + CURIE_BLOCK_NUMBER, + U256::ZERO, + Some("EIP-7702 transactions are disabled"), + )?; + Ok(()) + } + + #[test] + fn test_execute_transactions_l1_message() -> eyre::Result<()> { + // Execute l1 message on curie block + let expected_l1_fee = U256::ZERO; + execute_transactions(TxType::L1Message, CURIE_BLOCK_NUMBER, expected_l1_fee, None)?; + Ok(()) + } + + #[test] + fn test_execute_transactions_legacy_curie_fork() -> eyre::Result<()> { + // Execute legacy transaction on curie block + let expected_l1_fee = U256::from(10); + execute_transactions(TxType::Legacy, CURIE_BLOCK_NUMBER, expected_l1_fee, None)?; + Ok(()) + } + + #[test] + fn test_execute_transactions_legacy_not_curie_fork() -> eyre::Result<()> { + // Execute legacy before curie block + let expected_l1_fee = U256::from(2); + execute_transactions(TxType::Legacy, NOT_CURIE_BLOCK_NUMBER, expected_l1_fee, None)?; + Ok(()) + } + + #[test] + fn test_execute_transactions_eip2930_curie_fork() -> eyre::Result<()> { + // Execute eip2930 transaction on curie block + let expected_l1_fee = U256::from(10); + execute_transactions(TxType::Eip2930, CURIE_BLOCK_NUMBER, expected_l1_fee, None)?; + Ok(()) + } + + #[test] + fn test_execute_transactions_eip2930_not_curie_fork() -> eyre::Result<()> { + // Execute eip2930 transaction before curie block + execute_transactions( + TxType::Eip2930, + NOT_CURIE_BLOCK_NUMBER, + U256::ZERO, + Some("EIP-2930 transactions are disabled"), + )?; + Ok(()) + } + + #[test] + fn test_execute_transactions_eip1559_curie_fork() -> eyre::Result<()> { + // Execute eip1559 transaction on curie block + let expected_l1_fee = U256::from(10); + execute_transactions(TxType::Eip1559, CURIE_BLOCK_NUMBER, expected_l1_fee, None)?; + Ok(()) + } + + #[test] + fn test_execute_transactions_eip_not_curie_fork() -> eyre::Result<()> { + // Execute eip1559 transaction before curie block + execute_transactions( + TxType::Eip1559, + NOT_CURIE_BLOCK_NUMBER, + U256::ZERO, + Some("EIP-1559 transactions are disabled"), + )?; + Ok(()) + } +} diff --git a/crates/scroll/evm/src/lib.rs b/crates/scroll/evm/src/lib.rs new file mode 100644 index 000000000000..680a4f5b9fef --- /dev/null +++ b/crates/scroll/evm/src/lib.rs @@ -0,0 +1,11 @@ +//! Scroll evm execution implementation. +#![cfg(all(feature = "scroll", not(feature = "optimism")))] + +pub use config::ScrollEvmConfig; +mod config; + +pub use error::{ForkError, ScrollBlockExecutionError}; +mod error; + +pub use execute::{ScrollExecutionStrategy, ScrollExecutionStrategyFactory}; +mod execute; diff --git a/crates/scroll/execution/Cargo.toml b/crates/scroll/execution/Cargo.toml index 7a6af2b39798..ad6a369a9c05 100644 --- a/crates/scroll/execution/Cargo.toml +++ b/crates/scroll/execution/Cargo.toml @@ -14,6 +14,8 @@ workspace = true [dependencies] # reth reth-revm.workspace = true + +# scroll reth-scroll-storage = { workspace = true, optional = true } [features] diff --git a/crates/scroll/execution/src/context.rs b/crates/scroll/execution/src/finalize.rs similarity index 83% rename from crates/scroll/execution/src/context.rs rename to crates/scroll/execution/src/finalize.rs index 020aa0a7e318..940c19d06c3c 100644 --- a/crates/scroll/execution/src/context.rs +++ b/crates/scroll/execution/src/finalize.rs @@ -1,10 +1,12 @@ #![allow(clippy::useless_conversion)] +use reth_revm::{database::EvmStateProvider, revm::State}; + +#[cfg(feature = "test-utils")] +use reth_revm::EmptyDBTyped; #[cfg(any(not(feature = "scroll"), feature = "test-utils"))] -use reth_revm::{cached::CachedReadsDbMut, revm::CacheDB, DatabaseRef}; use reth_revm::{ - database::{EvmStateProvider, StateProviderDatabase}, - revm::State, + cached::CachedReadsDbMut, database::StateProviderDatabase, revm::CacheDB, DatabaseRef, }; #[cfg(feature = "scroll")] use reth_scroll_storage::ScrollStateProviderDatabase; @@ -73,3 +75,12 @@ impl FinalizeExecution for State> { self.take_bundle().into() } } + +#[cfg(feature = "test-utils")] +impl FinalizeExecution for State> { + type Output = reth_revm::db::BundleState; + + fn finalize(&mut self) -> Self::Output { + self.take_bundle().into() + } +} diff --git a/crates/scroll/execution/src/lib.rs b/crates/scroll/execution/src/lib.rs index 150a0e9700da..5328229afe0f 100644 --- a/crates/scroll/execution/src/lib.rs +++ b/crates/scroll/execution/src/lib.rs @@ -2,5 +2,5 @@ #![warn(unused_crate_dependencies)] -pub use context::FinalizeExecution; -mod context; +pub use finalize::FinalizeExecution; +mod finalize; diff --git a/crates/scroll/revm/Cargo.toml b/crates/scroll/revm/Cargo.toml index 7f1d25b9e9a2..8c8671b42e21 100644 --- a/crates/scroll/revm/Cargo.toml +++ b/crates/scroll/revm/Cargo.toml @@ -36,7 +36,7 @@ serde = [ "serde/std", "reth-scroll-primitives/serde" ] -scroll = [] +scroll = ["revm/scroll-poseidon-codehash"] test-utils = ["revm/test-utils"] std = [ "revm/std", diff --git a/crates/scroll/revm/src/lib.rs b/crates/scroll/revm/src/lib.rs index 750d8fdd1dec..0e918bb35109 100644 --- a/crates/scroll/revm/src/lib.rs +++ b/crates/scroll/revm/src/lib.rs @@ -7,7 +7,7 @@ pub mod states; #[cfg(feature = "test-utils")] mod test_utils; -#[cfg(feature = "optimism")] +#[cfg(all(feature = "optimism", not(feature = "scroll")))] pub use revm::{primitives::OptimismFields, L1BlockInfo, L1_BLOCK_CONTRACT}; pub use revm::{ diff --git a/crates/scroll/revm/src/test_utils.rs b/crates/scroll/revm/src/test_utils.rs index b06844699178..023454e66ff4 100644 --- a/crates/scroll/revm/src/test_utils.rs +++ b/crates/scroll/revm/src/test_utils.rs @@ -92,6 +92,10 @@ impl From for AccountInfo { nonce: info.nonce, code_hash: info.code_hash, code: info.code, + #[cfg(feature = "scroll")] + code_size: info.code_size as usize, + #[cfg(feature = "scroll")] + poseidon_code_hash: info.poseidon_code_hash, } } } diff --git a/crates/scroll/storage/Cargo.toml b/crates/scroll/storage/Cargo.toml index 9c5cc3744d55..aad2207902bc 100644 --- a/crates/scroll/storage/Cargo.toml +++ b/crates/scroll/storage/Cargo.toml @@ -33,4 +33,5 @@ reth-revm = { workspace = true, features = ["test-utils"] } scroll = [ "reth-primitives-traits/scroll", "reth-scroll-revm/scroll", + "reth-revm/scroll", ] diff --git a/crates/trie/trie/src/state.rs b/crates/trie/trie/src/state.rs index d0891e783597..4e20ba40d598 100644 --- a/crates/trie/trie/src/state.rs +++ b/crates/trie/trie/src/state.rs @@ -382,6 +382,7 @@ impl HashedStorageSorted { #[cfg(test)] mod tests { + #![allow(clippy::needless_update)] use super::*; use alloy_primitives::{keccak256, Address, Bytes}; use reth_trie_common::KeccakKeyHasher; @@ -527,6 +528,7 @@ mod tests { nonce: 5, code_hash: B256::random(), code: None, + ..Default::default() }; let mut storage = PlainStorage::default(); diff --git a/deny.toml b/deny.toml index 498ac05beaf0..71715c505f30 100644 --- a/deny.toml +++ b/deny.toml @@ -102,5 +102,9 @@ allow-git = [ "https://github.com/scroll-tech/sp1-intrinsics", "https://github.com/scroll-tech/poseidon-bn254", "https://github.com/scroll-tech/zktrie.git", - "https://github.com/scroll-tech/gobuild.git" + "https://github.com/scroll-tech/gobuild.git", + "https://github.com/scroll-tech/alloy-eips", + "https://github.com/scroll-tech/ff", + "https://github.com/scroll-tech/revm.git", + "https://github.com/scroll-tech/alloy-core.git" ]