diff --git a/.github/actions/build-docker/action.yml b/.github/actions/build-docker/action.yml index 7fd0c0a0187..7fd53835a6e 100644 --- a/.github/actions/build-docker/action.yml +++ b/.github/actions/build-docker/action.yml @@ -19,6 +19,14 @@ inputs: description: "Whether to push the built image to the registry" required: false default: "false" + artifact_path: + description: "The name of the artifact that is going to be pushed" + required: false + default: "ethrex_image.tar" + build_args: + description: "The arguments that are sent to the dockerfile to built. Format ARG=value" + required: false + default: "" outputs: artifact_path: @@ -31,7 +39,7 @@ runs: - id: vars shell: bash run: | - echo "artifact_path=/tmp/ethrex_image.tar" >> $GITHUB_OUTPUT + echo "artifact_path=/tmp/${{ inputs.artifact_path }}" >> $GITHUB_OUTPUT - name: Login to Docker registry if: inputs.username != '' && inputs.password != '' @@ -54,6 +62,7 @@ runs: outputs: ${{ inputs.push == 'false' && format('type=docker,dest={0}', steps.vars.outputs.artifact_path) || '' }} cache-from: type=gha cache-to: type=gha,mode=max + build-args: ${{ inputs.build_args }} # Since we're exporting the image as a tar, we need to load it manually as well - name: Load image locally diff --git a/.github/workflows/main_docker_publish.yaml b/.github/workflows/main_docker_publish.yaml index a58a095adf4..aaa54d2f907 100644 --- a/.github/workflows/main_docker_publish.yaml +++ b/.github/workflows/main_docker_publish.yaml @@ -25,9 +25,9 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - # Pushes to ghcr.io/lambdaclass/ethrex - - name: Build and push Docker image - id: push + # Pushes l1 to ghcr.io/lambdaclass/ethrex + - name: Build and push L1 Docker image + id: push_l1 uses: ./.github/actions/build-docker with: registry: ${{ env.REGISTRY }} @@ -35,3 +35,15 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} push: true tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:main + + # Pushe l2 to ghcr.io/lambdaclass/ethrex + - name: Build and push L2 Docker image + id: push_l2 + uses: ./.github/actions/build-docker + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + push: true + build_args: BUILD_FLAGS=--features l2,l2-sql + tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:main-l2 diff --git a/.github/workflows/main_prover.yaml b/.github/workflows/main_prover.yaml index 86a99062eb3..9094ddb353e 100644 --- a/.github/workflows/main_prover.yaml +++ b/.github/workflows/main_prover.yaml @@ -56,7 +56,7 @@ jobs: - name: Build test # if: ${{ always() && github.event_name == 'merge_group' }} run: | - cargo test l2 --no-run --release + cargo test l2 --features l2 --no-run --release - name: Start L1 & Deploy contracts # if: ${{ always() && github.event_name == 'merge_group' }} diff --git a/.github/workflows/pr-main_l1.yaml b/.github/workflows/pr-main_l1.yaml index 15691741c79..3efea9e2caf 100644 --- a/.github/workflows/pr-main_l1.yaml +++ b/.github/workflows/pr-main_l1.yaml @@ -31,12 +31,13 @@ jobs: with: components: rustfmt, clippy + # We don't run with workspace, as members of the workspace require l2. We run it with workspace + # om the l2 lint - name: Run cargo check - run: cargo check --workspace + run: cargo check - name: Run cargo clippy run: | - cargo clippy --workspace -- -D warnings cargo clippy -- -D warnings - name: Run cargo fmt diff --git a/.github/workflows/pr-main_l2.yaml b/.github/workflows/pr-main_l2.yaml index f10e23d5ba4..8e7a9c5567c 100644 --- a/.github/workflows/pr-main_l2.yaml +++ b/.github/workflows/pr-main_l2.yaml @@ -52,11 +52,11 @@ jobs: touch crates/l2/prover/src/guest_program/src/sp1/out/riscv32im-succinct-zkvm-elf - name: Run cargo check - run: cargo check --workspace + run: cargo check --workspace --features l2,l2-sql - name: Run cargo clippy run: | - cargo clippy --workspace -- -D warnings + cargo clippy --workspace --features l2,l2-sql -- -D warnings make lint - name: Run cargo fmt @@ -86,10 +86,37 @@ jobs: name: ethrex_image path: /tmp/ethrex_image.tar + # We build the docker image for the l2 usage. It needs to add + # The build args for l2. + build-docker-l2: + name: Build docker image L2 + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build L2 docker image + uses: ./.github/actions/build-docker + with: + username: ${{ vars.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + tags: ethrex:main-l2 + artifact_path: ethrex_image_l2.tar + build_args: BUILD_FLAGS=--features l2 + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: ethrex_image_l2 + path: /tmp/ethrex_image_l2.tar + integration-test: name: Integration Test - ${{ matrix.name }} runs-on: ubuntu-latest - needs: build-docker + needs: [build-docker, build-docker-l2] strategy: matrix: include: @@ -145,7 +172,7 @@ jobs: - name: Build test run: | - cargo test l2 --no-run --release + cargo test l2 --features l2 --no-run --release - name: Start Web3Signer if: matrix.web3signer @@ -163,6 +190,16 @@ jobs: run: | docker load --input /tmp/ethrex_image.tar + - name: Download ethrex L2 image artifact + uses: actions/download-artifact@v4 + with: + name: ethrex_image_l2 + path: /tmp + + - name: Load ethrex L2 image + run: | + docker load --input /tmp/ethrex_image_l2.tar + - name: Start L1 run: | cd crates/l2 @@ -258,7 +295,7 @@ jobs: integration-test-tdx: name: Integration Test - TDX runs-on: ubuntu-latest - needs: build-docker + needs: [build-docker, build-docker-l2] steps: - name: Free Disk Space (Ubuntu) uses: jlumbroso/free-disk-space@v1.3.1 @@ -288,6 +325,16 @@ jobs: run: | docker load --input /tmp/ethrex_image.tar + - name: Download ethrex L2 image artifact + uses: actions/download-artifact@v4 + with: + name: ethrex_image_l2 + path: /tmp + + - name: Load ethrex L2 image + run: | + docker load --input /tmp/ethrex_image_l2.tar + - name: Set up Nix uses: cachix/install-nix-action@v31 @@ -355,7 +402,7 @@ jobs: state-diff-test: name: State Reconstruction Tests runs-on: ubuntu-latest - needs: build-docker + needs: [build-docker, build-docker-l2] steps: - name: Checkout sources uses: actions/checkout@v4 @@ -372,6 +419,16 @@ jobs: run: | docker load --input /tmp/ethrex_image.tar + - name: Download ethrex L2 image artifact + uses: actions/download-artifact@v4 + with: + name: ethrex_image_l2 + path: /tmp + + - name: Load ethrex L2 image + run: | + docker load --input /tmp/ethrex_image_l2.tar + - name: Install solc uses: lambdaclass/get-solc@master with: diff --git a/.github/workflows/tag_release.yaml b/.github/workflows/tag_release.yaml index 571b0971c5d..5f8604c68fe 100644 --- a/.github/workflows/tag_release.yaml +++ b/.github/workflows/tag_release.yaml @@ -22,29 +22,45 @@ jobs: - ubuntu-22.04 - ubuntu-24.04-arm - macos-latest - gpu: - - true - - false + stack: + - l1 + - l2 + - l2_gpu + exclude: + - platform: macos-latest + stack: l2_gpu include: - platform: ubuntu-22.04 os: linux arch: x86_64 - prover_features: sp1,risc0 cpu_flags: RUSTFLAGS='-C target-cpu=x86-64-v2' - platform: ubuntu-24.04-arm os: linux arch: aarch64 - prover_features: sp1 - platform: macos-latest os: macos arch: aarch64 - - gpu: true + - platform: macos-latest + stack: l2 + features: l2,l2-sql + - platform: ubuntu-22.04 + stack: l2 + features: l2,l2-sql,sp1,risc0 + - platform: ubuntu-22.04 + stack: l2_gpu + features: l2,l2-sql,sp1,risc0,gpu + - platform: ubuntu-24.04-arm + stack: l2 + features: l2,l2-sql,sp1 + - platform: ubuntu-24.04-arm + stack: l2_gpu + features: l2,l2-sql,sp1,gpu + - stack: l2_gpu gpu_flags: NVCC_PREPEND_FLAGS='-arch=sm_70' - gpu_feature: ",gpu" + l2_suffix: "-l2" gpu_suffix: "-gpu" - exclude: - - platform: macos-latest - gpu: true + - stack: l2 + l2_suffix: "-l2" runs-on: ${{ matrix.platform }} steps: - name: Free Disk Space (Ubuntu) @@ -57,10 +73,8 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Rustup toolchain install - uses: dtolnay/rust-toolchain@master - with: - toolchain: ${{ vars.RUST_VERSION }} + - name: Setup Rust Environment + uses: ./.github/actions/setup-rust - name: Install SP1 (only Linux) if: ${{ matrix.os == 'linux' }} @@ -89,7 +103,7 @@ jobs: - name: Install CUDA (only Linux x86 GPU) uses: Jimver/cuda-toolkit@v0.2.24 - if: ${{ matrix.platform == 'ubuntu-22.04' && matrix.gpu }} + if: ${{ matrix.platform == 'ubuntu-22.04' && matrix.stack == 'l2_gpu' }} id: cuda-toolkit with: cuda: "12.9.0" @@ -116,11 +130,12 @@ jobs: - name: Build ethrex run: | - COMPILE_CONTRACTS=true ${{ matrix.cpu_flags }} ${{ matrix.gpu_flags }} cargo build --release --features "${{ matrix.prover_features }}${{ matrix.gpu_feature }}" --bin ethrex - mv target/release/ethrex ethrex-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.gpu_suffix }} + COMPILE_CONTRACTS=true ${{ matrix.cpu_flags }} ${{ matrix.gpu_flags }} cargo build --release --features "${{ matrix.features }}" --bin ethrex + chmod +x target/release/ethrex + mv target/release/ethrex ethrex${{ matrix.l2_suffix }}-${{ matrix.os }}-${{ matrix.arch }}${{ matrix.gpu_suffix }} - name: Copy verification keys - if: ${{ matrix.platform == 'ubuntu-22.04' && matrix.gpu }} # Run only once + if: ${{ matrix.platform == 'ubuntu-22.04' && matrix.stack == 'l2_gpu' }} # Run only once run: | mkdir -p ./verification_keys mv crates/l2/prover/src/guest_program/src/risc0/out/riscv32im-risc0-vk verification_keys/ethrex-riscv32im-risc0-vk @@ -129,11 +144,11 @@ jobs: - name: Upload artifact uses: actions/upload-artifact@v4 with: - name: ethrex-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.gpu_suffix }} - path: ethrex-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.gpu_suffix }} + name: ethrex${{ matrix.l2_suffix }}-${{ matrix.os }}-${{ matrix.arch }}${{ matrix.gpu_suffix }} + path: ethrex${{ matrix.l2_suffix }}-${{ matrix.os }}-${{ matrix.arch }}${{ matrix.gpu_suffix }} - name: Upload verification keys - if: ${{ matrix.platform == 'ubuntu-22.04' && matrix.gpu }} # Run only once + if: ${{ matrix.platform == 'ubuntu-22.04' && matrix.stack == 'l2_gpu' }} # Run only once uses: actions/upload-artifact@v4 with: name: verification_keys @@ -183,8 +198,8 @@ jobs: run: echo "TAG_VERSION=$(echo ${{ github.ref_name }} | tr -d v)" >> $GITHUB_ENV # Pushes to ghcr.io/lambdaclass/ethrex - - name: Build and push Docker image - id: push + - name: Build and push L1 Docker image + id: push_l1 uses: ./.github/actions/build-docker with: registry: ${{ env.REGISTRY }} @@ -193,7 +208,19 @@ jobs: push: true tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest,${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.TAG_VERSION }} - # Creates a draft release on GitHub with the binaries + # Pushes to ghcr.io/lambdaclass/ethrex + - name: Build and push L2 Docker image + id: push_l2 + uses: ./.github/actions/build-docker + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + push: true + tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest-l2,${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.TAG_VERSION }}-l2 + build_args: BUILD_FLAGS=--features l2,l2-sql + + # Creates a release on GitHub with the binaries finalize-release: needs: - build-ethrex @@ -218,6 +245,9 @@ jobs: echo "PREVIOUS_TAG: $name" echo "PREVIOUS_TAG=$name" >> $GITHUB_ENV + - name: Check release type + run: echo "TAG_SUFFIX=$(echo ${{ github.ref_name }}- | cut -d- -f2)" >> $GITHUB_ENV + - name: Update CHANGELOG id: changelog uses: requarks/changelog-action@v1 @@ -233,8 +263,8 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: files: ./bin/**/* - draft: false - prerelease: false + draft: ${{ startsWith(env.TAG_SUFFIX, 'rc') }} + prerelease: ${{ startsWith(env.TAG_SUFFIX, 'rc') }} tag_name: ${{ github.ref_name }} name: "ethrex: ${{ github.ref_name }}" body: > diff --git a/Makefile b/Makefile index ac0202f1e72..58c5dc04d02 100644 --- a/Makefile +++ b/Makefile @@ -8,16 +8,25 @@ help: ## ๐Ÿ“š Show help for each of the Makefile recipes build: ## ๐Ÿ”จ Build the client cargo build --workspace -lint: ## ๐Ÿงน Linter check - # Note that we are compiling without the "gpu" feature (see #4048 for why) - # To compile with it you can replace '-F' with '--all-features', but you need to have nvcc installed - cargo clippy --all-targets -F debug,risc0,sp1,sync-test \ +lint-l1: + cargo clippy --lib --bins -F debug,sync-test \ + --release -- -D warnings + +lint-l2: + cargo clippy --all-targets -F debug,sync-test,l2,l2-sql \ + --workspace --exclude ethrex-prover --exclude guest_program --exclude ef_tests-blockchain \ + --release -- -D warnings + +lint-gpu: + cargo clippy --all-targets -F debug,sync-test,l2,l2-sql,,sp1,risc0,gpu \ --workspace --exclude ethrex-prover --exclude guest_program --exclude ef_tests-blockchain \ --release -- -D warnings +lint: lint-l1 lint-l2 ## ๐Ÿงน Linter check + CRATE ?= * test: ## ๐Ÿงช Run each crate's tests - cargo test -p '$(CRATE)' --workspace --exclude ethrex-levm --exclude ef_tests-blockchain --exclude ethrex-l2 -- --skip test_contract_compilation + cargo test -p '$(CRATE)' --workspace --exclude ethrex-levm --exclude ef_tests-blockchain --exclude ethrex-l2 -F l2 -- --skip test_contract_compilation clean: clean-vectors ## ๐Ÿงน Remove build artifacts cargo clean diff --git a/cmd/ethrex/Cargo.toml b/cmd/ethrex/Cargo.toml index ae946ed1ec1..af671945306 100644 --- a/cmd/ethrex/Cargo.toml +++ b/cmd/ethrex/Cargo.toml @@ -9,20 +9,24 @@ build = "build.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -ethrex-config.workspace = true ethrex-blockchain.workspace = true -ethrex-rpc.workspace = true ethrex-common.workspace = true +ethrex-config.workspace = true ethrex-crypto.workspace = true +ethrex-dev = { path = "../../crates/blockchain/dev", optional = true } +ethrex-l2 = { workspace = true, optional = true } +ethrex-l2-common = { workspace = true, optional = true } +ethrex-l2-rpc = { workspace = true, optional = true } +ethrex-metrics = { path = "../../crates/blockchain/metrics" } ethrex-p2p.workspace = true +ethrex-prover = { workspace = true, optional = true, features = ["l2"] } +ethrex-rlp.workspace = true +ethrex-rpc.workspace = true +ethrex-sdk = { workspace = true, optional = true } ethrex-storage.workspace = true +ethrex-storage-rollup = { workspace = true, optional = true } ethrex-vm.workspace = true -ethrex-rlp.workspace = true -ethrex-storage-rollup.workspace = true -ethrex-l2.workspace = true -ethrex-l2-common.workspace = true -ethrex-sdk.workspace = true -ethrex-l2-rpc.workspace = true + tikv-jemallocator = { version = "0.6.0", optional = true, features = ["stats", "unprefixed_malloc_on_supported_platforms"] } bytes.workspace = true hex.workspace = true @@ -44,12 +48,11 @@ secp256k1.workspace = true reqwest.workspace = true thiserror.workspace = true itertools = "0.14.0" -tui-logger.workspace = true - -ethrex-dev = { path = "../../crates/blockchain/dev", optional = true } -ethrex-metrics = { path = "../../crates/blockchain/metrics" } url.workspace = true -ethrex-prover = { workspace = true, features = ["l2"] } + +# L2 external dependencies + +tui-logger = { workspace = true, optional = true } [target.'cfg(linux)'.dependencies] tikv-jemallocator = { version = "0.6.0", optional = true, features = ["stats", "unprefixed_malloc_on_supported_platforms", "background_threads"] } @@ -64,7 +67,7 @@ path = "./lib.rs" [features] debug = ["ethrex-vm/debug"] -default = ["rocksdb", "c-kzg", "rollup_storage_sql", "dev", "metrics", "jemalloc"] +default = ["rocksdb", "c-kzg", "metrics", "jemalloc", "dev"] dev = ["dep:ethrex-dev"] c-kzg = [ "ethrex-vm/c-kzg", @@ -73,7 +76,7 @@ c-kzg = [ "ethrex-p2p/c-kzg", "ethrex-crypto/c-kzg", ] -metrics = ["ethrex-blockchain/metrics", "ethrex-l2/metrics"] +metrics = ["ethrex-blockchain/metrics", "ethrex-l2?/metrics"] rocksdb = ["ethrex-storage/rocksdb", "ethrex-p2p/rocksdb"] jemalloc = ["dep:tikv-jemallocator"] jemalloc_profiling = [ @@ -82,8 +85,26 @@ jemalloc_profiling = [ # forward to the RPC crate so its handlers and dependency are enabled "ethrex-rpc/jemalloc_profiling", ] -rollup_storage_sql = ["ethrex-storage-rollup/sql"] sync-test = ["ethrex-p2p/sync-test"] + +l2 = [ + "ethrex-l2", + "ethrex-l2-common", + "ethrex-l2-rpc", + "ethrex-sdk", + "ethrex-p2p/l2", + "ethrex-prover", + "ethrex-storage-rollup", + "dep:tui-logger", + "dep:bytes", + "dep:genesis-tool", + "dep:thiserror", + "dep:serde_json", + "dep:hex", +] +l2-sql = [ + "ethrex-storage-rollup/sql", +] sp1 = ["ethrex-prover/sp1"] gpu = ["ethrex-prover/gpu"] risc0 = ["ethrex-prover/risc0"] @@ -94,9 +115,9 @@ criterion = { version = "0.5.1", features = [ "async_futures", "async_tokio", ] } -ethrex-sdk.workspace = true secp256k1.workspace = true tempfile.workspace = true +ethrex-l2-rpc.workspace = true [[bench]] path = "./bench/import_blocks_benchmark.rs" @@ -110,10 +131,10 @@ harness = false [build-dependencies] vergen-git2 = { version = "1.0.7", features = ["rustc"] } -ethrex-sdk.workspace = true -hex.workspace = true -bytes.workspace = true -ethrex-common.workspace = true -serde_json.workspace = true -genesis-tool = { path = "../../tooling/genesis" } -thiserror.workspace = true +ethrex-sdk = { workspace = true, optional = true } +hex = { workspace = true, optional = true } +bytes = { workspace = true, optional = true } +ethrex-common = { workspace = true, optional = true } +serde_json = { workspace = true, optional = true } +genesis-tool = { path = "../../tooling/genesis", optional = true } +thiserror = { workspace = true, optional = true } diff --git a/cmd/ethrex/build.rs b/cmd/ethrex/build.rs index 4b4eb70b8fe..8866d1c4a98 100644 --- a/cmd/ethrex/build.rs +++ b/cmd/ethrex/build.rs @@ -1,12 +1,7 @@ -#![allow(clippy::unwrap_used, clippy::expect_used)] use std::error::Error; -use std::fs::File; -use std::io::BufReader; -use std::{ - env, fs, - path::{Path, PathBuf}, -}; use vergen_git2::{Emitter, Git2Builder, RustcBuilder}; +#[cfg(feature = "l2")] +mod build_l2; // This build code is needed to add some env vars in order to construct the node client version // VERGEN_RUSTC_HOST_TRIPLE to get the build OS // VERGEN_RUSTC_SEMVER to get the rustc version @@ -15,16 +10,6 @@ use vergen_git2::{Emitter, Git2Builder, RustcBuilder}; // This script downloads dependencies and compiles contracts to be embedded as constants in the deployer. -const L2_GENESIS_PATH: &str = "../../fixtures/genesis/l2.json"; - -const DETERMINISTIC_DEPLOYMENT_CODE: [u8; 69] = [ - 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xe0, 0x36, 0x01, 0x60, 0x00, 0x81, 0x60, 0x20, 0x82, 0x37, 0x80, 0x35, 0x82, 0x82, 0x34, 0xf5, - 0x80, 0x15, 0x15, 0x60, 0x39, 0x57, 0x81, 0x82, 0xfd, 0x5b, 0x80, 0x82, 0x52, 0x50, 0x50, 0x50, - 0x60, 0x14, 0x60, 0x0c, 0xf3, -]; - fn main() -> Result<(), Box> { println!("cargo::rerun-if-changed=build.rs"); println!("cargo:rerun-if-env-changed=COMPILE_CONTRACTS"); @@ -44,443 +29,22 @@ fn main() -> Result<(), Box> { .add_instructions(&git2)? .emit()?; - download_script(); - - // If COMPILE_CONTRACTS is not set, skip - if env::var_os("COMPILE_CONTRACTS").is_some() { - let out_dir = env::var_os("OUT_DIR").unwrap(); - update_genesis_file(L2_GENESIS_PATH.as_ref(), Path::new(&out_dir))?; - } - - Ok(()) -} - -fn download_script() { - let out_dir = env::var_os("OUT_DIR").unwrap(); - let output_contracts_path = Path::new(&out_dir).join("contracts"); - println!( - "Compiling contracts to: {}", - output_contracts_path.display() - ); - let contracts_path = Path::new("../../crates/l2/contracts/src"); - - // If COMPILE_CONTRACTS is not set, skip and write empty files - if env::var_os("COMPILE_CONTRACTS").is_none() { - write_empty_bytecode_files(&output_contracts_path); - return; - } - - download_contract_deps(&output_contracts_path); - - // ERC1967Proxy contract. - compile_contract_to_bytecode( - &output_contracts_path, - &output_contracts_path.join("lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol"), - "ERC1967Proxy", - false, - None, - &[&output_contracts_path] - ); - - // SP1VerifierGroth16 contract - compile_contract_to_bytecode( - &output_contracts_path, - &output_contracts_path - .join("lib/sp1-contracts/contracts/src/v5.0.0/SP1VerifierGroth16.sol"), - "SP1Verifier", - false, - None, - &[&output_contracts_path], - ); - - let remappings = [( - "@openzeppelin/contracts", - output_contracts_path.join("lib/openzeppelin-contracts/contracts"), - )]; - - compile_contract_to_bytecode( - &output_contracts_path, - &output_contracts_path.join("lib/create2deployer/contracts/Create2Deployer.sol"), - "Create2Deployer", - true, - Some(&remappings), - &[contracts_path], - ); - - // Get the openzeppelin contracts remappings - let remappings = [ - ( - "@openzeppelin/contracts", - output_contracts_path.join( - "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts", - ), - ), - ( - "@openzeppelin/contracts-upgradeable", - output_contracts_path.join("lib/openzeppelin-contracts-upgradeable/contracts"), - ), - ]; - - // L1 contracts - let l1_contracts = [ - ( - &Path::new("../../crates/l2/contracts/src/l1/OnChainProposer.sol"), - "OnChainProposer", - ), - ( - &Path::new("../../crates/l2/contracts/src/l1/CommonBridge.sol"), - "CommonBridge", - ), - ]; - for (path, name) in l1_contracts { - compile_contract_to_bytecode( - &output_contracts_path, - path, - name, - false, - Some(&remappings), - &[contracts_path], - ); - } - // L2 contracts - let l2_contracts = [ - ( - &Path::new("../../crates/l2/contracts/src/l2/CommonBridgeL2.sol"), - "CommonBridgeL2", - ), - ( - &Path::new("../../crates/l2/contracts/src/l2/L2ToL1Messenger.sol"), - "L2ToL1Messenger", - ), - ( - &Path::new("../../crates/l2/contracts/src/l2/L2Upgradeable.sol"), - "UpgradeableSystemContract", - ), - ]; - for (path, name) in l2_contracts { - compile_contract_to_bytecode( - &output_contracts_path, - path, - name, - true, - Some(&remappings), - &[contracts_path], - ); - } - - // Based contracts - compile_contract_to_bytecode( - &output_contracts_path, - Path::new("../../crates/l2/contracts/src/l1/based/SequencerRegistry.sol"), - "SequencerRegistry", - false, - Some(&remappings), - &[contracts_path], - ); - ethrex_l2_sdk::compile_contract( - &output_contracts_path, - Path::new("../../crates/l2/contracts/src/l1/based/OnChainProposer.sol"), - false, - Some(&remappings), - &[contracts_path], - ) - .unwrap(); - - // To avoid colision with the original OnChainProposer bytecode, we rename it to OnChainProposerBased - let file_path = output_contracts_path.join("solc_out/OnChainProposer.bin"); - let output_file_path = output_contracts_path.join("solc_out/OnChainProposerBased.bytecode"); - decode_to_bytecode(&file_path, &output_file_path); -} - -fn write_empty_bytecode_files(output_contracts_path: &Path) { - let bytecode_dir = output_contracts_path.join("solc_out"); - fs::create_dir_all(&bytecode_dir).expect("Failed to create solc_out directory"); - - let contract_names = [ - "ERC1967Proxy", - "SP1Verifier", - "OnChainProposer", - "CommonBridge", - "CommonBridgeL2", - "L2ToL1Messenger", - "UpgradeableSystemContract", - "SequencerRegistry", - "OnChainProposerBased", - ]; - - for name in &contract_names { - let filename = format!("{name}.bytecode"); - let path = bytecode_dir.join(filename); - fs::write(&path, []).expect("Failed to write empty bytecode."); - } -} - -/// Clones OpenZeppelin, SP1 contracts and create2deployer into the specified path. -fn download_contract_deps(contracts_path: &Path) { - fs::create_dir_all(contracts_path.join("lib")).expect("Failed to create contracts/lib dir"); - - ethrex_l2_sdk::git_clone( - "https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable.git", - &contracts_path - .join("lib/openzeppelin-contracts-upgradeable") - .to_string_lossy(), - Some("release-v5.4"), - true, - ) - .expect("Failed to clone openzeppelin-contracts-upgradeable"); - - // Using version 4.9 for create2deployer - ethrex_l2_sdk::git_clone( - "https://github.com/OpenZeppelin/openzeppelin-contracts.git", - &contracts_path - .join("lib/openzeppelin-contracts") - .to_string_lossy(), - Some("release-v4.9"), - true, - ) - .expect("Failed to clone openzeppelin-contracts"); - - ethrex_l2_sdk::git_clone( - "https://github.com/succinctlabs/sp1-contracts.git", - &contracts_path.join("lib/sp1-contracts").to_string_lossy(), - None, - false, - ) - .expect("Failed to clone sp1-contracts"); - - ethrex_l2_sdk::git_clone( - "https://github.com/pcaversaccio/create2deployer", - &contracts_path.join("lib/create2deployer").to_string_lossy(), - None, - true, - ) - .expect("Failed to clone create2deployer"); -} - -fn compile_contract_to_bytecode( - output_dir: &Path, - contract_path: &Path, - contract_name: &str, - runtime_bin: bool, - remappings: Option<&[(&str, PathBuf)]>, - allow_paths: &[&Path], -) { - println!("Compiling {contract_name} contract"); - ethrex_l2_sdk::compile_contract( - output_dir, - contract_path, - runtime_bin, - remappings, - allow_paths, - ) - .expect("Failed to compile contract"); - println!("Successfully compiled {contract_name} contract"); - - // Resolve the resulted file path - let filename = if runtime_bin { - format!("{contract_name}.bin-runtime") - } else { - format!("{contract_name}.bin") - }; - let file_path = output_dir.join("solc_out").join(&filename); - - // Get the output file path - let output_file_path = output_dir - .join("solc_out") - .join(format!("{contract_name}.bytecode")); + #[cfg(feature = "l2")] + { + use build_l2::download_script; + use std::env; + use std::path::Path; - decode_to_bytecode(&file_path, &output_file_path); + use crate::build_l2::{L2_GENESIS_PATH, update_genesis_file}; - println!("Successfully generated {contract_name} bytecode"); -} - -fn decode_to_bytecode(input_file_path: &Path, output_file_path: &Path) { - let bytecode_hex = fs::read_to_string(input_file_path).expect("Failed to read file"); - - let bytecode = hex::decode(bytecode_hex.trim()).expect("Failed to decode bytecode"); - - fs::write(output_file_path, bytecode).expect("Failed to write bytecode"); -} - -use std::collections::HashMap; - -use bytes::Bytes; -use ethrex_common::types::{Genesis, GenesisAccount}; -use ethrex_common::{Address, H160, U256}; -use ethrex_l2_sdk::{ - COMMON_BRIDGE_L2_ADDRESS, CREATE2DEPLOYER_ADDRESS, DETERMINISTIC_DEPLOYMENT_PROXY_ADDRESS, - L2_TO_L1_MESSENGER_ADDRESS, SAFE_SINGLETON_FACTORY_ADDRESS, address_to_word, get_erc1967_slot, -}; -use genesis_tool::genesis::write_genesis_as_json; - -#[allow(clippy::enum_variant_names)] -#[derive(Debug, thiserror::Error)] -pub enum SystemContractsUpdaterError { - #[error("Failed to deploy contract: {0}")] - FailedToDecodeRuntimeCode(#[from] hex::FromHexError), - #[error("Failed to serialize modified genesis: {0}")] - FailedToSerializeModifiedGenesis(#[from] serde_json::Error), - #[error("Failed to write modified genesis file: {0}")] - FailedToWriteModifiedGenesisFile(#[from] std::io::Error), - #[error("Failed to read path: {0}")] - InvalidPath(String), - #[error( - "Contract bytecode not found. Make sure to compile the updater with `COMPILE_CONTRACTS` set." - )] - BytecodeNotFound, -} - -/// Bytecode of the CommonBridgeL2 contract. -fn common_bridge_l2_runtime(out_dir: &Path) -> Vec { - let path = out_dir.join("contracts/solc_out/CommonBridgeL2.bytecode"); - fs::read(path).expect("Failed to read bytecode file") -} - -/// Bytecode of the L2ToL1Messenger contract. -fn l2_to_l1_messenger_runtime(out_dir: &Path) -> Vec { - let path = out_dir.join("contracts/solc_out/L2ToL1Messenger.bytecode"); - fs::read(path).expect("Failed to read bytecode file") -} -/// Bytecode of the Create2Deployer contract. -fn create2deployer_runtime(out_dir: &Path) -> Vec { - let path = out_dir.join("contracts/solc_out/Create2Deployer.bytecode"); - fs::read(path).expect("Failedto read bytecode file") -} - -/// Bytecode of the L2Upgradeable contract. -fn l2_upgradeable_runtime(out_dir: &Path) -> Vec { - let path = out_dir.join("contracts/solc_out/UpgradeableSystemContract.bytecode"); - fs::read(path).expect("Failed to read bytecode file") -} - -/// Address authorized to perform system contract upgrades -/// 0x000000000000000000000000000000000000f000 -pub const ADMIN_ADDRESS: Address = H160([ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0xf0, 0x00, -]); - -/// Mask used to derive the initial implementation address -/// 0x0000000000000000000000000000000000001000 -pub const IMPL_MASK: Address = H160([ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x10, 0x00, -]); + download_script(); -fn add_with_proxy( - genesis: &mut Genesis, - address: Address, - code: Vec, - out_dir: &Path, -) -> Result<(), SystemContractsUpdaterError> { - let impl_address = address ^ IMPL_MASK; - - if code.is_empty() { - return Err(SystemContractsUpdaterError::BytecodeNotFound); + // If COMPILE_CONTRACTS is not set, skip + if env::var_os("COMPILE_CONTRACTS").is_some() { + let out_dir = env::var_os("OUT_DIR").unwrap(); + update_genesis_file(L2_GENESIS_PATH.as_ref(), Path::new(&out_dir))?; + } } - genesis.alloc.insert( - impl_address, - GenesisAccount { - code: Bytes::from(code), - storage: HashMap::new(), - balance: U256::zero(), - nonce: 1, - }, - ); - - let mut storage = HashMap::new(); - storage.insert( - get_erc1967_slot("eip1967.proxy.implementation"), - address_to_word(impl_address), - ); - storage.insert( - get_erc1967_slot("eip1967.proxy.admin"), - address_to_word(ADMIN_ADDRESS), - ); - genesis.alloc.insert( - address, - GenesisAccount { - code: Bytes::from(l2_upgradeable_runtime(out_dir)), - storage, - balance: U256::zero(), - nonce: 1, - }, - ); - Ok(()) -} - -pub fn update_genesis_file( - l2_genesis_path: &Path, - out_dir: &Path, -) -> Result<(), SystemContractsUpdaterError> { - let mut genesis = read_genesis_file(l2_genesis_path.to_str().ok_or( - SystemContractsUpdaterError::InvalidPath( - "Failed to convert l2 genesis path to string".to_string(), - ), - )?); - - add_with_proxy( - &mut genesis, - COMMON_BRIDGE_L2_ADDRESS, - common_bridge_l2_runtime(out_dir), - out_dir, - )?; - - add_with_proxy( - &mut genesis, - L2_TO_L1_MESSENGER_ADDRESS, - l2_to_l1_messenger_runtime(out_dir), - out_dir, - )?; - - add_deterministic_deployers(&mut genesis, out_dir); - - write_genesis_as_json(genesis, Path::new(l2_genesis_path)).map_err(std::io::Error::other)?; - Ok(()) } - -fn add_deterministic_deployers(genesis: &mut Genesis, out_dir: &Path) { - genesis.alloc.insert( - DETERMINISTIC_DEPLOYMENT_PROXY_ADDRESS, - GenesisAccount { - code: Bytes::from_static(&DETERMINISTIC_DEPLOYMENT_CODE), - storage: HashMap::new(), - balance: U256::zero(), - nonce: 1, - }, - ); - - genesis.alloc.insert( - SAFE_SINGLETON_FACTORY_ADDRESS, - GenesisAccount { - code: Bytes::from_static(&DETERMINISTIC_DEPLOYMENT_CODE), - storage: HashMap::new(), - balance: U256::zero(), - nonce: 1, - }, - ); - - genesis.alloc.insert( - CREATE2DEPLOYER_ADDRESS, - GenesisAccount { - code: Bytes::from(create2deployer_runtime(out_dir)), - storage: HashMap::new(), - balance: U256::zero(), - nonce: 1, - }, - ); -} - -// From cmd/ethrex -pub fn read_genesis_file(genesis_file_path: &str) -> Genesis { - let genesis_file = std::fs::File::open(genesis_file_path).expect("Failed to open genesis file"); - _genesis_file(genesis_file).expect("Failed to decode genesis file") -} - -// From cmd/ethrex/decode.rs -fn _genesis_file(file: File) -> Result { - let genesis_reader = BufReader::new(file); - serde_json::from_reader(genesis_reader) -} diff --git a/cmd/ethrex/build_l2.rs b/cmd/ethrex/build_l2.rs new file mode 100644 index 00000000000..f65b511455e --- /dev/null +++ b/cmd/ethrex/build_l2.rs @@ -0,0 +1,452 @@ +use ethrex_common::H160; +use genesis_tool::genesis::write_genesis_as_json; +use std::fs::File; +use std::io::BufReader; +use std::{ + env, fs, + path::{Path, PathBuf}, +}; + +use ethrex_common::{U256, types::GenesisAccount}; + +use std::collections::HashMap; + +use bytes::Bytes; +use ethrex_common::Address; +use ethrex_common::types::Genesis; + +pub const L2_GENESIS_PATH: &str = "../../fixtures/genesis/l2.json"; + +const DETERMINISTIC_DEPLOYMENT_CODE: [u8; 69] = [ + 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xe0, 0x36, 0x01, 0x60, 0x00, 0x81, 0x60, 0x20, 0x82, 0x37, 0x80, 0x35, 0x82, 0x82, 0x34, 0xf5, + 0x80, 0x15, 0x15, 0x60, 0x39, 0x57, 0x81, 0x82, 0xfd, 0x5b, 0x80, 0x82, 0x52, 0x50, 0x50, 0x50, + 0x60, 0x14, 0x60, 0x0c, 0xf3, +]; + +pub fn download_script() { + let out_dir = env::var_os("OUT_DIR").unwrap(); + let output_contracts_path = Path::new(&out_dir).join("contracts"); + println!( + "Compiling contracts to: {}", + output_contracts_path.display() + ); + let contracts_path = Path::new("../../crates/l2/contracts/src"); + + // If COMPILE_CONTRACTS is not set, skip and write empty files + if env::var_os("COMPILE_CONTRACTS").is_none() { + write_empty_bytecode_files(&output_contracts_path); + return; + } + + download_contract_deps(&output_contracts_path); + + // ERC1967Proxy contract. + compile_contract_to_bytecode( + &output_contracts_path, + &output_contracts_path.join("lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol"), + "ERC1967Proxy", + false, + None, + &[&output_contracts_path] + ); + + // SP1VerifierGroth16 contract + compile_contract_to_bytecode( + &output_contracts_path, + &output_contracts_path + .join("lib/sp1-contracts/contracts/src/v5.0.0/SP1VerifierGroth16.sol"), + "SP1Verifier", + false, + None, + &[&output_contracts_path], + ); + + let remappings = [( + "@openzeppelin/contracts", + output_contracts_path.join("lib/openzeppelin-contracts/contracts"), + )]; + + compile_contract_to_bytecode( + &output_contracts_path, + &output_contracts_path.join("lib/create2deployer/contracts/Create2Deployer.sol"), + "Create2Deployer", + true, + Some(&remappings), + &[contracts_path], + ); + + // Get the openzeppelin contracts remappings + let remappings = [ + ( + "@openzeppelin/contracts", + output_contracts_path.join( + "lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts", + ), + ), + ( + "@openzeppelin/contracts-upgradeable", + output_contracts_path.join("lib/openzeppelin-contracts-upgradeable/contracts"), + ), + ]; + + // L1 contracts + let l1_contracts = [ + ( + &Path::new("../../crates/l2/contracts/src/l1/OnChainProposer.sol"), + "OnChainProposer", + ), + ( + &Path::new("../../crates/l2/contracts/src/l1/CommonBridge.sol"), + "CommonBridge", + ), + ]; + for (path, name) in l1_contracts { + compile_contract_to_bytecode( + &output_contracts_path, + path, + name, + false, + Some(&remappings), + &[contracts_path], + ); + } + // L2 contracts + let l2_contracts = [ + ( + &Path::new("../../crates/l2/contracts/src/l2/CommonBridgeL2.sol"), + "CommonBridgeL2", + ), + ( + &Path::new("../../crates/l2/contracts/src/l2/L2ToL1Messenger.sol"), + "L2ToL1Messenger", + ), + ( + &Path::new("../../crates/l2/contracts/src/l2/L2Upgradeable.sol"), + "UpgradeableSystemContract", + ), + ]; + for (path, name) in l2_contracts { + compile_contract_to_bytecode( + &output_contracts_path, + path, + name, + true, + Some(&remappings), + &[contracts_path], + ); + } + + // Based contracts + compile_contract_to_bytecode( + &output_contracts_path, + Path::new("../../crates/l2/contracts/src/l1/based/SequencerRegistry.sol"), + "SequencerRegistry", + false, + Some(&remappings), + &[contracts_path], + ); + ethrex_l2_sdk::compile_contract( + &output_contracts_path, + Path::new("../../crates/l2/contracts/src/l1/based/OnChainProposer.sol"), + false, + Some(&remappings), + &[contracts_path], + ) + .unwrap(); + + // To avoid colision with the original OnChainProposer bytecode, we rename it to OnChainProposerBased + let file_path = output_contracts_path.join("solc_out/OnChainProposer.bin"); + let output_file_path = output_contracts_path.join("solc_out/OnChainProposerBased.bytecode"); + decode_to_bytecode(&file_path, &output_file_path); +} + +fn write_empty_bytecode_files(output_contracts_path: &Path) { + let bytecode_dir = output_contracts_path.join("solc_out"); + fs::create_dir_all(&bytecode_dir).expect("Failed to create solc_out directory"); + + let contract_names = [ + "ERC1967Proxy", + "SP1Verifier", + "OnChainProposer", + "CommonBridge", + "CommonBridgeL2", + "L2ToL1Messenger", + "UpgradeableSystemContract", + "SequencerRegistry", + "OnChainProposerBased", + ]; + + for name in &contract_names { + let filename = format!("{name}.bytecode"); + let path = bytecode_dir.join(filename); + fs::write(&path, []).expect("Failed to write empty bytecode."); + } +} + +/// Clones OpenZeppelin, SP1 contracts and create2deployer into the specified path. +fn download_contract_deps(contracts_path: &Path) { + fs::create_dir_all(contracts_path.join("lib")).expect("Failed to create contracts/lib dir"); + + ethrex_l2_sdk::git_clone( + "https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable.git", + &contracts_path + .join("lib/openzeppelin-contracts-upgradeable") + .to_string_lossy(), + Some("release-v5.4"), + true, + ) + .expect("Failed to clone openzeppelin-contracts-upgradeable"); + + // Using version 4.9 for create2deployer + ethrex_l2_sdk::git_clone( + "https://github.com/OpenZeppelin/openzeppelin-contracts.git", + &contracts_path + .join("lib/openzeppelin-contracts") + .to_string_lossy(), + Some("release-v4.9"), + true, + ) + .expect("Failed to clone openzeppelin-contracts"); + + ethrex_l2_sdk::git_clone( + "https://github.com/succinctlabs/sp1-contracts.git", + &contracts_path.join("lib/sp1-contracts").to_string_lossy(), + None, + false, + ) + .expect("Failed to clone sp1-contracts"); + + ethrex_l2_sdk::git_clone( + "https://github.com/pcaversaccio/create2deployer", + &contracts_path.join("lib/create2deployer").to_string_lossy(), + None, + true, + ) + .expect("Failed to clone create2deployer"); +} + +fn compile_contract_to_bytecode( + output_dir: &Path, + contract_path: &Path, + contract_name: &str, + runtime_bin: bool, + remappings: Option<&[(&str, PathBuf)]>, + allow_paths: &[&Path], +) { + println!("Compiling {contract_name} contract"); + ethrex_l2_sdk::compile_contract( + output_dir, + contract_path, + runtime_bin, + remappings, + allow_paths, + ) + .expect("Failed to compile contract"); + println!("Successfully compiled {contract_name} contract"); + + // Resolve the resulted file path + let filename = if runtime_bin { + format!("{contract_name}.bin-runtime") + } else { + format!("{contract_name}.bin") + }; + let file_path = output_dir.join("solc_out").join(&filename); + + // Get the output file path + let output_file_path = output_dir + .join("solc_out") + .join(format!("{contract_name}.bytecode")); + + decode_to_bytecode(&file_path, &output_file_path); + + println!("Successfully generated {contract_name} bytecode"); +} + +fn decode_to_bytecode(input_file_path: &Path, output_file_path: &Path) { + let bytecode_hex = fs::read_to_string(input_file_path).expect("Failed to read file"); + + let bytecode = hex::decode(bytecode_hex.trim()).expect("Failed to decode bytecode"); + + fs::write(output_file_path, bytecode).expect("Failed to write bytecode"); +} + +use ethrex_l2_sdk::{ + COMMON_BRIDGE_L2_ADDRESS, CREATE2DEPLOYER_ADDRESS, DETERMINISTIC_DEPLOYMENT_PROXY_ADDRESS, + L2_TO_L1_MESSENGER_ADDRESS, SAFE_SINGLETON_FACTORY_ADDRESS, address_to_word, get_erc1967_slot, +}; + +#[allow(clippy::enum_variant_names)] +#[derive(Debug, thiserror::Error)] +pub enum SystemContractsUpdaterError { + #[error("Failed to deploy contract: {0}")] + FailedToDecodeRuntimeCode(#[from] hex::FromHexError), + #[error("Failed to serialize modified genesis: {0}")] + FailedToSerializeModifiedGenesis(#[from] serde_json::Error), + #[error("Failed to write modified genesis file: {0}")] + FailedToWriteModifiedGenesisFile(#[from] std::io::Error), + #[error("Failed to read path: {0}")] + InvalidPath(String), + #[error( + "Contract bytecode not found. Make sure to compile the updater with `COMPILE_CONTRACTS` set." + )] + BytecodeNotFound, +} + +/// Address authorized to perform system contract upgrades +/// 0x000000000000000000000000000000000000f000 +pub const ADMIN_ADDRESS: Address = H160([ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0x00, +]); + +/// Mask used to derive the initial implementation address +/// 0x0000000000000000000000000000000000001000 +pub const IMPL_MASK: Address = H160([ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x10, 0x00, +]); +// From cmd/ethrex +pub fn read_genesis_file(genesis_file_path: &str) -> Genesis { + let genesis_file = std::fs::File::open(genesis_file_path).expect("Failed to open genesis file"); + _genesis_file(genesis_file).expect("Failed to decode genesis file") +} + +// From cmd/ethrex/decode.rs +fn _genesis_file(file: File) -> Result { + let genesis_reader = BufReader::new(file); + serde_json::from_reader(genesis_reader) +} + +/// Bytecode of the CommonBridgeL2 contract. +fn common_bridge_l2_runtime(out_dir: &Path) -> Vec { + let path = out_dir.join("contracts/solc_out/CommonBridgeL2.bytecode"); + fs::read(path).expect("Failed to read bytecode file") +} + +/// Bytecode of the L2ToL1Messenger contract. +fn l2_to_l1_messenger_runtime(out_dir: &Path) -> Vec { + let path = out_dir.join("contracts/solc_out/L2ToL1Messenger.bytecode"); + fs::read(path).expect("Failed to read bytecode file") +} + +/// Bytecode of the Create2Deployer contract. +fn create2deployer_runtime(out_dir: &Path) -> Vec { + let path = out_dir.join("contracts/solc_out/Create2Deployer.bytecode"); + fs::read(path).expect("Failedto read bytecode file") +} + +/// Bytecode of the L2Upgradeable contract. +fn l2_upgradeable_runtime(out_dir: &Path) -> Vec { + let path = out_dir.join("contracts/solc_out/UpgradeableSystemContract.bytecode"); + fs::read(path).expect("Failed to read bytecode file") +} + +fn add_with_proxy( + genesis: &mut Genesis, + address: Address, + code: Vec, + out_dir: &Path, +) -> Result<(), SystemContractsUpdaterError> { + let impl_address = address ^ IMPL_MASK; + + if code.is_empty() { + return Err(SystemContractsUpdaterError::BytecodeNotFound); + } + + genesis.alloc.insert( + impl_address, + GenesisAccount { + code: Bytes::from(code), + storage: HashMap::new(), + balance: U256::zero(), + nonce: 1, + }, + ); + + let mut storage = HashMap::new(); + + storage.insert( + get_erc1967_slot("eip1967.proxy.implementation"), + address_to_word(impl_address), + ); + + storage.insert( + get_erc1967_slot("eip1967.proxy.admin"), + address_to_word(ADMIN_ADDRESS), + ); + genesis.alloc.insert( + address, + GenesisAccount { + code: Bytes::from(l2_upgradeable_runtime(out_dir)), + storage, + balance: U256::zero(), + nonce: 1, + }, + ); + Ok(()) +} + +pub fn update_genesis_file( + l2_genesis_path: &Path, + out_dir: &Path, +) -> Result<(), SystemContractsUpdaterError> { + let mut genesis = read_genesis_file(l2_genesis_path.to_str().ok_or( + SystemContractsUpdaterError::InvalidPath( + "Failed to convert l2 genesis path to string".to_string(), + ), + )?); + + add_with_proxy( + &mut genesis, + COMMON_BRIDGE_L2_ADDRESS, + common_bridge_l2_runtime(out_dir), + out_dir, + )?; + + add_with_proxy( + &mut genesis, + L2_TO_L1_MESSENGER_ADDRESS, + l2_to_l1_messenger_runtime(out_dir), + out_dir, + )?; + + add_deterministic_deployers(&mut genesis, out_dir); + + write_genesis_as_json(genesis, Path::new(l2_genesis_path)).map_err(std::io::Error::other)?; + + Ok(()) +} + +fn add_deterministic_deployers(genesis: &mut Genesis, out_dir: &Path) { + genesis.alloc.insert( + DETERMINISTIC_DEPLOYMENT_PROXY_ADDRESS, + GenesisAccount { + code: Bytes::from_static(&DETERMINISTIC_DEPLOYMENT_CODE), + storage: HashMap::new(), + balance: U256::zero(), + nonce: 1, + }, + ); + + genesis.alloc.insert( + SAFE_SINGLETON_FACTORY_ADDRESS, + GenesisAccount { + code: Bytes::from_static(&DETERMINISTIC_DEPLOYMENT_CODE), + storage: HashMap::new(), + balance: U256::zero(), + nonce: 1, + }, + ); + + genesis.alloc.insert( + CREATE2DEPLOYER_ADDRESS, + GenesisAccount { + code: Bytes::from(create2deployer_runtime(out_dir)), + storage: HashMap::new(), + balance: U256::zero(), + nonce: 1, + }, + ); +} diff --git a/cmd/ethrex/cli.rs b/cmd/ethrex/cli.rs index 30dbf2330d9..9d8a2efd070 100644 --- a/cmd/ethrex/cli.rs +++ b/cmd/ethrex/cli.rs @@ -18,12 +18,13 @@ use tracing::{Level, info, warn}; use crate::{ initializers::{get_network, init_blockchain, init_store, init_tracing, load_store}, - l2::{ - self, - command::{DB_ETHREX_DEV_L1, DB_ETHREX_DEV_L2}, - }, utils::{self, default_datadir, get_client_version, get_minimal_client_version, init_datadir}, }; + +pub const DB_ETHREX_DEV_L1: &str = "dev_ethrex_l1"; + +#[cfg(feature = "l2")] +pub const DB_ETHREX_DEV_L2: &str = "dev_ethrex_l2"; use ethrex_config::networks::Network; #[allow(clippy::upper_case_acronyms)] @@ -251,6 +252,7 @@ impl Options { } } + #[cfg(feature = "l2")] pub fn default_l2() -> Self { Self { network: Some(Network::LocalDevnetL2), @@ -364,14 +366,16 @@ pub enum Subcommand { )] genesis_path: PathBuf, }, + #[cfg(feature = "l2")] #[command(name = "l2")] - L2(l2::L2Command), + L2(crate::l2::L2Command), } impl Subcommand { pub async fn run(self, opts: &Options) -> eyre::Result<()> { // L2 has its own init_tracing because of the ethrex monitor match self { + #[cfg(feature = "l2")] Self::L2(_) => {} _ => { init_tracing(opts); @@ -413,6 +417,7 @@ impl Subcommand { let state_root = genesis.compute_state_root(); println!("{state_root:#x}"); } + #[cfg(feature = "l2")] Subcommand::L2(command) => command.run().await?, } diff --git a/cmd/ethrex/initializers.rs b/cmd/ethrex/initializers.rs index ab478752e02..319ccd3cb45 100644 --- a/cmd/ethrex/initializers.rs +++ b/cmd/ethrex/initializers.rs @@ -11,11 +11,12 @@ use ethrex_common::types::Genesis; use ethrex_config::networks::Network; use ethrex_metrics::profiling::{FunctionProfilingLayer, initialize_block_processing_profile}; +#[cfg(feature = "l2")] +use ethrex_p2p::rlpx::l2::l2_connection::P2PBasedContext; use ethrex_p2p::{ discv4::peer_table::PeerTable, network::P2PContext, peer_handler::PeerHandler, - rlpx::l2::l2_connection::P2PBasedContext, sync::SyncMode, sync_manager::SyncManager, types::{Node, NodeRecord}, @@ -200,7 +201,7 @@ pub async fn init_network( store: Store, tracker: TaskTracker, blockchain: Arc, - based_context: Option, + #[cfg(feature = "l2")] based_context: Option, ) { if opts.dev { error!("Binary wasn't built with The feature flag `dev` enabled."); @@ -220,6 +221,7 @@ pub async fn init_network( store, blockchain.clone(), get_client_version(), + #[cfg(feature = "l2")] based_context, opts.tx_broadcasting_time_interval, ) @@ -486,6 +488,7 @@ pub async fn init_l1( store.clone(), tracker.clone(), blockchain.clone(), + #[cfg(feature = "l2")] None, ) .await; diff --git a/cmd/ethrex/l2/command.rs b/cmd/ethrex/l2/command.rs index 984588ae02d..90eeb46a21e 100644 --- a/cmd/ethrex/l2/command.rs +++ b/cmd/ethrex/l2/command.rs @@ -1,5 +1,5 @@ use crate::{ - cli::remove_db, + cli::{DB_ETHREX_DEV_L1, DB_ETHREX_DEV_L2, remove_db}, initializers::{init_l1, init_store, init_tracing}, l2::{ self, @@ -39,9 +39,6 @@ const _: () = { compile_error!("Database feature must be enabled (Available: `rocksdb`)."); }; -pub const DB_ETHREX_DEV_L1: &str = "dev_ethrex_l1"; -pub const DB_ETHREX_DEV_L2: &str = "dev_ethrex_l2"; - const PAUSE_CONTRACT_SELECTOR: &str = "pause()"; const UNPAUSE_CONTRACT_SELECTOR: &str = "unpause()"; const REVERT_BATCH_SELECTOR: &str = "revertBatch(uint256)"; @@ -381,8 +378,10 @@ impl Command { #[cfg(feature = "rocksdb")] let store_type = EngineType::RocksDB; - #[cfg(feature = "rollup_storage_sql")] + #[cfg(feature = "l2-sql")] let rollup_store_type = ethrex_storage_rollup::EngineTypeRollup::SQL; + #[cfg(not(feature = "l2-sql"))] + let rollup_store_type = ethrex_storage_rollup::EngineTypeRollup::InMemory; // Init stores let store = Store::new_from_genesis( diff --git a/cmd/ethrex/l2/initializers.rs b/cmd/ethrex/l2/initializers.rs index cf76e1b9929..521a66f6c16 100644 --- a/cmd/ethrex/l2/initializers.rs +++ b/cmd/ethrex/l2/initializers.rs @@ -98,9 +98,9 @@ fn get_valid_delegation_addresses(l2_opts: &L2Options) -> Vec
{ } pub async fn init_rollup_store(datadir: &Path) -> StoreRollup { - #[cfg(feature = "rollup_storage_sql")] + #[cfg(feature = "l2-sql")] let engine_type = EngineTypeRollup::SQL; - #[cfg(not(feature = "rollup_storage_sql"))] + #[cfg(not(feature = "l2-sql"))] let engine_type = EngineTypeRollup::InMemory; let rollup_store = StoreRollup::new(datadir, engine_type).expect("Failed to create StoreRollup"); diff --git a/cmd/ethrex/lib.rs b/cmd/ethrex/lib.rs index 28088e3d74a..6055d7cd3d6 100644 --- a/cmd/ethrex/lib.rs +++ b/cmd/ethrex/lib.rs @@ -1,5 +1,6 @@ pub mod cli; pub mod initializers; +#[cfg(feature = "l2")] pub mod l2; pub mod utils; diff --git a/crates/l2/Makefile b/crates/l2/Makefile index 8eb355dbe3f..5c0c0ad67a9 100644 --- a/crates/l2/Makefile +++ b/crates/l2/Makefile @@ -82,7 +82,7 @@ rm-db-l1: ## ๐Ÿ›‘ Removes the DB used by the L1 deploy-l1: ## ๐Ÿ“œ Deploys the L1 contracts COMPILE_CONTRACTS=true \ - cargo run --release --bin ethrex --manifest-path ../../Cargo.toml -- l2 deploy \ + cargo run --release --features l2,l2-sql --manifest-path ../../Cargo.toml -- l2 deploy \ --eth-rpc-url ${L1_RPC_URL} \ --private-key ${L1_PRIVATE_KEY} \ --risc0.verifier-address 0x00000000000000000000000000000000000000aa \ @@ -99,7 +99,7 @@ deploy-l1: ## ๐Ÿ“œ Deploys the L1 contracts ## Same as deploy-l1 but deploys the SP1 verifier deploy-l1-sp1: ## ๐Ÿ“œ Deploys the L1 contracts COMPILE_CONTRACTS=true \ - cargo run --release --bin ethrex --manifest-path ../../Cargo.toml -- l2 deploy \ + cargo run --release --features l2,l2-sql --manifest-path ../../Cargo.toml -- l2 deploy \ --eth-rpc-url ${L1_RPC_URL} \ --private-key ${L1_PRIVATE_KEY} \ --risc0.verifier-address 0x00000000000000000000000000000000000000aa \ @@ -119,7 +119,7 @@ deploy-l1-sp1: ## ๐Ÿ“œ Deploys the L1 contracts init-l2: ## ๐Ÿš€ Initializes an L2 Lambda ethrex Client export $(shell cat ../../cmd/.env | xargs); \ - cargo run --release --manifest-path ../../Cargo.toml --bin ethrex -- \ + cargo run --release --features l2,l2-sql --manifest-path ../../Cargo.toml -- \ l2 \ --watcher.block-delay 0 \ --network ${L2_GENESIS_FILE_PATH} \ @@ -139,7 +139,7 @@ init-l2: ## ๐Ÿš€ Initializes an L2 Lambda ethrex Client init-l2-dev: ## ๐Ÿš€ Initializes an L1 and L2 Lambda ethrex Client COMPILE_CONTRACTS=true \ - cargo run --release --manifest-path ../../Cargo.toml --bin ethrex -- \ + cargo run --release --features l2,l2-sql --manifest-path ../../Cargo.toml -- \ l2 --dev init-metrics: ## ๐Ÿš€ Initializes Grafana and Prometheus with containers @@ -156,7 +156,7 @@ down-l2: ## ๐Ÿ›‘ Shuts down the L2 Lambda ethrex Client pgrep -a -f "ethrex l2" | awk '!/prover/ {print $1}' | xargs -r kill -s SIGINT rm-db-l2: ## ๐Ÿ›‘ Removes the DB used by the L2 - cargo run --release --manifest-path ../../Cargo.toml --bin ethrex -- l2 removedb --datadir ${ethrex_L2_DEV_DB} --force + cargo run --release --features l2,l2-sql --manifest-path ../../Cargo.toml -- l2 removedb --datadir ${ethrex_L2_DEV_DB} --force restart-l2: down-l2 rm-db-l2 init-l2 ## ๐Ÿ”„ Restarts the L2 Lambda ethrex Client @@ -194,7 +194,7 @@ build-prover: cargo build --release \ --manifest-path ../../Cargo.toml \ --bin ethrex \ - --features "$$PROVER$$GPU" + --features "l2,l2-sql,$$PROVER$$GPU" # ============================================================================== @@ -243,7 +243,7 @@ integration-test-gpu: rm-db-l2 rm-db-l1 # State reconstruction tests state-diff-test: touch .env - cargo run --release --manifest-path ../../Cargo.toml -- \ + cargo run --release --features l2,l2-sql --manifest-path ../../Cargo.toml -- \ l2 reconstruct \ -g ../../fixtures/genesis/l2.json \ -b ../../fixtures/blobs/ \ diff --git a/crates/l2/docker-compose.yaml b/crates/l2/docker-compose.yaml index c68ca854315..7eda4238588 100644 --- a/crates/l2/docker-compose.yaml +++ b/crates/l2/docker-compose.yaml @@ -14,8 +14,11 @@ services: contract_deployer: container_name: contract_deployer - image: "ethrex:main" - build: ../../ + image: "ethrex:main-l2" + build: + context: ../../ + args: + - BUILD_FLAGS=--features l2,l2-sql volumes: # NOTE: DOCKER_ETHREX_WORKDIR is defined in crates/l2/Makefile - ./contracts:${DOCKER_ETHREX_WORKDIR}/contracts @@ -64,8 +67,11 @@ services: ethrex_l2: container_name: ethrex_l2 - image: "ethrex:main" - build: ../../ + image: "ethrex:main-l2" + build: + context: ../../ + args: + - BUILD_FLAGS=--features l2,l2-sql ports: # RPC - 127.0.0.1:1729:1729 @@ -89,7 +95,7 @@ services: entrypoint: - /bin/bash - -c - - export $(xargs < /env/.env); ./ethrex l2 "$0" "$@" + - export $(xargs < /env/.env); ./ethrex l2 "$0" "$@" # ETHREX_WATCHER_BRIDGE_ADDRESS and ETHREX_COMMITTER_ON_CHAIN_PROPOSER_ADDRESS are set in the .env file by the contract_deployer service. command: > --network /genesis/l2.json @@ -107,8 +113,11 @@ services: ethrex_prover: container_name: ethrex_prover - image: "ethrex:main" - build: ../../ + image: "ethrex:main-l2" + build: + context: ../../ + args: + - BUILD_FLAGS=--features l2,l2-sql command: > l2 prover --backend exec diff --git a/crates/l2/networking/rpc/Cargo.toml b/crates/l2/networking/rpc/Cargo.toml index 626702608ec..2c7f87497bf 100644 --- a/crates/l2/networking/rpc/Cargo.toml +++ b/crates/l2/networking/rpc/Cargo.toml @@ -11,7 +11,7 @@ documentation.workspace = true ethrex-common.workspace = true ethrex-storage.workspace = true ethrex-blockchain.workspace = true -ethrex-p2p.workspace = true +ethrex-p2p = { workspace = true, features = ["l2"] } ethrex-storage-rollup.workspace = true ethrex-l2-common.workspace = true ethrex-rpc.workspace = true diff --git a/crates/l2/prover/src/backend/sp1.rs b/crates/l2/prover/src/backend/sp1.rs index edbc77274ef..9a807c9fd4f 100644 --- a/crates/l2/prover/src/backend/sp1.rs +++ b/crates/l2/prover/src/backend/sp1.rs @@ -42,10 +42,11 @@ pub fn init_prover_setup(_endpoint: Option) -> ProverSetup { if let Some(endpoint) = _endpoint { CudaProverBuilder::default() .server( - &endpoint + #[expect(clippy::expect_used)] + endpoint .join("/twirp/") .expect("Failed to parse moongate server url") - .to_string(), + .as_ref(), ) .build() } else { diff --git a/crates/l2/sequencer/block_producer.rs b/crates/l2/sequencer/block_producer.rs index 85dabb0fb4c..f170292544c 100644 --- a/crates/l2/sequencer/block_producer.rs +++ b/crates/l2/sequencer/block_producer.rs @@ -204,7 +204,11 @@ impl BlockProducer { self.blockchain .store_block(block, account_updates_list, execution_result) .await?; - info!("Stored new block {:x}", block_hash); + info!( + "Stored new block {:x}, transaction_count {}", + block_hash, transactions_count + ); + // WARN: We're not storing the payload into the Store because there's no use to it by the L2 for now. self.rollup_store .store_account_updates_by_block_number(block_number, account_updates) diff --git a/crates/l2/sequencer/l1_committer.rs b/crates/l2/sequencer/l1_committer.rs index cb7d7380efc..39acc11b10b 100644 --- a/crates/l2/sequencer/l1_committer.rs +++ b/crates/l2/sequencer/l1_committer.rs @@ -47,7 +47,7 @@ use std::{ sync::Arc, }; use tokio_util::sync::CancellationToken; -use tracing::{debug, error, info, warn}; +use tracing::{debug, error, info, trace, warn}; use super::{errors::BlobEstimationError, utils::random_duration}; use spawned_concurrency::tasks::{ @@ -327,7 +327,7 @@ impl L1Committer { #[cfg(feature = "metrics")] let mut batch_gas_used = 0_u64; - info!("Preparing state diff from block {first_block_of_batch}"); + info!("Preparing state diff from block {first_block_of_batch}, {batch_number}"); loop { let block_to_commit_number = last_added_block_number + 1; @@ -466,6 +466,8 @@ impl L1Committer { break; }; + trace!("Got bundle, latest blob size {latest_blob_size}"); + // Save current blobs_bundle and continue to add more blocks. blobs_bundle = bundle; diff --git a/crates/networking/p2p/Cargo.toml b/crates/networking/p2p/Cargo.toml index 2460f375918..ee1bc1a24e5 100644 --- a/crates/networking/p2p/Cargo.toml +++ b/crates/networking/p2p/Cargo.toml @@ -13,7 +13,7 @@ ethrex-blockchain.workspace = true ethrex-rlp.workspace = true ethrex-storage.workspace = true ethrex-trie.workspace = true -ethrex-storage-rollup.workspace = true +ethrex-storage-rollup = { workspace = true, optional = true } ethrex-threadpool.workspace = true ethereum-types.workspace = true @@ -61,7 +61,7 @@ path = "./p2p.rs" default = ["c-kzg"] c-kzg = ["ethrex-blockchain/c-kzg", "ethrex-common/c-kzg"] sync-test = [] -rocksdb = ["dep:rocksdb"] +l2 = ["dep:ethrex-storage-rollup"] [lints.clippy] unwrap_used = "deny" diff --git a/crates/networking/p2p/metrics.rs b/crates/networking/p2p/metrics.rs index be560cabcf4..2d3b16b742e 100644 --- a/crates/networking/p2p/metrics.rs +++ b/crates/networking/p2p/metrics.rs @@ -485,6 +485,7 @@ impl Metrics { .and_modify(|e| *e += 1) .or_insert(1); } + #[cfg(feature = "l2")] PeerConnectionError::RollupStoreError(error) => { failures_grouped_by_reason .entry(format!("RollupStoreError - {error}")) diff --git a/crates/networking/p2p/network.rs b/crates/networking/p2p/network.rs index cbf74e2065c..67843eea006 100644 --- a/crates/networking/p2p/network.rs +++ b/crates/networking/p2p/network.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "l2")] +use crate::rlpx::l2::l2_connection::P2PBasedContext; use crate::{ discv4::{ peer_table::{PeerData, PeerTable}, @@ -7,7 +9,6 @@ use crate::{ rlpx::{ connection::server::{PeerConnBroadcastSender, PeerConnection}, initiator::RLPxInitiator, - l2::l2_connection::P2PBasedContext, message::Message, p2p::SUPPORTED_SNAP_CAPABILITIES, }, @@ -44,6 +45,7 @@ pub struct P2PContext { pub local_node: Node, pub local_node_record: Arc>, pub client_version: String, + #[cfg(feature = "l2")] pub based_context: Option, pub tx_broadcaster: GenServerHandle, } @@ -59,7 +61,7 @@ impl P2PContext { storage: Store, blockchain: Arc, client_version: String, - based_context: Option, + #[cfg(feature = "l2")] based_context: Option, tx_broadcasting_time_interval: u64, ) -> Result { let (channel_broadcast_send_end, _) = tokio::sync::broadcast::channel::<( @@ -87,6 +89,7 @@ impl P2PContext { blockchain, broadcast: channel_broadcast_send_end, client_version, + #[cfg(feature = "l2")] based_context, tx_broadcaster, }) diff --git a/crates/networking/p2p/rlpx/connection/handshake.rs b/crates/networking/p2p/rlpx/connection/handshake.rs index 048202f1909..1dc22687453 100644 --- a/crates/networking/p2p/rlpx/connection/handshake.rs +++ b/crates/networking/p2p/rlpx/connection/handshake.rs @@ -8,11 +8,12 @@ use super::{ codec::RLPxCodec, server::{Initiator, Receiver}, }; +#[cfg(feature = "l2")] +use crate::rlpx::l2::l2_connection::L2ConnState; use crate::{ rlpx::{ connection::server::{ConnectionState, Established}, error::PeerConnectionError, - l2::l2_connection::L2ConnState, message::EthCapVersion, utils::{ compress_pubkey, decompress_pubkey, ecdh_xchng, kdf, log_peer_debug, sha256, @@ -140,6 +141,7 @@ pub(crate) async fn perform( client_version: context.client_version.clone(), connection_broadcast_send: context.broadcast.clone(), peer_table: context.table.clone(), + #[cfg(feature = "l2")] l2_state: context .based_context .map_or_else(|| L2ConnState::Unsupported, L2ConnState::Disconnected), diff --git a/crates/networking/p2p/rlpx/connection/server.rs b/crates/networking/p2p/rlpx/connection/server.rs index 59a30a57d66..d6486c6460b 100644 --- a/crates/networking/p2p/rlpx/connection/server.rs +++ b/crates/networking/p2p/rlpx/connection/server.rs @@ -1,3 +1,44 @@ +use std::{ + collections::HashMap, + net::SocketAddr, + sync::{Arc, RwLock}, + time::Duration, +}; + +use ethrex_blockchain::Blockchain; +use ethrex_common::types::MempoolTransaction; +#[cfg(feature = "l2")] +use ethrex_common::types::Transaction; +use ethrex_storage::{Store, error::StoreError}; +use ethrex_trie::TrieError; +use futures::{SinkExt as _, Stream, stream::SplitSink}; +use rand::random; +use secp256k1::{PublicKey, SecretKey}; +use spawned_concurrency::{ + messages::Unused, + tasks::{ + CastResponse, GenServer, GenServerHandle, + InitResult::{self, NoSuccess, Success}, + send_interval, spawn_listener, + }, +}; +use spawned_rt::tasks::BroadcastStream; +use tokio::{ + net::TcpStream, + sync::{Mutex, broadcast, oneshot}, + task::{self, Id}, +}; +use tokio_stream::StreamExt; +use tokio_util::codec::Framed; +use tracing::{debug, error}; + +#[cfg(feature = "l2")] +use crate::rlpx::l2::{ + PERIODIC_BATCH_BROADCAST_INTERVAL, PERIODIC_BLOCK_BROADCAST_INTERVAL, + l2_connection::{ + self, L2Cast, L2ConnState, handle_based_capability_message, handle_l2_broadcast, + }, +}; use crate::{ discv4::peer_table::PeerTable, metrics::METRICS, @@ -14,13 +55,6 @@ use crate::{ transactions::{GetPooledTransactions, NewPooledTransactionHashes}, update::BlockRangeUpdate, }, - l2::{ - self, PERIODIC_BATCH_BROADCAST_INTERVAL, PERIODIC_BLOCK_BROADCAST_INTERVAL, - l2_connection::{ - self, L2Cast, L2ConnState, broadcast_l2_message, handle_based_capability_message, - handle_l2_broadcast, - }, - }, message::EthCapVersion, p2p::{ self, Capability, DisconnectMessage, DisconnectReason, PingMessage, PongMessage, @@ -36,36 +70,6 @@ use crate::{ tx_broadcaster::{InMessage, TxBroadcaster, send_tx_hashes}, types::Node, }; -use ethrex_blockchain::Blockchain; -use ethrex_common::types::{MempoolTransaction, Transaction}; -use ethrex_storage::{Store, error::StoreError}; -use ethrex_trie::TrieError; -use futures::{SinkExt as _, Stream, stream::SplitSink}; -use rand::random; -use secp256k1::{PublicKey, SecretKey}; -use spawned_concurrency::{ - messages::Unused, - tasks::{ - CastResponse, GenServer, GenServerHandle, - InitResult::{self, NoSuccess, Success}, - send_interval, spawn_listener, - }, -}; -use spawned_rt::tasks::BroadcastStream; -use std::{ - collections::HashMap, - net::SocketAddr, - sync::{Arc, RwLock}, - time::Duration, -}; -use tokio::{ - net::TcpStream, - sync::{Mutex, broadcast, oneshot}, - task::{self, Id}, -}; -use tokio_stream::StreamExt; -use tokio_util::codec::Framed; -use tracing::{debug, error}; const PING_INTERVAL: Duration = Duration::from_secs(10); const BLOCK_RANGE_UPDATE_INTERVAL: Duration = Duration::from_secs(60); @@ -184,6 +188,7 @@ pub struct Established { /// See https://github.com/lambdaclass/ethrex/issues/3388 pub(crate) connection_broadcast_send: PeerConnBroadcastSender, pub(crate) peer_table: PeerTable, + #[cfg(feature = "l2")] pub(crate) l2_state: L2ConnState, pub(crate) tx_broadcaster: GenServerHandle, pub(crate) current_requests: HashMap)>, @@ -227,6 +232,7 @@ pub enum CastMessage { /// Received a message to broadcast. Used only for L2, we have to move this logic to tx_broadcaster. BroadcastMessage(task::Id, Arc), /// L2 message + #[cfg(feature = "l2")] L2(L2Cast), } @@ -316,6 +322,7 @@ impl GenServer for PeerConnectionServer { _handle: &GenServerHandle, ) -> CastResponse { if let ConnectionState::Established(ref mut established_state) = self.state { + #[cfg(feature = "l2")] let peer_supports_l2 = established_state.l2_state.connection_state().is_ok(); let result = match message { Self::CastMsg::IncomingMessage(message) => { @@ -369,6 +376,7 @@ impl GenServer for PeerConnectionServer { log_peer_debug(&established_state.node, "Block Range Update"); handle_block_range_update(established_state).await } + #[cfg(feature = "l2")] Self::CastMsg::L2(msg) if peer_supports_l2 => { log_peer_debug(&established_state.node, "Handling cast for L2 msg: {msg:?}"); match msg { @@ -376,10 +384,11 @@ impl GenServer for PeerConnectionServer { l2_connection::send_sealed_batch(established_state).await } L2Cast::BlockBroadcast => { - l2::l2_connection::send_new_block(established_state).await + l2_connection::send_new_block(established_state).await } } } + #[cfg(feature = "l2")] _ => Err(PeerConnectionError::MessageNotHandled( "Unknown message or capability not handled".to_string(), )), @@ -522,6 +531,7 @@ where CastMessage::BlockRangeUpdate, ); + #[cfg(feature = "l2")] // Periodic L2 messages events. if state.l2_state.connection_state().is_ok() { send_interval( @@ -731,13 +741,17 @@ async fn exchange_hello_messages( where S: Unpin + Stream>, { + // This allow is because in l2 we mut the capabilities + // to include the l2 cap + #[allow(unused_mut)] let mut supported_capabilities: Vec = [ &SUPPORTED_ETH_CAPABILITIES[..], &SUPPORTED_SNAP_CAPABILITIES[..], ] .concat(); + #[cfg(feature = "l2")] if state.l2_state.is_supported() { - supported_capabilities.push(l2::SUPPORTED_BASED_CAPABILITIES[0].clone()); + supported_capabilities.push(crate::rlpx::l2::SUPPORTED_BASED_CAPABILITIES[0].clone()); } let hello_msg = Message::Hello(p2p::HelloMessage::new( supported_capabilities, @@ -783,6 +797,7 @@ where negotiated_snap_version = cap.version; } } + #[cfg(feature = "l2")] "based" if state.l2_state.is_supported() => { state.l2_state.set_established()?; } @@ -849,6 +864,7 @@ async fn handle_incoming_message( message: Message, ) -> Result<(), PeerConnectionError> { let peer_supports_eth = state.negotiated_eth_capability.is_some(); + #[cfg(feature = "l2")] let peer_supports_l2 = state.l2_state.connection_state().is_ok(); match message { Message::Disconnect(msg_data) => { @@ -893,9 +909,11 @@ async fn handle_incoming_message( Message::Transactions(txs) if peer_supports_eth => { // https://github.com/ethereum/devp2p/blob/master/caps/eth.md#transactions-0x02 if state.blockchain.is_synced() { + #[cfg(feature = "l2")] let is_l2_mode = state.l2_state.is_supported(); for tx in &txs.transactions { // Reject blob transactions in L2 mode + #[cfg(feature = "l2")] if is_l2_mode && matches!(tx, Transaction::EIP4844Transaction(_)) { log_peer_debug( &state.node, @@ -1001,7 +1019,11 @@ async fn handle_incoming_message( state.requested_pooled_txs.remove(&msg.id); } } + #[cfg(feature = "l2")] let is_l2_mode = state.l2_state.is_supported(); + + #[cfg(not(feature = "l2"))] + let is_l2_mode = false; msg.handle(&state.node, &state.blockchain, is_l2_mode) .await?; } @@ -1029,6 +1051,7 @@ async fn handle_incoming_message( Err(_) => send(state, Message::TrieNodes(TrieNodes { id, nodes: vec![] })).await?, } } + #[cfg(feature = "l2")] Message::L2(req) if peer_supports_l2 => { handle_based_capability_message(state, req).await?; } @@ -1088,6 +1111,7 @@ async fn handle_broadcast( ) -> Result<(), PeerConnectionError> { if id != tokio::task::id() { match broadcasted_msg.as_ref() { + #[cfg(feature = "l2")] l2_msg @ Message::L2(_) => { handle_l2_broadcast(state, l2_msg).await?; } @@ -1108,17 +1132,3 @@ async fn handle_block_range_update(state: &mut Established) -> Result<(), PeerCo Ok(()) } } - -pub(crate) fn broadcast_message( - state: &Established, - msg: Message, -) -> Result<(), PeerConnectionError> { - match msg { - l2_msg @ Message::L2(_) => broadcast_l2_message(state, l2_msg), - msg => { - let error_message = format!("Broadcasting for msg: {msg} is not supported"); - log_peer_error(&state.node, &error_message); - Err(PeerConnectionError::BroadcastError(error_message)) - } - } -} diff --git a/crates/networking/p2p/rlpx/error.rs b/crates/networking/p2p/rlpx/error.rs index e6c659ccfde..fe5e1db9426 100644 --- a/crates/networking/p2p/rlpx/error.rs +++ b/crates/networking/p2p/rlpx/error.rs @@ -3,6 +3,7 @@ use crate::discv4::peer_table::PeerTableError; use ethrex_blockchain::error::{ChainError, MempoolError}; use ethrex_rlp::error::{RLPDecodeError, RLPEncodeError}; use ethrex_storage::error::StoreError; +#[cfg(feature = "l2")] use ethrex_storage_rollup::RollupStoreError; use thiserror::Error; @@ -54,6 +55,7 @@ pub enum PeerConnectionError { #[error(transparent)] StoreError(#[from] StoreError), #[error(transparent)] + #[cfg(feature = "l2")] RollupStoreError(#[from] RollupStoreError), #[error("Error in cryptographic library: {0}")] CryptographyError(String), diff --git a/crates/networking/p2p/rlpx/l2/l2_connection.rs b/crates/networking/p2p/rlpx/l2/l2_connection.rs index c8d975869dd..a30c2959aa1 100644 --- a/crates/networking/p2p/rlpx/l2/l2_connection.rs +++ b/crates/networking/p2p/rlpx/l2/l2_connection.rs @@ -1,4 +1,4 @@ -use crate::rlpx::connection::server::{broadcast_message, send}; +use crate::rlpx::connection::server::send; use crate::rlpx::l2::messages::{BatchSealed, L2Message, NewBlock}; use crate::rlpx::utils::log_peer_error; use crate::rlpx::{connection::server::Established, error::PeerConnectionError, message::Message}; @@ -42,6 +42,17 @@ pub enum L2ConnState { Connected(L2ConnectedState), } +fn broadcast_message(state: &Established, msg: Message) -> Result<(), PeerConnectionError> { + match msg { + l2_msg @ Message::L2(_) => broadcast_l2_message(state, l2_msg), + msg => { + let error_message = format!("Broadcasting for msg: {msg} is not supported"); + log_peer_error(&state.node, &error_message); + Err(PeerConnectionError::BroadcastError(error_message)) + } + } +} + #[derive(Debug, Clone)] pub enum L2Cast { BlockBroadcast, diff --git a/crates/networking/p2p/rlpx/message.rs b/crates/networking/p2p/rlpx/message.rs index de020cb0cdb..6e8f6165bd6 100644 --- a/crates/networking/p2p/rlpx/message.rs +++ b/crates/networking/p2p/rlpx/message.rs @@ -14,7 +14,9 @@ use super::eth::transactions::{ GetPooledTransactions, NewPooledTransactionHashes, PooledTransactions, Transactions, }; use super::eth::update::BlockRangeUpdate; +#[cfg(feature = "l2")] use super::l2::messages::{BatchSealed, L2Message, NewBlock}; +#[cfg(feature = "l2")] use super::l2::{self, messages}; use super::p2p::{DisconnectMessage, HelloMessage, PingMessage, PongMessage}; @@ -93,6 +95,7 @@ pub enum Message { GetTrieNodes(GetTrieNodes), TrieNodes(TrieNodes), // based capability + #[cfg(feature = "l2")] L2(messages::L2Message), } @@ -145,6 +148,7 @@ impl Message { Message::GetTrieNodes(_) => eth_version.snap_capability_offset() + GetTrieNodes::CODE, Message::TrieNodes(_) => eth_version.snap_capability_offset() + TrieNodes::CODE, + #[cfg(feature = "l2")] // based capability Message::L2(l2_msg) => { eth_version.based_capability_offset() + { @@ -227,7 +231,8 @@ impl Message { } } else { // based capability - Ok(Message::L2( + #[cfg(feature = "l2")] + return Ok(Message::L2( match msg_id - eth_version.based_capability_offset() { messages::NewBlock::CODE => { let decoded = l2::messages::NewBlock::decode(data)?; @@ -239,7 +244,10 @@ impl Message { } _ => return Err(RLPDecodeError::MalformedData), }, - )) + )); + + #[cfg(not(feature = "l2"))] + Err(RLPDecodeError::MalformedData) } } @@ -276,6 +284,7 @@ impl Message { Message::ByteCodes(msg) => msg.encode(buf), Message::GetTrieNodes(msg) => msg.encode(buf), Message::TrieNodes(msg) => msg.encode(buf), + #[cfg(feature = "l2")] Message::L2(l2_msg) => match l2_msg { L2Message::BatchSealed(msg) => msg.encode(buf), L2Message::NewBlock(msg) => msg.encode(buf), @@ -311,8 +320,9 @@ impl Message { | Message::Status69(_) | Message::Transactions(_) | Message::NewPooledTransactionHashes(_) - | Message::BlockRangeUpdate(_) - | Message::L2(_) => None, + | Message::BlockRangeUpdate(_) => None, + #[cfg(feature = "l2")] + Message::L2(_) => None, } } } @@ -346,6 +356,7 @@ impl Display for Message { Message::ByteCodes(_) => "snap:ByteCodes".fmt(f), Message::GetTrieNodes(_) => "snap:GetTrieNodes".fmt(f), Message::TrieNodes(_) => "snap:TrieNodes".fmt(f), + #[cfg(feature = "l2")] Message::L2(l2_msg) => match l2_msg { L2Message::BatchSealed(_) => "based:BatchSealed".fmt(f), L2Message::NewBlock(_) => "based:NewBlock".fmt(f), diff --git a/crates/networking/p2p/rlpx/mod.rs b/crates/networking/p2p/rlpx/mod.rs index 0e0b8e40326..bedcc4c9197 100644 --- a/crates/networking/p2p/rlpx/mod.rs +++ b/crates/networking/p2p/rlpx/mod.rs @@ -2,6 +2,7 @@ pub mod connection; pub mod error; pub mod eth; pub mod initiator; +#[cfg(feature = "l2")] pub mod l2; pub mod message; pub mod p2p; diff --git a/docs/CLI.md b/docs/CLI.md index ecf631e13d7..d80d91c1449 100644 --- a/docs/CLI.md +++ b/docs/CLI.md @@ -14,7 +14,6 @@ Commands: import Import blocks to the database export Export blocks in the current chain into a file in rlp encoding compute-state-root Compute the state root from a genesis file - l2 help Print this message or the help of the given subcommand(s) Options: diff --git a/tooling/reorgs/Cargo.toml b/tooling/reorgs/Cargo.toml index bce23b2ec0c..a51b4564a15 100644 --- a/tooling/reorgs/Cargo.toml +++ b/tooling/reorgs/Cargo.toml @@ -4,7 +4,7 @@ version.workspace = true edition.workspace = true [dependencies] -ethrex.workspace = true +ethrex = { workspace = true, features = ["l2"] } ethrex-common.workspace = true ethrex-blockchain.workspace = true ethrex-rpc.workspace = true