From ce7eb98ddaa73fab7219c1f0f2de4b7ebd258f9e Mon Sep 17 00:00:00 2001 From: Rodrigo Oliveri Date: Thu, 31 Oct 2024 17:31:12 -0300 Subject: [PATCH 1/9] chore(l1): reduce unnecessary docker builds and remove duplicate workflow executions in main (#1032) **Motivation** We are re-building our docker image across the jobs/workflows multiple times, the idea is to remove as much of the duplicate executions as possible. At the same time, while merging to main we run duplicated jobs, the idea is to remove that duplication. **Description** This PR accomplish a couple of things: - It creates a new make tasks for running hive without depending on the image build - It generates a new job in the hive workflow to build the image once and then share it with every simulation - Merges the assertoor stability check with hive to leverage the docker image already built - Fix an issue were workflows were triggered two times in a row while merging to main (both `merge_group` and `push` rules were set) Resolves #1008 Resolves #1041 --- .github/workflows/assertoor.yaml | 43 -------- .github/workflows/ci.yaml | 2 - .github/workflows/hive.yaml | 67 ------------ .github/workflows/hive_and_assertoor.yaml | 124 ++++++++++++++++++++++ .github/workflows/levm_bench.yaml | 2 - Makefile | 3 + 6 files changed, 127 insertions(+), 114 deletions(-) delete mode 100644 .github/workflows/assertoor.yaml delete mode 100644 .github/workflows/hive.yaml create mode 100644 .github/workflows/hive_and_assertoor.yaml diff --git a/.github/workflows/assertoor.yaml b/.github/workflows/assertoor.yaml deleted file mode 100644 index 77431ed9c..000000000 --- a/.github/workflows/assertoor.yaml +++ /dev/null @@ -1,43 +0,0 @@ -name: Assertoor -on: - merge_group: - push: - branches: [ main ] - pull_request: - branches: [ '*' ] - paths-ignore: - - "crates/l2/**" - - 'README.md' - - 'LICENSE' - - "**/README.md" - - "**/docs/**" - -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -env: - RUST_VERSION: 1.80.1 - -jobs: - test-run: - name: Stability Check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Build Docker image - uses: docker/build-push-action@v5 - with: - context: . - file: ./Dockerfile - load: true # Important for building without pushing - tags: ethereum_rust - - name: Setup kurtosis testnet and run assertoor tests - uses: ethpandaops/kurtosis-assertoor-github-action@v1 - with: - kurtosis_version: '1.3.1' - ethereum_package_url: 'github.com/lambdaclass/ethereum-package' - ethereum_package_branch: 'ethereum-rust-integration' - ethereum_package_args: './test_data/network_params.yaml' diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 5e2648116..e5a31c14b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,8 +1,6 @@ name: CI on: merge_group: - push: - branches: [main] pull_request: branches: ["**"] paths-ignore: diff --git a/.github/workflows/hive.yaml b/.github/workflows/hive.yaml deleted file mode 100644 index 1f0fe044b..000000000 --- a/.github/workflows/hive.yaml +++ /dev/null @@ -1,67 +0,0 @@ -# Runs the specified hive testing suites -name: Hive -on: - merge_group: - push: - branches: [main] - pull_request: - branches: ["*"] - paths-ignore: - - "crates/l2/**" - - 'README.md' - - 'LICENSE' - - "**/README.md" - - "**/docs/**" - -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -env: - RUST_VERSION: 1.80.1 - -jobs: - run-hive: - name: ${{ matrix.name }} - runs-on: ubuntu-latest - strategy: - matrix: - include: - - simulation: rpc-compat - name: "Rpc Compat tests" - run_command: make run-hive SIMULATION=ethereum/rpc-compat TEST_PATTERN="/eth_chainId|eth_getTransactionByBlockHashAndIndex|eth_getTransactionByBlockNumberAndIndex|eth_getCode|eth_getStorageAt|eth_call|eth_getTransactionByHash|eth_getBlockByHash|eth_getBlockByNumber|eth_createAccessList|eth_getBlockTransactionCountByNumber|eth_getBlockTransactionCountByHash|eth_getBlockReceipts|eth_getTransactionReceipt|eth_blobGasPrice|eth_blockNumber|ethGetTransactionCount|debug_getRawHeader|debug_getRawBlock|debug_getRawTransaction|debug_getRawReceipts|eth_estimateGas|eth_getBalance|eth_sendRawTransaction|eth_getProof|eth_getLogs" - - simulation: rpc-auth - name: "Rpc Auth tests" - run_command: make run-hive SIMULATION=ethereum/rpc-compat TEST_PATTERN="/engine-auth" - - simulation: discv4 - name: "Devp2p discv4 tests" - run_command: make run-hive SIMULATION=devp2p TEST_PATTERN="discv4" - - simulation: snap - name: "Devp2p snap tests" - run_command: make run-hive SIMULATION=devp2p TEST_PATTERN="/AccountRange" - - simulation: engine - name: "Engine tests" - run_command: make run-hive SIMULATION=ethereum/engine TEST_PATTERN="/Blob Transactions On Block 1, Cancun Genesis|Blob Transactions On Block 1, Shanghai Genesis|Blob Transaction Ordering, Single Account, Single Blob|Blob Transaction Ordering, Single Account, Dual Blob|Blob Transaction Ordering, Multiple Accounts|Replace Blob Transactions|Parallel Blob Transactions|ForkchoiceUpdatedV3 Modifies Payload ID on Different Beacon Root|NewPayloadV3 After Cancun|NewPayloadV3 Versioned Hashes|ForkchoiceUpdated Version on Payload Request" - - simulation: engine-cancun - name: "Cancun Engine tests" - run_command: make run-hive SIMULATION=ethereum/engine TEST_PATTERN="cancun/Unique Payload ID|ParentHash equals BlockHash on NewPayload|Re-Execute Payload|Payload Build after New Invalid Payload|RPC|Build Payload with Invalid ChainID|Invalid PayloadAttributes, Zero timestamp, Syncing=False|Invalid PayloadAttributes, Parent timestamp, Syncing=False|Invalid PayloadAttributes, Missing BeaconRoot, Syncing=False|Suggested Fee Recipient Test|PrevRandao Opcode Transactions Test|Invalid Missing Ancestor ReOrg, StateRoot" - steps: - - name: Checkout sources - uses: actions/checkout@v3 - - - name: Rustup toolchain install - uses: dtolnay/rust-toolchain@stable - with: - toolchain: ${{ env.RUST_VERSION }} - - - name: Setup Go - uses: actions/setup-go@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Download Hive - run: make setup-hive - - - name: Run Hive Simulation - run: ${{ matrix.run_command }} diff --git a/.github/workflows/hive_and_assertoor.yaml b/.github/workflows/hive_and_assertoor.yaml new file mode 100644 index 000000000..be66b53b0 --- /dev/null +++ b/.github/workflows/hive_and_assertoor.yaml @@ -0,0 +1,124 @@ +name: "Hive & Assertoor" +on: + merge_group: + paths-ignore: + - "crates/l2/**" + - 'README.md' + - 'LICENSE' + - "**/README.md" + - "**/docs/**" + pull_request: + branches: ["**"] + paths-ignore: + - "crates/l2/**" + - 'README.md' + - 'LICENSE' + - "**/README.md" + - "**/docs/**" + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +env: + RUST_VERSION: 1.80.1 + +jobs: + docker-build: + name: Docker Build image + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile + load: true + tags: ethereum_rust + outputs: type=docker,dest=/tmp/ethereum_rust_image.tar + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: ethereum_rust_image + path: /tmp/ethereum_rust_image.tar + + run-hive: + name: Hive - ${{ matrix.name }} + needs: [docker-build] + runs-on: ubuntu-latest + strategy: + matrix: + include: + - simulation: rpc-compat + name: "Rpc Compat tests" + run_command: make run-hive-on-latest SIMULATION=ethereum/rpc-compat TEST_PATTERN="/eth_chainId|eth_getTransactionByBlockHashAndIndex|eth_getTransactionByBlockNumberAndIndex|eth_getCode|eth_getStorageAt|eth_call|eth_getTransactionByHash|eth_getBlockByHash|eth_getBlockByNumber|eth_createAccessList|eth_getBlockTransactionCountByNumber|eth_getBlockTransactionCountByHash|eth_getBlockReceipts|eth_getTransactionReceipt|eth_blobGasPrice|eth_blockNumber|ethGetTransactionCount|debug_getRawHeader|debug_getRawBlock|debug_getRawTransaction|debug_getRawReceipts|eth_estimateGas|eth_getBalance|eth_sendRawTransaction|eth_getProof|eth_getLogs" + - simulation: rpc-auth + name: "Rpc Auth tests" + run_command: make run-hive-on-latest SIMULATION=ethereum/rpc-compat TEST_PATTERN="/engine-auth" + - simulation: discv4 + name: "Devp2p discv4 tests" + run_command: make run-hive-on-latest SIMULATION=devp2p TEST_PATTERN="discv4" + - simulation: snap + name: "Devp2p snap tests" + run_command: make run-hive-on-latest SIMULATION=devp2p TEST_PATTERN="/AccountRange" + - simulation: engine + name: "Engine tests" + run_command: make run-hive-on-latest SIMULATION=ethereum/engine TEST_PATTERN="/Blob Transactions On Block 1, Cancun Genesis|Blob Transactions On Block 1, Shanghai Genesis|Blob Transaction Ordering, Single Account, Single Blob|Blob Transaction Ordering, Single Account, Dual Blob|Blob Transaction Ordering, Multiple Accounts|Replace Blob Transactions|Parallel Blob Transactions|ForkchoiceUpdatedV3 Modifies Payload ID on Different Beacon Root|NewPayloadV3 After Cancun|NewPayloadV3 Versioned Hashes|ForkchoiceUpdated Version on Payload Request" + - simulation: engine-cancun + name: "Cancun Engine tests" + run_command: make run-hive-on-latest SIMULATION=ethereum/engine TEST_PATTERN="cancun/Unique Payload ID|ParentHash equals BlockHash on NewPayload|Re-Execute Payload|Payload Build after New Invalid Payload|RPC|Build Payload with Invalid ChainID|Invalid PayloadAttributes, Zero timestamp, Syncing=False|Invalid PayloadAttributes, Parent timestamp, Syncing=False|Invalid PayloadAttributes, Missing BeaconRoot, Syncing=False|Suggested Fee Recipient Test|PrevRandao Opcode Transactions Test|Invalid Missing Ancestor ReOrg, StateRoot" + steps: + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + name: ethereum_rust_image + path: /tmp + + - name: Load image + run: | + docker load --input /tmp/ethereum_rust_image.tar + + - name: Checkout sources + uses: actions/checkout@v3 + + - name: Rustup toolchain install + uses: dtolnay/rust-toolchain@stable + with: + toolchain: ${{ env.RUST_VERSION }} + + - name: Setup Go + uses: actions/setup-go@v3 + + - name: Run Hive Simulation + run: ${{ matrix.run_command }} + + run-assertoor: + name: Assertoor - Stability Check + runs-on: ubuntu-latest + needs: [docker-build] + steps: + - uses: actions/checkout@v4 + + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + name: ethereum_rust_image + path: /tmp + + - name: Load image + run: | + docker load --input /tmp/ethereum_rust_image.tar + + - name: Setup kurtosis testnet and run assertoor tests + uses: ethpandaops/kurtosis-assertoor-github-action@v1 + with: + kurtosis_version: '1.3.1' + ethereum_package_url: 'github.com/lambdaclass/ethereum-package' + ethereum_package_branch: 'ethereum-rust-integration' + ethereum_package_args: './test_data/network_params.yaml' diff --git a/.github/workflows/levm_bench.yaml b/.github/workflows/levm_bench.yaml index 6a4aed5fa..d297972c2 100644 --- a/.github/workflows/levm_bench.yaml +++ b/.github/workflows/levm_bench.yaml @@ -2,10 +2,8 @@ name: LEVM benchmarks on: merge_group: - push: paths: - 'crates/vm/levm/**' - branches: [ main ] pull_request: paths: - 'crates/vm/levm/**' diff --git a/Makefile b/Makefile index 39440f534..4a6b3ff07 100644 --- a/Makefile +++ b/Makefile @@ -97,6 +97,9 @@ TEST_PATTERN ?= / run-hive: build-image setup-hive ## πŸ§ͺ Run Hive testing suite cd hive && ./hive --sim $(SIMULATION) --client ethereumrust --sim.limit "$(TEST_PATTERN)" +run-hive-on-latest: setup-hive ## πŸ§ͺ Run Hive testing suite with the latest docker image + cd hive && ./hive --sim $(SIMULATION) --client ethereumrust --sim.limit "$(TEST_PATTERN)" + run-hive-debug: build-image setup-hive ## 🐞 Run Hive testing suite in debug mode cd hive && ./hive --sim $(SIMULATION) --client ethereumrust --sim.limit "$(TEST_PATTERN)" --docker.output From 4c18bd43314c043a46b995b76da78474bc4ee785 Mon Sep 17 00:00:00 2001 From: Federico Borello <156438142+fborello-lambda@users.noreply.github.com> Date: Thu, 31 Oct 2024 18:16:09 -0300 Subject: [PATCH 2/9] feat(l2): standalone test for prover (#969) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Motivation** There is a need to run the zkVMs efficiently and quickly to perform time measurements for performance estimations. **Description** The PR adds: - Standalone test for the prover. - CI for the prover running the test in `dev` mode - No Proof Generation. Closes #968 --------- Co-authored-by: EstΓ©fano Bargas --- .github/workflows/ci.yaml | 24 +----- .github/workflows/l2_prover_ci.yaml | 57 +++++++++++++ Makefile | 2 +- crates/l2/Makefile | 3 + crates/l2/docs/prover.md | 80 +++++++++++++++--- crates/l2/prover/Cargo.toml | 7 ++ crates/l2/prover/Makefile | 10 +++ crates/l2/prover/src/prover.rs | 51 ++++++----- crates/l2/prover/tests/perf_zkvm.rs | 80 ++++++++++++++++++ .../l2/prover/zkvm/interface/guest/Cargo.toml | 3 +- .../prover/zkvm/interface/guest/src/main.rs | 8 +- crates/l2/utils/mod.rs | 1 + crates/l2/utils/test_data_io.rs | 72 ++++++++++++++++ test_data/l2-loadtest.rlp | Bin 0 -> 274983 bytes 14 files changed, 338 insertions(+), 60 deletions(-) create mode 100644 .github/workflows/l2_prover_ci.yaml create mode 100644 crates/l2/prover/Makefile create mode 100644 crates/l2/prover/tests/perf_zkvm.rs create mode 100644 crates/l2/utils/test_data_io.rs create mode 100644 test_data/l2-loadtest.rlp diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e5a31c14b..c9e4cd144 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -42,7 +42,7 @@ jobs: - name: Run cargo clippy run: | - cargo clippy --all-targets --all-features --workspace -- -D warnings + cargo clippy --all-targets --all-features --workspace --exclude ethereum_rust-prover -- -D warnings - name: Run cargo fmt run: | @@ -84,25 +84,3 @@ jobs: context: . file: ./Dockerfile load: true # Important for building without pushing - - prover: - name: Build RISC-V zkVM program - runs-on: ubuntu-latest - steps: - - name: Checkout sources - uses: actions/checkout@v3 - - - name: Rust toolchain install - uses: dtolnay/rust-toolchain@stable - with: - toolchain: ${{ env.RUST_VERSION }} - - - name: RISC-V zkVM toolchain install - run: | - curl -L https://risczero.com/install | bash - ~/.risc0/bin/rzup install - - - name: Build prover and zkVM - run: | - cd crates/l2/prover - cargo build --release --features build_zkvm diff --git a/.github/workflows/l2_prover_ci.yaml b/.github/workflows/l2_prover_ci.yaml new file mode 100644 index 000000000..d437832d0 --- /dev/null +++ b/.github/workflows/l2_prover_ci.yaml @@ -0,0 +1,57 @@ +name: L2 Prover CI +on: + push: + branches: ["main"] + paths: + - "crates/l2/prover/**" + pull_request: + branches: ["**"] + paths: + - "crates/l2/prover/**" + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + strategy: + fail-fast: true + matrix: + action: + - command: check + args: -p ethereum_rust-prover + - command: clippy + args: -p ethereum_rust-prover --all-targets --no-default-features + steps: + - name: Checkout sources + uses: actions/checkout@v4 + - name: Add Rust Cache + uses: Swatinem/rust-cache@v2 + - name: ${{ matrix.action.command }} Command + run: cargo ${{ matrix.action.command }} ${{ matrix.action.args }} + + build_and_test: + name: Build and Test RISC-V zkVM program + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v4 + - name: Rust toolchain install + uses: dtolnay/rust-toolchain@stable + - name: RISC-V zkVM toolchain install + run: | + curl -L https://risczero.com/install | bash + ~/.risc0/bin/rzup install + - name: Caching + uses: Swatinem/rust-cache@v2 + - name: Build prover and zkVM + run: | + cd crates/l2/prover + cargo build --release --features build_zkvm + - name: Test Prover Execution + run: | + cd crates/l2/prover + RUST_LOG=info make perf_test_proving diff --git a/Makefile b/Makefile index 4a6b3ff07..dd958c872 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ SPECTEST_VECTORS_DIR := cmd/ef_tests/vectors CRATE ?= * test: $(SPECTEST_VECTORS_DIR) ## πŸ§ͺ Run each crate's tests - cargo test -p '$(CRATE)' --workspace + cargo test -p '$(CRATE)' --workspace --exclude ethereum_rust-prover clean: clean-vectors ## 🧹 Remove build artifacts cargo clean diff --git a/crates/l2/Makefile b/crates/l2/Makefile index 23914ec55..a43fcc7d6 100644 --- a/crates/l2/Makefile +++ b/crates/l2/Makefile @@ -61,3 +61,6 @@ restart-l2: down-l2 init-l2 ## πŸ”„ Restarts the L2 Lambda Ethereum Rust Client init-l2-prover: ## πŸš€ Initializes the Prover cargo run --release --features build_zkvm --manifest-path ../../Cargo.toml --bin ethereum_rust_prover + +init-l2-prover-gpu: ## πŸš€ Initializes the Prover with GPU support + cargo run --release --features "build_zkvm,cuda" --manifest-path ../../Cargo.toml --bin ethereum_rust_prover diff --git a/crates/l2/docs/prover.md b/crates/l2/docs/prover.md index 88dcf2ceb..df3c45060 100644 --- a/crates/l2/docs/prover.md +++ b/crates/l2/docs/prover.md @@ -3,29 +3,28 @@ ## ToC - [ToC](#toc) -- [Prover](#prover) - - [How to Run](#how-to-run) +- [What](#what) - [Workflow](#workflow) +- [How](#how) + - [Dev Mode](#dev-mode) + - [Quick Test](#quick-test) + - [GPU mode](#gpu-mode) + - [Proving Process Test](#proving-process-test) - [Configuration](#configuration) >[!NOTE] > The shipping/deploying process and the `Prover` itself is under development. -## Prover +## What -The RISC-V zkVM Prover currently runs an empty program. To mock proof generation and avoid RAM requirements, you can use the following envar: `RISC0_DEV_MODE=1`. [risczero - dev-mode](https://dev.risczero.com/api/generating-proofs/dev-mode). +The prover consists of two main components: handling incoming proving data from the `L2 proposer`, specifically the `prover_server` component, and the `zkVM`. The `prover_client` is responsible for this first part, while the `zkVM` serves as a RISC-V emulator executing code specified in `crates/l2/prover/zkvm/interface/guest/src`. +Before the `zkVM` code (or guest), there is a directory called `interface`, which indicates that we access the `zkVM` through the "interface" crate. -### How to Run - -Dependencies: `cargo-risczero` [dev - risczero - installation](https://dev.risczero.com/api/zkvm/install) - -If you are at `crates/l2`, you will have to set the `.env` file (the `.example.env` can be used) and then run `make init-l2-prover`. - -The `build_zkvm` flag is used, if you don't have the risc0's "sdk", you can build the prover without the feature to check all the surrounding components of the `zkvm`. +In summary, the `prover_client` manages the inputs from the `prover_server` and then "calls" the `zkVM` to perform the proving process and generate the `groth16` ZK proof. ## Workflow -The `Prover Server` is monitoring requests for new jobs from the `Prover Client`, sent when the prover is free. When a new job arrives, the Prover will generate the proof and then the `Prover Client` will send it to the `Prover Server`. +The `Prover Server` monitors requests for new jobs from the `Prover Client`, which are sent when the prover is available. Upon receiving a new job, the Prover generates the proof, after which the `Prover Client` sends the proof back to the `Prover Server`. ```mermaid sequenceDiagram @@ -40,6 +39,63 @@ sequenceDiagram ProverServer-->>-ProverClient: ProofData::SubmitAck(block_number) ``` +## How + +### Dev Mode + +**Dependencies:** +- [RISC0](https://dev.risczero.com/api/zkvm/install) + +To run the blockchain (`proposer`) and prover in conjunction in a development environment, set the following environment variable: `RISC0_DEV_MODE=1` [(docs)](https://dev.risczero.com/api/generating-proofs/dev-mode). If you are in the `crates/l2` directory, you will need to set the environment variable for `dev_mode`. The `.env.example` file should suffice. + +To start the `prover_client`, use the following command: + +```sh +make init-l2-prover +``` + +The `build_zkvm` flag is used, if you don't have the risc0's "sdk", you can build the prover without the feature to check if all the surrounding components of the `zkvm` can be compiled. + +#### Quick Test + +To test the `zkvm` execution quickly, the following test can be run: + +```sh +cd crates/l2/prover +make perf_test_proving +``` + +### GPU mode + +**Dependencies (based on the Docker CUDA image):** + +>[!NOTE] +> If you don't want to run it inside a Docker container based on the NVIDIA CUDA image, [the following steps from RISC0](https://dev.risczero.com/api/generating-proofs/local-proving) may be helpful. + +- [Rust](https://www.rust-lang.org/tools/install) +- [RISC0](https://dev.risczero.com/api/zkvm/install) + +Next, install the following packages: + +```sh +sudo apt-get install libssl-dev pkg-config libclang-dev clang +``` + +To start the `prover_client`, use the following command: + +```sh +make init-l2-prover-gpu +``` + +#### Proving Process Test + +To test the `zkvm` proving process using a `gpu` quickly, the following test can be run: + +```sh +cd crates/l2/prover +make perf_gpu +``` + ## Configuration The following environment variables are available to configure the prover: diff --git a/crates/l2/prover/Cargo.toml b/crates/l2/prover/Cargo.toml index df2239f1c..3c93e46b5 100644 --- a/crates/l2/prover/Cargo.toml +++ b/crates/l2/prover/Cargo.toml @@ -16,6 +16,7 @@ hex.workspace = true # ethereum_rust ethereum_rust-core.workspace = true +ethereum_rust-vm.workspace = true ethereum_rust-rlp.workspace = true # l2 @@ -32,6 +33,11 @@ revm = { version = "14.0.3", features = [ "kzg-rs", ], default-features = false } +[dev-dependencies] +ethereum_rust-vm.workspace = true +ethereum_rust-storage.workspace = true +ethereum_rust-blockchain.workspace = true + [lib] name = "ethereum_rust_prover_lib" path = "src/lib.rs" @@ -43,3 +49,4 @@ path = "src/main.rs" [features] default = [] build_zkvm = ["zkvm_interface/build_zkvm"] +gpu = ["risc0-zkvm/cuda"] diff --git a/crates/l2/prover/Makefile b/crates/l2/prover/Makefile new file mode 100644 index 000000000..468e14c7a --- /dev/null +++ b/crates/l2/prover/Makefile @@ -0,0 +1,10 @@ +RISC0_DEV_MODE?=1 +RUST_LOG?="debug" +perf_test_proving: + @echo "Using RISC0_DEV_MODE: ${RISC0_DEV_MODE}" + RISC0_DEV_MODE=${RISC0_DEV_MODE} RUST_LOG=${RUST_LOG} cargo test --release --test perf_zkvm --features build_zkvm -- --show-output +.PHONY: perf_test_proving + +perf_gpu: + RUSTFLAGS="-C target-cpu=native" RISC0_DEV_MODE=0 RUST_LOG="debug" cargo test --release --test perf_zkvm --features "build_zkvm,gpu" -- --show-output +.PHONY: perf_gpu diff --git a/crates/l2/prover/src/prover.rs b/crates/l2/prover/src/prover.rs index 4826755a6..1840d2b25 100644 --- a/crates/l2/prover/src/prover.rs +++ b/crates/l2/prover/src/prover.rs @@ -1,4 +1,4 @@ -use ethereum_rust_core::types::Block; +use serde::Deserialize; use tracing::info; // risc0 @@ -6,10 +6,24 @@ use zkvm_interface::methods::{ZKVM_PROGRAM_ELF, ZKVM_PROGRAM_ID}; use risc0_zkvm::{default_prover, ExecutorEnv, ExecutorEnvBuilder, ProverOpts}; +use ethereum_rust_core::types::Receipt; +use ethereum_rust_l2::{ + proposer::prover_server::ProverInputData, utils::config::prover_client::ProverClientConfig, +}; use ethereum_rust_rlp::encode::RLPEncode; - -use ethereum_rust_l2::proposer::prover_server::ProverInputData; -use ethereum_rust_l2::utils::config::prover_client::ProverClientConfig; +use ethereum_rust_vm::execution_db::ExecutionDB; + +// The order of variables in this structure should match the order in which they were +// committed in the zkVM, with each variable represented by a field. +#[derive(Debug, Deserialize)] +pub struct ProverOutputData { + /// It is rlp encoded, it has to be decoded. + /// Block::decode(&prover_output_data.block).unwrap()); + pub _block: Vec, + pub _execution_db: ExecutionDB, + pub _parent_block_header: Vec, + pub block_receipts: Vec, +} pub struct Prover<'a> { env_builder: ExecutorEnvBuilder<'a>, @@ -48,36 +62,33 @@ impl<'a> Prover<'a> { /// Example: /// let prover = Prover::new(); /// let proof = prover.set_input(inputs).prove().unwrap(); - pub fn prove(&mut self) -> Result { - let env = self - .env_builder - .build() - .map_err(|_| "Failed to Build env".to_string())?; + pub fn prove(&mut self) -> Result> { + let env = self.env_builder.build()?; // Generate the Receipt let prover = default_prover(); // Proof information by proving the specified ELF binary. // This struct contains the receipt along with statistics about execution of the guest - let prove_info = prover - .prove_with_opts(env, self.elf, &ProverOpts::groth16()) - .map_err(|_| "Failed to prove".to_string())?; + let prove_info = prover.prove_with_opts(env, self.elf, &ProverOpts::groth16())?; // extract the receipt. let receipt = prove_info.receipt; - let executed_block: Block = receipt.journal.decode().map_err(|err| err.to_string())?; - - info!( - "Successfully generated execution proof receipt for block {}", - executed_block.header.compute_block_hash() - ); + info!("Successfully generated execution receipt."); Ok(receipt) } - pub fn verify(&self, receipt: &risc0_zkvm::Receipt) -> Result<(), String> { + pub fn verify(&self, receipt: &risc0_zkvm::Receipt) -> Result<(), Box> { // Verify the proof. - receipt.verify(self.id).unwrap(); + receipt.verify(self.id)?; Ok(()) } + + pub fn get_commitment( + receipt: &risc0_zkvm::Receipt, + ) -> Result> { + let commitment: ProverOutputData = receipt.journal.decode()?; + Ok(commitment) + } } diff --git a/crates/l2/prover/tests/perf_zkvm.rs b/crates/l2/prover/tests/perf_zkvm.rs new file mode 100644 index 000000000..7fdec57d3 --- /dev/null +++ b/crates/l2/prover/tests/perf_zkvm.rs @@ -0,0 +1,80 @@ +use std::path::PathBuf; +use tracing::info; + +use ethereum_rust_blockchain::add_block; +use ethereum_rust_l2::proposer::prover_server::ProverInputData; +use ethereum_rust_prover_lib::prover::Prover; +use ethereum_rust_storage::{EngineType, Store}; +use ethereum_rust_vm::execution_db::ExecutionDB; + +#[tokio::test] +async fn test_performance_zkvm() { + tracing_subscriber::fmt::init(); + + let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + // Go back 3 levels (Go to the root of the project) + for _ in 0..3 { + path.pop(); + } + path.push("test_data"); + + // Another use is genesis-execution-api.json in conjunction with chain.rlp(20 blocks not too loaded). + let genesis_file_path = path.join("genesis-l2.json"); + // l2-loadtest.rlp has blocks with many txs. + let chain_file_path = path.join("l2-loadtest.rlp"); + + let store = Store::new("memory", EngineType::InMemory).expect("Failed to create Store"); + + let genesis = ethereum_rust_l2::utils::test_data_io::read_genesis_file( + genesis_file_path.to_str().unwrap(), + ); + store.add_initial_state(genesis.clone()).unwrap(); + + let blocks = + ethereum_rust_l2::utils::test_data_io::read_chain_file(chain_file_path.to_str().unwrap()); + info!("Number of blocks to insert: {}", blocks.len()); + + for block in &blocks { + add_block(block, &store).unwrap(); + } + let block_to_prove = blocks.last().unwrap(); + + let db = ExecutionDB::from_exec(block_to_prove, &store).unwrap(); + + let parent_header = store + .get_block_header_by_hash(block_to_prove.header.parent_hash) + .unwrap() + .unwrap(); + + let input = ProverInputData { + db, + block: block_to_prove.clone(), + parent_header, + }; + + let mut prover = Prover::new(); + prover.set_input(input); + + let start = std::time::Instant::now(); + + let receipt = prover.prove().unwrap(); + + let duration = start.elapsed(); + info!( + "Number of EIP1559 transactions in the proven block: {}", + block_to_prove.body.transactions.len() + ); + info!("[SECONDS] Proving Took: {:?}", duration); + info!("[MINUTES] Proving Took: {}[m]", duration.as_secs() / 60); + + prover.verify(&receipt).unwrap(); + + let output = Prover::get_commitment(&receipt).unwrap(); + + let execution_cumulative_gas_used = output.block_receipts.last().unwrap().cumulative_gas_used; + info!("Cumulative Gas Used {execution_cumulative_gas_used}"); + + let gas_per_second = execution_cumulative_gas_used as f64 / duration.as_secs_f64(); + + info!("Gas per Second: {}", gas_per_second); +} diff --git a/crates/l2/prover/zkvm/interface/guest/Cargo.toml b/crates/l2/prover/zkvm/interface/guest/Cargo.toml index ff0d14608..a0f8c93e8 100644 --- a/crates/l2/prover/zkvm/interface/guest/Cargo.toml +++ b/crates/l2/prover/zkvm/interface/guest/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" [workspace] [dependencies] -risc0-zkvm = { version = "1.1.2", default-features = false, features = ['std'] } +risc0-zkvm = { version = "1.1.2", default-features = false, features = ["std"] } ethereum_rust-core = { path = "../../../../../common", default-features = false } ethereum_rust-rlp = { path = "../../../../../common/rlp" } @@ -18,3 +18,4 @@ crypto-bigint = { git = "https://github.com/risc0/RustCrypto-crypto-bigint", tag k256 = { git = "https://github.com/risc0/RustCrypto-elliptic-curves", tag = "k256/v0.13.3-risczero.0" } sha2 = { git = "https://github.com/risc0/RustCrypto-hashes", tag = "sha2-v0.10.6-risczero.0" } secp256k1 = { git = "https://github.com/sp1-patches/rust-secp256k1", branch = "patch-secp256k1-v0.29.1" } +ecdsa-core = { git = "https://github.com/sp1-patches/signatures", package = "ecdsa", branch = "patch-ecdsa-v0.16.9" } diff --git a/crates/l2/prover/zkvm/interface/guest/src/main.rs b/crates/l2/prover/zkvm/interface/guest/src/main.rs index 95eefaf92..967f0eeb1 100644 --- a/crates/l2/prover/zkvm/interface/guest/src/main.rs +++ b/crates/l2/prover/zkvm/interface/guest/src/main.rs @@ -1,4 +1,4 @@ -use ethereum_rust_rlp::{decode::RLPDecode, error::RLPDecodeError}; +use ethereum_rust_rlp::{decode::RLPDecode, encode::RLPEncode, error::RLPDecodeError}; use risc0_zkvm::guest::env; use ethereum_rust_blockchain::{validate_block, validate_gas_used}; @@ -14,6 +14,8 @@ fn main() { let receipts = execute_block(&block, &mut state).unwrap(); + env::commit(&receipts); + validate_gas_used(&receipts, &block.header).expect("invalid gas used"); let _account_updates = get_state_transitions(&mut state); @@ -31,9 +33,9 @@ fn read_inputs() -> Result<(Block, ExecutionDB, BlockHeader), RLPDecodeError> { let parent_header = BlockHeader::decode(&parent_header_bytes)?; // make inputs public - env::commit(&block); + env::commit(&block.encode_to_vec()); env::commit(&execution_db); - env::commit(&parent_header); + env::commit(&parent_header.encode_to_vec()); Ok((block, execution_db, parent_header)) } diff --git a/crates/l2/utils/mod.rs b/crates/l2/utils/mod.rs index 56e2b0a25..6b6123054 100644 --- a/crates/l2/utils/mod.rs +++ b/crates/l2/utils/mod.rs @@ -5,6 +5,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; pub mod config; pub mod eth_client; pub mod merkle_tree; +pub mod test_data_io; pub fn secret_key_deserializer<'de, D>(deserializer: D) -> Result where diff --git a/crates/l2/utils/test_data_io.rs b/crates/l2/utils/test_data_io.rs new file mode 100644 index 000000000..dcc7833ca --- /dev/null +++ b/crates/l2/utils/test_data_io.rs @@ -0,0 +1,72 @@ +use ethereum_rust_core::types::{Block, Genesis}; +use ethereum_rust_rlp::{decode::RLPDecode, encode::RLPEncode}; +use ethereum_rust_storage::Store; +use tracing::info; + +use std::{ + fs::File, + io::{BufReader, Read as _, Write}, + path::PathBuf, +}; + +// From cmd/ethereum_rust +pub fn read_chain_file(chain_rlp_path: &str) -> Vec { + let chain_file = File::open(chain_rlp_path).expect("Failed to open chain rlp file"); + _chain_file(chain_file).expect("Failed to decode chain rlp file") +} + +// From cmd/ethereum_rust +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") +} + +/// Generates a `test.rlp` file for use by the prover during testing. +/// Place this in the `proposer/mod.rs` file, +/// specifically in the `start` function, +/// before calling `send_commitment()` to send the block commitment. +pub fn generate_rlp( + up_to_block_number: u64, + block: Block, + store: &Store, +) -> Result<(), Box> { + if block.header.number == up_to_block_number { + let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let file_name = "l2-test.rlp"; + + path.push(file_name); + + let mut file = std::fs::File::create(path.to_str().unwrap())?; + for i in 1..up_to_block_number { + let body = store.get_block_body(i)?.unwrap(); + let header = store.get_block_header(i)?.unwrap(); + + let block = Block::new(header, body); + let vec = block.encode_to_vec(); + file.write_all(&vec)?; + } + + info!("TEST RLP GENERATED AT: {path:?}"); + } + Ok(()) +} + +// From cmd/ethereum_rust/decode.rs +fn _chain_file(file: File) -> Result, Box> { + let mut chain_rlp_reader = BufReader::new(file); + let mut buf = vec![]; + chain_rlp_reader.read_to_end(&mut buf)?; + let mut blocks = Vec::new(); + while !buf.is_empty() { + let (item, rest) = Block::decode_unfinished(&buf)?; + blocks.push(item); + buf = rest.to_vec(); + } + Ok(blocks) +} + +// From cmd/ethereum_rust/decode.rs +fn _genesis_file(file: File) -> Result { + let genesis_reader = BufReader::new(file); + serde_json::from_reader(genesis_reader) +} diff --git a/test_data/l2-loadtest.rlp b/test_data/l2-loadtest.rlp new file mode 100644 index 0000000000000000000000000000000000000000..75b0a1d607960bbcb23abf67fc0ac2ff476a895f GIT binary patch literal 274983 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 literal 0 HcmV?d00001 From 59bab9abd7a179ee4b6c924053a56a083d9151a7 Mon Sep 17 00:00:00 2001 From: Akash S M Date: Fri, 1 Nov 2024 22:00:33 +0530 Subject: [PATCH 3/9] refactor(l1): reduce storage costs in the OnChainProposer contract (#1040) **Motivation** Drop the `verifiedBlocks` mapping in favor of just storing a `lastVerifiedBlock` block number to reduce storage costs. Closes #998 --- crates/l2/contracts/src/l1/OnChainProposer.sol | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/crates/l2/contracts/src/l1/OnChainProposer.sol b/crates/l2/contracts/src/l1/OnChainProposer.sol index e78e3d0b7..6f3b30285 100644 --- a/crates/l2/contracts/src/l1/OnChainProposer.sol +++ b/crates/l2/contracts/src/l1/OnChainProposer.sol @@ -21,11 +21,12 @@ contract OnChainProposer is IOnChainProposer, ReentrancyGuard { /// @dev It is used by other contracts to verify if a block was committed. mapping(uint256 => BlockCommitmentInfo) public blockCommitments; - /// @notice The verified blocks. - /// @dev If a block is verified, the block hash is stored here. - /// @dev If a block was not verified yet, it won't be here. - /// @dev It is used by other contracts to verify if a block was verified. - mapping(uint256 => bool) public verifiedBlocks; + /// @notice The latest verified block number. + /// @dev This variable holds the block number of the most recently verified block. + /// @dev All blocks with a block number less than or equal to `lastVerifiedBlock` are considered verified. + /// @dev Blocks with a block number greater than `lastVerifiedBlock` have not been verified yet. + /// @dev This is crucial for ensuring that only valid and confirmed blocks are processed in the contract. + uint256 public lastVerifiedBlock; address public BRIDGE; @@ -54,7 +55,7 @@ contract OnChainProposer is IOnChainProposer, ReentrancyGuard { bytes32 depositLogs ) external override { require( - !verifiedBlocks[blockNumber], + blockNumber == lastVerifiedBlock + 1, "OnChainProposer: block already verified" ); require( @@ -92,11 +93,11 @@ contract OnChainProposer is IOnChainProposer, ReentrancyGuard { "OnChainProposer: block not committed" ); require( - !verifiedBlocks[blockNumber], + blockNumber == lastVerifiedBlock + 1, "OnChainProposer: block already verified" ); - verifiedBlocks[blockNumber] = true; + lastVerifiedBlock = blockNumber; ICommonBridge(BRIDGE).removeDepositLogs( // The first 2 bytes are the number of deposits. uint16(uint256(blockCommitments[blockNumber].depositLogs >> 240)) From f00cbfb22c706fafb121944420ef129306392dc7 Mon Sep 17 00:00:00 2001 From: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Date: Fri, 1 Nov 2024 17:46:49 -0300 Subject: [PATCH 4/9] feat(levm): general refactors (#1052) **Motivation** `levm`'s code is a little bit messy. Even though there's much more work to do, this PR aims to be the first of several to clean the codebase even more. **Description** - Modularize environment logic. - Modularize account logic. - Unify types. - Clean unused or repeated dependencies (repeated relative to the `ethereum_rust-core` lib). - Improve imports declarations. --- crates/vm/levm/Cargo.toml | 7 +- .../vm/levm/bench/revm_comparison/Cargo.toml | 17 +- .../vm/levm/bench/revm_comparison/src/lib.rs | 5 +- crates/vm/levm/src/account.rs | 87 ++++++++++ crates/vm/levm/src/block.rs | 10 -- crates/vm/levm/src/call_frame.rs | 20 +-- crates/vm/levm/src/constants.rs | 14 +- crates/vm/levm/src/db.rs | 5 +- crates/vm/levm/src/environment.rs | 43 +++++ crates/vm/levm/src/errors.rs | 8 +- crates/vm/levm/src/lib.rs | 7 +- crates/vm/levm/src/memory.rs | 8 +- .../vm/levm/src/opcode_handlers/arithmetic.rs | 9 +- .../src/opcode_handlers/bitwise_comparison.rs | 9 +- crates/vm/levm/src/opcode_handlers/block.rs | 18 ++- crates/vm/levm/src/opcode_handlers/dup.rs | 9 +- .../levm/src/opcode_handlers/environment.rs | 9 +- .../vm/levm/src/opcode_handlers/exchange.rs | 9 +- crates/vm/levm/src/opcode_handlers/keccak.rs | 11 +- crates/vm/levm/src/opcode_handlers/logging.rs | 17 +- crates/vm/levm/src/opcode_handlers/mod.rs | 10 -- crates/vm/levm/src/opcode_handlers/push.rs | 11 +- .../stack_memory_storage_flow.rs | 13 +- crates/vm/levm/src/opcode_handlers/system.rs | 6 +- crates/vm/levm/src/operations.rs | 7 +- crates/vm/levm/src/primitives.rs | 2 - crates/vm/levm/src/utils.rs | 14 +- crates/vm/levm/src/vm.rs | 150 ++---------------- crates/vm/levm/tests/tests.rs | 66 ++++---- 29 files changed, 322 insertions(+), 279 deletions(-) create mode 100644 crates/vm/levm/src/account.rs delete mode 100644 crates/vm/levm/src/block.rs create mode 100644 crates/vm/levm/src/environment.rs delete mode 100644 crates/vm/levm/src/primitives.rs diff --git a/crates/vm/levm/Cargo.toml b/crates/vm/levm/Cargo.toml index cd09d7d50..a6eaa81db 100644 --- a/crates/vm/levm/Cargo.toml +++ b/crates/vm/levm/Cargo.toml @@ -4,15 +4,16 @@ version.workspace = true edition.workspace = true [dependencies] -ethereum-types.workspace = true +ethereum_rust-core.workspace = true +ethereum_rust-rlp.workspace = true + bytes.workspace = true sha3 = "0.10.8" datatest-stable = "0.2.9" serde = { version = "1.0.203", features = ["derive", "rc"] } serde_json = { version = "1.0.117" } walkdir = "2.5.0" -ethereum_rust-rlp.workspace = true -keccak-hash = "0.10.0" +keccak-hash = "0.11.0" [dev-dependencies] hex = "0.4.3" diff --git a/crates/vm/levm/bench/revm_comparison/Cargo.toml b/crates/vm/levm/bench/revm_comparison/Cargo.toml index 5656b7e1e..a97b97b81 100644 --- a/crates/vm/levm/bench/revm_comparison/Cargo.toml +++ b/crates/vm/levm/bench/revm_comparison/Cargo.toml @@ -11,19 +11,20 @@ path = "src/lib.rs" ethereum_rust-levm = { path = "../../" } hex = "0.4.3" revm = "9.0.0" +bytes = "1.8.0" [[bin]] -name="levm_factorial" -path="src/levm_factorial.rs" +name = "levm_factorial" +path = "src/levm_factorial.rs" [[bin]] -name="revm_factorial" -path="src/revm_factorial.rs" +name = "revm_factorial" +path = "src/revm_factorial.rs" [[bin]] -name="levm_fibonacci" -path="src/levm_fibonacci.rs" +name = "levm_fibonacci" +path = "src/levm_fibonacci.rs" [[bin]] -name="revm_fibonacci" -path="src/revm_fibonacci.rs" +name = "revm_fibonacci" +path = "src/revm_fibonacci.rs" diff --git a/crates/vm/levm/bench/revm_comparison/src/lib.rs b/crates/vm/levm/bench/revm_comparison/src/lib.rs index 8b1ffcb0a..d4ef0b712 100644 --- a/crates/vm/levm/bench/revm_comparison/src/lib.rs +++ b/crates/vm/levm/bench/revm_comparison/src/lib.rs @@ -1,6 +1,5 @@ -use ethereum_rust_levm::{ - call_frame::CallFrame, errors::TxResult, primitives::Bytes, utils::new_vm_with_bytecode, -}; +use bytes::Bytes; +use ethereum_rust_levm::{call_frame::CallFrame, errors::TxResult, utils::new_vm_with_bytecode}; use revm::{ db::BenchmarkDB, primitives::{address, Bytecode, TransactTo}, diff --git a/crates/vm/levm/src/account.rs b/crates/vm/levm/src/account.rs new file mode 100644 index 000000000..c45ace97b --- /dev/null +++ b/crates/vm/levm/src/account.rs @@ -0,0 +1,87 @@ +use std::{collections::HashMap, str::FromStr}; + +use bytes::Bytes; +use ethereum_rust_core::{H256, U256}; +use keccak_hash::keccak; + +use crate::constants::EMPTY_CODE_HASH_STR; + +#[derive(Clone, Default, Debug, PartialEq, Eq)] +pub struct AccountInfo { + pub balance: U256, + pub bytecode: Bytes, + pub nonce: u64, +} + +impl AccountInfo { + pub fn is_empty(&self) -> bool { + self.balance.is_zero() && self.nonce == 0 && self.bytecode.is_empty() + } +} + +#[derive(Clone, Default, Debug, PartialEq, Eq)] +pub struct Account { + pub info: AccountInfo, + pub storage: HashMap, +} + +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct StorageSlot { + pub original_value: U256, + pub current_value: U256, +} + +impl Account { + pub fn new( + balance: U256, + bytecode: Bytes, + nonce: u64, + storage: HashMap, + ) -> Self { + Self { + info: AccountInfo { + balance, + bytecode, + nonce, + }, + storage, + } + } + + pub fn has_code(&self) -> bool { + !(self.info.bytecode.is_empty() + || self.bytecode_hash() == H256::from_str(EMPTY_CODE_HASH_STR).unwrap()) + } + + pub fn bytecode_hash(&self) -> H256 { + keccak(self.info.bytecode.as_ref()).0.into() + } + + pub fn is_empty(&self) -> bool { + self.info.balance.is_zero() && self.info.nonce == 0 && self.info.bytecode.is_empty() + } + + pub fn with_balance(mut self, balance: U256) -> Self { + self.info.balance = balance; + self + } + + pub fn with_bytecode(mut self, bytecode: Bytes) -> Self { + self.info.bytecode = bytecode; + self + } + + pub fn with_storage(mut self, storage: HashMap) -> Self { + self.storage = storage; + self + } + + pub fn with_nonce(mut self, nonce: u64) -> Self { + self.info.nonce = nonce; + self + } + + pub fn increment_nonce(&mut self) { + self.info.nonce += 1; + } +} diff --git a/crates/vm/levm/src/block.rs b/crates/vm/levm/src/block.rs deleted file mode 100644 index 7a258bdad..000000000 --- a/crates/vm/levm/src/block.rs +++ /dev/null @@ -1,10 +0,0 @@ -use ethereum_types::U256; - -pub const LAST_AVAILABLE_BLOCK_LIMIT: U256 = U256([256, 0, 0, 0]); - -// EIP-4844 constants. -pub const MIN_BLOB_GASPRICE: U256 = U256([1, 0, 0, 0]); -pub const BLOB_GASPRICE_UPDATE_FRACTION: U256 = U256([3338477, 0, 0, 0]); -pub const GAS_PER_BLOB: U256 = U256([131072, 0, 0, 0]); // 1 << 17 -pub const TARGET_BLOB_NUMBER_PER_BLOCK: U256 = U256([3, 0, 0, 0]); -pub const TARGET_BLOB_GAS_PER_BLOCK: U256 = U256([393216, 0, 0, 0]); // TARGET_BLOB_NUMBER_PER_BLOCK * GAS_PER_BLOB diff --git a/crates/vm/levm/src/call_frame.rs b/crates/vm/levm/src/call_frame.rs index 2d94b63b1..a3d828a56 100644 --- a/crates/vm/levm/src/call_frame.rs +++ b/crates/vm/levm/src/call_frame.rs @@ -1,25 +1,11 @@ -use ethereum_types::H32; - -use crate::{ - constants::STACK_LIMIT, - errors::VMError, - memory::Memory, - opcodes::Opcode, - primitives::{Address, Bytes, U256}, -}; +use crate::{constants::STACK_LIMIT, errors::VMError, memory::Memory, opcodes::Opcode}; +use bytes::Bytes; +use ethereum_rust_core::{types::Log, Address, U256}; use std::collections::HashMap; /// [EIP-1153]: https://eips.ethereum.org/EIPS/eip-1153#reference-implementation pub type TransientStorage = HashMap<(Address, U256), U256>; -#[derive(Clone, Debug, Default, Eq, PartialEq, Hash)] -/// Data record produced during the execution of a transaction. -pub struct Log { - pub address: Address, - pub topics: Vec, - pub data: Bytes, -} - #[derive(Debug, Clone, Default, PartialEq, Eq)] pub struct Stack { pub stack: Vec, diff --git a/crates/vm/levm/src/constants.rs b/crates/vm/levm/src/constants.rs index c9733334a..50c131fd2 100644 --- a/crates/vm/levm/src/constants.rs +++ b/crates/vm/levm/src/constants.rs @@ -1,4 +1,4 @@ -use ethereum_types::U256; +use ethereum_rust_core::U256; pub const SUCCESS_FOR_CALL: i32 = 1; pub const REVERT_FOR_CALL: i32 = 0; @@ -9,7 +9,7 @@ pub const WORD_SIZE: usize = 32; /// Contains the gas costs of the EVM instructions (in wei) pub mod gas_cost { - use ethereum_types::U256; + use ethereum_rust_core::U256; pub const ADD: U256 = U256([3, 0, 0, 0]); pub const MUL: U256 = U256([5, 0, 0, 0]); @@ -91,7 +91,7 @@ pub mod gas_cost { // Costs in gas for call opcodes (in wei) pub mod call_opcode { - use ethereum_types::U256; + use ethereum_rust_core::U256; pub const WARM_ADDRESS_ACCESS_COST: U256 = U256([100, 0, 0, 0]); pub const COLD_ADDRESS_ACCESS_COST: U256 = U256([2600, 0, 0, 0]); @@ -126,7 +126,11 @@ pub fn init_code_cost(init_code_length: usize) -> u64 { pub const VERSIONED_HASH_VERSION_KZG: u8 = 0x01; pub const MAX_BLOB_NUMBER_PER_BLOCK: usize = 6; -// Blob consts -pub const TARGET_BLOB_GAS_PER_BLOCK: U256 = U256([393216, 0, 0, 0]); +// Blob constants +pub const TARGET_BLOB_GAS_PER_BLOCK: U256 = U256([393216, 0, 0, 0]); // TARGET_BLOB_NUMBER_PER_BLOCK * GAS_PER_BLOB pub const MIN_BASE_FEE_PER_BLOB_GAS: U256 = U256([1, 0, 0, 0]); pub const BLOB_BASE_FEE_UPDATE_FRACTION: U256 = U256([3338477, 0, 0, 0]); + +// Block constants + +pub const LAST_AVAILABLE_BLOCK_LIMIT: U256 = U256([256, 0, 0, 0]); diff --git a/crates/vm/levm/src/db.rs b/crates/vm/levm/src/db.rs index ec0f14e5f..c70668d44 100644 --- a/crates/vm/levm/src/db.rs +++ b/crates/vm/levm/src/db.rs @@ -1,6 +1,5 @@ -use crate::vm::{Account, AccountInfo, StorageSlot}; -use ethereum_types::{Address, U256}; -use keccak_hash::H256; +use crate::account::{Account, AccountInfo, StorageSlot}; +use ethereum_rust_core::{Address, H256, U256}; use std::collections::HashMap; pub trait Database { diff --git a/crates/vm/levm/src/environment.rs b/crates/vm/levm/src/environment.rs new file mode 100644 index 000000000..1538c924c --- /dev/null +++ b/crates/vm/levm/src/environment.rs @@ -0,0 +1,43 @@ +use crate::constants::TX_BASE_COST; +use ethereum_rust_core::{Address, H256, U256}; + +#[derive(Debug, Default, Clone)] +pub struct Environment { + /// The sender address of the transaction that originated + /// this execution. + pub origin: Address, + pub consumed_gas: U256, + pub refunded_gas: U256, + pub gas_limit: U256, + pub block_number: U256, + pub coinbase: Address, + pub timestamp: U256, + pub prev_randao: Option, + pub chain_id: U256, + pub base_fee_per_gas: U256, + pub gas_price: U256, + pub block_excess_blob_gas: Option, + pub block_blob_gas_used: Option, + pub tx_blob_hashes: Option>, +} + +impl Environment { + pub fn default_from_address(origin: Address) -> Self { + Self { + origin, + consumed_gas: TX_BASE_COST, + refunded_gas: U256::zero(), + gas_limit: U256::MAX, + block_number: Default::default(), + coinbase: Default::default(), + timestamp: Default::default(), + prev_randao: Default::default(), + chain_id: U256::one(), + base_fee_per_gas: Default::default(), + gas_price: Default::default(), + block_excess_blob_gas: Default::default(), + block_blob_gas_used: Default::default(), + tx_blob_hashes: Default::default(), + } + } +} diff --git a/crates/vm/levm/src/errors.rs b/crates/vm/levm/src/errors.rs index c3f372276..32fcce116 100644 --- a/crates/vm/levm/src/errors.rs +++ b/crates/vm/levm/src/errors.rs @@ -1,9 +1,7 @@ -use std::collections::HashMap; - +use crate::account::Account; use bytes::Bytes; -use ethereum_types::Address; - -use crate::{call_frame::Log, vm::Account}; +use ethereum_rust_core::{types::Log, Address}; +use std::collections::HashMap; /// Errors that halt the program #[derive(Debug, Clone, PartialEq, Eq, Hash)] diff --git a/crates/vm/levm/src/lib.rs b/crates/vm/levm/src/lib.rs index d07a5dc33..9ffbbfd75 100644 --- a/crates/vm/levm/src/lib.rs +++ b/crates/vm/levm/src/lib.rs @@ -1,12 +1,15 @@ -pub mod block; +pub mod account; pub mod call_frame; pub mod constants; pub mod db; +pub mod environment; pub mod errors; pub mod memory; pub mod opcode_handlers; pub mod opcodes; pub mod operations; -pub mod primitives; pub mod utils; pub mod vm; + +pub use account::*; +pub use environment::*; diff --git a/crates/vm/levm/src/memory.rs b/crates/vm/levm/src/memory.rs index eff1d1590..b209daa63 100644 --- a/crates/vm/levm/src/memory.rs +++ b/crates/vm/levm/src/memory.rs @@ -1,6 +1,8 @@ -use crate::constants::{MEMORY_EXPANSION_QUOTIENT, WORD_SIZE}; -use crate::errors::VMError; -use crate::primitives::U256; +use crate::{ + constants::{MEMORY_EXPANSION_QUOTIENT, WORD_SIZE}, + errors::VMError, +}; +use ethereum_rust_core::U256; #[derive(Debug, Clone, Default, PartialEq)] pub struct Memory { diff --git a/crates/vm/levm/src/opcode_handlers/arithmetic.rs b/crates/vm/levm/src/opcode_handlers/arithmetic.rs index fa1f09e37..1b76a66d2 100644 --- a/crates/vm/levm/src/opcode_handlers/arithmetic.rs +++ b/crates/vm/levm/src/opcode_handlers/arithmetic.rs @@ -1,6 +1,13 @@ +use crate::{ + call_frame::CallFrame, + constants::gas_cost, + errors::{OpcodeSuccess, VMError}, + vm::VM, +}; +use ethereum_rust_core::{U256, U512}; + // Arithmetic Operations (11) // Opcodes: ADD, SUB, MUL, DIV, SDIV, MOD, SMOD, ADDMOD, MULMOD, EXP, SIGNEXTEND -use super::*; impl VM { // ADD operation diff --git a/crates/vm/levm/src/opcode_handlers/bitwise_comparison.rs b/crates/vm/levm/src/opcode_handlers/bitwise_comparison.rs index 589e5326f..b7c180940 100644 --- a/crates/vm/levm/src/opcode_handlers/bitwise_comparison.rs +++ b/crates/vm/levm/src/opcode_handlers/bitwise_comparison.rs @@ -1,8 +1,13 @@ -use crate::constants::WORD_SIZE; +use crate::{ + call_frame::CallFrame, + constants::{gas_cost, WORD_SIZE}, + errors::{OpcodeSuccess, VMError}, + vm::VM, +}; +use ethereum_rust_core::U256; // Comparison and Bitwise Logic Operations (14) // Opcodes: LT, GT, SLT, SGT, EQ, ISZERO, AND, OR, XOR, NOT, BYTE, SHL, SHR, SAR -use super::*; impl VM { // LT operation diff --git a/crates/vm/levm/src/opcode_handlers/block.rs b/crates/vm/levm/src/opcode_handlers/block.rs index 019cc19e5..b66a7ab26 100644 --- a/crates/vm/levm/src/opcode_handlers/block.rs +++ b/crates/vm/levm/src/opcode_handlers/block.rs @@ -1,12 +1,17 @@ use crate::{ - block::LAST_AVAILABLE_BLOCK_LIMIT, - constants::{BLOB_BASE_FEE_UPDATE_FRACTION, MIN_BASE_FEE_PER_BLOB_GAS}, + call_frame::CallFrame, + constants::{gas_cost, LAST_AVAILABLE_BLOCK_LIMIT}, + errors::{OpcodeSuccess, VMError}, + vm::VM, }; -use keccak_hash::H256; +use ethereum_rust_core::{ + types::{BLOB_BASE_FEE_UPDATE_FRACTION, MIN_BASE_FEE_PER_BLOB_GAS}, + Address, H256, U256, +}; +use std::str::FromStr; // Block Information (11) // Opcodes: BLOCKHASH, COINBASE, TIMESTAMP, NUMBER, PREVRANDAO, GASLIMIT, CHAINID, SELFBALANCE, BASEFEE, BLOBHASH, BLOBBASEFEE -use super::*; impl VM { // BLOCKHASH operation @@ -183,10 +188,10 @@ impl VM { fn get_blob_gasprice(&mut self) -> U256 { fake_exponential( - MIN_BASE_FEE_PER_BLOB_GAS, + MIN_BASE_FEE_PER_BLOB_GAS.into(), // Use unwrap because env should have a Some value in excess_blob_gas attribute self.env.block_excess_blob_gas.unwrap(), - BLOB_BASE_FEE_UPDATE_FRACTION, + BLOB_BASE_FEE_UPDATE_FRACTION.into(), ) } @@ -205,7 +210,6 @@ impl VM { } } -use std::str::FromStr; fn address_to_word(address: Address) -> U256 { // This unwrap can't panic, as Address are 20 bytes long and U256 use 32 bytes U256::from_str(&format!("{address:?}")).unwrap() diff --git a/crates/vm/levm/src/opcode_handlers/dup.rs b/crates/vm/levm/src/opcode_handlers/dup.rs index cd9926125..7590f8e6e 100644 --- a/crates/vm/levm/src/opcode_handlers/dup.rs +++ b/crates/vm/levm/src/opcode_handlers/dup.rs @@ -1,6 +1,13 @@ +use crate::{ + call_frame::CallFrame, + constants::gas_cost, + errors::{OpcodeSuccess, VMError}, + opcodes::Opcode, + vm::VM, +}; + // Duplication Operation (16) // Opcodes: DUP1 ... DUP16 -use super::*; impl VM { // DUP operation diff --git a/crates/vm/levm/src/opcode_handlers/environment.rs b/crates/vm/levm/src/opcode_handlers/environment.rs index d017ae10f..24e23cb0c 100644 --- a/crates/vm/levm/src/opcode_handlers/environment.rs +++ b/crates/vm/levm/src/opcode_handlers/environment.rs @@ -1,11 +1,14 @@ -use super::*; use crate::{ + call_frame::CallFrame, constants::{ call_opcode::{COLD_ADDRESS_ACCESS_COST, WARM_ADDRESS_ACCESS_COST}, - WORD_SIZE, + gas_cost, WORD_SIZE, }, - vm::word_to_address, + errors::{OpcodeSuccess, VMError}, + vm::{word_to_address, VM}, }; +use bytes::Bytes; +use ethereum_rust_core::U256; use sha3::{Digest, Keccak256}; // Environmental Information (16) diff --git a/crates/vm/levm/src/opcode_handlers/exchange.rs b/crates/vm/levm/src/opcode_handlers/exchange.rs index 53bfa3ab4..ceb2877df 100644 --- a/crates/vm/levm/src/opcode_handlers/exchange.rs +++ b/crates/vm/levm/src/opcode_handlers/exchange.rs @@ -1,6 +1,13 @@ +use crate::{ + call_frame::CallFrame, + constants::gas_cost, + errors::{OpcodeSuccess, VMError}, + opcodes::Opcode, + vm::VM, +}; + // Exchange Operations (16) // Opcodes: SWAP1 ... SWAP16 -use super::*; impl VM { // SWAP operation diff --git a/crates/vm/levm/src/opcode_handlers/keccak.rs b/crates/vm/levm/src/opcode_handlers/keccak.rs index 9d08d203c..fcff2fcc8 100644 --- a/crates/vm/levm/src/opcode_handlers/keccak.rs +++ b/crates/vm/levm/src/opcode_handlers/keccak.rs @@ -1,9 +1,14 @@ -use crate::constants::WORD_SIZE; +use crate::{ + call_frame::CallFrame, + constants::{gas_cost, WORD_SIZE}, + errors::{OpcodeSuccess, VMError}, + vm::VM, +}; +use ethereum_rust_core::U256; +use sha3::{Digest, Keccak256}; // KECCAK256 (1) // Opcodes: KECCAK256 -use super::*; -use sha3::{Digest, Keccak256}; impl VM { pub fn op_keccak256( diff --git a/crates/vm/levm/src/opcode_handlers/logging.rs b/crates/vm/levm/src/opcode_handlers/logging.rs index 09430a1c5..bfd370dac 100644 --- a/crates/vm/levm/src/opcode_handlers/logging.rs +++ b/crates/vm/levm/src/opcode_handlers/logging.rs @@ -1,6 +1,15 @@ // Logging Operations (5) // Opcodes: LOG0 ... LOG4 -use super::*; + +use crate::{ + call_frame::CallFrame, + constants::gas_cost, + errors::{OpcodeSuccess, VMError}, + opcodes::Opcode, + vm::VM, +}; +use bytes::Bytes; +use ethereum_rust_core::{types::Log, H256}; impl VM { // LOG operation @@ -26,8 +35,10 @@ impl VM { .unwrap_or(usize::MAX); let mut topics = Vec::new(); for _ in 0..number_of_topics { - let topic = current_call_frame.stack.pop()?.as_u32(); - topics.push(H32::from_slice(topic.to_be_bytes().as_ref())); + let topic = current_call_frame.stack.pop()?; + let mut topic_bytes = [0u8; 32]; + topic.to_big_endian(&mut topic_bytes); + topics.push(H256::from_slice(&topic_bytes)); } let memory_expansion_cost = current_call_frame.memory.expansion_cost(offset + size)?; diff --git a/crates/vm/levm/src/opcode_handlers/mod.rs b/crates/vm/levm/src/opcode_handlers/mod.rs index d98f7fb2b..87e0c986c 100644 --- a/crates/vm/levm/src/opcode_handlers/mod.rs +++ b/crates/vm/levm/src/opcode_handlers/mod.rs @@ -9,13 +9,3 @@ pub mod logging; pub mod push; pub mod stack_memory_storage_flow; pub mod system; - -use crate::{ - call_frame::{CallFrame, Log}, - constants::gas_cost, - errors::*, - opcodes::Opcode, - vm::VM, -}; -use bytes::Bytes; -use ethereum_types::{Address, H32, U256, U512}; diff --git a/crates/vm/levm/src/opcode_handlers/push.rs b/crates/vm/levm/src/opcode_handlers/push.rs index 9a9b9bf7d..5e6f4eab8 100644 --- a/crates/vm/levm/src/opcode_handlers/push.rs +++ b/crates/vm/levm/src/opcode_handlers/push.rs @@ -1,6 +1,15 @@ +use ethereum_rust_core::U256; + +use crate::{ + call_frame::CallFrame, + constants::gas_cost, + errors::{OpcodeSuccess, VMError}, + opcodes::Opcode, + vm::VM, +}; + // Push Operations // Opcodes: PUSH0, PUSH1 ... PUSH32 -use super::*; impl VM { // PUSH operation diff --git a/crates/vm/levm/src/opcode_handlers/stack_memory_storage_flow.rs b/crates/vm/levm/src/opcode_handlers/stack_memory_storage_flow.rs index 4d3fbffa4..25fddb40f 100644 --- a/crates/vm/levm/src/opcode_handlers/stack_memory_storage_flow.rs +++ b/crates/vm/levm/src/opcode_handlers/stack_memory_storage_flow.rs @@ -1,8 +1,11 @@ -use keccak_hash::H256; - -use crate::{constants::WORD_SIZE, vm::StorageSlot}; - -use super::*; +use crate::{ + account::StorageSlot, + call_frame::CallFrame, + constants::{gas_cost, WORD_SIZE}, + errors::{OpcodeSuccess, VMError}, + vm::VM, +}; +use ethereum_rust_core::{H256, U256}; // Stack, Memory, Storage and Flow Operations (15) // Opcodes: POP, MLOAD, MSTORE, MSTORE8, SLOAD, SSTORE, JUMP, JUMPI, PC, MSIZE, GAS, JUMPDEST, TLOAD, TSTORE, MCOPY diff --git a/crates/vm/levm/src/opcode_handlers/system.rs b/crates/vm/levm/src/opcode_handlers/system.rs index ad1b8fa21..95d25c686 100644 --- a/crates/vm/levm/src/opcode_handlers/system.rs +++ b/crates/vm/levm/src/opcode_handlers/system.rs @@ -1,8 +1,10 @@ -use super::*; use crate::{ + call_frame::CallFrame, constants::{call_opcode, SUCCESS_FOR_RETURN}, - errors::ResultReason, + errors::{OpcodeSuccess, ResultReason, VMError}, + vm::VM, }; +use ethereum_rust_core::{Address, U256}; // System Operations (10) // Opcodes: CREATE, CALL, CALLCODE, RETURN, DELEGATECALL, CREATE2, STATICCALL, REVERT, INVALID, SELFDESTRUCT diff --git a/crates/vm/levm/src/operations.rs b/crates/vm/levm/src/operations.rs index 3f6a96c49..b9994f1b5 100644 --- a/crates/vm/levm/src/operations.rs +++ b/crates/vm/levm/src/operations.rs @@ -1,7 +1,6 @@ -use crate::{ - opcodes::Opcode, - primitives::{Bytes, U256}, -}; +use crate::opcodes::Opcode; +use bytes::Bytes; +use ethereum_rust_core::U256; #[derive(Debug, PartialEq, Eq, Clone)] pub enum Operation { diff --git a/crates/vm/levm/src/primitives.rs b/crates/vm/levm/src/primitives.rs deleted file mode 100644 index fe32edef8..000000000 --- a/crates/vm/levm/src/primitives.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub use bytes::Bytes; -pub use ethereum_types::{Address, H160, H256, H32, U256, U512}; diff --git a/crates/vm/levm/src/utils.rs b/crates/vm/levm/src/utils.rs index 8cd9bebd9..a468d5eab 100644 --- a/crates/vm/levm/src/utils.rs +++ b/crates/vm/levm/src/utils.rs @@ -1,13 +1,15 @@ use crate::{ + account::{Account, AccountInfo}, db::{Cache, Db}, + environment::Environment, operations::Operation, - vm::{Account, AccountInfo, Environment, VM}, + vm::VM, }; use bytes::Bytes; -use ethereum_types::{Address, U256}; +use ethereum_rust_core::{types::TxKind, Address, U256}; use std::collections::HashMap; -pub fn ops_to_bytecde(operations: &[Operation]) -> Bytes { +pub fn ops_to_bytecode(operations: &[Operation]) -> Bytes { operations .iter() .flat_map(Operation::to_bytecode) @@ -25,7 +27,7 @@ pub fn new_vm_with_bytecode(bytecode: Bytes) -> VM { } pub fn new_vm_with_ops(operations: &[Operation]) -> VM { - let bytecode = ops_to_bytecde(operations); + let bytecode = ops_to_bytecode(operations); new_vm_with_ops_addr_bal_db( bytecode, Address::from_low_u64_be(100), @@ -36,7 +38,7 @@ pub fn new_vm_with_ops(operations: &[Operation]) -> VM { } pub fn new_vm_with_ops_db(operations: &[Operation], db: Db) -> VM { - let bytecode = ops_to_bytecde(operations); + let bytecode = ops_to_bytecode(operations); new_vm_with_ops_addr_bal_db( bytecode, Address::from_low_u64_be(100), @@ -90,7 +92,7 @@ pub fn new_vm_with_ops_addr_bal_db( let env = Environment::default_from_address(sender_address); VM::new( - Some(Address::from_low_u64_be(42)), + TxKind::Call(Address::from_low_u64_be(42)), env, Default::default(), Default::default(), diff --git a/crates/vm/levm/src/vm.rs b/crates/vm/levm/src/vm.rs index 0af24e548..45c841cda 100644 --- a/crates/vm/levm/src/vm.rs +++ b/crates/vm/levm/src/vm.rs @@ -1,97 +1,19 @@ use crate::{ + account::{Account, StorageSlot}, call_frame::CallFrame, constants::*, db::{Cache, Database}, + environment::Environment, errors::{OpcodeSuccess, ResultReason, TransactionReport, TxResult, VMError}, opcodes::Opcode, - primitives::{Address, Bytes, H256, U256}, }; +use bytes::Bytes; +use ethereum_rust_core::{types::TxKind, Address, H256, U256}; use ethereum_rust_rlp; use ethereum_rust_rlp::encode::RLPEncode; -use ethereum_types::H160; -use keccak_hash::keccak; use sha3::{Digest, Keccak256}; use std::{collections::HashMap, str::FromStr}; -#[derive(Clone, Default, Debug, PartialEq, Eq)] -pub struct AccountInfo { - pub balance: U256, - pub bytecode: Bytes, - pub nonce: u64, -} -impl AccountInfo { - pub fn is_empty(&self) -> bool { - self.balance.is_zero() && self.nonce == 0 && self.bytecode.is_empty() - } -} - -#[derive(Clone, Default, Debug, PartialEq, Eq)] -pub struct Account { - pub info: AccountInfo, - pub storage: HashMap, -} - -#[derive(Debug, Clone, Default, PartialEq, Eq)] -pub struct StorageSlot { - pub original_value: U256, - pub current_value: U256, -} - -impl Account { - pub fn new( - balance: U256, - bytecode: Bytes, - nonce: u64, - storage: HashMap, - ) -> Self { - Self { - info: AccountInfo { - balance, - bytecode, - nonce, - }, - storage, - } - } - - pub fn has_code(&self) -> bool { - !(self.info.bytecode.is_empty() - || self.bytecode_hash() == H256::from_str(EMPTY_CODE_HASH_STR).unwrap()) - } - - pub fn bytecode_hash(&self) -> H256 { - keccak(self.info.bytecode.as_ref()) - } - - pub fn is_empty(&self) -> bool { - self.info.balance.is_zero() && self.info.nonce == 0 && self.info.bytecode.is_empty() - } - - pub fn with_balance(mut self, balance: U256) -> Self { - self.info.balance = balance; - self - } - - pub fn with_bytecode(mut self, bytecode: Bytes) -> Self { - self.info.bytecode = bytecode; - self - } - - pub fn with_storage(mut self, storage: HashMap) -> Self { - self.storage = storage; - self - } - - pub fn with_nonce(mut self, nonce: u64) -> Self { - self.info.nonce = nonce; - self - } - - pub fn increment_nonce(&mut self) { - self.info.nonce += 1; - } -} - pub type Storage = HashMap; #[derive(Debug, Clone, Default)] @@ -102,47 +24,6 @@ pub struct Substate { // pub accessed_storage_keys: HashSet<(Address, U256)>, } -#[derive(Debug, Default, Clone)] -pub struct Environment { - /// The sender address of the transaction that originated - /// this execution. - pub origin: Address, - pub consumed_gas: U256, - pub refunded_gas: U256, - pub gas_limit: U256, - pub block_number: U256, - pub coinbase: Address, - pub timestamp: U256, - pub prev_randao: Option, - pub chain_id: U256, - pub base_fee_per_gas: U256, - pub gas_price: U256, - pub block_excess_blob_gas: Option, - pub block_blob_gas_used: Option, - pub tx_blob_hashes: Option>, -} - -impl Environment { - pub fn default_from_address(origin: Address) -> Self { - Self { - origin, - consumed_gas: TX_BASE_COST, - refunded_gas: U256::zero(), - gas_limit: U256::MAX, - block_number: Default::default(), - coinbase: Default::default(), - timestamp: Default::default(), - prev_randao: Default::default(), - chain_id: U256::one(), - base_fee_per_gas: Default::default(), - gas_price: Default::default(), - block_excess_blob_gas: Default::default(), - block_blob_gas_used: Default::default(), - tx_blob_hashes: Default::default(), - } - } -} - pub struct VM { pub call_frames: Vec, pub env: Environment, @@ -153,12 +34,7 @@ pub struct VM { /// states. pub db: Box, pub cache: Cache, - pub tx_type: TxType, -} - -pub enum TxType { - CALL, - CREATE, + pub tx_kind: TxKind, } fn address_to_word(address: Address) -> U256 { @@ -176,7 +52,7 @@ impl VM { // TODO: Refactor this. #[allow(clippy::too_many_arguments)] pub fn new( - to: Option

, + to: TxKind, env: Environment, value: U256, calldata: Bytes, @@ -188,7 +64,7 @@ impl VM { // Maybe this decision should be made in an upper layer match to { - Some(address_to) => { + TxKind::Call(address_to) => { // CALL tx let initial_call_frame = CallFrame::new( env.origin, @@ -209,10 +85,10 @@ impl VM { env, accrued_substate: Substate::default(), cache, - tx_type: TxType::CALL, + tx_kind: to, } } - None => { + TxKind::Create => { // CREATE tx let sender_account_info = db.get_account_info(env.origin); // Note that this is a copy of account, not the real one @@ -249,7 +125,7 @@ impl VM { env, accrued_substate: Substate::default(), cache, - tx_type: TxType::CREATE, + tx_kind: TxKind::Create, } } } @@ -462,7 +338,7 @@ impl VM { } fn is_create(&self) -> bool { - matches!(self.tx_type, TxType::CREATE) + matches!(self.tx_kind, TxKind::Create) } fn revert_create(&mut self) -> Result<(), VMError> { @@ -652,7 +528,7 @@ impl VM { /// Calculates the address of a new conctract using the CREATE opcode as follow /// /// address = keccak256(rlp([sender_address,sender_nonce]))[12:] - pub fn calculate_create_address(sender_address: Address, sender_nonce: u64) -> H160 { + pub fn calculate_create_address(sender_address: Address, sender_nonce: u64) -> Address { let mut encoded = Vec::new(); sender_address.encode(&mut encoded); sender_nonce.encode(&mut encoded); @@ -670,7 +546,7 @@ impl VM { sender_address: Address, initialization_code: &Bytes, salt: U256, - ) -> H160 { + ) -> Address { let mut hasher = Keccak256::new(); hasher.update(initialization_code.clone()); let initialization_code_hash = hasher.finalize(); diff --git a/crates/vm/levm/tests/tests.rs b/crates/vm/levm/tests/tests.rs index ec3b57e70..a03252cd3 100644 --- a/crates/vm/levm/tests/tests.rs +++ b/crates/vm/levm/tests/tests.rs @@ -1,13 +1,15 @@ +use bytes::Bytes; +use ethereum_rust_core::{types::TxKind, Address, H256, U256}; use ethereum_rust_levm::{ + account::Account, constants::*, db::{Cache, Db}, errors::{TxResult, VMError}, operations::Operation, - primitives::{Address, Bytes, H256, U256}, utils::{new_vm_with_ops, new_vm_with_ops_addr_bal_db, new_vm_with_ops_db}, - vm::{word_to_address, Account, Environment, Storage, VM}, + vm::{word_to_address, Storage, VM}, + Environment, }; -use ethereum_types::H32; use std::collections::HashMap; fn create_opcodes(size: usize, offset: usize, value_to_transfer: usize) -> Vec { @@ -2967,7 +2969,7 @@ fn log0() { #[test] fn log1() { - let mut topic1: [u8; 4] = [0x00; 4]; + let mut topic1 = [0u8; 32]; topic1[3] = 1; let data: [u8; 32] = [0xff; 32]; @@ -2991,15 +2993,15 @@ fn log1() { let data = [0xff_u8; 32].as_slice(); assert_eq!(logs.len(), 1); assert_eq!(logs[0].data, data.to_vec()); - assert_eq!(logs[0].topics, vec![H32::from_slice(&topic1)]); + assert_eq!(logs[0].topics, vec![H256::from_slice(&topic1)]); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 1027); } #[test] fn log2() { - let mut topic1: [u8; 4] = [0x00; 4]; + let mut topic1 = [0u8; 32]; topic1[3] = 1; - let mut topic2: [u8; 4] = [0x00; 4]; + let mut topic2 = [0u8; 32]; topic2[3] = 2; let data: [u8; 32] = [0xff; 32]; @@ -3026,18 +3028,18 @@ fn log2() { assert_eq!(logs[0].data, data.to_vec()); assert_eq!( logs[0].topics, - vec![H32::from_slice(&topic1), H32::from_slice(&topic2)] + vec![H256::from_slice(&topic1), H256::from_slice(&topic2)] ); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 1405); } #[test] fn log3() { - let mut topic1: [u8; 4] = [0x00; 4]; + let mut topic1 = [0u8; 32]; topic1[3] = 1; - let mut topic2: [u8; 4] = [0x00; 4]; + let mut topic2 = [0u8; 32]; topic2[3] = 2; - let mut topic3: [u8; 4] = [0x00; 4]; + let mut topic3 = [0u8; 32]; topic3[3] = 3; let data: [u8; 32] = [0xff; 32]; @@ -3066,9 +3068,9 @@ fn log3() { assert_eq!( logs[0].topics, vec![ - H32::from_slice(&topic1), - H32::from_slice(&topic2), - H32::from_slice(&topic3) + H256::from_slice(&topic1), + H256::from_slice(&topic2), + H256::from_slice(&topic3) ] ); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 1783); @@ -3076,13 +3078,13 @@ fn log3() { #[test] fn log4() { - let mut topic1: [u8; 4] = [0x00; 4]; + let mut topic1 = [0u8; 32]; topic1[3] = 1; - let mut topic2: [u8; 4] = [0x00; 4]; + let mut topic2 = [0u8; 32]; topic2[3] = 2; - let mut topic3: [u8; 4] = [0x00; 4]; + let mut topic3 = [0u8; 32]; topic3[3] = 3; - let mut topic4: [u8; 4] = [0x00; 4]; + let mut topic4 = [0u8; 32]; topic4[3] = 4; let data: [u8; 32] = [0xff; 32]; @@ -3112,10 +3114,10 @@ fn log4() { assert_eq!( logs[0].topics, vec![ - H32::from_slice(&topic1), - H32::from_slice(&topic2), - H32::from_slice(&topic3), - H32::from_slice(&topic4) + H256::from_slice(&topic1), + H256::from_slice(&topic2), + H256::from_slice(&topic3), + H256::from_slice(&topic4) ] ); assert_eq!(vm.env.consumed_gas, TX_BASE_COST + 2161); @@ -3201,7 +3203,7 @@ fn log_with_data_in_memory_smaller_than_size() { #[test] fn multiple_logs_of_different_types() { - let mut topic1: [u8; 4] = [0x00; 4]; + let mut topic1 = [0u8; 32]; topic1[3] = 1; let data: [u8; 32] = [0xff; 32]; @@ -3229,7 +3231,7 @@ fn multiple_logs_of_different_types() { assert_eq!(logs.len(), 2); assert_eq!(logs[0].data, data.to_vec()); assert_eq!(logs[1].data, data.to_vec()); - assert_eq!(logs[0].topics, vec![H32::from_slice(&topic1)]); + assert_eq!(logs[0].topics, vec![H256::from_slice(&topic1)]); assert_eq!(logs[1].topics.len(), 0); } @@ -3974,7 +3976,7 @@ fn caller_op() { let env = Environment::default_from_address(caller); let mut vm = VM::new( - Some(address_that_has_the_code), + TxKind::Call(address_that_has_the_code), env, Default::default(), Default::default(), @@ -4016,7 +4018,7 @@ fn origin_op() { let env = Environment::default_from_address(msg_sender); let mut vm = VM::new( - Some(address_that_has_the_code), + TxKind::Call(address_that_has_the_code), env, Default::default(), Default::default(), @@ -4084,7 +4086,7 @@ fn address_op() { let env = Environment::default_from_address(Address::from_low_u64_be(42)); let mut vm = VM::new( - Some(address_that_has_the_code), + TxKind::Call(address_that_has_the_code), env, Default::default(), Default::default(), @@ -4130,7 +4132,7 @@ fn selfbalance_op() { let env = Environment::default_from_address(Address::from_low_u64_be(42)); let mut vm = VM::new( - Some(address_that_has_the_code), + TxKind::Call(address_that_has_the_code), env, Default::default(), Default::default(), @@ -4170,7 +4172,7 @@ fn callvalue_op() { let env = Environment::default_from_address(Address::from_low_u64_be(42)); let mut vm = VM::new( - Some(address_that_has_the_code), + TxKind::Call(address_that_has_the_code), env, value, Default::default(), @@ -4209,7 +4211,7 @@ fn codesize_op() { let env = Environment::default_from_address(Address::from_low_u64_be(42)); let mut vm = VM::new( - Some(address_that_has_the_code), + TxKind::Call(address_that_has_the_code), env, Default::default(), Default::default(), @@ -4251,7 +4253,7 @@ fn gasprice_op() { env.gas_price = U256::from_str_radix("9876", 16).unwrap(); let mut vm = VM::new( - Some(address_that_has_the_code), + TxKind::Call(address_that_has_the_code), env, Default::default(), Default::default(), @@ -4309,7 +4311,7 @@ fn codecopy_op() { let env = Environment::default_from_address(Address::from_low_u64_be(42)); let mut vm = VM::new( - Some(address_that_has_the_code), + TxKind::Call(address_that_has_the_code), env, Default::default(), Default::default(), From fcde32f007c34d740b660ca2f026650a2c974a7a Mon Sep 17 00:00:00 2001 From: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Date: Fri, 1 Nov 2024 19:08:30 -0300 Subject: [PATCH 5/9] fix(l2): contracts (#1056) **Motivation** The compilation of L1 contracts was broken in a previous PR. **Description** Fixes the contracts. --- crates/l2/contracts/src/l1/CommonBridge.sol | 5 ++--- crates/l2/contracts/src/l1/interfaces/IOnChainProposer.sol | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/crates/l2/contracts/src/l1/CommonBridge.sol b/crates/l2/contracts/src/l1/CommonBridge.sol index 60f670f30..013571557 100644 --- a/crates/l2/contracts/src/l1/CommonBridge.sol +++ b/crates/l2/contracts/src/l1/CommonBridge.sol @@ -116,9 +116,8 @@ contract CommonBridge is ICommonBridge, Ownable, ReentrancyGuard { "CommonBridge: the block that emitted the withdrawal logs was not committed" ); require( - IOnChainProposer(ON_CHAIN_PROPOSER).verifiedBlocks( - withdrawalBlockNumber - ), + withdrawalBlockNumber <= + IOnChainProposer(ON_CHAIN_PROPOSER).lastVerifiedBlock(), "CommonBridge: the block that emitted the withdrawal logs was not verified" ); require( diff --git a/crates/l2/contracts/src/l1/interfaces/IOnChainProposer.sol b/crates/l2/contracts/src/l1/interfaces/IOnChainProposer.sol index a7feecd83..e2347f902 100644 --- a/crates/l2/contracts/src/l1/interfaces/IOnChainProposer.sol +++ b/crates/l2/contracts/src/l1/interfaces/IOnChainProposer.sol @@ -6,8 +6,8 @@ pragma solidity ^0.8.27; /// @notice A OnChainProposer contract ensures the advancement of the L2. It is used /// by the proposer to commit blocks and verify block proofs. interface IOnChainProposer { - /// @notice The commitments of the committed blocks. - function verifiedBlocks(uint256) external view returns (bool); + /// @notice The latest verified block number. + function lastVerifiedBlock() external view returns (uint256); /// @notice A block has been committed. /// @dev Event emitted when a block is committed. From 2980c6e91946c7ef5aa0be4519bd2919f54c56dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Rodr=C3=ADguez=20Chatruc?= <49622509+jrchatruc@users.noreply.github.com> Date: Fri, 1 Nov 2024 19:39:56 -0300 Subject: [PATCH 6/9] docs(l2): add L2 interop milestone (#1057) **Motivation** **Description** --- README.md | 43 ++++++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index b07f34874..145770426 100644 --- a/README.md +++ b/README.md @@ -350,7 +350,31 @@ It also supports EIP 4844 for L1 commit transactions, which means state diffs ar | Adapt the prover to prove a KZG commitment to the state diff and use the point evaluation precompile to show that the blob sent to the L1 is indeed the correct one through a proof of equivalence protocol | ❌ | | Add a command to the CLI to reconstructing the full L2 state from all the blob data on the L1. | ❌ | -### Milestone 4: Custom Native token +### Milestone 4: Account Abstraction + +The L2 supports native account abstraction following EIP 7702, allowing for custom transaction validation logic and paymaster flows. + +#### Status + +| Task Description | Status | +| -------------------------------------------------------------------------- | ------ | +| Add support for `SET_CODE_TX_TYPE` transactions (i.e. implement EIP 7702). | ❌ | +| Add examples of WebAuthn signing and paymaster flows using EIP 7702 | ❌ | + +### Milestone 5: L2s interoperability + +Support multiple L2s sharing the same bridge contract on L1 for seamless interoperability. + +#### Status + +| Task Description | Status | +| ------------------------------------------------------------------------------------------ | ------ | +| Change state of the `commonBridge` and `onChainProposer` to be a mapping over `chainId` | ❌ | +| Adapt sequencer to be aware of its chain id and interact with the L1 contracts accordingly | ❌ | + +TODO: Expand on tasks about proper interoperability between chains (seamlessly bridging between chains, etc). + +### Milestone 6: Custom Native token The L2 can also be deployed using a custom native token, meaning that a certain ERC20 can be the common currency that's used for paying network fees. @@ -362,7 +386,7 @@ The L2 can also be deployed using a custom native token, meaning that a certain | On the `commonBridge`, for custom native token deposits, `msg.value` should always be zero, and the amount of the native token to mint should be a new `valueToMintOnL2` argument. The amount should be deducted from the caller thorugh a `transferFrom`. | ❌ | | On the CLI, add support for custom native token deposits and withdrawals | ❌ | -### Milestone 5: Security (TEEs and Multi Prover support) +### Milestone 7: Security (TEEs and Multi Prover support) The L2 has added security mechanisms in place, running on Trusted Execution Environments and Multi Prover setup where multiple guarantees (Execution on TEEs, zkVMs/proving systems) are required for settlement on the L1. This better protects against possible security bugs on implementations. @@ -374,18 +398,7 @@ The L2 has added security mechanisms in place, running on Trusted Execution Envi | Support verifying multiple different zkVM executions on the `onChainProposer` L1 contract. | ❌ | | Support running the operator on a TEE environment | ❌ | -### Milestone 6: Account Abstraction - -The L2 supports native account abstraction following EIP 7702, allowing for custom transaction validation logic and paymaster flows. - -#### Status - -| Task Description | Status | -| ---------------- | ------ | - -TODO: Expand on account abstraction tasks. - -### Milestone 7: Based Contestable Rollup +### Milestone 8: Based Contestable Rollup The network can be run as a Based Rollup, meaning sequencing is done by the Ethereum Validator set; transactions are sent to a private mempool and L1 Validators that opt into the L2 sequencing propose blocks for the L2 on every L1 block. @@ -397,7 +410,7 @@ The network can be run as a Based Rollup, meaning sequencing is done by the Ethe TODO: Expand on this. -### Milestone 8: Validium +### Milestone 9: Validium The L2 can be initialized in Validium Mode, meaning the Data Availability layer is no longer the L1, but rather a DA layer of the user's choice. From 7a07e512f6448d1c3d27a200d294db7c273fd8d2 Mon Sep 17 00:00:00 2001 From: Federico Carrone Date: Sun, 3 Nov 2024 12:32:15 -0300 Subject: [PATCH 7/9] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 145770426..09ab317ac 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ In a bit more detail: | Add `libmdbx` bindings and basic API, create tables for state (blocks, transactions, etc) | βœ… | EVM wrapper for block execution | βœ… | | JSON RPC API server setup | βœ… | -| RPC State-serving endpoints | πŸ—οΈ (almost done, a few endpoint are left) | +| RPC State-serving endpoints | πŸ—οΈ (almost done, a few endpoints are left) | | Basic Engine API implementation. Set new chain head (`forkchoiceUpdated`) and new block (`newPayload`). | βœ… See detailed issues and progress for this milestone [here](https://github.com/lambdaclass/ethereum_rust/milestone/1). From 5ded681ade228a78be902bf41b581aee6c8f9b0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jerem=C3=ADas=20Salom=C3=B3n?= <48994069+JereSalo@users.noreply.github.com> Date: Mon, 4 Nov 2024 11:42:52 -0300 Subject: [PATCH 8/9] feat(levm): opcodes revert invalid selfdestruct (#946) **Motivation** Implement opcodes This PR is going to be resumed when Db changes are merged to main. **Description** - Opcodes Revert, Invalid and Selfdestruct implementation Caveat: - Revert opcode has yet to be tested in different scenarios. Most important ones are CREATE and XCALL. This is linked in issue #1061 Closes #535 Closes #536 Closes #537 --- crates/vm/levm/docs/substate.md | 2 +- crates/vm/levm/src/constants.rs | 3 + crates/vm/levm/src/db.rs | 2 +- crates/vm/levm/src/errors.rs | 5 +- crates/vm/levm/src/opcode_handlers/system.rs | 104 ++++++++++++++++++- crates/vm/levm/src/opcodes.rs | 9 +- crates/vm/levm/src/operations.rs | 12 +-- crates/vm/levm/src/vm.rs | 98 ++++++++++------- crates/vm/levm/tests/tests.rs | 70 ++++++++++++- 9 files changed, 248 insertions(+), 57 deletions(-) diff --git a/crates/vm/levm/docs/substate.md b/crates/vm/levm/docs/substate.md index 806511ed2..1307d9a00 100644 --- a/crates/vm/levm/docs/substate.md +++ b/crates/vm/levm/docs/substate.md @@ -1,3 +1,3 @@ ## Substate -`accessed_addresses` and `accessed_storage_keys` follow the structure defined in [EIP 2929](https://eips.ethereum.org/EIPS/eip-2929#specification) +`accessed_addresses` and `accessed_storage_keys` belong to the Substate but in our VM implementation they are not there because we already know what the warm addresses and storage keys are by looking at the `Cache` structure. diff --git a/crates/vm/levm/src/constants.rs b/crates/vm/levm/src/constants.rs index 50c131fd2..0ab760d6d 100644 --- a/crates/vm/levm/src/constants.rs +++ b/crates/vm/levm/src/constants.rs @@ -87,6 +87,9 @@ pub mod gas_cost { pub const CODECOPY_DYNAMIC_BASE: U256 = U256([3, 0, 0, 0]); pub const GASPRICE: U256 = U256([2, 0, 0, 0]); pub const EXTCODECOPY_DYNAMIC_BASE: U256 = U256([3, 0, 0, 0]); + pub const SELFDESTRUCT_STATIC: U256 = U256([5000, 0, 0, 0]); + pub const SELFDESTRUCT_DYNAMIC: U256 = U256([25000, 0, 0, 0]); + pub const COLD_ADDRESS_ACCESS_COST: U256 = U256([2600, 0, 0, 0]); } // Costs in gas for call opcodes (in wei) diff --git a/crates/vm/levm/src/db.rs b/crates/vm/levm/src/db.rs index c70668d44..de350fbbc 100644 --- a/crates/vm/levm/src/db.rs +++ b/crates/vm/levm/src/db.rs @@ -71,7 +71,7 @@ impl Database for Db { } } -#[derive(Debug, Default, Clone)] +#[derive(Debug, Default, Clone, Eq, PartialEq)] pub struct Cache { pub accounts: HashMap, } diff --git a/crates/vm/levm/src/errors.rs b/crates/vm/levm/src/errors.rs index 32fcce116..d7eafd401 100644 --- a/crates/vm/levm/src/errors.rs +++ b/crates/vm/levm/src/errors.rs @@ -17,9 +17,11 @@ pub enum VMError { OverflowInArithmeticOp, FatalError, InvalidTransaction, + RevertOpcode, + InvalidOpcode, MissingBlobHashes, BlobHashIndexOutOfBounds, - RevertOpcode, + SenderAccountDoesNotExist, AddressDoesNotMatchAnAccount, SenderAccountShouldNotHaveBytecode, SenderBalanceShouldContainTransferValue, @@ -40,6 +42,7 @@ pub enum ResultReason { Stop, Revert, Return, + SelfDestruct, } #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/crates/vm/levm/src/opcode_handlers/system.rs b/crates/vm/levm/src/opcode_handlers/system.rs index 95d25c686..82cedab8c 100644 --- a/crates/vm/levm/src/opcode_handlers/system.rs +++ b/crates/vm/levm/src/opcode_handlers/system.rs @@ -1,10 +1,10 @@ use crate::{ call_frame::CallFrame, - constants::{call_opcode, SUCCESS_FOR_RETURN}, + constants::{call_opcode, gas_cost, SUCCESS_FOR_RETURN}, errors::{OpcodeSuccess, ResultReason, VMError}, - vm::VM, + vm::{word_to_address, VM}, }; -use ethereum_rust_core::{Address, U256}; +use ethereum_rust_core::{types::TxKind, Address, U256}; // System Operations (10) // Opcodes: CREATE, CALL, CALLCODE, RETURN, DELEGATECALL, CREATE2, STATICCALL, REVERT, INVALID, SELFDESTRUCT @@ -39,6 +39,10 @@ impl VM { .try_into() .unwrap_or(usize::MAX); + if current_call_frame.is_static && !value.is_zero() { + return Err(VMError::OpcodeNotAllowedInStaticContext); + } + let memory_byte_size = (args_offset + args_size).max(ret_offset + ret_size); let memory_expansion_cost = current_call_frame.memory.expansion_cost(memory_byte_size)?; @@ -253,4 +257,98 @@ impl VM { current_call_frame, ) } + + // REVERT operation + pub fn op_revert( + &mut self, + current_call_frame: &mut CallFrame, + ) -> Result { + // Description: Gets values from stack, calculates gas cost and sets return data. + // Returns: VMError RevertOpcode if executed correctly. + // Notes: + // The actual reversion of changes is made in the execute() function. + + let offset = current_call_frame.stack.pop()?.as_usize(); + + let size = current_call_frame.stack.pop()?.as_usize(); + + let gas_cost = current_call_frame.memory.expansion_cost(offset + size)?; + + self.increase_consumed_gas(current_call_frame, gas_cost)?; + + current_call_frame.returndata = current_call_frame.memory.load_range(offset, size).into(); + + Err(VMError::RevertOpcode) + } + + /// ### INVALID operation + /// Reverts consuming all gas, no return data. + pub fn op_invalid(&mut self) -> Result { + Err(VMError::InvalidOpcode) + } + + // SELFDESTRUCT operation + pub fn op_selfdestruct( + &mut self, + current_call_frame: &mut CallFrame, + ) -> Result { + // Sends all ether in the account to the target address + // Steps: + // 1. Pop the target address from the stack + // 2. Get current account and: Store the balance in a variable, set it's balance to 0 + // 3. Get the target account, checking if it is empty and if it is cold. Update gas cost accordingly. + // 4. Add the balance of the current account to the target account + // 5. Register account to be destroyed in accrued substate. + + // Notes: + // If context is Static, return error. + // If executed in the same transaction a contract was created, the current account is registered to be destroyed + if current_call_frame.is_static { + return Err(VMError::OpcodeNotAllowedInStaticContext); + } + + // Gas costs variables + let static_gas_cost = gas_cost::SELFDESTRUCT_STATIC; + let dynamic_gas_cost = gas_cost::SELFDESTRUCT_DYNAMIC; + let cold_gas_cost = gas_cost::COLD_ADDRESS_ACCESS_COST; + let mut gas_cost = static_gas_cost; + + // 1. Pop the target address from the stack + let target_address = word_to_address(current_call_frame.stack.pop()?); + + // 2. Get current account and: Store the balance in a variable, set it's balance to 0 + let mut current_account = self.get_account(¤t_call_frame.to); + let current_account_balance = current_account.info.balance; + + current_account.info.balance = U256::zero(); + + // 3 & 4. Get target account and add the balance of the current account to it + // TODO: If address is cold, there is an additional cost of 2600. + if !self.cache.is_account_cached(&target_address) { + gas_cost += cold_gas_cost; + } + + let mut target_account = self.get_account(&target_address); + if target_account.is_empty() { + gas_cost += dynamic_gas_cost; + } + target_account.info.balance += current_account_balance; + + // 5. Register account to be destroyed in accrued substate IF executed in the same transaction a contract was created + if self.tx_kind == TxKind::Create { + self.accrued_substate + .selfdestrutct_set + .insert(current_call_frame.to); + } + // Accounts in SelfDestruct set should be destroyed at the end of the transaction. + + // Update cache after modifying accounts. + self.cache + .add_account(¤t_call_frame.to, ¤t_account); + self.cache.add_account(&target_address, &target_account); + + self.increase_consumed_gas(current_call_frame, gas_cost)?; + + Ok(OpcodeSuccess::Result(ResultReason::SelfDestruct)) + } } diff --git a/crates/vm/levm/src/opcodes.rs b/crates/vm/levm/src/opcodes.rs index 5c6a7993b..be93c6ffc 100644 --- a/crates/vm/levm/src/opcodes.rs +++ b/crates/vm/levm/src/opcodes.rs @@ -165,9 +165,9 @@ pub enum Opcode { DELEGATECALL = 0xF4, CREATE2 = 0xF5, STATICCALL = 0xFA, - // REVERT = 0xFD, - // INVALID = 0xFE, - // SELFDESTRUCT = 0xFF, + REVERT = 0xFD, + INVALID = 0xFE, + SELFDESTRUCT = 0xFF, } impl Copy for Opcode {} @@ -321,6 +321,9 @@ impl From for Opcode { 0xF5 => Opcode::CREATE2, 0xF4 => Opcode::DELEGATECALL, 0xFA => Opcode::STATICCALL, + 0xFD => Opcode::REVERT, + 0xFE => Opcode::INVALID, + 0xFF => Opcode::SELFDESTRUCT, _ => panic!("Unknown opcode: 0x{:02X}", byte), } } diff --git a/crates/vm/levm/src/operations.rs b/crates/vm/levm/src/operations.rs index b9994f1b5..332c12628 100644 --- a/crates/vm/levm/src/operations.rs +++ b/crates/vm/levm/src/operations.rs @@ -85,9 +85,9 @@ pub enum Operation { DelegateCall, Create2, StaticCall, - // Revert, - // Invalid, - // SelfDestruct, + Revert, + Invalid, + SelfDestruct, } impl Operation { @@ -201,9 +201,9 @@ impl Operation { Operation::DelegateCall => Bytes::copy_from_slice(&[Opcode::DELEGATECALL as u8]), Operation::Create2 => Bytes::copy_from_slice(&[Opcode::CREATE2 as u8]), Operation::StaticCall => Bytes::copy_from_slice(&[Opcode::STATICCALL as u8]), - // Operation::Revert => Bytes::copy_from_slice(&[Opcode::REVERT as u8]), - // Operation::Invalid => Bytes::copy_from_slice(&[Opcode::INVALID as u8]), - // Operation::SelfDestruct => Bytes::copy_from_slice(&[Opcode::SELFDESTRUCT as u8]), + Operation::Revert => Bytes::copy_from_slice(&[Opcode::REVERT as u8]), + Operation::Invalid => Bytes::copy_from_slice(&[Opcode::INVALID as u8]), + Operation::SelfDestruct => Bytes::copy_from_slice(&[Opcode::SELFDESTRUCT as u8]), } } } diff --git a/crates/vm/levm/src/vm.rs b/crates/vm/levm/src/vm.rs index 45c841cda..ac4e69527 100644 --- a/crates/vm/levm/src/vm.rs +++ b/crates/vm/levm/src/vm.rs @@ -12,7 +12,10 @@ use ethereum_rust_core::{types::TxKind, Address, H256, U256}; use ethereum_rust_rlp; use ethereum_rust_rlp::encode::RLPEncode; use sha3::{Digest, Keccak256}; -use std::{collections::HashMap, str::FromStr}; +use std::{ + collections::{HashMap, HashSet}, + str::FromStr, +}; pub type Storage = HashMap; @@ -22,6 +25,7 @@ pub struct Substate { // accessed addresses and storage keys are considered WARM // pub accessed_addresses: HashSet
, // pub accessed_storage_keys: HashSet<(Address, U256)>, + pub selfdestrutct_set: HashSet
, } pub struct VM { @@ -133,10 +137,13 @@ impl VM { } pub fn execute(&mut self, current_call_frame: &mut CallFrame) -> TransactionReport { - // let mut current_call_frame = self - // .call_frames - // .pop() - // .expect("Fatal Error: This should not happen"); // if this happens during execution, we are cooked πŸ’€ + // Backup of Database, Substate and Gas Refunds if sub-context is reverted + let (backup_db, backup_substate, backup_refunded_gas) = ( + self.cache.clone(), + self.accrued_substate.clone(), + self.env.refunded_gas, + ); + loop { let opcode = current_call_frame.next_opcode().unwrap_or(Opcode::STOP); let op_result: Result = match opcode { @@ -232,6 +239,10 @@ impl VM { Opcode::EXTCODESIZE => self.op_extcodesize(current_call_frame), Opcode::EXTCODECOPY => self.op_extcodecopy(current_call_frame), Opcode::EXTCODEHASH => self.op_extcodehash(current_call_frame), + Opcode::REVERT => self.op_revert(current_call_frame), + Opcode::INVALID => self.op_invalid(), + Opcode::SELFDESTRUCT => self.op_selfdestruct(current_call_frame), + _ => Err(VMError::OpcodeNotFound), }; @@ -254,19 +265,21 @@ impl VM { Err(error) => { self.call_frames.push(current_call_frame.clone()); - // CONSUME ALL GAS UNLESS THE ERROR IS FROM REVERT OPCODE + // Unless error is from Revert opcode, all gas is consumed if error != VMError::RevertOpcode { let left_gas = current_call_frame.gas_limit - current_call_frame.gas_used; current_call_frame.gas_used += left_gas; self.env.consumed_gas += left_gas; } + self.restore_state(backup_db, backup_substate, backup_refunded_gas); + return TransactionReport { result: TxResult::Revert(error), new_state: self.cache.accounts.clone(), gas_used: current_call_frame.gas_used.low_u64(), gas_refunded: self.env.refunded_gas.low_u64(), - output: current_call_frame.returndata.clone(), + output: current_call_frame.returndata.clone(), // Bytes::new() if error is not RevertOpcode logs: current_call_frame.logs.clone(), created_address: None, }; @@ -275,6 +288,18 @@ impl VM { } } + fn restore_state( + &mut self, + backup_cache: Cache, + backup_substate: Substate, + backup_refunded_gas: U256, + ) { + self.cache = backup_cache; + self.accrued_substate = backup_substate; + self.env.refunded_gas = backup_refunded_gas; + } + + // let account = self.db.accounts.get(&self.env.origin).unwrap(); /// Based on Ethereum yellow paper's initial tests of intrinsic validity (Section 6). The last version is /// Shanghai, so there are probably missing Cancun validations. The intrinsic validations are: /// @@ -418,6 +443,7 @@ impl VM { self.call_frames.last_mut().unwrap() } + // TODO: Improve and test REVERT behavior for XCALL opcodes. Issue: https://github.com/lambdaclass/lambda_ethereum_rust/issues/1061 #[allow(clippy::too_many_arguments)] pub fn generic_call( &mut self, @@ -434,52 +460,40 @@ impl VM { ret_offset: usize, ret_size: usize, ) -> Result { - // check balance - if !self.cache.is_account_cached(¤t_call_frame.msg_sender) { - self.cache_from_db(¤t_call_frame.msg_sender); - } + let mut sender_account = self.get_account(¤t_call_frame.msg_sender); - if self - .cache - .get_account(current_call_frame.msg_sender) - .unwrap() - .info - .balance - < value - { + if sender_account.info.balance < value { current_call_frame.stack.push(U256::from(REVERT_FOR_CALL))?; return Ok(OpcodeSuccess::Continue); } + let mut recipient_account = self.get_account(&to); + // transfer value - // transfer(¤t_call_frame.msg_sender, &address, value); + sender_account.info.balance -= value; + recipient_account.info.balance += value; + + let code_address_bytecode = self.get_account(&code_address).info.bytecode; - let code_address_bytecode = self - .cache - .get_account(code_address) - .unwrap() - .info - .bytecode - .clone(); if code_address_bytecode.is_empty() { - // should stop current_call_frame .stack .push(U256::from(SUCCESS_FOR_CALL))?; return Ok(OpcodeSuccess::Result(ResultReason::Stop)); } - self.cache.increment_account_nonce(&code_address); + // self.cache.increment_account_nonce(&code_address); // Internal call doesn't increment account nonce. let calldata = current_call_frame .memory .load_range(args_offset, args_size) .into(); - let gas_limit = std::cmp::min( - gas_limit, - (current_call_frame.gas_limit - current_call_frame.gas_used) / 64 * 63, - ); + // I don't know if this gas limit should be calculated before or after consuming gas + let gas_limit = std::cmp::min(gas_limit, { + let remaining_gas = current_call_frame.gas_limit - current_call_frame.gas_used; + remaining_gas - remaining_gas / 64 + }); let mut new_call_frame = CallFrame::new( msg_sender, @@ -497,10 +511,15 @@ impl VM { current_call_frame.sub_return_data_offset = ret_offset; current_call_frame.sub_return_data_size = ret_size; + // Update sender account and recipient in cache + self.cache + .add_account(¤t_call_frame.msg_sender, &sender_account); + self.cache.add_account(&to, &recipient_account); + // self.call_frames.push(new_call_frame.clone()); let tx_report = self.execute(&mut new_call_frame); - current_call_frame.gas_used += tx_report.gas_used.into(); // We add the gas used by the sub-context to the current one after it's execution. + current_call_frame.gas_used += tx_report.gas_used.into(); // Add gas used by the sub-context to the current one after it's execution. current_call_frame.logs.extend(tx_report.logs); current_call_frame .memory @@ -514,10 +533,8 @@ impl VM { .stack .push(U256::from(SUCCESS_FOR_CALL))?; } - TxResult::Revert(_error) => { - // Behavior for revert between contexts goes here if necessary - // It is also possible to differentiate between RevertOpcode error and other kinds of revert. - + TxResult::Revert(_) => { + // Push 0 to stack current_call_frame.stack.push(U256::from(REVERT_FOR_CALL))?; } } @@ -563,6 +580,7 @@ impl VM { /// Common behavior for CREATE and CREATE2 opcodes /// /// Could be used for CREATE type transactions + // TODO: Improve and test REVERT behavior for CREATE. Issue: https://github.com/lambdaclass/lambda_ethereum_rust/issues/1061 pub fn create( &mut self, value_in_wei_to_send: U256, @@ -607,7 +625,7 @@ impl VM { return Ok(OpcodeSuccess::Result(ResultReason::Revert)); }; sender_account.info.nonce = new_nonce; - sender_account.info.balance -= value_in_wei_to_send; + // sender_account.info.balance -= value_in_wei_to_send; // This is done in the generic_call let code = Bytes::from( current_call_frame .memory @@ -631,7 +649,7 @@ impl VM { return Ok(OpcodeSuccess::Result(ResultReason::Revert)); } - let new_account = Account::new(value_in_wei_to_send, code.clone(), 0, Default::default()); + let new_account = Account::new(U256::zero(), code.clone(), 0, Default::default()); self.cache.add_account(&new_address, &new_account); current_call_frame diff --git a/crates/vm/levm/tests/tests.rs b/crates/vm/levm/tests/tests.rs index a03252cd3..4d6eec3b2 100644 --- a/crates/vm/levm/tests/tests.rs +++ b/crates/vm/levm/tests/tests.rs @@ -3650,7 +3650,7 @@ fn create_happy_path() { .get_account(word_to_address(returned_addr)) .unwrap(); assert_eq!(new_account.info.balance, U256::from(value_to_transfer)); - assert_eq!(new_account.info.nonce, 1); + assert_eq!(new_account.info.nonce, 0); // This was previously set to 1 but I understand that a new account should have nonce 0 // Check that the sender account is updated let sender_account = vm.cache.get_account(sender_addr).unwrap(); @@ -3916,7 +3916,7 @@ fn create2_happy_path() { .get_account(word_to_address(returned_addr)) .unwrap(); assert_eq!(new_account.info.balance, U256::from(value)); - assert_eq!(new_account.info.nonce, 1); + assert_eq!(new_account.info.nonce, 0); // I understand new account should have nonce 0, not 1. // Check that the sender account is updated let sender_account = vm.cache.get_account(sender_addr).unwrap(); @@ -4469,3 +4469,69 @@ fn extcodehash_non_existing_account() { ); assert_eq!(vm.env.consumed_gas, 23603.into()); } + +#[test] +fn invalid_opcode() { + let operations = [Operation::Invalid, Operation::Stop]; + + let mut vm = new_vm_with_ops(&operations); + + let mut current_call_frame = vm.call_frames.pop().unwrap(); + let tx_report = vm.execute(&mut current_call_frame); + + assert!(matches!( + tx_report.result, + TxResult::Revert(VMError::InvalidOpcode) + )); +} + +// Revert Opcode has correct output and result +#[test] +fn revert_opcode() { + let ops = vec![ + Operation::Push((32, U256::from(0xA))), // value + Operation::Push((32, U256::from(0xFF))), // offset + Operation::Mstore, + Operation::Push((32, U256::from(32))), // size + Operation::Push((32, U256::from(0xFF))), // offset + Operation::Revert, + ]; + + let mut vm = new_vm_with_ops(&ops); + + let mut current_call_frame = vm.call_frames.pop().unwrap(); + let tx_report = vm.execute(&mut current_call_frame); + + assert_eq!(U256::from_big_endian(&tx_report.output), U256::from(0xA)); + assert!(matches!( + tx_report.result, + TxResult::Revert(VMError::RevertOpcode) + )); +} + +// Store something in the database, then revert. Database should be like it was before the store. +#[test] +fn revert_sstore() { + let key = U256::from(80); + let value = U256::from(100); + let sender_address = Address::from_low_u64_be(3000); + let operations = vec![ + Operation::Push((1, value)), + Operation::Push((1, key)), + Operation::Sstore, + Operation::Revert, + ]; + + let mut vm = new_vm_with_ops(&operations); + vm.current_call_frame_mut().code_address = sender_address; + vm.cache.add_account(&sender_address, &Account::default()); + + let mut current_call_frame = vm.call_frames.pop().unwrap(); + + // Cache state before the SSTORE + let cache_backup = vm.cache.clone(); + + vm.execute(&mut current_call_frame); + + assert_eq!(vm.cache, cache_backup); +} From ad30e09274eab047fc25b55d8820d4dafcad90ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Rodr=C3=ADguez=20Chatruc?= <49622509+jrchatruc@users.noreply.github.com> Date: Mon, 4 Nov 2024 16:06:45 -0300 Subject: [PATCH 9/9] feat(core): in memory mempool (#1002) **Motivation** This PR aims to move the mempool (i.e. transaction pool) from being a `libmdbx` table to an in-memory data structure. This makes sense as the mempool is not something meant to be persisted for a long time, but rather a temporary place for transactions for (ideally) a few seconds/minutes until they're included in blocks or discarded. If the node goes down and then back up for whatever reason, mempool transactions will come back from the p2p gossip network anyway. More concretely, though, the mempool being on disk was being a huge bottleneck on the load tests we were running, which were sending approximately 5k transactions per second to the node. When doing this with the current on-disk mempool, the node would hang and completely stop executing new blocks until the load test finished sending all transactions, at which point it would resume execution. With this (very naive and rushed) in-memory implementation, the node does not hang and can keep executing blocks at a normal pace while also receiving and storing all incoming transactions. Things to fix in the future: - Limiting the size of the mempool. We'd like to avoid the possibility of an OOM due to too many transactions (note that this was also a problem with the on-disk structure, running out of disk space instead of memory though). - Optimizing the way transactions are stored, filtered and picked to be included on each block. To build a block, the node currently does the following: - Keep a data structure of all the mempool transactions, grouped by sender address and sorted by nonce: for each sender address, we want transactions ordered by nonce in increasing order to know what the next transaction is for an account quickly. This is the `txs` field of the `TransactionQueue` struct right now. - Keep a data structure of all the `head` transactions sorted by fee tip in decreasing order, i.e., a list that has, for each sender address, its first transaction in terms of nonce, with the highest paying one first and so on (this is just a subset of the above where we just keep the first transaction for each key on a list and then sort that by fee). This is the `heads` field of the `TransactionQueue`. - While there's still gas left in the block, take the first transaction from the head transactions list and execute it; for that transaction's sender, take their next transaction (in terms of nonce) and add it to the head transactions list, making sure it remains sorted after insertion. Repeat until the block is full. This ensures the node builds a block with all valid transactions (i.e. no invalid nonces) and with the highest possible fee for them. However right now there's a lot of copying going on because the mempool is kept separately from the `TransactionQueue`; to build the `TransactionQueue` the builder first takes each mempool tx (stored on the `mempool` field of the `Store`) and passes it through a filter, then copies the remains of that filter to the Queue and builds the sorted head transactions list from there. My impression is that a lot of this copying and duplicating of transactions can be removed. --- crates/blockchain/mempool.rs | 61 +--------------- crates/blockchain/payload.rs | 4 +- crates/storage/store/engines/api.rs | 38 +--------- crates/storage/store/engines/in_memory.rs | 58 +-------------- crates/storage/store/engines/libmdbx.rs | 88 +---------------------- crates/storage/store/rlp.rs | 7 +- crates/storage/store/storage.rs | 74 +++++++++++++++---- 7 files changed, 71 insertions(+), 259 deletions(-) diff --git a/crates/blockchain/mempool.rs b/crates/blockchain/mempool.rs index 3b7fe0a0e..cb0835c9e 100644 --- a/crates/blockchain/mempool.rs +++ b/crates/blockchain/mempool.rs @@ -54,14 +54,6 @@ pub fn add_transaction(transaction: Transaction, store: Store) -> Result Result, MempoolError> { - Ok(store.get_transaction_from_pool(hash)?) -} - /// Fetch a blobs bundle from the mempool given its blob transaction hash pub fn get_blobs_bundle(tx_hash: H256, store: Store) -> Result, MempoolError> { Ok(store.get_blobs_bundle_from_pool(tx_hash)?) @@ -107,7 +99,7 @@ pub fn filter_transactions( } /// Remove a transaction from the mempool -pub fn remove_transaction(hash: H256, store: &Store) -> Result<(), StoreError> { +pub fn remove_transaction(hash: &H256, store: &Store) -> Result<(), StoreError> { store.remove_transaction_from_pool(hash) } @@ -283,9 +275,7 @@ mod tests { TX_DATA_ZERO_GAS_COST, TX_GAS_COST, TX_INIT_CODE_WORD_GAS_COST, }; - use super::{ - add_transaction, get_transaction, transaction_intrinsic_gas, validate_transaction, - }; + use super::{transaction_intrinsic_gas, validate_transaction}; use ethereum_rust_core::types::{ BlockHeader, ChainConfig, EIP1559Transaction, EIP4844Transaction, Transaction, TxKind, }; @@ -305,16 +295,6 @@ mod tests { Ok(store) } - fn tx_equal(t1: &Transaction, t2: &Transaction) -> bool { - t1.nonce() == t2.nonce() - && t1.max_priority_fee().unwrap_or_default() - == t2.max_priority_fee().unwrap_or_default() - && t1.max_fee_per_gas().unwrap_or_default() == t2.max_fee_per_gas().unwrap_or_default() - && t1.gas_limit() == t2.gas_limit() - && t1.value() == t2.value() - && *t1.data() == *t2.data() - } - fn build_basic_config_and_header( istanbul_active: bool, shanghai_active: bool, @@ -336,43 +316,6 @@ mod tests { (config, header) } - #[test] - fn store_and_fetch_transaction_happy_path() { - let config = ChainConfig { - shanghai_time: Some(10), - ..Default::default() - }; - - let header = BlockHeader { - number: 123, - gas_limit: 30_000_000, - gas_used: 0, - timestamp: 20, - ..Default::default() - }; - - let store = setup_storage(config, header).expect("Setup failed: "); - - let tx = EIP1559Transaction { - nonce: 3, - max_priority_fee_per_gas: 0, - max_fee_per_gas: 0, - gas_limit: 100_000, - to: TxKind::Call(Address::from_low_u64_be(1)), - value: U256::zero(), - data: Bytes::default(), - access_list: Default::default(), - ..Default::default() - }; - - let tx = Transaction::EIP1559Transaction(tx); - let hash = add_transaction(tx.clone(), store.clone()).expect("Add transaction"); - let ret_tx = get_transaction(hash, store).expect("Get transaction"); - assert!(ret_tx.is_some()); - let ret_tx = ret_tx.unwrap(); - assert!(tx_equal(&tx, &ret_tx)) - } - #[test] fn normal_transaction_intrinsic_gas() { let (config, header) = build_basic_config_and_header(false, false); diff --git a/crates/blockchain/payload.rs b/crates/blockchain/payload.rs index e2fd6a624..85f184add 100644 --- a/crates/blockchain/payload.rs +++ b/crates/blockchain/payload.rs @@ -310,7 +310,7 @@ pub fn fill_transactions(context: &mut PayloadBuildContext) -> Result<(), ChainE debug!("Ignoring replay-protected transaction: {}", tx_hash); txs.pop(); mempool::remove_transaction( - tx_hash, + &head_tx.tx.compute_hash(), context .store() .ok_or(ChainError::StoreError(StoreError::MissingStore))?, @@ -323,7 +323,7 @@ pub fn fill_transactions(context: &mut PayloadBuildContext) -> Result<(), ChainE txs.shift()?; // Pull transaction from the mempool mempool::remove_transaction( - tx_hash, + &head_tx.tx.compute_hash(), context .store() .ok_or(ChainError::StoreError(StoreError::MissingStore))?, diff --git a/crates/storage/store/engines/api.rs b/crates/storage/store/engines/api.rs index 6f6fc2ac6..e8647e65e 100644 --- a/crates/storage/store/engines/api.rs +++ b/crates/storage/store/engines/api.rs @@ -1,10 +1,9 @@ use bytes::Bytes; use ethereum_rust_core::types::{ - BlobsBundle, Block, BlockBody, BlockHash, BlockHeader, BlockNumber, ChainConfig, Index, - MempoolTransaction, Receipt, Transaction, + Block, BlockBody, BlockHash, BlockHeader, BlockNumber, ChainConfig, Index, Receipt, Transaction, }; use ethereum_types::{Address, H256, U256}; -use std::{collections::HashMap, fmt::Debug, panic::RefUnwindSafe}; +use std::{fmt::Debug, panic::RefUnwindSafe}; use crate::error::StoreError; use ethereum_rust_trie::Trie; @@ -85,39 +84,6 @@ pub trait StoreEngine: Debug + Send + Sync + RefUnwindSafe { transaction_hash: H256, ) -> Result, StoreError>; - /// Add transaction to the pool - fn add_transaction_to_pool( - &self, - hash: H256, - transaction: MempoolTransaction, - ) -> Result<(), StoreError>; - - /// Get a transaction from the pool - fn get_transaction_from_pool( - &self, - hash: H256, - ) -> Result, StoreError>; - - /// Store blobs bundle into the pool table by its blob transaction's hash - fn add_blobs_bundle_to_pool( - &self, - tx_hash: H256, - blobs_bundle: BlobsBundle, - ) -> Result<(), StoreError>; - - /// Get a blobs bundle from pool table given its blob transaction's hash - fn get_blobs_bundle_from_pool(&self, tx_hash: H256) -> Result, StoreError>; - - /// Remove a transaction from the pool - fn remove_transaction_from_pool(&self, hash: H256) -> Result<(), StoreError>; - - /// Applies the filter and returns a set of suitable transactions from the mempool. - /// These transactions will be grouped by sender and sorted by nonce - fn filter_pool_transactions( - &self, - filter: &dyn Fn(&Transaction) -> bool, - ) -> Result>, StoreError>; - /// Add receipt fn add_receipt( &self, diff --git a/crates/storage/store/engines/in_memory.rs b/crates/storage/store/engines/in_memory.rs index 5249ebcf2..161741c99 100644 --- a/crates/storage/store/engines/in_memory.rs +++ b/crates/storage/store/engines/in_memory.rs @@ -1,8 +1,7 @@ use crate::error::StoreError; use bytes::Bytes; use ethereum_rust_core::types::{ - BlobsBundle, Block, BlockBody, BlockHash, BlockHeader, BlockNumber, ChainConfig, Index, - MempoolTransaction, Receipt, Transaction, + Block, BlockBody, BlockHash, BlockHeader, BlockNumber, ChainConfig, Index, Receipt, }; use ethereum_rust_trie::{InMemoryTrieDB, Trie}; use ethereum_types::{Address, H256, U256}; @@ -30,10 +29,6 @@ struct StoreInner { account_codes: HashMap, // Maps transaction hashes to their blocks (height+hash) and index within the blocks. transaction_locations: HashMap>, - // Stores pooled transactions by their hashes - transaction_pool: HashMap, - // Stores the blobs_bundle for each blob transaction in the transaction_pool - blobs_bundle_pool: HashMap, receipts: HashMap>, state_trie_nodes: NodeMap, storage_trie_nodes: HashMap, @@ -178,57 +173,6 @@ impl StoreEngine for Store { })) } - fn add_transaction_to_pool( - &self, - hash: H256, - transaction: MempoolTransaction, - ) -> Result<(), StoreError> { - self.inner().transaction_pool.insert(hash, transaction); - Ok(()) - } - - fn get_transaction_from_pool( - &self, - hash: H256, - ) -> Result, StoreError> { - Ok(self.inner().transaction_pool.get(&hash).cloned()) - } - - fn add_blobs_bundle_to_pool( - &self, - tx_hash: H256, - blobs_bundle: BlobsBundle, - ) -> Result<(), StoreError> { - self.inner().blobs_bundle_pool.insert(tx_hash, blobs_bundle); - Ok(()) - } - - fn get_blobs_bundle_from_pool(&self, tx_hash: H256) -> Result, StoreError> { - Ok(self.inner().blobs_bundle_pool.get(&tx_hash).cloned()) - } - - fn remove_transaction_from_pool(&self, hash: H256) -> Result<(), StoreError> { - self.inner().transaction_pool.remove(&hash); - Ok(()) - } - - fn filter_pool_transactions( - &self, - filter: &dyn Fn(&Transaction) -> bool, - ) -> Result>, StoreError> { - let mut txs_by_sender: HashMap> = HashMap::new(); - for (_, tx) in self.inner().transaction_pool.iter() { - if filter(tx) { - txs_by_sender - .entry(tx.sender()) - .or_default() - .push(tx.clone()) - } - } - txs_by_sender.iter_mut().for_each(|(_, txs)| txs.sort()); - Ok(txs_by_sender) - } - fn add_receipt( &self, block_hash: BlockHash, diff --git a/crates/storage/store/engines/libmdbx.rs b/crates/storage/store/engines/libmdbx.rs index 97281ce17..2b0883d44 100644 --- a/crates/storage/store/engines/libmdbx.rs +++ b/crates/storage/store/engines/libmdbx.rs @@ -1,15 +1,13 @@ use super::api::StoreEngine; use crate::error::StoreError; use crate::rlp::{ - AccountCodeHashRLP, AccountCodeRLP, BlobsBubdleRLP, BlockBodyRLP, BlockHashRLP, BlockHeaderRLP, - BlockRLP, BlockTotalDifficultyRLP, MempoolTransactionRLP, ReceiptRLP, Rlp, TransactionHashRLP, - TupleRLP, + AccountCodeHashRLP, AccountCodeRLP, BlockBodyRLP, BlockHashRLP, BlockHeaderRLP, BlockRLP, + BlockTotalDifficultyRLP, ReceiptRLP, Rlp, TransactionHashRLP, TupleRLP, }; use anyhow::Result; use bytes::Bytes; use ethereum_rust_core::types::{ - BlobsBundle, Block, BlockBody, BlockHash, BlockHeader, BlockNumber, ChainConfig, Index, - MempoolTransaction, Receipt, Transaction, + Block, BlockBody, BlockHash, BlockHeader, BlockNumber, ChainConfig, Index, Receipt, Transaction, }; use ethereum_rust_rlp::decode::RLPDecode; use ethereum_rust_rlp::encode::RLPEncode; @@ -23,7 +21,6 @@ use libmdbx::{ }; use libmdbx::{DatabaseOptions, Mode, ReadWriteOptions}; use serde_json; -use std::collections::HashMap; use std::fmt::{Debug, Formatter}; use std::path::Path; use std::sync::Arc; @@ -55,17 +52,6 @@ impl Store { txn.get::(key).map_err(StoreError::LibmdbxError) } - // Helper method to remove from a libmdbx table - fn remove(&self, key: T::Key) -> Result<(), StoreError> { - let txn = self - .db - .begin_readwrite() - .map_err(StoreError::LibmdbxError)?; - txn.delete::(key, None) - .map_err(StoreError::LibmdbxError)?; - txn.commit().map_err(StoreError::LibmdbxError) - } - fn get_block_hash_by_block_number( &self, number: BlockNumber, @@ -217,62 +203,6 @@ impl StoreEngine for Store { })) } - fn add_transaction_to_pool( - &self, - hash: H256, - transaction: MempoolTransaction, - ) -> Result<(), StoreError> { - self.write::(hash.into(), transaction.into())?; - Ok(()) - } - - fn get_transaction_from_pool( - &self, - hash: H256, - ) -> Result, StoreError> { - Ok(self.read::(hash.into())?.map(|t| t.to())) - } - - fn add_blobs_bundle_to_pool( - &self, - tx_hash: H256, - blobs_bundle: BlobsBundle, - ) -> Result<(), StoreError> { - self.write::(tx_hash.into(), blobs_bundle.into())?; - Ok(()) - } - - fn get_blobs_bundle_from_pool(&self, tx_hash: H256) -> Result, StoreError> { - Ok(self - .read::(tx_hash.into())? - .map(|bb| bb.to())) - } - - fn remove_transaction_from_pool(&self, hash: H256) -> Result<(), StoreError> { - self.remove::(hash.into()) - } - - fn filter_pool_transactions( - &self, - filter: &dyn Fn(&Transaction) -> bool, - ) -> Result>, StoreError> { - let txn = self.db.begin_read().map_err(StoreError::LibmdbxError)?; - let cursor = txn - .cursor::() - .map_err(StoreError::LibmdbxError)?; - let tx_iter = cursor - .walk(None) - .map_while(|res| res.ok().map(|(_, tx)| tx.to())); - let mut txs_by_sender: HashMap> = HashMap::new(); - for tx in tx_iter { - if filter(&tx) { - txs_by_sender.entry(tx.sender()).or_default().push(tx) - } - } - txs_by_sender.iter_mut().for_each(|(_, txs)| txs.sort()); - Ok(txs_by_sender) - } - /// Stores the chain config serialized as json fn set_chain_config(&self, chain_config: &ChainConfig) -> Result<(), StoreError> { self.write::( @@ -545,16 +475,6 @@ dupsort!( ( TransactionLocations ) TransactionHashRLP => Rlp<(BlockNumber, BlockHash, Index)> ); -table!( - /// Transaction pool table. - ( TransactionPool ) TransactionHashRLP => MempoolTransactionRLP -); - -table!( - /// BlobsBundle pool table, contains the corresponding blobs bundle for each blob transaction in the TransactionPool table - ( BlobsBundlePool ) TransactionHashRLP => BlobsBubdleRLP -); - table!( /// Stores chain data, each value is unique and stored as its rlp encoding /// See [ChainDataIndex] for available chain values @@ -672,8 +592,6 @@ pub fn init_db(path: Option>) -> Database { table_info!(AccountCodes), table_info!(Receipts), table_info!(TransactionLocations), - table_info!(TransactionPool), - table_info!(BlobsBundlePool), table_info!(ChainData), table_info!(StateTrieNodes), table_info!(StorageTriesNodes), diff --git a/crates/storage/store/rlp.rs b/crates/storage/store/rlp.rs index c4b7f9c23..7d2aca7d3 100644 --- a/crates/storage/store/rlp.rs +++ b/crates/storage/store/rlp.rs @@ -2,7 +2,7 @@ use std::marker::PhantomData; use bytes::Bytes; use ethereum_rust_core::{ - types::{BlobsBundle, Block, BlockBody, BlockHash, BlockHeader, MempoolTransaction, Receipt}, + types::{Block, BlockBody, BlockHash, BlockHeader, Receipt}, H256, }; use ethereum_rust_rlp::{decode::RLPDecode, encode::RLPEncode}; @@ -27,14 +27,11 @@ pub type ReceiptRLP = Rlp; // Transaction types pub type TransactionHashRLP = Rlp; -pub type MempoolTransactionRLP = Rlp; - -pub type BlobsBubdleRLP = Rlp; // Wrapper for tuples. Used mostly for indexed keys. pub type TupleRLP = Rlp<(A, B)>; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Rlp(Vec, PhantomData); impl From for Rlp { diff --git a/crates/storage/store/storage.rs b/crates/storage/store/storage.rs index 269286cf6..ea7abd4c6 100644 --- a/crates/storage/store/storage.rs +++ b/crates/storage/store/storage.rs @@ -7,7 +7,7 @@ use engines::api::StoreEngine; use ethereum_rust_core::types::{ code_hash, AccountInfo, AccountState, BlobsBundle, Block, BlockBody, BlockHash, BlockHeader, BlockNumber, ChainConfig, Genesis, GenesisAccount, Index, MempoolTransaction, Receipt, - Transaction, EMPTY_TRIE_HASH, + Transaction, TxType, EMPTY_TRIE_HASH, }; use ethereum_rust_rlp::decode::RLPDecode; use ethereum_rust_rlp::encode::RLPEncode; @@ -16,7 +16,7 @@ use ethereum_types::{Address, H256, U256}; use sha3::{Digest as _, Keccak256}; use std::collections::HashMap; use std::fmt::Debug; -use std::sync::Arc; +use std::sync::{Arc, Mutex}; use tracing::info; mod engines; @@ -27,6 +27,8 @@ mod rlp; pub struct Store { // TODO: Check if we can remove this mutex and move it to the in_memory::Store struct engine: Arc, + pub mempool: Arc>>, + pub blobs_bundle_pool: Arc>>, } #[allow(dead_code)] @@ -74,9 +76,13 @@ impl Store { #[cfg(feature = "libmdbx")] EngineType::Libmdbx => Self { engine: Arc::new(LibmdbxStore::new(path)?), + mempool: Arc::new(Mutex::new(HashMap::new())), + blobs_bundle_pool: Arc::new(Mutex::new(HashMap::new())), }, EngineType::InMemory => Self { engine: Arc::new(InMemoryStore::new()), + mempool: Arc::new(Mutex::new(HashMap::new())), + blobs_bundle_pool: Arc::new(Mutex::new(HashMap::new())), }, }; info!("Started store engine"); @@ -228,15 +234,13 @@ impl Store { hash: H256, transaction: MempoolTransaction, ) -> Result<(), StoreError> { - self.engine.add_transaction_to_pool(hash, transaction) - } + let mut mempool = self + .mempool + .lock() + .map_err(|error| StoreError::Custom(error.to_string()))?; + mempool.insert(hash, transaction); - /// Get a transaction from the pool - pub fn get_transaction_from_pool( - &self, - hash: H256, - ) -> Result, StoreError> { - self.engine.get_transaction_from_pool(hash) + Ok(()) } /// Add a blobs bundle to the pool by its blob transaction hash @@ -245,7 +249,11 @@ impl Store { tx_hash: H256, blobs_bundle: BlobsBundle, ) -> Result<(), StoreError> { - self.engine.add_blobs_bundle_to_pool(tx_hash, blobs_bundle) + self.blobs_bundle_pool + .lock() + .map_err(|error| StoreError::Custom(error.to_string()))? + .insert(tx_hash, blobs_bundle); + Ok(()) } /// Get a blobs bundle to the pool given its blob transaction hash @@ -253,12 +261,32 @@ impl Store { &self, tx_hash: H256, ) -> Result, StoreError> { - self.engine.get_blobs_bundle_from_pool(tx_hash) + Ok(self + .blobs_bundle_pool + .lock() + .map_err(|error| StoreError::Custom(error.to_string()))? + .get(&tx_hash) + .cloned()) } /// Remove a transaction from the pool - pub fn remove_transaction_from_pool(&self, hash: H256) -> Result<(), StoreError> { - self.engine.remove_transaction_from_pool(hash) + pub fn remove_transaction_from_pool(&self, hash: &H256) -> Result<(), StoreError> { + let mut mempool = self + .mempool + .lock() + .map_err(|error| StoreError::Custom(error.to_string()))?; + if let Some(tx) = mempool.get(hash) { + if matches!(tx.tx_type(), TxType::EIP4844) { + self.blobs_bundle_pool + .lock() + .map_err(|error| StoreError::Custom(error.to_string()))? + .remove(&tx.compute_hash()); + } + + mempool.remove(hash); + }; + + Ok(()) } /// Applies the filter and returns a set of suitable transactions from the mempool. @@ -267,7 +295,23 @@ impl Store { &self, filter: &dyn Fn(&Transaction) -> bool, ) -> Result>, StoreError> { - self.engine.filter_pool_transactions(filter) + let mut txs_by_sender: HashMap> = HashMap::new(); + let mempool = self + .mempool + .lock() + .map_err(|error| StoreError::Custom(error.to_string()))?; + + for (_, tx) in mempool.iter() { + if filter(tx) { + txs_by_sender + .entry(tx.sender()) + .or_default() + .push(tx.clone()) + } + } + + txs_by_sender.iter_mut().for_each(|(_, txs)| txs.sort()); + Ok(txs_by_sender) } fn add_account_code(&self, code_hash: H256, code: Bytes) -> Result<(), StoreError> {