From 838e530d508e9f9816ccfdf9ffb732ceeffa3d42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jerem=C3=ADas=20Salom=C3=B3n?= <48994069+JereSalo@users.noreply.github.com> Date: Wed, 27 Nov 2024 16:55:17 -0300 Subject: [PATCH 01/23] feat(levm): running all ef tests with revm and fixing problems (#1275) **Motivation** - We want to run all eftests with revm and make them pass so it can be a reliable source for comparing execution with levm. **Description** - Runs all tests with revm -> Done - Fix execution of revm -> Most of Cancun tests have been fixed, 8 of them don't pass but it is good enough - Access lists are now deserialized, in .json files there are 1 access list per `data` element. So we use `data` index for accessing them. - I didn't make serious error handling for running with revm because it is not a core functionality, doing that will take a little bit more work but maybe it is not necessary. Closes #issue_number --------- Co-authored-by: ilitteri Co-authored-by: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> --- cmd/ef_tests/levm/deserialize.rs | 62 ++++++- cmd/ef_tests/levm/runner/levm_runner.rs | 2 +- cmd/ef_tests/levm/runner/mod.rs | 39 +++++ cmd/ef_tests/levm/runner/revm_runner.rs | 204 +++++++++++++++++++++--- cmd/ef_tests/levm/types.rs | 26 ++- 5 files changed, 310 insertions(+), 23 deletions(-) diff --git a/cmd/ef_tests/levm/deserialize.rs b/cmd/ef_tests/levm/deserialize.rs index 612e170459..a1acfee2be 100644 --- a/cmd/ef_tests/levm/deserialize.rs +++ b/cmd/ef_tests/levm/deserialize.rs @@ -1,6 +1,6 @@ -use crate::types::{EFTest, EFTests}; +use crate::types::{EFTest, EFTestAccessListItem, EFTests}; use bytes::Bytes; -use ethrex_core::U256; +use ethrex_core::{H256, U256}; use serde::Deserialize; use std::{collections::HashMap, str::FromStr}; @@ -65,6 +65,50 @@ where ) } +pub fn deserialize_h256_vec_optional_safe<'de, D>( + deserializer: D, +) -> Result>, D::Error> +where + D: serde::Deserializer<'de>, +{ + let s = Option::>::deserialize(deserializer)?; + match s { + Some(s) => { + let mut ret = Vec::new(); + for s in s { + ret.push(H256::from_str(s.trim_start_matches("0x")).map_err(|err| { + serde::de::Error::custom(format!( + "error parsing H256 when deserializing H256 vec optional: {err}" + )) + })?); + } + Ok(Some(ret)) + } + None => Ok(None), + } +} + +pub fn deserialize_access_lists<'de, D>( + deserializer: D, +) -> Result>>, D::Error> +where + D: serde::Deserializer<'de>, +{ + let access_lists: Option>>> = + Option::>>>::deserialize(deserializer)?; + + let mut final_access_lists: Vec> = Vec::new(); + + if let Some(access_lists) = access_lists { + for access_list in access_lists { + // Treat `null` as an empty vector + final_access_lists.push(access_list.unwrap_or_default()); + } + } + + Ok(Some(final_access_lists)) +} + pub fn deserialize_u256_optional_safe<'de, D>(deserializer: D) -> Result, D::Error> where D: serde::Deserializer<'de>, @@ -164,6 +208,20 @@ impl<'de> Deserialize<'de> for EFTests { sender: raw_tx.sender, to: raw_tx.to.clone(), value: *value, + blob_versioned_hashes: raw_tx + .blob_versioned_hashes + .clone() + .unwrap_or_default(), + max_fee_per_blob_gas: raw_tx.max_fee_per_blob_gas, + max_priority_fee_per_gas: raw_tx.max_priority_fee_per_gas, + max_fee_per_gas: raw_tx.max_fee_per_gas, + access_list: raw_tx + .access_lists + .clone() + .unwrap_or_default() + .get(data_id) + .cloned() + .unwrap_or_default(), }; transactions.insert((data_id, gas_limit_id, value_id), tx); } diff --git a/cmd/ef_tests/levm/runner/levm_runner.rs b/cmd/ef_tests/levm/runner/levm_runner.rs index 4cf9ee66bb..44406b02d4 100644 --- a/cmd/ef_tests/levm/runner/levm_runner.rs +++ b/cmd/ef_tests/levm/runner/levm_runner.rs @@ -78,7 +78,7 @@ pub fn prepare_vm_for_tx(vector: &TestVector, test: &EFTest) -> Result, + ef_tests: &[EFTest], +) -> Result<(), EFTestRunnerError> { + let revm_run_time = std::time::Instant::now(); + let mut revm_run_spinner = Spinner::new( + Dots, + "Running all tests with REVM...".to_owned(), + Color::Cyan, + ); + for (idx, test) in ef_tests.iter().enumerate() { + let total_tests = ef_tests.len(); + revm_run_spinner.update_text(format!( + "{} {}/{total_tests} - {}", + "Running all tests with REVM".bold(), + idx + 1, + format_duration_as_mm_ss(revm_run_time.elapsed()) + )); + let ef_test_report = match revm_runner::_run_ef_test_revm(test) { + Ok(ef_test_report) => ef_test_report, + Err(EFTestRunnerError::Internal(err)) => return Err(EFTestRunnerError::Internal(err)), + non_internal_errors => { + return Err(EFTestRunnerError::Internal(InternalError::FirstRunInternal(format!( + "Non-internal error raised when executing revm. This should not happen: {non_internal_errors:?}", + )))) + } + }; + reports.push(ef_test_report); + revm_run_spinner.update_text(report::progress(reports, revm_run_time.elapsed())); + } + revm_run_spinner.success(&format!( + "Ran all tests with REVM in {}", + format_duration_as_mm_ss(revm_run_time.elapsed()) + )); + Ok(()) +} + fn re_run_with_revm( reports: &mut [EFTestReport], ef_tests: &[EFTest], diff --git a/cmd/ef_tests/levm/runner/revm_runner.rs b/cmd/ef_tests/levm/runner/revm_runner.rs index e3b6bb1e85..73c2e61598 100644 --- a/cmd/ef_tests/levm/runner/revm_runner.rs +++ b/cmd/ef_tests/levm/runner/revm_runner.rs @@ -1,10 +1,14 @@ use crate::{ report::{AccountUpdatesReport, EFTestReport, TestReRunReport, TestVector}, - runner::{levm_runner, EFTestRunnerError, InternalError}, - types::EFTest, + runner::{ + levm_runner::{self, post_state_root}, + EFTestRunnerError, InternalError, + }, + types::{EFTest, EFTestTransaction}, utils::load_initial_state, }; -use ethrex_core::{types::TxKind, Address, H256}; +use bytes::Bytes; +use ethrex_core::{types::TxKind, Address, H256, U256}; use ethrex_levm::{ errors::{TransactionReport, TxResult}, Account, StorageSlot, @@ -15,8 +19,8 @@ use revm::{ db::State, inspectors::TracerEip3155 as RevmTracerEip3155, primitives::{ - BlobExcessGasAndPrice, BlockEnv as RevmBlockEnv, EVMError as REVMError, - ExecutionResult as RevmExecutionResult, TxEnv as RevmTxEnv, TxKind as RevmTxKind, + AccessListItem, BlobExcessGasAndPrice, BlockEnv as RevmBlockEnv, EVMError as REVMError, + ExecutionResult as RevmExecutionResult, TxEnv as RevmTxEnv, TxKind as RevmTxKind, B256, }, Evm as Revm, }; @@ -86,6 +90,19 @@ pub fn re_run_failed_ef_test_tx( Ok(()) } +// If gas price is not provided, calculate it with current base fee and priority fee +pub fn effective_gas_price(test: &EFTest, tx: &&EFTestTransaction) -> U256 { + match tx.gas_price { + None => { + let current_base_fee = test.env.current_base_fee.unwrap(); + let priority_fee = tx.max_priority_fee_per_gas.unwrap(); + let max_fee_per_gas = tx.max_fee_per_gas.unwrap(); + std::cmp::min(max_fee_per_gas, current_base_fee + priority_fee) + } + Some(price) => price, + } +} + pub fn prepare_revm_for_tx<'state>( initial_state: &'state mut EvmState, vector: &TestVector, @@ -102,12 +119,10 @@ pub fn prepare_revm_for_tx<'state>( basefee: RevmU256::from_limbs(test.env.current_base_fee.unwrap_or_default().0), difficulty: RevmU256::from_limbs(test.env.current_difficulty.0), prevrandao: test.env.current_random.map(|v| v.0.into()), - blob_excess_gas_and_price: Some(BlobExcessGasAndPrice::new( - test.env - .current_excess_blob_gas - .unwrap_or_default() - .as_u64(), - )), + blob_excess_gas_and_price: test + .env + .current_excess_blob_gas + .map(|gas| BlobExcessGasAndPrice::new(gas.as_u64())), }; let tx = &test .transactions @@ -116,10 +131,24 @@ pub fn prepare_revm_for_tx<'state>( "Vector {vector:?} not found in test {}", test.name )))?; + + let revm_access_list: Vec = tx + .access_list + .iter() + .map(|eftest_access_list_item| AccessListItem { + address: RevmAddress(eftest_access_list_item.address.0.into()), + storage_keys: eftest_access_list_item + .storage_keys + .iter() + .map(|key| B256::from(key.0)) + .collect(), + }) + .collect(); + let tx_env = RevmTxEnv { caller: tx.sender.0.into(), gas_limit: tx.gas_limit.as_u64(), - gas_price: RevmU256::from_limbs(tx.gas_price.unwrap_or_default().0), + gas_price: RevmU256::from_limbs(effective_gas_price(test, tx).0), transact_to: match tx.to { TxKind::Call(to) => RevmTxKind::Call(to.0.into()), TxKind::Create => RevmTxKind::Create, @@ -127,18 +156,27 @@ pub fn prepare_revm_for_tx<'state>( value: RevmU256::from_limbs(tx.value.0), data: tx.data.to_vec().into(), nonce: Some(tx.nonce.as_u64()), - chain_id: None, - access_list: Vec::default(), - gas_priority_fee: None, - blob_hashes: Vec::default(), - max_fee_per_blob_gas: None, + chain_id: Some(chain_spec.chain_id), //TODO: See what to do with this... ChainId test fails IDK why. + access_list: revm_access_list, + gas_priority_fee: tx + .max_priority_fee_per_gas + .map(|fee| RevmU256::from_limbs(fee.0)), + blob_hashes: tx + .blob_versioned_hashes + .iter() + .map(|h256| B256::from(h256.0)) + .collect::>(), + max_fee_per_blob_gas: tx + .max_fee_per_blob_gas + .map(|fee| RevmU256::from_limbs(fee.0)), authorization_list: None, }; + let evm_builder = Revm::builder() .with_block_env(block_env) .with_tx_env(tx_env) .modify_cfg_env(|cfg| cfg.chain_id = chain_spec.chain_id) - .with_spec_id(SpecId::CANCUN) + .with_spec_id(SpecId::CANCUN) //TODO: In the future replace cancun for the actual spec id .with_external_context( RevmTracerEip3155::new(Box::new(std::io::stderr())).without_summary(), ); @@ -322,3 +360,133 @@ pub fn compare_levm_revm_account_updates( .collect::>(), } } + +pub fn _run_ef_test_revm(test: &EFTest) -> Result { + dbg!(&test.name); + let mut ef_test_report = EFTestReport::new( + test.name.clone(), + test._info.generated_test_hash, + test.fork(), + ); + for (vector, _tx) in test.transactions.iter() { + match _run_ef_test_tx_revm(vector, test) { + Ok(_) => continue, + Err(EFTestRunnerError::VMInitializationFailed(reason)) => { + ef_test_report.register_vm_initialization_failure(reason, *vector); + } + Err(EFTestRunnerError::FailedToEnsurePreState(reason)) => { + ef_test_report.register_pre_state_validation_failure(reason, *vector); + } + Err(EFTestRunnerError::ExecutionFailedUnexpectedly(error)) => { + ef_test_report.register_unexpected_execution_failure(error, *vector); + } + Err(EFTestRunnerError::FailedToEnsurePostState(transaction_report, reason)) => { + ef_test_report.register_post_state_validation_failure( + transaction_report, + reason, + *vector, + ); + } + Err(EFTestRunnerError::VMExecutionMismatch(_)) => { + return Err(EFTestRunnerError::Internal(InternalError::FirstRunInternal( + "VM execution mismatch errors should only happen when COMPARING LEVM AND REVM. This failed during revm's execution." + .to_owned(), + ))); + } + Err(EFTestRunnerError::Internal(reason)) => { + return Err(EFTestRunnerError::Internal(reason)); + } + } + } + Ok(ef_test_report) +} + +pub fn _run_ef_test_tx_revm(vector: &TestVector, test: &EFTest) -> Result<(), EFTestRunnerError> { + // dbg!(vector); + let (mut state, _block_hash) = load_initial_state(test); + let mut revm = prepare_revm_for_tx(&mut state, vector, test)?; + let revm_execution_result = revm.transact_commit(); + drop(revm); // Need to drop the state mutable reference. + + _ensure_post_state_revm(revm_execution_result, vector, test, &mut state)?; + + Ok(()) +} + +pub fn _ensure_post_state_revm( + revm_execution_result: Result>, + vector: &TestVector, + test: &EFTest, + revm_state: &mut EvmState, +) -> Result<(), EFTestRunnerError> { + match revm_execution_result { + Ok(_execution_result) => { + match test.post.vector_post_value(vector).expect_exception { + // Execution result was successful but an exception was expected. + Some(expected_exception) => { + let error_reason = format!("Expected exception: {expected_exception}"); + println!("Expected exception: {expected_exception}"); + return Err(EFTestRunnerError::FailedToEnsurePostState( + TransactionReport { + result: TxResult::Success, + gas_used: 42, + gas_refunded: 42, + logs: vec![], + output: Bytes::new(), + new_state: HashMap::new(), + created_address: None, + }, + //TODO: This is not a TransactionReport because it is REVM + error_reason, + )); + } + // Execution result was successful and no exception was expected. + None => { + let revm_account_updates = ethrex_vm::get_state_transitions(revm_state); + let pos_state_root = post_state_root(&revm_account_updates, test); + let expected_post_state_root_hash = test.post.vector_post_value(vector).hash; + if expected_post_state_root_hash != pos_state_root { + println!( + "Post-state root mismatch: expected {expected_post_state_root_hash:#x}, got {pos_state_root:#x}", + ); + let error_reason = format!( + "Post-state root mismatch: expected {expected_post_state_root_hash:#x}, got {pos_state_root:#x}", + ); + return Err(EFTestRunnerError::FailedToEnsurePostState( + TransactionReport { + result: TxResult::Success, + gas_used: 42, + gas_refunded: 42, + logs: vec![], + output: Bytes::new(), + new_state: HashMap::new(), + created_address: None, + }, + //TODO: This is not a TransactionReport because it is REVM + error_reason, + )); + } + } + } + } + Err(err) => { + match test.post.vector_post_value(vector).expect_exception { + // Execution result was unsuccessful and an exception was expected. + // TODO: See if we want to map revm exceptions to expected exceptions, probably not. + Some(_expected_exception) => {} + // Execution result was unsuccessful but no exception was expected. + None => { + println!( + "Unexpected exception. Name: {}, vector: {:?}, error: {:?}", + &test.name, vector, err + ); + return Err(EFTestRunnerError::ExecutionFailedUnexpectedly( + ethrex_levm::errors::VMError::AddressAlreadyOccupied, + //TODO: Use another kind of error for this. + )); + } + } + } + }; + Ok(()) +} diff --git a/cmd/ef_tests/levm/types.rs b/cmd/ef_tests/levm/types.rs index be399ca5ed..02f005ef60 100644 --- a/cmd/ef_tests/levm/types.rs +++ b/cmd/ef_tests/levm/types.rs @@ -1,6 +1,7 @@ use crate::{ deserialize::{ - deserialize_ef_post_value_indexes, deserialize_hex_bytes, deserialize_hex_bytes_vec, + deserialize_access_lists, deserialize_ef_post_value_indexes, + deserialize_h256_vec_optional_safe, deserialize_hex_bytes, deserialize_hex_bytes_vec, deserialize_u256_optional_safe, deserialize_u256_safe, deserialize_u256_valued_hashmap_safe, deserialize_u256_vec_safe, }, @@ -230,6 +231,13 @@ impl From<&EFTestPreValue> for GenesisAccount { } } +#[derive(Debug, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct EFTestAccessListItem { + pub address: Address, + pub storage_keys: Vec, +} + #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct EFTestRawTransaction { @@ -246,6 +254,16 @@ pub struct EFTestRawTransaction { pub to: TxKind, #[serde(deserialize_with = "deserialize_u256_vec_safe")] pub value: Vec, + #[serde(default, deserialize_with = "deserialize_u256_optional_safe")] + pub max_fee_per_gas: Option, + #[serde(default, deserialize_with = "deserialize_u256_optional_safe")] + pub max_priority_fee_per_gas: Option, + #[serde(default, deserialize_with = "deserialize_u256_optional_safe")] + pub max_fee_per_blob_gas: Option, + #[serde(default, deserialize_with = "deserialize_h256_vec_optional_safe")] + pub blob_versioned_hashes: Option>, + #[serde(default, deserialize_with = "deserialize_access_lists")] + pub access_lists: Option>>, } #[derive(Debug, Deserialize)] @@ -253,7 +271,6 @@ pub struct EFTestRawTransaction { pub struct EFTestTransaction { pub data: Bytes, pub gas_limit: U256, - #[serde(default, deserialize_with = "deserialize_u256_optional_safe")] pub gas_price: Option, #[serde(deserialize_with = "deserialize_u256_safe")] pub nonce: U256, @@ -261,4 +278,9 @@ pub struct EFTestTransaction { pub sender: Address, pub to: TxKind, pub value: U256, + pub max_fee_per_gas: Option, + pub max_priority_fee_per_gas: Option, + pub max_fee_per_blob_gas: Option, + pub blob_versioned_hashes: Vec, + pub access_list: Vec, } From 94d725e0e38f361b8c6d91e578f377f5594ad803 Mon Sep 17 00:00:00 2001 From: Martin Paulucci Date: Wed, 27 Nov 2024 21:00:40 +0100 Subject: [PATCH 02/23] ci(l1): add integration tests workflow with hive and assertoor. (#1315) **Motivation** This is the only way to make it work, using `workflow_run` sucks. **Description** - Merged assertoor and hive tests into the same workflow so that they can reuse the `docker_build` job - Also fixed an issue with assertoor and kurtosis when having prism as part of the network --------- Co-authored-by: Rodrigo Oliveri --- .github/workflows/asertoor.yaml | 40 ------------ .github/workflows/docker_build.yaml | 39 ----------- .github/workflows/docker_publish.yaml | 2 +- .../workflows/{hive.yaml => integration.yaml} | 65 ++++++++++++++++--- test_data/network_params.yaml | 2 +- 5 files changed, 59 insertions(+), 89 deletions(-) delete mode 100644 .github/workflows/asertoor.yaml delete mode 100644 .github/workflows/docker_build.yaml rename .github/workflows/{hive.yaml => integration.yaml} (71%) diff --git a/.github/workflows/asertoor.yaml b/.github/workflows/asertoor.yaml deleted file mode 100644 index 5ae72a401d..0000000000 --- a/.github/workflows/asertoor.yaml +++ /dev/null @@ -1,40 +0,0 @@ -name: Assertoor -on: - workflow_run: - workflows: [Docker build] - types: [completed] - -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -env: - RUST_VERSION: 1.80.1 - -jobs: - run-assertoor: - name: Stability Check - runs-on: ubuntu-latest - if: ${{ github.event.workflow_run.conclusion == 'success' }} - steps: - - uses: actions/checkout@v4 - - - name: Download artifacts - uses: actions/download-artifact@v4 - with: - name: ethrex_image - path: /tmp - github-token: ${{ secrets.GITHUB_TOKEN }} - run-id: ${{ github.event.workflow_run.id }} - - - name: Load image - run: | - docker load --input /tmp/ethrex_image.tar - - - name: Setup kurtosis testnet and run assertoor tests - uses: ethpandaops/kurtosis-assertoor-github-action@v1 - with: - kurtosis_version: "1.4.2" - ethereum_package_url: "github.com/lambdaclass/ethereum-package" - ethereum_package_branch: "ethrex-integration" - ethereum_package_args: "./test_data/network_params.yaml" diff --git a/.github/workflows/docker_build.yaml b/.github/workflows/docker_build.yaml deleted file mode 100644 index 666028278a..0000000000 --- a/.github/workflows/docker_build.yaml +++ /dev/null @@ -1,39 +0,0 @@ -name: Docker build - -# This workflow is later used in assertoor and hive workflows -on: - push: - branches: ["main"] - merge_group: - pull_request: - branches: ["**"] - # paths-ignore: - # - "README.md" - # - "LICENSE" - # - "**/README.md" - # - "**/docs/**" - -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: ethrex - outputs: type=docker,dest=/tmp/ethrex_image.tar - - - name: Upload artifacts - uses: actions/upload-artifact@v4 - with: - name: ethrex_image - path: /tmp/ethrex_image.tar diff --git a/.github/workflows/docker_publish.yaml b/.github/workflows/docker_publish.yaml index 3427bc0bac..48837f4352 100644 --- a/.github/workflows/docker_publish.yaml +++ b/.github/workflows/docker_publish.yaml @@ -51,6 +51,6 @@ jobs: - name: Generate artifact attestation uses: actions/attest-build-provenance@v1 with: - subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest + subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} subject-digest: ${{ steps.push.outputs.digest }} push-to-registry: true diff --git a/.github/workflows/hive.yaml b/.github/workflows/integration.yaml similarity index 71% rename from .github/workflows/hive.yaml rename to .github/workflows/integration.yaml index f7dfd6db73..398a0898e1 100644 --- a/.github/workflows/hive.yaml +++ b/.github/workflows/integration.yaml @@ -1,8 +1,10 @@ -name: Hive +name: Integration Test on: - workflow_run: - workflows: [Docker build] - types: [completed] + push: + branches: ["main"] + merge_group: + pull_request: + branches: ["**"] concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} @@ -12,10 +14,59 @@ 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: ethrex + outputs: type=docker,dest=/tmp/ethrex_image.tar + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: ethrex_image + path: /tmp/ethrex_image.tar + + 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: ethrex_image + path: /tmp + + - name: Load image + run: | + docker load --input /tmp/ethrex_image.tar + + - name: Setup kurtosis testnet and run assertoor tests + uses: ethpandaops/kurtosis-assertoor-github-action@v1 + with: + kurtosis_version: "1.4.2" + ethereum_package_url: "github.com/lambdaclass/ethereum-package" + ethereum_package_branch: "ethrex-integration" + ethereum_package_args: "./test_data/network_params.yaml" + run-hive: - name: ${{ matrix.name }} + name: Hive - ${{ matrix.name }} runs-on: ubuntu-latest - if: ${{ github.event.workflow_run.conclusion == 'success' }} + needs: [docker_build] strategy: matrix: include: @@ -46,8 +97,6 @@ jobs: with: name: ethrex_image path: /tmp - github-token: ${{ secrets.GITHUB_TOKEN }} - run-id: ${{ github.event.workflow_run.id }} - name: Load image run: | diff --git a/test_data/network_params.yaml b/test_data/network_params.yaml index 2015947155..c452529dbe 100644 --- a/test_data/network_params.yaml +++ b/test_data/network_params.yaml @@ -6,7 +6,7 @@ participants: cl_type: lighthouse validator_count: 32 - el_type: geth - cl_type: prysm + cl_type: lighthouse validator_count: 32 additional_services: From 685ecd6b76168f5255867725d740d029c5229bce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Rodr=C3=ADguez=20Chatruc?= <49622509+jrchatruc@users.noreply.github.com> Date: Wed, 27 Nov 2024 17:04:59 -0300 Subject: [PATCH 03/23] feat(l2): fix load tests and better document load test comparisons (#1273) **Motivation** **Description** --------- Co-authored-by: Federico Borello <156438142+fborello-lambda@users.noreply.github.com> --- cmd/ethrex_l2/src/commands/test.rs | 2 +- crates/l2/docs/README.md | 58 ++- test_data/genesis-load-test.json | 561 +++++++++++++++++++++++++++++ 3 files changed, 616 insertions(+), 5 deletions(-) create mode 100644 test_data/genesis-load-test.json diff --git a/cmd/ethrex_l2/src/commands/test.rs b/cmd/ethrex_l2/src/commands/test.rs index 5ac6d8c665..3551fa423e 100644 --- a/cmd/ethrex_l2/src/commands/test.rs +++ b/cmd/ethrex_l2/src/commands/test.rs @@ -72,7 +72,7 @@ async fn transfer_from( let client = EthClient::new(&cfg.network.l2_rpc_url); let private_key = SecretKey::from_slice(pk.parse::().unwrap().as_bytes()).unwrap(); - let mut buffer = [0u8; 64]; + let mut buffer = [0u8; 32]; let public_key = private_key.public_key(secp256k1::SECP256K1).serialize(); buffer.copy_from_slice(&public_key[1..]); diff --git a/crates/l2/docs/README.md b/crates/l2/docs/README.md index d39a88343b..9e591cc39a 100644 --- a/crates/l2/docs/README.md +++ b/crates/l2/docs/README.md @@ -17,12 +17,62 @@ Configuration is done through env vars. A detailed list is available in each par ## Testing -Load tests are available via L2 CLI. The test take a list of private keys and send a bunch of transactions from each of them to some address. To run them, use the following command: +Load tests are available via L2 CLI. The test take a list of private keys and send a bunch of transactions from each of them to some address. To run them, use the following command on the root of this repo: ```bash -cargo run --bin ethrex_l2 -- test load --path +ethrex_l2 test load --path ./test_data/private_keys.txt -i 1000 -v --value 1 ``` -The path should point to a plain text file containing a list of private keys, one per line. Those account must be funded on the L2 network. Use `--help` to see more available options. +The command will, for each private key in the `private_keys.txt` file, send 1000 transactions with a value of `1` to a random account. If you want to send all transfers to the same account, pass -In the `test_data/` directory, you can find a list of private keys that are funded by the genesis. +``` +--to +``` + +The `private_keys.txt` file contains the private key of every account we use for load tests. + +Use `--help` to see more available options. + +## Load test comparison against Reth + +To run a load test on Reth, clone the repo, then run + +``` +cargo run --release -- node --chain --dev --dev.block-time 5000ms --http.port 1729 +``` + +to spin up a reth node in `dev` mode that will produce a block every 5 seconds. + +Reth has a default mempool size of 10k transactions. If the load test goes too fast it will reach the limit; if you want to increase mempool limits pass the following flags: + +``` +--txpool.max-pending-txns 100000000 --txpool.max-new-txns 1000000000 --txpool.pending-max-count 100000000 --txpool.pending-max-size 10000000000 --txpool.basefee-max-count 100000000000 --txpool.basefee-max-size 1000000000000 --txpool.queued-max-count 1000000000 +``` + +### Changing block gas limit + +By default the block gas limit is the one Ethereum mainnet uses, i.e. 30 million gas. If you wish to change it, just edit the `gasLimit` field in the genesis file (in the case of `ethrex` it's `genesis-l2.json`, in the case of `reth` it's `genesis-load-test.json`). Note that the number has to be passed as a hextstring. + +## Flamegraphs + +To analyze performance during load tests (both `ethrex` and `reth`) you can use `cargo flamegraph` to generate a flamegraph of the node. + +For `ethrex`, you can run the server with: + +``` +sudo -E CARGO_PROFILE_RELEASE_DEBUG=true cargo flamegraph --bin ethrex --features dev -- --network test_data/genesis-l2.json --http.port 1729 +``` + +For `reth`: + +``` +sudo cargo flamegraph --profile profiling -- node --chain --dev --dev.block-time 5000ms --http.port 1729 +``` + +### Samply + +To run with samply, run + +``` +samply record ./target/profiling/reth node --chain ../ethrex/test_data/genesis-load-test.json --dev --dev.block-time 5000ms --http.port 1729 +``` diff --git a/test_data/genesis-load-test.json b/test_data/genesis-load-test.json new file mode 100644 index 0000000000..733ab3ba7b --- /dev/null +++ b/test_data/genesis-load-test.json @@ -0,0 +1,561 @@ +{ + "config": { + "chainId": 1729, + "homesteadBlock": 0, + "eip150Block": 0, + "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "eip155Block": 0, + "eip158Block": 0, + "daoForkBlock": 0, + "frontierBlock": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "muirGlacierBlock": 0, + "istanbulBlock": 0, + "berlinBlock": 0, + "londonBlock": 0, + "terminalTotalDifficulty": "0x0", + "mergeNetsplitBlock": 0, + "shanghaiTime": 0, + "cancunTime": 0, + "clique": { + "period": 0, + "epoch": 30000 + } + }, + "nonce": "0x0", + "timestamp": "0x5ca9158b", + "gasLimit": "0x37e11d600", + "difficulty": "0x0", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x0000000000000000000000000000000000000000", + "alloc": { + "0x0000bd19F707CA481886244bDd20Bd6B8a81bd3e": { + "balance": "0xc097ce7bc90715b34b9f1000000000", + "nonce": "0" + }, + "0x000cD1537A823Ae7609E3897DA8d95801B557a8a": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0006d77295a0260ceAC113c5Aa15CFf0d28d9723": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000eA2e72065A2ceCA7f677Bc5E648279c2D843d": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000a52D537c4150ec274dcE3962a0d179B7E71B0": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0009aEFF154De37C8e02E83f93D2FeC5EC96f8a3": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000f1EB7F258D4A7683E5D0FC3C01058841DDC6f": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000aC79590dCc656c00c4453f123AcBf10DBb086": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0002Bf507275217c9E5EE250bC1B5ca177bb4f74": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000a3fC3BFD55b37025E6F4f57B0B6121F54e5bF": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000b4C43cce938dfD3420F975591Ee46D872C136": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0004b0C6de796fD980554cc7ff7B062b3B5079E1": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x00025eea83bA285532F5054b238c938076833d13": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000352E93fe11f9B715fdc61864315970B3DC082": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000c0d6b7C4516a5B274C51EA331A9410fe69127": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000D06C23EeD09A7Fa81cADd7eD5C783E8a25635": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0003Ea7fDFCdb89E9ddAb0128ec5C628F8D09D45": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0005C34d7B8b06CE8019C3Bb232dE82B2748A560": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x00079f33619F70F1DCE64EB6782E45D3498d807C": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0003E72436Ff296B3d39339784499D021b72Aca5": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x00075af7E665F3Ca4A4b05520CD6d5c13BbFEAf8": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000b59AeD48ADCd6c36Ae5f437AbB9CA730a2c43": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0004e4dfCed9d798767A4d7BA2B03495cE80A2b7": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000e73282F60E2CdE0D4FA9B323B6D54d860f330": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x00010AB05661Bfde304A4d884DF99d3011A83C54": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000B9Ea41A9dF00b7ae597afc0D10AF42666081F": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x00087C666bf7f52758DE186570979C4C79747157": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0008a52c83D34f0791D07FfeD04Fb6b14f94E2D4": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000A7Bbde38Fc53925D0De9cc1beE3038d36c2d2": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000Aa0154ed6560257d222B5dbE6ce4b66c48979": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000b681738e1f8aF387c41b2b1f0A04E0C33e9DB": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000D66A7706f2DD5F557d5b68e01E07E8FFDfaf5": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x00069DA530A71Dc92D02090d7f5f63e326e9beD0": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000db74a3da16609F183ACE7AF65B43D896349CE": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0003B1aB565508e095a543C89531e3fbc4a349DA": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0001c94c108BcE19CDb36b00F867A1798A81DedA": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000995137728C7C2a9142F4628f95c98Cac433d7": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000Ec60762AD0425A04C40c118Db5B9710Aa639e": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000Ebf88AE1BA960B06b0a9bbE576baa3B72E92E": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000e1a554572dd96fF3d1F2664832F3E4a66E7b7": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x00032C03f3b02D816128Fb5D2752398E2919a03c": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000A073dAC5ec2058a0De0e175874D5E297E086E": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000e06626Bb8618D9A1867362D46ddb1bF95ad75": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000212949b4866db43bAF7c4e0975426710ED081": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x00094cc0653B52406170105F4eb96C5e2f31Ab74": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000E67E4b1A23A3826304099cb24f337c916CF4b": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000885A4932ebeD6D760EA381e4EdAe51A53db05": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000883A40409Fa2193b698928459CB9E4DD5f8D8": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0002590DD45738F909115B163F1322A8A24a8B4E": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0005f132597da3152a6Da6beDB7C10bcC9B1B7f5": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x00031470def99c1d4dfE1fd08DD7A8520Ce21DB7": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0001Ebe3a3bA36f57F5989B3F0e5BEEBc710569C": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0006Bd0469166f63D0A1c33F71898D2b2E009b9b": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x00000A8d3f37af8DeF18832962Ee008d8dCa4F7b": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000e490f26249951F8527779399aa8F281509aC0": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0000638374f7dB166990BDc6aBeE884Ee01a8920": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x00031dE95353DeE86dc9B1248e825500DE0B39aF": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000511B42328794337D8b6846E5cFFef30c2d77A": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000d0576AdEf7083d53F6676bfc7c30d03b6Db1B": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0001E8Ff6406a7cd9071F46B8255Db6C16178448": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000C47c771A8db282eC233b28AD8525dc74D13FE": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000798832bb08268dB237898b95A8DaE9D58b62c": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000c877a5D9b9De61e5318B3f4330c56ecdC0865": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0003Ffc1f09d39FBFE87eD63E98249039C7b1d9A": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000d72403c18B2516d8ada074E1E7822bF1084DB": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x00054e17Db8C8Db028B19cB0f631888AdEb35E4b": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0002d9b2a816717C4d70040D66A714795F9B27a4": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0002AfCC1B0B608E86b5a1Dc45dE08184E629796": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000b1db69627F04688aA47951d847c8BFAB3fFaE": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000c2de896E4a92e796d6A9c1E4B01feB3e6Ed61": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000EDC52118DadB4B81f013005b6db2665B682ac": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0009e10C0D2F1a7A2b00b61c476aa8b608c60aDc": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000f2AbaA7581fAA2ad5C82b604C77ef68c3eAD9": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000F74AA6EE08C15076b3576eE33Ed3a80c9A1AD": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0001533C6C5b425815b2BaDdCdd42DFF3be04BCb": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0002D79686DeF20a0aB43FEA4a41a1Ad56529621": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x00077A336FCA40F933a7A301F4a39C26594F3EB5": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000B05E15C62CBC266A4DD1804b017d1f6dB078b": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000130badE00212bE1AA2F4aCFe965934635C9cD": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0008Bd31Ee6A758e168844cBEA107Ca4d87251aF": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000A390975F21371F1Cf3C783a4A7C1aF49074Fe": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000701F7d594Fb146e4d1c71342012e48A788055": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0005c6BeD054FEad199D72C6f663fC6fBf996153": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0009d862F87F26c638AAd14F2cc48FCa54DBf49d": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x00029637dA962294449549f804f8184046F5fbB0": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000279CB54E00B858774afEA4601034Db41c1A05": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0003dDe6f01e3B755e24891a5B0f2463BaD83e15": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000086Eeea461Ca48e4D319F9789F3Efd134E574": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0004351AD413792131011CC7ed8299dd783C6487": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x00097B4463159340Ac83B9bdf657C304cD70c11c": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0004ad0D0823e3d31C6ECA2A3495373fA76c43aC": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0005E815c1A3F40011Bd70C76062bbcBc51c546B": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0006A070bAC6195b59d4bC7f73741DCBe4e16b5e": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0006cEE23d8E9BC8d99E826cDa50481394aD9bDD": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000a523148845bEe3EE1e9F83df8257a1191C85B": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000D268F322F10925cdB5d2AD527E582259Da655": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000E5DE0a0175866d21F4Ec6c41F0422A05f14D6": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000cDF8Dba2393a40857cbCB0FCD9b998a941078": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000A341763112a5E3452c7AEE45c382a3fb7dc78": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000635BCbB109781Cea0Cd53e9f1370Dbac9937f": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000E0ea540095B3853c4cb09E5Cdd197330D3B55": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x00044cbfb4Ef6054667994C37c0fe0B6BB639718": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x00065fC4337dF331242bEE738031dAf35817Ee9e": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000815A8A659a51A8EF01F02441947Ea99182568": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0004C8da21c68dED2F63efD9836De7D43e7cDa10": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0006ed38815a9439c59bD917c12f77a9A7D39BCE": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0004Aa0442d0d43222431b3017912EC6a099771C": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000b3F6da04b6261B4154C8FaEd119632C49DBd5": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000AEBc2568796FDB763CAB67B31e0feE58Fe17d": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000425E97fC6692891876012824a210451cC06C4": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000036e0f87f8Cd3e97f9cfDB2e4E5Ff193c217a": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000305CD7184aB37fdd3D826B92A640218D09527": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000c95f1D83De53B76a0828F1bCdB1DfE12C0ab3": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000882c5FbD315801e4C367BCB04dBD299B9F571": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0000E101815A78EbB9FBBa34F4871aD32d5eb6CD": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000A997c1ceCB1DA78C16249e032e77d1865646a": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x00056bde49E3cAA9166C2a4C4951d0Cf067956A0": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000e65342176C7dac47bc75113F569695d6A113C": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0008D608884cd733642ab17aCa0c8504850B94fA": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000dFE27e1b71a49B641ad762aB95558584878D1": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x00085D9D1a71acf1080cED44CB501B350900627f": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0007d272a1f7Dfe862b030adE2922D149f3bDe3B": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0004b230511F921934F33E8B4425E43295232680": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0007514395022786B59ff91408692462C48d872c": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0005b34eB0d99dE72DB14d466f692009c4049D46": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0001a2c749FE0Ab1C09f1131BA17530f9D764fBC": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000c6c1D8F778D981968F9904772B0c455E1C17c": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000e64e0a2Fd76B4883c800833c82c5F2420b813": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000577bDc84B4019F77D9D09BDD8ED6145E0e890": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000029bD811D292E7f1CF36c0FA08fd753C45074": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000cE6740261E297FaD4c975D6D8F89f95C29add": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0001d0bAE8B1B9fe61d0B788E562A987813cbD98": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000E3388598A0534275104Ad44745620AF31EC7E": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000791D3185781e14eBb342E5df3BC9910f62E6F": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000Df55E76cf6dfD9598DD2b54948dE937f50f2B": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000055acf237931902ceBf4B905BF59813180555": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x00009074D8fc5Eeb25f1548Df05AD955E21FB08D": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000C1aE5FeCf09595C0C76Db609FEB2a5Af0962E": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000F76B2Fe7cCC13474de28586A877664EBA16B4": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000F7cFBa0B176Afc2eBadA9d4764d2eA6BBC5a1": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x00002132cE94eEfB06eB15898C1AABd94feb0AC2": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x00069dC0cc6b9d7B48B5348b12F625E8aB704104": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000A0191cf913E03bd594bC8817FC3B2895C0a25": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0007316aEDc52EB35c9B5c2E44e9fD712d1DF887": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000EBd066B6FEBB9d7f3B767DF06C08e369Dc20F": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x00096af89fd96f0d6E1721d9145944e813317d46": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000C5e39879228A1Fc8dF2470822CB8ce2Af8e07": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000ea86B4A3d7e4AF8CFab052c8b9a040149b507": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000796370C839773893a2cEFA5fc81f2332936fB": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000990B05481b1661bc6211298f6429451B09425": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0008a02d3E8507621f430345b98478058cDca79A": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000d35f8cd11bd989216b3669cBaac6fd8c07196": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000541653a96ABAdDba52fAA8D118e570d529543": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0006264bf7E3395309F728222641Ff8D0e1ad2C0": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000688AA0fBfB3F1e6554A63dF13bE08cB671b3b": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x00030da862690D170F096074e9E8b38db7D6f037": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0005e37296348571bd3604f7E56B67a7022801f6": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000ed6E0F4Fdc3615663BF4A601E35e7A8d66E1c": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000c53b37fA4977B59FD3Efdb473D8069844aDeA": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x00057714949aD700733C5b8E6cF3e8c6B7D228a2": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000C8FC4132881c31f67638c3941dF8D94a92299": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000fA71E446e1EcFd74d835b5bD6fA848A770d26": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000784B47aC2843419Df4cAd697d4e7b65CE1F93": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0002869e27c6FaEe08cCA6b765a726E7a076Ee0F": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0003135C47c441506b58483Ec6173F767182670B": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0006E80d584cbF9EB8C41CF2b009C607744a70F6": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000C1C05dBFf111c79D5c9E91420DFBEA1c31716": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0009Bf72AF31A4E6B8Ef6FbbFcb017823E4d2aF2": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x00021C20F3e68F930077Cca109Ca3C044E8B39bD": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000E90875aC71eD46A11dc1b509d2B35E2c9C31F": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x000f17eB09AA3f28132323E6075C672949526d5A": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x0000000000000000000000000000000000000000": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "0x3d1e15a1a55578f7c920884a9943b3b35d0d885b": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + } + }, + "number": "0x0", + "gasUsed": "0x0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "baseFeePerGas": 1, + "excessBlobGas": "0x0", + "blobGasUsed": 0 +} From bd973556b5f6dd25b4a590d4186b8b7044a12e8a Mon Sep 17 00:00:00 2001 From: Federico Borello <156438142+fborello-lambda@users.noreply.github.com> Date: Wed, 27 Nov 2024 17:05:52 -0300 Subject: [PATCH 04/23] feat(l2): info command (#1299) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Motivation** Implement the `info` command for the `ethrex_l2` cli. When we edit the `config` it's being displayed always, don't display by default, add a flag `-s` to display the changed config. **Description** -s flag example: Show the changes(note the `-s`): ``` ethrex_l2 config edit -s ``` Info command: ``` ❯ ethrex_l2 info --help Gets L2's information. Usage: ethrex_l2 info Commands: latest-blocks, -l Get latestCommittedBlock and latestVerifiedBlock from the OnChainProposer. block-number, -b Get the current block_number. transaction, -t Get the transaction's info ``` --- cmd/ethrex_l2/src/cli.rs | 5 +- cmd/ethrex_l2/src/commands/config.rs | 46 +++++++-- cmd/ethrex_l2/src/commands/info.rs | 95 +++++++++++++++++++ cmd/ethrex_l2/src/commands/mod.rs | 1 + cmd/ethrex_l2/src/config.rs | 12 ++- .../src/utils/config/default_values.rs | 5 + cmd/ethrex_l2/src/utils/config/mod.rs | 23 ++++- cmd/ethrex_l2/src/utils/messages.rs | 1 + crates/l2/.env.example | 2 + crates/l2/Makefile | 4 +- crates/l2/utils/eth_client/mod.rs | 48 +++++++++- 11 files changed, 219 insertions(+), 23 deletions(-) create mode 100644 cmd/ethrex_l2/src/commands/info.rs diff --git a/cmd/ethrex_l2/src/cli.rs b/cmd/ethrex_l2/src/cli.rs index e4d7570a70..4a2b6a257c 100644 --- a/cmd/ethrex_l2/src/cli.rs +++ b/cmd/ethrex_l2/src/cli.rs @@ -1,5 +1,5 @@ use crate::{ - commands::{autocomplete, config, stack, test, utils, wallet}, + commands::{autocomplete, config, info, stack, test, utils, wallet}, config::load_selected_config, }; use clap::{Parser, Subcommand}; @@ -35,6 +35,8 @@ enum EthrexL2Command { Test(test::Command), #[clap(subcommand, about = "Generate shell completion scripts.")] Autocomplete(autocomplete::Command), + #[clap(subcommand, about = "Gets L2's information.")] + Info(info::Command), } pub async fn start() -> eyre::Result<()> { @@ -50,6 +52,7 @@ pub async fn start() -> eyre::Result<()> { EthrexL2Command::Autocomplete(cmd) => cmd.run()?, EthrexL2Command::Config(_) => unreachable!(), EthrexL2Command::Test(cmd) => cmd.run(cfg).await?, + EthrexL2Command::Info(cmd) => cmd.run(cfg).await?, }; Ok(()) } diff --git a/cmd/ethrex_l2/src/commands/config.rs b/cmd/ethrex_l2/src/commands/config.rs index 0cd482aaa2..abe1f7cdf6 100644 --- a/cmd/ethrex_l2/src/commands/config.rs +++ b/cmd/ethrex_l2/src/commands/config.rs @@ -17,13 +17,29 @@ pub(crate) enum Command { #[clap(about = "Edit an existing config.")] Edit { config_name: Option, + #[arg( + long, + help = "Show the config after editing?", + short = 's', + default_value_t = false + )] + show: bool, #[command(flatten)] - opts: EditConfigOpts, + opts: Box, }, #[clap(about = "Create a new config.")] Create { config_name: String }, #[clap(about = "Set the config to use.")] - Set { config_name: Option }, + Set { + #[arg( + long, + help = "Show the config after editing?", + short = 's', + default_value_t = false + )] + show: bool, + config_name: Option, + }, #[clap(about = "Display a config.")] Display { config_name: Option }, #[clap(about = "List all configs.")] @@ -55,6 +71,8 @@ pub struct EditConfigOpts { pub address: Option
, #[arg(long, requires = "config_name", required = false)] pub common_bridge: Option
, + #[arg(long, requires = "config_name", required = false)] + pub on_chain_proposer: Option
, } impl EditConfigOpts { @@ -66,13 +84,18 @@ impl EditConfigOpts { && self.private_key.is_none() && self.address.is_none() && self.common_bridge.is_none() + && self.on_chain_proposer.is_none() } } impl Command { pub async fn run(self) -> eyre::Result<()> { match self { - Command::Edit { config_name, opts } => { + Command::Edit { + config_name, + opts, + show, + } => { let (new_config, config_path) = if let Some(ref config_name) = config_name { let config_path = config_path(config_name)?; if !config_path.exists() { @@ -81,7 +104,7 @@ impl Command { let new_config = if opts.is_empty() { edit_config_by_name_interactively(&config_path)? } else { - edit_config_by_name_with_args(&config_path, opts)? + edit_config_by_name_with_args(&config_path, *opts)? }; (new_config, config_path) } else { @@ -89,9 +112,10 @@ impl Command { }; let toml_config = toml::to_string_pretty(&new_config)?; std::fs::write(&config_path, &toml_config)?; - set_new_config(config_path.clone()).await?; - println!("Config updated at: {}", config_path.display()); - println!("\n{toml_config}"); + if show { + println!("Config updated at: {}", config_path.display()); + } + set_new_config(config_path.clone(), show).await?; } Command::Create { config_name } => { let config_path = config_path(&config_name)?; @@ -110,7 +134,7 @@ impl Command { ); std::fs::write(config_path, toml_config)?; } - Command::Set { config_name } => { + Command::Set { config_name, show } => { let config_path_to_select = if let Some(config_name) = config_name { let config_path_to_select = config_path(&config_name)?; if !config_path_to_select.exists() { @@ -122,7 +146,9 @@ impl Command { }; let selected_config = std::fs::read_to_string(config_path_to_select)?; std::fs::write(selected_config_path()?, &selected_config)?; - println!("Config \"{selected_config}\" set"); + if show { + println!("Config:\n{selected_config}"); + } } Command::Display { config_name } => { let config_to_display_path = if let Some(config_name) = config_name { @@ -150,7 +176,7 @@ impl Command { } } Command::Delete { config_name } => { - let config_path = if let Some(config_name) = config_name { + let config_path: std::path::PathBuf = if let Some(config_name) = config_name { config_path(&config_name)? } else { config_path_interactive_selection(CONFIG_SELECTION_TO_DELETE_PROMPT_MSG)? diff --git a/cmd/ethrex_l2/src/commands/info.rs b/cmd/ethrex_l2/src/commands/info.rs new file mode 100644 index 0000000000..4b19b95786 --- /dev/null +++ b/cmd/ethrex_l2/src/commands/info.rs @@ -0,0 +1,95 @@ +use crate::config::EthrexL2Config; +use clap::Subcommand; +use colored::{self, Colorize}; +use ethrex_l2::utils::eth_client::EthClient; +use keccak_hash::H256; +use std::str::FromStr; + +#[derive(Subcommand)] +pub(crate) enum Command { + #[clap( + about = "Get latestCommittedBlock and latestVerifiedBlock from the OnChainProposer.", + short_flag = 'l' + )] + LatestBlocks, + #[clap(about = "Get the current block_number.", short_flag = 'b')] + BlockNumber { + #[arg(long = "l2", required = false)] + l2: bool, + #[arg(long = "l1", required = false)] + l1: bool, + }, + #[clap(about = "Get the transaction's info.", short_flag = 't')] + Transaction { + #[arg(long = "l2", required = false)] + l2: bool, + #[arg(long = "l1", required = false)] + l1: bool, + #[arg(short = 'h', required = true)] + tx_hash: String, + }, +} + +impl Command { + pub async fn run(self, cfg: EthrexL2Config) -> eyre::Result<()> { + let eth_client = EthClient::new(&cfg.network.l1_rpc_url); + let rollup_client = EthClient::new(&cfg.network.l2_rpc_url); + let on_chain_proposer_address = cfg.contracts.on_chain_proposer; + match self { + Command::LatestBlocks => { + let last_committed_block = + EthClient::get_last_committed_block(ð_client, on_chain_proposer_address) + .await?; + + let last_verified_block = + EthClient::get_last_verified_block(ð_client, on_chain_proposer_address) + .await?; + + println!( + "latestCommittedBlock: {}", + format!("{last_committed_block}").bright_cyan() + ); + + println!( + "latestVerifiedBlock: {}", + format!("{last_verified_block}").bright_cyan() + ); + } + Command::BlockNumber { l2, l1 } => { + if !l1 || l2 { + let block_number = rollup_client.get_block_number().await?; + println!( + "[L2] BlockNumber: {}", + format!("{block_number}").bright_cyan() + ); + } + if l1 { + let block_number = eth_client.get_block_number().await?; + println!( + "[L1] BlockNumber: {}", + format!("{block_number}").bright_cyan() + ); + } + } + Command::Transaction { l2, l1, tx_hash } => { + let hash = H256::from_str(&tx_hash)?; + + if !l1 || l2 { + let tx = rollup_client + .get_transaction_by_hash(hash) + .await? + .ok_or(eyre::Error::msg("Not found"))?; + println!("[L2]:\n{tx}"); + } + if l1 { + let tx = eth_client + .get_transaction_by_hash(hash) + .await? + .ok_or(eyre::Error::msg("Not found"))?; + println!("[L1]:\n{tx}"); + } + } + } + Ok(()) + } +} diff --git a/cmd/ethrex_l2/src/commands/mod.rs b/cmd/ethrex_l2/src/commands/mod.rs index 7204bce57a..e202cef579 100644 --- a/cmd/ethrex_l2/src/commands/mod.rs +++ b/cmd/ethrex_l2/src/commands/mod.rs @@ -1,5 +1,6 @@ pub(crate) mod autocomplete; pub(crate) mod config; +pub(crate) mod info; pub(crate) mod stack; pub(crate) mod test; pub(crate) mod utils; diff --git a/cmd/ethrex_l2/src/config.rs b/cmd/ethrex_l2/src/config.rs index c568c2877a..b951f47878 100644 --- a/cmd/ethrex_l2/src/config.rs +++ b/cmd/ethrex_l2/src/config.rs @@ -40,6 +40,7 @@ pub struct WalletConfig { #[derive(Deserialize, Serialize, Clone)] pub struct ContractsConfig { pub common_bridge: Address, + pub on_chain_proposer: Address, } pub async fn try_load_selected_config() -> eyre::Result> { @@ -57,10 +58,13 @@ pub async fn load_selected_config() -> eyre::Result { let config_path = selected_config_path()?; if !config_path.exists() { println!("No config set, please select a config to set"); - if (commands::config::Command::Set { config_name: None }) - .run() - .await - .is_err() + if (commands::config::Command::Set { + config_name: None, + show: true, + }) + .run() + .await + .is_err() { let config_name = prompt( CONFIG_CREATE_NAME_PROMPT_MSG, diff --git a/cmd/ethrex_l2/src/utils/config/default_values.rs b/cmd/ethrex_l2/src/utils/config/default_values.rs index 92bce75784..7066b7b3b4 100644 --- a/cmd/ethrex_l2/src/utils/config/default_values.rs +++ b/cmd/ethrex_l2/src/utils/config/default_values.rs @@ -22,3 +22,8 @@ pub const DEFAULT_CONTRACTS_COMMON_BRIDGE_ADDRESS: H160 = H160([ 0xB5, 0xC0, 0x64, 0xF5, 0x9b, 0x03, 0x69, 0x23, 0x61, 0xC3, 0x75, 0x0D, 0x6d, 0x21, 0x18, 0xB5, 0xcf, 0xA1, 0xcf, 0x91, ]); +// 0xe9927d77c931f8648da4cc6751ef4e5e2ce74608 +pub const DEFAULT_CONTRACTS_ON_CHAIN_PROPOSER_ADDRESS: H160 = H160([ + 0xe9, 0x92, 0x7d, 0x77, 0xc9, 0x31, 0xf8, 0x64, 0x8d, 0xa4, 0xcc, 0x67, 0x51, 0xef, 0x4e, 0x5e, + 0x2c, 0xe7, 0x46, 0x08, +]); diff --git a/cmd/ethrex_l2/src/utils/config/mod.rs b/cmd/ethrex_l2/src/utils/config/mod.rs index e02e1ea1b1..5c405db541 100644 --- a/cmd/ethrex_l2/src/utils/config/mod.rs +++ b/cmd/ethrex_l2/src/utils/config/mod.rs @@ -16,12 +16,13 @@ use std::{path::PathBuf, str::FromStr}; pub mod default_values; use default_values::{ - DEFAULT_ADDRESS, DEFAULT_CONTRACTS_COMMON_BRIDGE_ADDRESS, DEFAULT_L1_CHAIN_ID, - DEFAULT_L1_EXPLORER_URL, DEFAULT_L1_RPC_URL, DEFAULT_L2_CHAIN_ID, DEFAULT_L2_EXPLORER_URL, - DEFAULT_L2_RPC_URL, DEFAULT_PRIVATE_KEY, + DEFAULT_ADDRESS, DEFAULT_CONTRACTS_COMMON_BRIDGE_ADDRESS, + DEFAULT_CONTRACTS_ON_CHAIN_PROPOSER_ADDRESS, DEFAULT_L1_CHAIN_ID, DEFAULT_L1_EXPLORER_URL, + DEFAULT_L1_RPC_URL, DEFAULT_L2_CHAIN_ID, DEFAULT_L2_EXPLORER_URL, DEFAULT_L2_RPC_URL, + DEFAULT_PRIVATE_KEY, }; -use super::messages::CONTRACTS_COMMON_BRIDGE_PROMPT_MSG; +use super::messages::{CONTRACTS_COMMON_BRIDGE_PROMPT_MSG, CONTRACTS_ON_CHAIN_PROPOSER_PROMPT_MSG}; pub const SELECTED_CONFIG_FILE_NAME: &str = ".selected"; @@ -125,6 +126,10 @@ pub fn prompt_config() -> eyre::Result { CONTRACTS_COMMON_BRIDGE_PROMPT_MSG, DEFAULT_CONTRACTS_COMMON_BRIDGE_ADDRESS, )?, + on_chain_proposer: prompt( + CONTRACTS_ON_CHAIN_PROPOSER_PROMPT_MSG, + DEFAULT_CONTRACTS_ON_CHAIN_PROPOSER_ADDRESS, + )?, }, }; Ok(prompted_config) @@ -172,7 +177,7 @@ pub fn edit_config_interactively() -> eyre::Result<(EthrexL2Config, PathBuf)> { Ok((new_config, config_path)) } -pub async fn set_new_config(config_path: PathBuf) -> eyre::Result<()> { +pub async fn set_new_config(config_path: PathBuf, show: bool) -> eyre::Result<()> { Box::pin(async { commands::config::Command::Set { config_name: Some( @@ -183,6 +188,7 @@ pub async fn set_new_config(config_path: PathBuf) -> eyre::Result<()> { .into_string() .map_err(|e| eyre::eyre!("Invalid file name: {:?}", e.into_string()))?, ), + show, } .run() .await @@ -226,6 +232,10 @@ pub fn edit_existing_config_interactively( CONTRACTS_COMMON_BRIDGE_PROMPT_MSG, existing_config.contracts.common_bridge, )?, + on_chain_proposer: prompt( + CONTRACTS_ON_CHAIN_PROPOSER_PROMPT_MSG, + existing_config.contracts.on_chain_proposer, + )?, }, }; Ok(config) @@ -269,6 +279,9 @@ pub fn edit_existing_config_non_interactively( common_bridge: opts .common_bridge .unwrap_or(existing_config.contracts.common_bridge), + on_chain_proposer: opts + .on_chain_proposer + .unwrap_or(existing_config.contracts.on_chain_proposer), }, }; Ok(config) diff --git a/cmd/ethrex_l2/src/utils/messages.rs b/cmd/ethrex_l2/src/utils/messages.rs index ee86c36534..c0a5ff3c6b 100644 --- a/cmd/ethrex_l2/src/utils/messages.rs +++ b/cmd/ethrex_l2/src/utils/messages.rs @@ -16,3 +16,4 @@ pub const L1_EXPLORER_URL_PROMPT_MSG: &str = "L1 Explorer URL"; pub const PRIVATE_KEY_PROMPT_MSG: &str = "Private key"; pub const ADDRESS_PROMPT_MSG: &str = "Address"; pub const CONTRACTS_COMMON_BRIDGE_PROMPT_MSG: &str = "Bridge contract address"; +pub const CONTRACTS_ON_CHAIN_PROPOSER_PROMPT_MSG: &str = "OnChainProposer contract address"; diff --git a/crates/l2/.env.example b/crates/l2/.env.example index 01181ddd72..0cc6969721 100644 --- a/crates/l2/.env.example +++ b/crates/l2/.env.example @@ -4,6 +4,8 @@ ETH_RPC_URL=http://localhost:8545 DEPLOYER_CONTRACT_VERIFIER=0x00000000000000000000000000000000000000AA # Risc0Groth16Verifier Sepolia Address # DEPLOYER_CONTRACT_VERIFIER=0xd9b0d07CeCd808a8172F21fA7C97992168f045CA +# Risc0Groth16Verifier Holesky Address +# DEPLOYER_CONTRACT_VERIFIER=0x44c220f0598345195cE99AD6A57aDfFcb9Ea33e7 DEPLOYER_ADDRESS=0x3d1e15a1a55578f7c920884a9943b3b35d0d885b DEPLOYER_PRIVATE_KEY=0x385c546456b6a603a1cfcaa9ec9494ba4832da08dd6bcf4de9a71e4a01b74924 # If set to false, the salt will be randomized. diff --git a/crates/l2/Makefile b/crates/l2/Makefile index 665d133929..5f4eb92694 100644 --- a/crates/l2/Makefile +++ b/crates/l2/Makefile @@ -57,7 +57,7 @@ down-local-l1: ## 🛑 Shuts down the L1 Lambda ethrex Client restart-local-l1: down-local-l1 init-local-l1 ## 🔄 Restarts the L1 Lambda ethrex Client -rm_dev_libmdbx_l1: ## 🛑 Removes the Libmdbx DB used by the L1 +rm_db_l1: ## 🛑 Removes the DB used by the L1 cargo run --release --manifest-path ../../Cargo.toml --bin ethrex -- removedb --datadir ${ethrex_L1_DEV_LIBMDBX} # Contracts @@ -92,7 +92,7 @@ init-l2-prover: ## 🚀 Initializes the Prover init-l2-prover-gpu: ## 🚀 Initializes the Prover with GPU support cargo run --release --features "build_zkvm,gpu" --manifest-path ../../Cargo.toml --bin ethrex_prover -rm_dev_libmdbx_l2: ## 🛑 Removes the Libmdbx DB used by the L2 +rm_db_l2: ## 🛑 Removes the DB used by the L2 cargo run --release --manifest-path ../../Cargo.toml --bin ethrex -- removedb --datadir ${ethrex_L2_DEV_LIBMDBX} diff --git a/crates/l2/utils/eth_client/mod.rs b/crates/l2/utils/eth_client/mod.rs index 653a723792..7fc95cb103 100644 --- a/crates/l2/utils/eth_client/mod.rs +++ b/crates/l2/utils/eth_client/mod.rs @@ -1,4 +1,4 @@ -use std::time::Duration; +use std::{fmt, time::Duration}; use crate::utils::config::eth::EthConfig; use bytes::Bytes; @@ -1021,3 +1021,49 @@ pub struct GetTransactionByHashTransaction { #[serde(default, with = "ethrex_core::serde_utils::u64::hex_str")] pub transaction_index: u64, } + +impl fmt::Display for GetTransactionByHashTransaction { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + r#" + chain_id: {}, + nonce: {}, + max_priority_fee_per_gas: {}, + max_fee_per_gas: {}, + gas_limit: {}, + to: {:#x}, + value: {}, + data: {:#?}, + access_list: {:#?}, + type: {:?}, + signature_y_parity: {}, + signature_r: {:x}, + signature_s: {:x}, + block_number: {}, + block_hash: {:#x}, + from: {:#x}, + hash: {:#x}, + transaction_index: {} + "#, + self.chain_id, + self.nonce, + self.max_priority_fee_per_gas, + self.max_fee_per_gas, + self.gas_limit, + self.to, + self.value, + self.data, + self.access_list, + self.r#type, + self.signature_y_parity, + self.signature_r, + self.signature_s, + self.block_number, + self.block_hash, + self.from, + self.hash, + self.transaction_index + ) + } +} From 745823b26c958e5ed5663f5a64fc29b3720b1e7c Mon Sep 17 00:00:00 2001 From: LeanSerra <46695152+LeanSerra@users.noreply.github.com> Date: Wed, 27 Nov 2024 17:18:22 -0300 Subject: [PATCH 05/23] chore(l2): forbid unwrap (#1290) **Motivation** Remove all the unwraps from the l2 module **Description** - Add `"unwrap_used = "deny"` to all `cargo.toml` files - Add `allow-unwrap-in-tests = true` to `clippy.toml` - Add new error types or use existing ones when possible. - Remove all unwraps, change functions to return `Result`, panic and log when errors are unrecovereable Closes #1266 --------- Co-authored-by: Leandro Serra Co-authored-by: Federico Borello <156438142+fborello-lambda@users.noreply.github.com> --- crates/l2/Cargo.toml | 3 + crates/l2/Clippy.toml | 1 + crates/l2/contracts/Cargo.toml | 4 + crates/l2/contracts/deployer.rs | 199 +++++++++++++++-------- crates/l2/proposer/errors.rs | 2 + crates/l2/proposer/l1_committer.rs | 2 +- crates/l2/proposer/mod.rs | 9 +- crates/l2/prover/Cargo.toml | 3 + crates/l2/prover/src/main.rs | 4 +- crates/l2/prover/src/prover.rs | 3 - crates/l2/prover/tests/perf_zkvm.rs | 1 + crates/l2/sdk/Cargo.toml | 4 + crates/l2/sdk/src/sdk.rs | 52 ++++-- crates/l2/tests/tests.rs | 20 ++- crates/l2/utils/eth_client/eth_sender.rs | 2 +- crates/l2/utils/test_data_io.rs | 1 + 16 files changed, 215 insertions(+), 95 deletions(-) create mode 100644 crates/l2/Clippy.toml diff --git a/crates/l2/Cargo.toml b/crates/l2/Cargo.toml index 408198c353..806599a787 100644 --- a/crates/l2/Cargo.toml +++ b/crates/l2/Cargo.toml @@ -38,3 +38,6 @@ rand = "0.8.5" [lib] path = "./l2.rs" + +[lints.clippy] +unwrap_used = "deny" diff --git a/crates/l2/Clippy.toml b/crates/l2/Clippy.toml new file mode 100644 index 0000000000..154626ef4e --- /dev/null +++ b/crates/l2/Clippy.toml @@ -0,0 +1 @@ +allow-unwrap-in-tests = true diff --git a/crates/l2/contracts/Cargo.toml b/crates/l2/contracts/Cargo.toml index ea5a655107..5459595037 100644 --- a/crates/l2/contracts/Cargo.toml +++ b/crates/l2/contracts/Cargo.toml @@ -16,6 +16,7 @@ spinoff = "0.8.0" colored = "2.1.0" lazy_static = "1.5.0" tracing.workspace = true +thiserror.workspace = true ethrex-l2 = { path = "../../l2" } ethrex-core = { path = "../../common" } @@ -23,3 +24,6 @@ ethrex-core = { path = "../../common" } [[bin]] name = "ethrex_l2_l1_deployer" path = "./deployer.rs" + +[lints.clippy] +unwrap_used = "deny" diff --git a/crates/l2/contracts/deployer.rs b/crates/l2/contracts/deployer.rs index 775419f25e..ef1a3549db 100644 --- a/crates/l2/contracts/deployer.rs +++ b/crates/l2/contracts/deployer.rs @@ -16,6 +16,24 @@ use std::{ }; use tracing::warn; +struct SetupResult { + deployer_address: Address, + deployer_private_key: SecretKey, + committer_address: Address, + verifier_address: Address, + contract_verifier_address: Address, + eth_client: EthClient, + contracts_path: PathBuf, +} + +#[derive(Debug, thiserror::Error)] +pub enum DeployError { + #[error("Failed to lock SALT: {0}")] + FailedToLockSALT(String), + #[error("The path is not a valid utf-8 string")] + FailedToGetStringFromPath, +} + // 0x4e59b44847b379578588920cA78FbF26c0B4956C const DETERMINISTIC_CREATE2_ADDRESS: Address = H160([ 0x4e, 0x59, 0xb4, 0x48, 0x47, 0xb3, 0x79, 0x57, 0x85, 0x88, 0x92, 0x0c, 0xa7, 0x8f, 0xbf, 0x26, @@ -28,37 +46,50 @@ lazy_static::lazy_static! { #[tokio::main] async fn main() { - let ( - deployer, - deployer_private_key, - committer_private_key, - verifier_private_key, - contract_verifier_address, - eth_client, - contracts_path, - ) = setup(); - download_contract_deps(&contracts_path); - compile_contracts(&contracts_path); - let (on_chain_proposer, bridge_address) = - deploy_contracts(deployer, deployer_private_key, ð_client, &contracts_path).await; + let Ok(setup_result) = setup() else { + panic!("Failed on setup"); + }; + if let Err(e) = download_contract_deps(&setup_result.contracts_path) { + panic!("Failed to download contracts {e}"); + }; + if let Err(e) = compile_contracts(&setup_result.contracts_path) { + panic!("Failed to compile contracts {e}"); + }; + + let on_chain_proposer; + let bridge_address; + match deploy_contracts( + setup_result.deployer_address, + setup_result.deployer_private_key, + &setup_result.eth_client, + &setup_result.contracts_path, + ) + .await + { + Ok((ocp, ba)) => { + on_chain_proposer = ocp; + bridge_address = ba; + } + Err(e) => panic!("Failed to deploy contracts {e}"), + }; initialize_contracts( - deployer, - deployer_private_key, - committer_private_key, - verifier_private_key, + setup_result.deployer_address, + setup_result.deployer_private_key, + setup_result.committer_address, + setup_result.verifier_address, on_chain_proposer, bridge_address, - contract_verifier_address, - ð_client, + setup_result.contract_verifier_address, + &setup_result.eth_client, ) .await; let env_lines = read_env_as_lines().expect("Failed to read env file as lines."); let mut wr_lines: Vec = Vec::new(); - for line in env_lines { - let mut line = line.unwrap(); + let mut env_lines_iter = env_lines.into_iter(); + while let Some(Ok(mut line)) = env_lines_iter.next() { if let Some(eq) = line.find('=') { let (envar, _) = line.split_at(eq); line = match envar { @@ -76,21 +107,13 @@ async fn main() { write_env(wr_lines).expect("Failed to write changes to the .env file."); } -fn setup() -> ( - Address, - SecretKey, - Address, - Address, - Address, - EthClient, - PathBuf, -) { +fn setup() -> Result { if let Err(e) = read_env_file() { warn!("Failed to read .env file: {e}"); } let eth_client = EthClient::new(&std::env::var("ETH_RPC_URL").expect("ETH_RPC_URL not set")); - let deployer = std::env::var("DEPLOYER_ADDRESS") + let deployer_address = std::env::var("DEPLOYER_ADDRESS") .expect("DEPLOYER_ADDRESS not set") .parse() .expect("Malformed DEPLOYER_ADDRESS"); @@ -106,11 +129,11 @@ fn setup() -> ( ) .expect("Malformed DEPLOYER_PRIVATE_KEY (SecretKey::parse)"); - let committer = std::env::var("COMMITTER_L1_ADDRESS") + let committer_address = std::env::var("COMMITTER_L1_ADDRESS") .expect("COMMITTER_L1_ADDRESS not set") .parse() .expect("Malformed COMMITTER_L1_ADDRESS"); - let verifier = std::env::var("PROVER_SERVER_VERIFIER_ADDRESS") + let verifier_address = std::env::var("PROVER_SERVER_VERIFIER_ADDRESS") .expect("PROVER_SERVER_VERIFIER_ADDRESS not set") .parse() .expect("Malformed PROVER_SERVER_VERIFIER_ADDRESS"); @@ -127,7 +150,9 @@ fn setup() -> ( match input.trim().to_lowercase().as_str() { "true" | "1" => (), "false" | "0" => { - let mut salt = SALT.lock().unwrap(); + let mut salt = SALT + .lock() + .map_err(|err| DeployError::FailedToLockSALT(err.to_string()))?; *salt = H256::random(); } _ => panic!("Invalid boolean string: {input}"), @@ -136,18 +161,18 @@ fn setup() -> ( .expect("DEPLOYER_CONTRACT_VERIFIER not set") .parse() .expect("Malformed DEPLOYER_CONTRACT_VERIFIER"); - ( - deployer, + Ok(SetupResult { + deployer_address, deployer_private_key, - committer, - verifier, + committer_address, + verifier_address, contract_verifier_address, eth_client, contracts_path, - ) + }) } -fn download_contract_deps(contracts_path: &Path) { +fn download_contract_deps(contracts_path: &Path) -> Result<(), DeployError> { std::fs::create_dir_all(contracts_path.join("lib")).expect("Failed to create contracts/lib"); Command::new("git") .arg("clone") @@ -156,15 +181,16 @@ fn download_contract_deps(contracts_path: &Path) { contracts_path .join("lib/openzeppelin-contracts") .to_str() - .unwrap(), + .ok_or(DeployError::FailedToGetStringFromPath)?, ) .spawn() .expect("Failed to spawn git") .wait() .expect("Failed to wait for git"); + Ok(()) } -fn compile_contracts(contracts_path: &Path) { +fn compile_contracts(contracts_path: &Path) -> Result<(), DeployError> { // Both the contract path and the output path are relative to where the Makefile is. assert!( Command::new("solc") @@ -173,13 +199,22 @@ fn compile_contracts(contracts_path: &Path) { contracts_path .join("src/l1/OnChainProposer.sol") .to_str() - .unwrap() + .ok_or(DeployError::FailedToGetStringFromPath)? ) .arg("-o") - .arg(contracts_path.join("solc_out").to_str().unwrap()) + .arg( + contracts_path + .join("solc_out") + .to_str() + .ok_or(DeployError::FailedToGetStringFromPath)?, + ) .arg("--overwrite") .arg("--allow-paths") - .arg(contracts_path.to_str().unwrap()) + .arg( + contracts_path + .to_str() + .ok_or(DeployError::FailedToGetStringFromPath)?, + ) .spawn() .expect("Failed to spawn solc") .wait() @@ -195,13 +230,22 @@ fn compile_contracts(contracts_path: &Path) { contracts_path .join("src/l1/CommonBridge.sol") .to_str() - .unwrap() + .ok_or(DeployError::FailedToGetStringFromPath)?, ) .arg("-o") - .arg(contracts_path.join("solc_out").to_str().unwrap()) + .arg( + contracts_path + .join("solc_out") + .to_str() + .ok_or(DeployError::FailedToGetStringFromPath)?, + ) .arg("--overwrite") .arg("--allow-paths") - .arg(contracts_path.to_str().unwrap()) + .arg( + contracts_path + .to_str() + .ok_or(DeployError::FailedToGetStringFromPath)?, + ) .spawn() .expect("Failed to spawn solc") .wait() @@ -209,6 +253,7 @@ fn compile_contracts(contracts_path: &Path) { .success(), "Failed to compile CommonBridge.sol" ); + Ok(()) } async fn deploy_contracts( @@ -216,7 +261,7 @@ async fn deploy_contracts( deployer_private_key: SecretKey, eth_client: &EthClient, contracts_path: &Path, -) -> (Address, Address) { +) -> Result<(Address, Address), DeployError> { let deploy_frames = spinner!(["📭❱❱", "❱📬❱", "❱❱📫"], 220); let mut spinner = Spinner::new( @@ -226,7 +271,8 @@ async fn deploy_contracts( ); let (on_chain_proposer_deployment_tx_hash, on_chain_proposer_address) = - deploy_on_chain_proposer(deployer, deployer_private_key, eth_client, contracts_path).await; + deploy_on_chain_proposer(deployer, deployer_private_key, eth_client, contracts_path) + .await?; let msg = format!( "OnChainProposer:\n\tDeployed at address {} with tx hash {}", @@ -237,7 +283,7 @@ async fn deploy_contracts( let mut spinner = Spinner::new(deploy_frames, "Deploying CommonBridge", Color::Cyan); let (bridge_deployment_tx_hash, bridge_address) = - deploy_bridge(deployer, deployer_private_key, eth_client, contracts_path).await; + deploy_bridge(deployer, deployer_private_key, eth_client, contracts_path).await?; let msg = format!( "CommonBridge:\n\tDeployed at address {} with tx hash {}", @@ -246,7 +292,7 @@ async fn deploy_contracts( ); spinner.success(&msg); - (on_chain_proposer_address, bridge_address) + Ok((on_chain_proposer_address, bridge_address)) } async fn deploy_on_chain_proposer( @@ -254,7 +300,7 @@ async fn deploy_on_chain_proposer( deployer_private_key: SecretKey, eth_client: &EthClient, contracts_path: &Path, -) -> (H256, Address) { +) -> Result<(H256, Address), DeployError> { let on_chain_proposer_init_code = hex::decode( std::fs::read_to_string(contracts_path.join("solc_out/OnChainProposer.bin")) .expect("Failed to read on_chain_proposer_init_code"), @@ -268,9 +314,9 @@ async fn deploy_on_chain_proposer( &on_chain_proposer_init_code, eth_client, ) - .await; + .await?; - (deploy_tx_hash, on_chain_proposer) + Ok((deploy_tx_hash, on_chain_proposer)) } async fn deploy_bridge( @@ -278,7 +324,7 @@ async fn deploy_bridge( deployer_private_key: SecretKey, eth_client: &EthClient, contracts_path: &Path, -) -> (H256, Address) { +) -> Result<(H256, Address), DeployError> { let mut bridge_init_code = hex::decode( std::fs::read_to_string(contracts_path.join("solc_out/CommonBridge.bin")) .expect("Failed to read bridge_init_code"), @@ -300,9 +346,9 @@ async fn deploy_bridge( &bridge_init_code.into(), eth_client, ) - .await; + .await?; - (deploy_tx_hash, bridge_address) + Ok((deploy_tx_hash, bridge_address)) } async fn create2_deploy( @@ -310,8 +356,14 @@ async fn create2_deploy( deployer_private_key: SecretKey, init_code: &Bytes, eth_client: &EthClient, -) -> (H256, Address) { - let calldata = [SALT.lock().unwrap().as_bytes(), init_code].concat(); +) -> Result<(H256, Address), DeployError> { + let calldata = [ + SALT.lock() + .map_err(|err| DeployError::FailedToLockSALT(err.to_string()))? + .as_bytes(), + init_code, + ] + .concat(); let deploy_tx = eth_client .build_eip1559_transaction( DETERMINISTIC_CREATE2_ADDRESS, @@ -330,18 +382,20 @@ async fn create2_deploy( wait_for_transaction_receipt(deploy_tx_hash, eth_client).await; - let deployed_address = create2_address(keccak(init_code)); + let deployed_address = create2_address(keccak(init_code))?; - (deploy_tx_hash, deployed_address) + Ok((deploy_tx_hash, deployed_address)) } -fn create2_address(init_code_hash: H256) -> Address { - Address::from_slice( +fn create2_address(init_code_hash: H256) -> Result { + Ok(Address::from_slice( keccak( [ &[0xff], DETERMINISTIC_CREATE2_ADDRESS.as_bytes(), - SALT.lock().unwrap().as_bytes(), + SALT.lock() + .map_err(|err| DeployError::FailedToLockSALT(err.to_string()))? + .as_bytes(), init_code_hash.as_bytes(), ] .concat(), @@ -349,7 +403,7 @@ fn create2_address(init_code_hash: H256) -> Address { .as_bytes() .get(12..) .expect("Failed to get create2 address"), - ) + )) } #[allow(clippy::too_many_arguments)] @@ -530,6 +584,7 @@ async fn wait_for_transaction_receipt(tx_hash: H256, eth_client: &EthClient) { } } +#[allow(clippy::unwrap_used)] #[cfg(test)] mod test { use crate::{compile_contracts, download_contract_deps}; @@ -547,17 +602,21 @@ mod test { if let Err(e) = std::fs::remove_dir_all(&solc_out) { if e.kind() != std::io::ErrorKind::NotFound { - panic!(); + panic!("Failed to remove directory solc_out"); } } if let Err(e) = std::fs::remove_dir_all(&lib) { if e.kind() != std::io::ErrorKind::NotFound { - panic!(); + panic!("failed to remove directory lib"); } } - download_contract_deps(Path::new("contracts")); - compile_contracts(Path::new("contracts")); + if download_contract_deps(Path::new("contracts")).is_err() { + panic!("failed to download contract deps"); + }; + if compile_contracts(Path::new("contracts")).is_err() { + panic!("failed to compile contracts"); + }; std::fs::remove_dir_all(solc_out).unwrap(); std::fs::remove_dir_all(lib).unwrap(); diff --git a/crates/l2/proposer/errors.rs b/crates/l2/proposer/errors.rs index e8d3e6dcb2..aa1f7eec08 100644 --- a/crates/l2/proposer/errors.rs +++ b/crates/l2/proposer/errors.rs @@ -68,6 +68,8 @@ pub enum ProposerError { FailedToRetrieveBlockFromStorage(#[from] StoreError), #[error("Proposer failed retrieve block from storaga, data is None.")] StorageDataIsNone, + #[error("Proposer failed to read jwt_secret: {0}")] + FailedToReadJWT(#[from] std::io::Error), } #[derive(Debug, thiserror::Error)] diff --git a/crates/l2/proposer/l1_committer.rs b/crates/l2/proposer/l1_committer.rs index bd792f250f..084eaad942 100644 --- a/crates/l2/proposer/l1_committer.rs +++ b/crates/l2/proposer/l1_committer.rs @@ -318,7 +318,7 @@ impl Committer { Bytes::from(calldata), Overrides { from: Some(self.l1_address), - gas_price_per_blob: Some(U256::from_dec_str("100000000000").unwrap()), + gas_price_per_blob: Some(U256::from(100000000000_i64)), ..Default::default() }, blobs_bundle, diff --git a/crates/l2/proposer/mod.rs b/crates/l2/proposer/mod.rs index c37b1298eb..777d4af47d 100644 --- a/crates/l2/proposer/mod.rs +++ b/crates/l2/proposer/mod.rs @@ -74,9 +74,16 @@ impl Proposer { .ok_or(ProposerError::StorageDataIsNone)? }; + let Ok(jwt_secret) = std::fs::read(&self.engine_config.jwt_path) else { + panic!( + "Failed to read jwt_secret from: {}", + &self.engine_config.jwt_path + ); + }; + ethrex_dev::block_producer::start_block_producer( self.engine_config.rpc_url.clone(), - std::fs::read(&self.engine_config.jwt_path).unwrap().into(), + jwt_secret.into(), head_block_hash, 10, self.block_production_interval, diff --git a/crates/l2/prover/Cargo.toml b/crates/l2/prover/Cargo.toml index e3274f200b..54fcc40953 100644 --- a/crates/l2/prover/Cargo.toml +++ b/crates/l2/prover/Cargo.toml @@ -50,3 +50,6 @@ path = "src/main.rs" default = [] build_zkvm = ["zkvm_interface/build_zkvm"] gpu = ["risc0-zkvm/cuda"] + +[lints.clippy] +unwrap_used = "deny" diff --git a/crates/l2/prover/src/main.rs b/crates/l2/prover/src/main.rs index 7c99f88bb2..d026309899 100644 --- a/crates/l2/prover/src/main.rs +++ b/crates/l2/prover/src/main.rs @@ -15,7 +15,9 @@ async fn main() { warn!("Failed to read .env file: {e}"); } - let config = ProverClientConfig::from_env().unwrap(); + let Ok(config) = ProverClientConfig::from_env() else { + panic!("Failed to read ProverClientConfig from .env file"); + }; debug!("Prover Client has started"); init_client(config).await; } diff --git a/crates/l2/prover/src/prover.rs b/crates/l2/prover/src/prover.rs index 24ff09a259..86bdecb950 100644 --- a/crates/l2/prover/src/prover.rs +++ b/crates/l2/prover/src/prover.rs @@ -8,8 +8,6 @@ use zkvm_interface::{ use risc0_zkvm::{default_prover, ExecutorEnv, ProverOpts}; -use ethrex_l2::utils::config::prover_client::ProverClientConfig; - pub struct Prover<'a> { elf: &'a [u8], pub id: [u32; 8], @@ -18,7 +16,6 @@ pub struct Prover<'a> { impl<'a> Default for Prover<'a> { fn default() -> Self { - let _config = ProverClientConfig::from_env().unwrap(); Self::new() } } diff --git a/crates/l2/prover/tests/perf_zkvm.rs b/crates/l2/prover/tests/perf_zkvm.rs index 558a1c0f88..abcec2e421 100644 --- a/crates/l2/prover/tests/perf_zkvm.rs +++ b/crates/l2/prover/tests/perf_zkvm.rs @@ -1,3 +1,4 @@ +#![allow(clippy::unwrap_used)] use std::path::Path; use tracing::info; diff --git a/crates/l2/sdk/Cargo.toml b/crates/l2/sdk/Cargo.toml index c4c937e097..a1d8a044b3 100644 --- a/crates/l2/sdk/Cargo.toml +++ b/crates/l2/sdk/Cargo.toml @@ -14,7 +14,11 @@ hex.workspace = true keccak-hash = "0.11.0" secp256k1.workspace = true itertools = "0.13.0" +thiserror.workspace = true [lib] name = "ethrex_l2_sdk" path = "src/sdk.rs" + +[lints.clippy] +unwrap_used = "deny" diff --git a/crates/l2/sdk/src/sdk.rs b/crates/l2/sdk/src/sdk.rs index a7d4042ff4..e149e9a64c 100644 --- a/crates/l2/sdk/src/sdk.rs +++ b/crates/l2/sdk/src/sdk.rs @@ -19,19 +19,25 @@ pub const DEFAULT_BRIDGE_ADDRESS: Address = H160([ 0xac, 0xbb, 0xe4, 0x54, ]); +#[derive(Debug, thiserror::Error)] +pub enum SdkError { + #[error("Failed to parse address from hex")] + FailedToParseAddressFromHex, +} + /// BRIDGE_ADDRESS or 0x6bf26397c5676a208d5c4e5f35cb479bacbbe454 -pub fn bridge_address() -> Address { +pub fn bridge_address() -> Result { std::env::var("BRIDGE_ADDRESS") .unwrap_or(format!("{DEFAULT_BRIDGE_ADDRESS:#x}")) .parse() - .unwrap() + .map_err(|_| SdkError::FailedToParseAddressFromHex) } pub async fn wait_for_transaction_receipt( tx_hash: H256, client: &EthClient, max_retries: u64, -) -> RpcReceipt { +) -> Option { let mut receipt = client .get_transaction_receipt(tx_hash) .await @@ -52,7 +58,7 @@ pub async fn wait_for_transaction_receipt( .await .expect("Failed to get transaction receipt"); } - receipt.unwrap() + receipt } pub async fn transfer( @@ -90,7 +96,14 @@ pub async fn deposit( eth_client: &EthClient, ) -> Result { println!("Depositing {amount} from {from:#x} to bridge"); - transfer(amount, from, bridge_address(), from_pk, eth_client).await + transfer( + amount, + from, + bridge_address().map_err(|err| EthClientError::Custom(err.to_string()))?, + from_pk, + eth_client, + ) + .await } pub async fn withdraw( @@ -195,7 +208,7 @@ pub async fn claim_withdraw( let claim_tx = eth_client .build_eip1559_transaction( - bridge_address(), + bridge_address().map_err(|err| EthClientError::Custom(err.to_string()))?, from, claim_withdrawal_data.into(), Overrides { @@ -215,7 +228,13 @@ pub async fn get_withdraw_merkle_proof( client: &EthClient, tx_hash: H256, ) -> Result<(u64, Vec), EthClientError> { - let tx_receipt = client.get_transaction_receipt(tx_hash).await?.unwrap(); + let tx_receipt = + client + .get_transaction_receipt(tx_hash) + .await? + .ok_or(EthClientError::Custom( + "Failed to get transaction receipt".to_string(), + ))?; let block = client .get_block_by_hash(tx_receipt.block_info.block_hash) @@ -225,8 +244,7 @@ pub async fn get_withdraw_merkle_proof( BlockBodyWrapper::Full(body) => body.transactions, BlockBodyWrapper::OnlyHashes(_) => unreachable!(), }; - - let (index, tx_withdrawal_hash) = transactions + let Some(Some((index, tx_withdrawal_hash))) = transactions .iter() .filter(|tx| match &tx.tx { Transaction::PrivilegedL2Transaction(tx) => tx.tx_type == PrivilegedTxType::Withdrawal, @@ -234,12 +252,18 @@ pub async fn get_withdraw_merkle_proof( }) .find_position(|tx| tx.hash == tx_hash) .map(|(i, tx)| match &tx.tx { - Transaction::PrivilegedL2Transaction(tx) => { - (i as u64, tx.get_withdrawal_hash().unwrap()) + Transaction::PrivilegedL2Transaction(privileged_l2_transaction) => { + privileged_l2_transaction + .get_withdrawal_hash() + .map(|withdrawal_hash| (i as u64, (withdrawal_hash))) } _ => unreachable!(), }) - .unwrap(); + else { + return Err(EthClientError::Custom( + "Failed to get widthdrawal hash, transaction is not a withdrawal".to_string(), + )); + }; let path = merkle_proof( transactions @@ -251,7 +275,9 @@ pub async fn get_withdraw_merkle_proof( .collect(), tx_withdrawal_hash, ) - .unwrap(); + .ok_or(EthClientError::Custom( + "Failed to generate merkle proof, element is not on the tree".to_string(), + ))?; Ok((index, path)) } diff --git a/crates/l2/tests/tests.rs b/crates/l2/tests/tests.rs index a4b53931b6..a7794833d5 100644 --- a/crates/l2/tests/tests.rs +++ b/crates/l2/tests/tests.rs @@ -1,3 +1,4 @@ +#![allow(clippy::unwrap_used)] use bytes::Bytes; use ethereum_types::{Address, H160, U256}; use ethrex_l2::utils::eth_client::{eth_sender::Overrides, EthClient}; @@ -71,7 +72,9 @@ async fn testito() { println!("Waiting for deposit transaction receipt"); let _deposit_tx_receipt = - ethrex_l2_sdk::wait_for_transaction_receipt(deposit_tx, ð_client, 5).await; + ethrex_l2_sdk::wait_for_transaction_receipt(deposit_tx, ð_client, 5) + .await + .unwrap(); // 3. Check balances on L1 and L2 @@ -141,7 +144,9 @@ async fn testito() { .await .unwrap(); let _transfer_tx_receipt = - ethrex_l2_sdk::wait_for_transaction_receipt(transfer_tx, &proposer_client, 30).await; + ethrex_l2_sdk::wait_for_transaction_receipt(transfer_tx, &proposer_client, 30) + .await + .unwrap(); // 5. Check balances on L2 @@ -185,7 +190,9 @@ async fn testito() { .await .unwrap(); let withdraw_tx_receipt = - ethrex_l2_sdk::wait_for_transaction_receipt(withdraw_tx, &proposer_client, 30).await; + ethrex_l2_sdk::wait_for_transaction_receipt(withdraw_tx, &proposer_client, 30) + .await + .unwrap(); // 7. Check balances on L1 and L2 @@ -251,8 +258,9 @@ async fn testito() { .await .unwrap(); - let _claim_tx_receipt = - ethrex_l2_sdk::wait_for_transaction_receipt(claim_tx, ð_client, 15).await; + let _claim_tx_receipt = ethrex_l2_sdk::wait_for_transaction_receipt(claim_tx, ð_client, 15) + .await + .unwrap(); // 9. Check balances on L1 and L2 @@ -289,6 +297,7 @@ fn proposer_client() -> EthClient { EthClient::new(&std::env::var("PROPOSER_URL").unwrap_or(DEFAULT_PROPOSER_URL.to_owned())) } +#[allow(clippy::unwrap_used)] fn l1_rich_wallet_address() -> Address { std::env::var("L1_RICH_WALLET_ADDRESS") .unwrap_or(format!("{DEFAULT_L1_RICH_WALLET_ADDRESS:#x}")) @@ -296,6 +305,7 @@ fn l1_rich_wallet_address() -> Address { .unwrap() } +#[allow(clippy::unwrap_used)] fn l1_rich_wallet_private_key() -> SecretKey { std::env::var("L1_RICH_WALLET_PRIVATE_KEY") .map(|s| SecretKey::from_slice(H256::from_str(&s).unwrap().as_bytes()).unwrap()) diff --git a/crates/l2/utils/eth_client/eth_sender.rs b/crates/l2/utils/eth_client/eth_sender.rs index 87bccd3a64..4f2c6e1e8a 100644 --- a/crates/l2/utils/eth_client/eth_sender.rs +++ b/crates/l2/utils/eth_client/eth_sender.rs @@ -89,7 +89,7 @@ impl EthClient { let encoded_from = deployer.encode_to_vec(); // FIXME: We'll probably need to use nonce - 1 since it was updated above. - let encoded_nonce = self.get_nonce(deployer).await.unwrap().encode_to_vec(); + let encoded_nonce = self.get_nonce(deployer).await?.encode_to_vec(); let mut encoded = vec![(0xc0 + encoded_from.len() + encoded_nonce.len()) as u8]; encoded.extend(encoded_from.clone()); encoded.extend(encoded_nonce.clone()); diff --git a/crates/l2/utils/test_data_io.rs b/crates/l2/utils/test_data_io.rs index 782b0b7e0c..1e8cd9fc1d 100644 --- a/crates/l2/utils/test_data_io.rs +++ b/crates/l2/utils/test_data_io.rs @@ -1,3 +1,4 @@ +#![allow(clippy::unwrap_used)] use ethrex_core::types::{Block, Genesis}; use ethrex_rlp::{decode::RLPDecode, encode::RLPEncode}; use ethrex_storage::Store; From 203ef4a281eb09376bb07bdee1599c5be911d5db Mon Sep 17 00:00:00 2001 From: Akash S M Date: Thu, 28 Nov 2024 01:54:08 +0530 Subject: [PATCH 06/23] chore(l1): replace `info!` with `debug!` tracing log (#1174) **Motivation** Replace `info!` tracing logs with `debug!` to avoid flooding the console. Closes #1134 --------- Co-authored-by: ElFantasma --- crates/networking/p2p/net.rs | 2 +- crates/networking/p2p/rlpx/connection.rs | 26 ++++++++++++------------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/crates/networking/p2p/net.rs b/crates/networking/p2p/net.rs index 4c3b00e687..c5c2255313 100644 --- a/crates/networking/p2p/net.rs +++ b/crates/networking/p2p/net.rs @@ -794,7 +794,7 @@ async fn handle_peer_as_initiator( table: Arc>, connection_broadcast: broadcast::Sender<(tokio::task::Id, Arc)>, ) { - info!("Trying RLPx connection with {node:?}"); + debug!("Trying RLPx connection with {node:?}"); let stream = TcpSocket::new_v4() .unwrap() .connect(SocketAddr::new(node.ip, node.tcp_port)) diff --git a/crates/networking/p2p/rlpx/connection.rs b/crates/networking/p2p/rlpx/connection.rs index bb4815b626..ca22000300 100644 --- a/crates/networking/p2p/rlpx/connection.rs +++ b/crates/networking/p2p/rlpx/connection.rs @@ -45,7 +45,7 @@ use tokio::{ task, time::{sleep, Instant}, }; -use tracing::{error, info}; +use tracing::{debug, error}; const CAP_P2P: (Capability, u8) = (Capability::P2p, 5); const CAP_ETH: (Capability, u8) = (Capability::Eth, 68); const CAP_SNAP: (Capability, u8) = (Capability::Snap, 1); @@ -167,13 +167,13 @@ impl RLPxConnection { reason: self.match_disconnect_reason(&error), })) .await - .unwrap_or_else(|e| info!("Could not send Disconnect message: ({e})")); + .unwrap_or_else(|e| debug!("Could not send Disconnect message: ({e})")); if let Ok(node_id) = self.get_remote_node_id() { // Discard peer from kademlia table - info!("{error_text}: ({error}), discarding peer {node_id}"); + debug!("{error_text}: ({error}), discarding peer {node_id}"); table.lock().await.replace_peer(node_id); } else { - info!("{error_text}: ({error}), unknown peer") + debug!("{error_text}: ({error}), unknown peer") } } @@ -201,7 +201,7 @@ impl RLPxConnection { )) } }; - info!("Completed handshake!"); + debug!("Completed handshake!"); self.exchange_hello_messages().await?; Ok(()) @@ -240,7 +240,7 @@ impl RLPxConnection { async fn handle_peer_conn(&mut self) -> Result<(), RLPxError> { if let RLPxConnectionState::Established(_) = &self.state { self.init_peer_conn().await?; - info!("Started peer main loop"); + debug!("Started peer main loop"); // Wait for eth status message or timeout. let mut broadcaster_receive = { if self.capabilities.contains(&CAP_ETH) { @@ -301,7 +301,7 @@ impl RLPxConnection { async fn check_periodic_tasks(&mut self) -> Result<(), RLPxError> { if Instant::now() >= self.next_periodic_task_check { self.send(Message::Ping(PingMessage {})).await?; - info!("Ping sent"); + debug!("Ping sent"); self.next_periodic_task_check = Instant::now() + PERIODIC_TASKS_CHECK_INTERVAL; }; Ok(()) @@ -311,20 +311,20 @@ impl RLPxConnection { let peer_supports_eth = self.capabilities.contains(&CAP_ETH); match message { Message::Disconnect(msg_data) => { - info!("Received Disconnect: {:?}", msg_data.reason); + debug!("Received Disconnect: {:?}", msg_data.reason); // Returning a Disonnect error to be handled later at the call stack return Err(RLPxError::Disconnect()); } Message::Ping(_) => { - info!("Received Ping"); + debug!("Received Ping"); self.send(Message::Pong(PongMessage {})).await?; - info!("Pong sent"); + debug!("Pong sent"); } Message::Pong(_) => { // We ignore received Pong messages } Message::Status(msg_data) if !peer_supports_eth => { - info!("Received Status"); + debug!("Received Status"); backend::validate_status(msg_data, &self.storage)? } Message::GetAccountRange(req) => { @@ -397,7 +397,7 @@ impl RLPxConnection { // Sending eth Status if peer supports it if self.capabilities.contains(&CAP_ETH) { let status = backend::get_status(&self.storage)?; - info!("Sending status"); + debug!("Sending status"); self.send(Message::Status(status)).await?; // The next immediate message in the ETH protocol is the // status, reference here: @@ -405,7 +405,7 @@ impl RLPxConnection { match self.receive().await? { Message::Status(msg_data) => { // TODO: Check message status is correct. - info!("Received Status"); + debug!("Received Status"); backend::validate_status(msg_data, &self.storage)? } _msg => { From 3741772c7a227161e5c227adf3364c0aaa9ab247 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Arjovsky?= Date: Wed, 27 Nov 2024 22:04:24 +0100 Subject: [PATCH 07/23] feat(l1): add other suites to the hive coverage report (#1277) Changes: - Added hive suits in matrix so they run in parallel. - The hive run step runs the matrix and generates jsons and uploads them as artifacts. - The report generation downloads all json artifacts and posts reports to slack and the action summary. Closes #1276 --- .github/scripts/publish.sh | 2 +- .github/workflows/hive_coverage.yaml | 54 ++++++++++++++++++++++------ 2 files changed, 44 insertions(+), 12 deletions(-) diff --git a/.github/scripts/publish.sh b/.github/scripts/publish.sh index 37d7bc37fd..ef140e3b06 100644 --- a/.github/scripts/publish.sh +++ b/.github/scripts/publish.sh @@ -7,7 +7,7 @@ $(jq -n --arg text "$(cat results.md)" '{ "type": "header", "text": { "type": "plain_text", - "text": "Daily report" + "text": "Daily Hive Coverage report" } }, { diff --git a/.github/workflows/hive_coverage.yaml b/.github/workflows/hive_coverage.yaml index 3ed33b45e6..6485fb0d3c 100644 --- a/.github/workflows/hive_coverage.yaml +++ b/.github/workflows/hive_coverage.yaml @@ -10,9 +10,22 @@ env: RUST_VERSION: 1.80.1 jobs: - hive-coverage: - name: Daily Hive Coverage + run-hive: + name: Run engine hive simulator to gather coverage information. 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 HIVE_EXTRA_ARGS="--sim.parallelism 4" + - simulation: devp2p + name: "Devp2p eth tests" + run_command: make run-hive-on-latest SIMULATION=devp2p HIVE_EXTRA_ARGS="--sim.parallelism 4" + - simulation: engine + name: "Cancun Engine tests" + run_command: make run-hive-on-latest SIMULATION=ethereum/engine HIVE_EXTRA_ARGS="--sim.parallelism 4" + steps: - name: Pull image run: | @@ -22,25 +35,44 @@ jobs: - 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: make run-hive-on-latest SIMULATION=ethereum/engine + run: ${{ matrix.run_command }} continue-on-error: true + - name: Upload results + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.simulation }}_logs + path: hive/workspace/logs/*-*.json + + hive-report: + name: Generate report and upload to summary and slack + needs: run-hive + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v3 + + - name: Rustup toolchain install + uses: dtolnay/rust-toolchain@stable + with: + toolchain: ${{ env.RUST_VERSION }} + + - name: Download all results + uses: actions/download-artifact@v4 + with: + path: hive/workspace/logs + pattern: "*_logs" + merge-multiple: true + - name: Caching uses: Swatinem/rust-cache@v2 - name: Generate the hive report - id: report - run: | - cargo run -p hive_report > results.md + run: cargo run -p hive_report > results.md - name: Post results in summary run: | From 1a75d339661a0fd70024680bdf7186a826445ed3 Mon Sep 17 00:00:00 2001 From: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Date: Thu, 28 Nov 2024 09:50:07 -0300 Subject: [PATCH 08/23] feat(levm): add job that runs LEVM EF Tests and publishes the report in Slack (#1322) --- .../scripts/publish_levm_ef_tests_summary.sh | 25 +++ .github/workflows/levm_reporter.yaml | 56 +++++ .gitignore | 4 +- cmd/ef_tests/levm/report.rs | 210 +++++++++++++----- cmd/ef_tests/levm/runner/mod.rs | 17 +- crates/vm/levm/Makefile | 4 + 6 files changed, 251 insertions(+), 65 deletions(-) create mode 100644 .github/scripts/publish_levm_ef_tests_summary.sh create mode 100644 .github/workflows/levm_reporter.yaml diff --git a/.github/scripts/publish_levm_ef_tests_summary.sh b/.github/scripts/publish_levm_ef_tests_summary.sh new file mode 100644 index 0000000000..1483a893fd --- /dev/null +++ b/.github/scripts/publish_levm_ef_tests_summary.sh @@ -0,0 +1,25 @@ +curl -X POST $url \ +-H 'Content-Type: application/json; charset=utf-8' \ +--data @- <> $GITHUB_STEP_SUMMARY + cat levm_ef_tests_summary_github.txt >> $GITHUB_STEP_SUMMARY + + - name: Post results to ethrex L1 slack channel + env: + url: ${{ secrets.ETHREX_L1_SLACK_WEBHOOK }} + run: sh .github/scripts/publish_levm_ef_tests_summary.sh + + - name: Post results to ethrex L2 slack channel + env: + url: ${{ secrets.ETHREX_L2_SLACK_WEBHOOK }} + run: sh .github/scripts/publish_levm_ef_tests_summary.sh + + - name: Post results to levm slack channel + env: + url: ${{ secrets.LEVM_SLACK_WEBHOOK }} + run: sh .github/scripts/publish_levm_ef_tests_summary.sh diff --git a/.gitignore b/.gitignore index 6cfabcf47c..87bbcdadd4 100644 --- a/.gitignore +++ b/.gitignore @@ -48,5 +48,7 @@ tests_v3.0.0.tar.gz .env levm_ef_tests_report.txt - +levm_ef_tests_summary_slack.txt +levm_ef_tests_summary_github.txt +levm_ef_tests_summary.txt loc_report.md diff --git a/cmd/ef_tests/levm/report.rs b/cmd/ef_tests/levm/report.rs index 60db3958e2..335c5e6791 100644 --- a/cmd/ef_tests/levm/report.rs +++ b/cmd/ef_tests/levm/report.rs @@ -17,6 +17,10 @@ use std::{ time::Duration, }; +pub const LEVM_EF_TESTS_SUMMARY_SLACK_FILE_PATH: &str = "./levm_ef_tests_summary_slack.txt"; +pub const LEVM_EF_TESTS_SUMMARY_GITHUB_FILE_PATH: &str = "./levm_ef_tests_summary_slack.txt"; +pub const EF_TESTS_CACHE_FILE_PATH: &str = "./levm_ef_tests_cache.json"; + pub type TestVector = (usize, usize, usize); pub fn progress(reports: &[EFTestReport], time: Duration) -> String { @@ -39,30 +43,12 @@ pub fn progress(reports: &[EFTestReport], time: Duration) -> String { format_duration_as_mm_ss(time) ) } -pub fn summary(reports: &[EFTestReport]) -> String { - let total_passed = reports.iter().filter(|report| report.passed()).count(); - let total_run = reports.len(); - format!( - "{} {}/{total_run}\n\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n", - "Summary:".bold(), - if total_passed == total_run { - format!("{}", total_passed).green() - } else if total_passed > 0 { - format!("{}", total_passed).yellow() - } else { - format!("{}", total_passed).red() - }, - fork_summary(reports, SpecId::CANCUN), - fork_summary(reports, SpecId::SHANGHAI), - fork_summary(reports, SpecId::HOMESTEAD), - fork_summary(reports, SpecId::ISTANBUL), - fork_summary(reports, SpecId::LONDON), - fork_summary(reports, SpecId::BYZANTIUM), - fork_summary(reports, SpecId::BERLIN), - fork_summary(reports, SpecId::CONSTANTINOPLE), - fork_summary(reports, SpecId::MERGE), - fork_summary(reports, SpecId::FRONTIER), - ) + +pub fn format_duration_as_mm_ss(duration: Duration) -> String { + let total_seconds = duration.as_secs(); + let minutes = total_seconds / 60; + let seconds = total_seconds % 60; + format!("{minutes:02}:{seconds:02}") } pub fn write(reports: &[EFTestReport]) -> Result { @@ -86,8 +72,6 @@ pub fn write(reports: &[EFTestReport]) -> Result { Ok(report_file_path) } -pub const EF_TESTS_CACHE_FILE_PATH: &str = "./levm_ef_tests_cache.json"; - pub fn cache(reports: &[EFTestReport]) -> Result { let cache_file_path = PathBuf::from(EF_TESTS_CACHE_FILE_PATH); let cache = serde_json::to_string_pretty(&reports).map_err(|err| { @@ -122,11 +106,135 @@ pub fn load() -> Result, EFTestRunnerError> { } } -pub fn format_duration_as_mm_ss(duration: Duration) -> String { - let total_seconds = duration.as_secs(); - let minutes = total_seconds / 60; - let seconds = total_seconds % 60; - format!("{minutes:02}:{seconds:02}") +pub fn summary_for_slack(reports: &[EFTestReport]) -> String { + let total_passed = reports.iter().filter(|report| report.passed()).count(); + let total_run = reports.len(); + let success_percentage = (total_passed as f64 / total_run as f64) * 100.0; + format!( + r#"*Summary*: {total_passed}/{total_run} ({success_percentage:.2}%)\n\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n"#, + fork_summary_for_slack(reports, SpecId::CANCUN), + fork_summary_for_slack(reports, SpecId::SHANGHAI), + fork_summary_for_slack(reports, SpecId::HOMESTEAD), + fork_summary_for_slack(reports, SpecId::ISTANBUL), + fork_summary_for_slack(reports, SpecId::LONDON), + fork_summary_for_slack(reports, SpecId::BYZANTIUM), + fork_summary_for_slack(reports, SpecId::BERLIN), + fork_summary_for_slack(reports, SpecId::CONSTANTINOPLE), + fork_summary_for_slack(reports, SpecId::MERGE), + fork_summary_for_slack(reports, SpecId::FRONTIER), + ) +} + +fn fork_summary_for_slack(reports: &[EFTestReport], fork: SpecId) -> String { + let fork_str: &str = fork.into(); + let (fork_tests, fork_passed_tests, fork_success_percentage) = fork_statistics(reports, fork); + format!(r#"*{fork_str}:* {fork_passed_tests}/{fork_tests} ({fork_success_percentage:.2}%)"#) +} + +pub fn write_summary_for_slack(reports: &[EFTestReport]) -> Result { + let summary_file_path = PathBuf::from(LEVM_EF_TESTS_SUMMARY_SLACK_FILE_PATH); + std::fs::write( + LEVM_EF_TESTS_SUMMARY_SLACK_FILE_PATH, + summary_for_slack(reports), + ) + .map_err(|err| { + EFTestRunnerError::Internal(InternalError::MainRunnerInternal(format!( + "Failed to write summary to file: {err}" + ))) + })?; + Ok(summary_file_path) +} + +pub fn summary_for_github(reports: &[EFTestReport]) -> String { + let total_passed = reports.iter().filter(|report| report.passed()).count(); + let total_run = reports.len(); + let success_percentage = (total_passed as f64 / total_run as f64) * 100.0; + format!( + r#"Summary: {total_passed}/{total_run} ({success_percentage:.2}%)\n\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n"#, + fork_summary_for_github(reports, SpecId::CANCUN), + fork_summary_for_github(reports, SpecId::SHANGHAI), + fork_summary_for_github(reports, SpecId::HOMESTEAD), + fork_summary_for_github(reports, SpecId::ISTANBUL), + fork_summary_for_github(reports, SpecId::LONDON), + fork_summary_for_github(reports, SpecId::BYZANTIUM), + fork_summary_for_github(reports, SpecId::BERLIN), + fork_summary_for_github(reports, SpecId::CONSTANTINOPLE), + fork_summary_for_github(reports, SpecId::MERGE), + fork_summary_for_github(reports, SpecId::FRONTIER), + ) +} + +fn fork_summary_for_github(reports: &[EFTestReport], fork: SpecId) -> String { + let fork_str: &str = fork.into(); + let (fork_tests, fork_passed_tests, fork_success_percentage) = fork_statistics(reports, fork); + format!("{fork_str}: {fork_passed_tests}/{fork_tests} ({fork_success_percentage:.2}%)") +} + +pub fn write_summary_for_github(reports: &[EFTestReport]) -> Result { + let summary_file_path = PathBuf::from(LEVM_EF_TESTS_SUMMARY_GITHUB_FILE_PATH); + std::fs::write( + LEVM_EF_TESTS_SUMMARY_GITHUB_FILE_PATH, + summary_for_github(reports), + ) + .map_err(|err| { + EFTestRunnerError::Internal(InternalError::MainRunnerInternal(format!( + "Failed to write summary to file: {err}" + ))) + })?; + Ok(summary_file_path) +} + +pub fn summary_for_shell(reports: &[EFTestReport]) -> String { + let total_passed = reports.iter().filter(|report| report.passed()).count(); + let total_run = reports.len(); + let success_percentage = (total_passed as f64 / total_run as f64) * 100.0; + format!( + "{} {}/{total_run} ({success_percentage:.2})\n\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n", + "Summary:".bold(), + if total_passed == total_run { + format!("{}", total_passed).green() + } else if total_passed > 0 { + format!("{}", total_passed).yellow() + } else { + format!("{}", total_passed).red() + }, + fork_summary_shell(reports, SpecId::CANCUN), + fork_summary_shell(reports, SpecId::SHANGHAI), + fork_summary_shell(reports, SpecId::HOMESTEAD), + fork_summary_shell(reports, SpecId::ISTANBUL), + fork_summary_shell(reports, SpecId::LONDON), + fork_summary_shell(reports, SpecId::BYZANTIUM), + fork_summary_shell(reports, SpecId::BERLIN), + fork_summary_shell(reports, SpecId::CONSTANTINOPLE), + fork_summary_shell(reports, SpecId::MERGE), + fork_summary_shell(reports, SpecId::FRONTIER), + ) +} + +fn fork_summary_shell(reports: &[EFTestReport], fork: SpecId) -> String { + let fork_str: &str = fork.into(); + let (fork_tests, fork_passed_tests, fork_success_percentage) = fork_statistics(reports, fork); + format!( + "{}: {}/{fork_tests} ({fork_success_percentage:.2}%)", + fork_str.bold(), + if fork_passed_tests == fork_tests { + format!("{}", fork_passed_tests).green() + } else if fork_passed_tests > 0 { + format!("{}", fork_passed_tests).yellow() + } else { + format!("{}", fork_passed_tests).red() + }, + ) +} + +fn fork_statistics(reports: &[EFTestReport], fork: SpecId) -> (usize, usize, f64) { + let fork_tests = reports.iter().filter(|report| report.fork == fork).count(); + let fork_passed_tests = reports + .iter() + .filter(|report| report.fork == fork && report.passed()) + .count(); + let fork_success_percentage = (fork_passed_tests as f64 / fork_tests as f64) * 100.0; + (fork_tests, fork_passed_tests, fork_success_percentage) } #[derive(Debug, Default, Clone)] @@ -149,16 +257,16 @@ impl Display for EFTestsReport { }, )?; writeln!(f)?; - writeln!(f, "{}", fork_summary(&self.0, SpecId::CANCUN))?; - writeln!(f, "{}", fork_summary(&self.0, SpecId::SHANGHAI))?; - writeln!(f, "{}", fork_summary(&self.0, SpecId::HOMESTEAD))?; - writeln!(f, "{}", fork_summary(&self.0, SpecId::ISTANBUL))?; - writeln!(f, "{}", fork_summary(&self.0, SpecId::LONDON))?; - writeln!(f, "{}", fork_summary(&self.0, SpecId::BYZANTIUM))?; - writeln!(f, "{}", fork_summary(&self.0, SpecId::BERLIN))?; - writeln!(f, "{}", fork_summary(&self.0, SpecId::CONSTANTINOPLE))?; - writeln!(f, "{}", fork_summary(&self.0, SpecId::MERGE))?; - writeln!(f, "{}", fork_summary(&self.0, SpecId::FRONTIER))?; + writeln!(f, "{}", fork_summary_shell(&self.0, SpecId::CANCUN))?; + writeln!(f, "{}", fork_summary_shell(&self.0, SpecId::SHANGHAI))?; + writeln!(f, "{}", fork_summary_shell(&self.0, SpecId::HOMESTEAD))?; + writeln!(f, "{}", fork_summary_shell(&self.0, SpecId::ISTANBUL))?; + writeln!(f, "{}", fork_summary_shell(&self.0, SpecId::LONDON))?; + writeln!(f, "{}", fork_summary_shell(&self.0, SpecId::BYZANTIUM))?; + writeln!(f, "{}", fork_summary_shell(&self.0, SpecId::BERLIN))?; + writeln!(f, "{}", fork_summary_shell(&self.0, SpecId::CONSTANTINOPLE))?; + writeln!(f, "{}", fork_summary_shell(&self.0, SpecId::MERGE))?; + writeln!(f, "{}", fork_summary_shell(&self.0, SpecId::FRONTIER))?; writeln!(f)?; writeln!(f, "{}", "Failed tests:".bold())?; writeln!(f)?; @@ -236,26 +344,6 @@ impl Display for EFTestsReport { } } -fn fork_summary(reports: &[EFTestReport], fork: SpecId) -> String { - let fork_str: &str = fork.into(); - let fork_tests = reports.iter().filter(|report| report.fork == fork).count(); - let fork_passed_tests = reports - .iter() - .filter(|report| report.fork == fork && report.passed()) - .count(); - format!( - "{}: {}/{fork_tests}", - fork_str.bold(), - if fork_passed_tests == fork_tests { - format!("{}", fork_passed_tests).green() - } else if fork_passed_tests > 0 { - format!("{}", fork_passed_tests).yellow() - } else { - format!("{}", fork_passed_tests).red() - }, - ) -} - #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct EFTestReport { pub name: String, diff --git a/cmd/ef_tests/levm/runner/mod.rs b/cmd/ef_tests/levm/runner/mod.rs index a0fff006fc..72aca963b9 100644 --- a/cmd/ef_tests/levm/runner/mod.rs +++ b/cmd/ef_tests/levm/runner/mod.rs @@ -44,15 +44,20 @@ pub struct EFTestRunnerOptions { pub fork: Vec, #[arg(short, long, value_name = "TESTS")] pub tests: Vec, + #[arg(short, long, value_name = "SUMMARY", default_value = "false")] + pub summary: bool, } pub fn run_ef_tests( ef_tests: Vec, - _opts: &EFTestRunnerOptions, + opts: &EFTestRunnerOptions, ) -> Result<(), EFTestRunnerError> { let mut reports = report::load()?; if reports.is_empty() { - run_with_levm(&mut reports, &ef_tests)?; + run_with_levm(&mut reports, &ef_tests, opts)?; + } + if opts.summary { + return Ok(()); } re_run_with_revm(&mut reports, &ef_tests)?; write_report(&reports) @@ -61,6 +66,7 @@ pub fn run_ef_tests( fn run_with_levm( reports: &mut Vec, ef_tests: &[EFTest], + opts: &EFTestRunnerOptions, ) -> Result<(), EFTestRunnerError> { let levm_run_time = std::time::Instant::now(); let mut levm_run_spinner = Spinner::new( @@ -83,8 +89,13 @@ fn run_with_levm( } levm_run_spinner.success(&report::progress(reports, levm_run_time.elapsed())); + if opts.summary { + report::write_summary_for_slack(reports)?; + report::write_summary_for_github(reports)?; + } + let mut summary_spinner = Spinner::new(Dots, "Loading summary...".to_owned(), Color::Cyan); - summary_spinner.success(&report::summary(reports)); + summary_spinner.success(&report::summary_for_shell(reports)); Ok(()) } diff --git a/crates/vm/levm/Makefile b/crates/vm/levm/Makefile index 552c4015d6..8c7bed4190 100644 --- a/crates/vm/levm/Makefile +++ b/crates/vm/levm/Makefile @@ -35,6 +35,10 @@ run-evm-ef-tests: ## 🏃‍♂️ Run EF Tests cd ../../../ && \ time cargo test -p ef_tests-levm --test ef_tests_levm +generate-evm-ef-tests-report: ## 📊 Generate EF Tests Report + cd ../../../ && \ + cargo test -p ef_tests-levm --test ef_tests_levm -- --summary + clean-evm-ef-tests: ## 🗑️ Clean test vectors rm -rf $(SPECTEST_VECTORS_DIR) From b930c83ee771103326acc81c86b18c6e1905cdf4 Mon Sep 17 00:00:00 2001 From: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Date: Thu, 28 Nov 2024 10:24:54 -0300 Subject: [PATCH 09/23] fix(levm): `extcodecopy` non-compliance patch bug (#1327) Resolves #1324 --- crates/vm/levm/src/opcode_handlers/environment.rs | 10 ++++++---- crates/vm/levm/tests/edge_case_tests.rs | 14 ++++++++++++++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/crates/vm/levm/src/opcode_handlers/environment.rs b/crates/vm/levm/src/opcode_handlers/environment.rs index 83d318cc58..c1e9cfe142 100644 --- a/crates/vm/levm/src/opcode_handlers/environment.rs +++ b/crates/vm/levm/src/opcode_handlers/environment.rs @@ -1,6 +1,6 @@ use crate::{ call_frame::CallFrame, - constants::{BALANCE_COLD_ADDRESS_ACCESS_COST, WARM_ADDRESS_ACCESS_COST}, + constants::{BALANCE_COLD_ADDRESS_ACCESS_COST, WARM_ADDRESS_ACCESS_COST, WORD_SIZE}, errors::{InternalError, OpcodeSuccess, OutOfGasError, VMError}, gas_cost, vm::{word_to_address, VM}, @@ -328,10 +328,12 @@ impl VM { let bytecode = self.get_account(&address).info.bytecode; - let new_memory_size = (((!size).checked_add(1).ok_or(VMError::Internal( - InternalError::ArithmeticOperationOverflow, - ))?) & 31) + let new_memory_size = dest_offset .checked_add(size) + .ok_or(VMError::Internal( + InternalError::ArithmeticOperationOverflow, + ))? + .checked_next_multiple_of(WORD_SIZE) .ok_or(VMError::Internal( InternalError::ArithmeticOperationOverflow, ))?; diff --git a/crates/vm/levm/tests/edge_case_tests.rs b/crates/vm/levm/tests/edge_case_tests.rs index b4a5228401..50a5bdade6 100644 --- a/crates/vm/levm/tests/edge_case_tests.rs +++ b/crates/vm/levm/tests/edge_case_tests.rs @@ -237,3 +237,17 @@ fn test_non_compliance_smod() { &U256::zero() ); } + +#[test] +fn test_non_compliance_extcodecopy_size_and_destoffset() { + let mut vm = new_vm_with_bytecode(Bytes::copy_from_slice(&[ + 0x60, 17, 0x60, 17, 0x60, 17, 0x60, 17, 0x3c, 0x59, + ])) + .unwrap(); + let mut current_call_frame = vm.call_frames.pop().unwrap(); + vm.execute(&mut current_call_frame); + assert_eq!( + current_call_frame.stack.stack.first().unwrap(), + &U256::from(64) + ); +} From d74aeffd9b1f7fe6dc2cd7f5c76ea8779767fda2 Mon Sep 17 00:00:00 2001 From: Dylan Socolobsky Date: Thu, 28 Nov 2024 10:38:38 -0300 Subject: [PATCH 10/23] chore(l2): forbid expects (#1289) **Motivation** We should not be using `.expect()` and use Errors instead. **Description** * `expect_used = "deny"` was added to `Cargo.toml` to throw clippy errors on expects. * `.expect()` usage was replaced with errors Closes #1267 --- crates/l2/Cargo.toml | 1 + crates/l2/clippy.toml | 1 + crates/l2/contracts/Cargo.toml | 1 + crates/l2/contracts/deployer.rs | 323 +++++++++++++++++----------- crates/l2/proposer/l1_committer.rs | 9 +- crates/l2/proposer/l1_watcher.rs | 9 +- crates/l2/proposer/mod.rs | 42 ++-- crates/l2/proposer/prover_server.rs | 16 +- crates/l2/prover/Cargo.toml | 1 + crates/l2/prover/src/main.rs | 6 +- crates/l2/prover/tests/perf_zkvm.rs | 2 +- crates/l2/sdk/Cargo.toml | 1 + crates/l2/sdk/src/sdk.rs | 16 +- crates/l2/tests/tests.rs | 3 +- crates/l2/utils/config/errors.rs | 10 + crates/l2/utils/test_data_io.rs | 1 + 16 files changed, 272 insertions(+), 170 deletions(-) create mode 100644 crates/l2/clippy.toml diff --git a/crates/l2/Cargo.toml b/crates/l2/Cargo.toml index 806599a787..9194d5a569 100644 --- a/crates/l2/Cargo.toml +++ b/crates/l2/Cargo.toml @@ -41,3 +41,4 @@ path = "./l2.rs" [lints.clippy] unwrap_used = "deny" +expect_used = "deny" diff --git a/crates/l2/clippy.toml b/crates/l2/clippy.toml new file mode 100644 index 0000000000..154626ef4e --- /dev/null +++ b/crates/l2/clippy.toml @@ -0,0 +1 @@ +allow-unwrap-in-tests = true diff --git a/crates/l2/contracts/Cargo.toml b/crates/l2/contracts/Cargo.toml index 5459595037..022a3ab50d 100644 --- a/crates/l2/contracts/Cargo.toml +++ b/crates/l2/contracts/Cargo.toml @@ -27,3 +27,4 @@ path = "./deployer.rs" [lints.clippy] unwrap_used = "deny" +expect_used = "deny" diff --git a/crates/l2/contracts/deployer.rs b/crates/l2/contracts/deployer.rs index ef1a3549db..726fb7f375 100644 --- a/crates/l2/contracts/deployer.rs +++ b/crates/l2/contracts/deployer.rs @@ -2,6 +2,7 @@ use bytes::Bytes; use colored::Colorize; use ethereum_types::{Address, H160, H256}; use ethrex_core::U256; +use ethrex_l2::utils::eth_client::errors::EthClientError; use ethrex_l2::utils::{ config::{read_env_as_lines, read_env_file, write_env}, eth_client::{eth_sender::Overrides, EthClient}, @@ -32,6 +33,18 @@ pub enum DeployError { FailedToLockSALT(String), #[error("The path is not a valid utf-8 string")] FailedToGetStringFromPath, + #[error("Deployer setup error: {0} not set")] + ConfigValueNotSet(String), + #[error("Deployer setup parse error: {0}")] + ParseError(String), + #[error("Deployer dependency error: {0}")] + DependencyError(String), + #[error("Deployer compilation error: {0}")] + CompilationError(String), + #[error("Deployer EthClient error: {0}")] + EthClientError(#[from] EthClientError), + #[error("Deployer decoding error: {0}")] + DecodingError(String), } // 0x4e59b44847b379578588920cA78FbF26c0B4956C @@ -73,7 +86,7 @@ async fn main() { Err(e) => panic!("Failed to deploy contracts {e}"), }; - initialize_contracts( + if let Err(err) = initialize_contracts( setup_result.deployer_address, setup_result.deployer_private_key, setup_result.committer_address, @@ -83,9 +96,14 @@ async fn main() { setup_result.contract_verifier_address, &setup_result.eth_client, ) - .await; + .await + { + panic!("Failed to initialize contracts: {err}"); + } - let env_lines = read_env_as_lines().expect("Failed to read env file as lines."); + let Ok(env_lines) = read_env_as_lines() else { + panic!("Failed to read env file as lines."); + }; let mut wr_lines: Vec = Vec::new(); let mut env_lines_iter = env_lines.into_iter(); @@ -104,7 +122,12 @@ async fn main() { } wr_lines.push(line); } - write_env(wr_lines).expect("Failed to write changes to the .env file."); + if let Err(err) = write_env(wr_lines) { + panic!( + "{}", + format!("Failed to write changes to the .env file: {err}") + ); + } } fn setup() -> Result { @@ -112,31 +135,33 @@ fn setup() -> Result { warn!("Failed to read .env file: {e}"); } - let eth_client = EthClient::new(&std::env::var("ETH_RPC_URL").expect("ETH_RPC_URL not set")); - let deployer_address = std::env::var("DEPLOYER_ADDRESS") - .expect("DEPLOYER_ADDRESS not set") - .parse() - .expect("Malformed DEPLOYER_ADDRESS"); + let eth_client = EthClient::new(&read_env_var("ETH_RPC_URL")?); + + let deployer_address = parse_env_var("DEPLOYER_ADDRESS")?; let deployer_private_key = SecretKey::from_slice( H256::from_str( - std::env::var("DEPLOYER_PRIVATE_KEY") - .expect("DEPLOYER_PRIVATE_KEY not set") + read_env_var("DEPLOYER_PRIVATE_KEY")? .strip_prefix("0x") - .expect("Malformed DEPLOYER PRIVATE KEY (strip_prefix(\"0x\"))"), + .ok_or(DeployError::ParseError( + "Malformed DEPLOYER PRIVATE KEY (strip_prefix(\"0x\"))".to_owned(), + ))?, ) - .expect("Malformed DEPLOYER_PRIVATE_KEY (H256::from_str)") + .map_err(|err| { + DeployError::ParseError(format!( + "Malformed DEPLOYER PRIVATE KEY (H256::from_str): {err}" + )) + })? .as_bytes(), ) - .expect("Malformed DEPLOYER_PRIVATE_KEY (SecretKey::parse)"); + .map_err(|err| { + DeployError::ParseError(format!( + "Malformed DEPLOYER_PRIVATE_KEY (SecretKey::parse): {err}" + )) + })?; - let committer_address = std::env::var("COMMITTER_L1_ADDRESS") - .expect("COMMITTER_L1_ADDRESS not set") - .parse() - .expect("Malformed COMMITTER_L1_ADDRESS"); - let verifier_address = std::env::var("PROVER_SERVER_VERIFIER_ADDRESS") - .expect("PROVER_SERVER_VERIFIER_ADDRESS not set") - .parse() - .expect("Malformed PROVER_SERVER_VERIFIER_ADDRESS"); + let committer_address = parse_env_var("COMMITTER_L1_ADDRESS")?; + + let verifier_address = parse_env_var("PROVER_SERVER_VERIFIER_ADDRESS")?; let contracts_path = Path::new( std::env::var("DEPLOYER_CONTRACTS_PATH") @@ -155,12 +180,13 @@ fn setup() -> Result { .map_err(|err| DeployError::FailedToLockSALT(err.to_string()))?; *salt = H256::random(); } - _ => panic!("Invalid boolean string: {input}"), + _ => { + return Err(DeployError::ParseError(format!( + "Invalid boolean string: {input}" + ))); + } }; - let contract_verifier_address = std::env::var("DEPLOYER_CONTRACT_VERIFIER") - .expect("DEPLOYER_CONTRACT_VERIFIER not set") - .parse() - .expect("Malformed DEPLOYER_CONTRACT_VERIFIER"); + let contract_verifier_address = parse_env_var("DEPLOYER_CONTRACT_VERIFIER")?; Ok(SetupResult { deployer_address, deployer_private_key, @@ -172,8 +198,20 @@ fn setup() -> Result { }) } +fn read_env_var(key: &str) -> Result { + std::env::var(key).map_err(|_| DeployError::ConfigValueNotSet(key.to_owned())) +} + +fn parse_env_var(key: &str) -> Result { + read_env_var(key)? + .parse() + .map_err(|err| DeployError::ParseError(format!("Malformed {key}: {err}"))) +} + fn download_contract_deps(contracts_path: &Path) -> Result<(), DeployError> { - std::fs::create_dir_all(contracts_path.join("lib")).expect("Failed to create contracts/lib"); + std::fs::create_dir_all(contracts_path.join("lib")).map_err(|err| { + DeployError::DependencyError(format!("Failed to create contracts/lib: {err}")) + })?; Command::new("git") .arg("clone") .arg("https://github.com/OpenZeppelin/openzeppelin-contracts.git") @@ -184,75 +222,79 @@ fn download_contract_deps(contracts_path: &Path) -> Result<(), DeployError> { .ok_or(DeployError::FailedToGetStringFromPath)?, ) .spawn() - .expect("Failed to spawn git") + .map_err(|err| DeployError::DependencyError(format!("Failed to spawn git: {err}")))? .wait() - .expect("Failed to wait for git"); + .map_err(|err| DeployError::DependencyError(format!("Failed to wait for git: {err}")))?; Ok(()) } fn compile_contracts(contracts_path: &Path) -> Result<(), DeployError> { // Both the contract path and the output path are relative to where the Makefile is. - assert!( - Command::new("solc") - .arg("--bin") - .arg( - contracts_path - .join("src/l1/OnChainProposer.sol") - .to_str() - .ok_or(DeployError::FailedToGetStringFromPath)? - ) - .arg("-o") - .arg( - contracts_path - .join("solc_out") - .to_str() - .ok_or(DeployError::FailedToGetStringFromPath)?, - ) - .arg("--overwrite") - .arg("--allow-paths") - .arg( - contracts_path - .to_str() - .ok_or(DeployError::FailedToGetStringFromPath)?, - ) - .spawn() - .expect("Failed to spawn solc") - .wait() - .expect("Failed to wait for solc") - .success(), - "Failed to compile OnChainProposer.sol" - ); + if !Command::new("solc") + .arg("--bin") + .arg( + contracts_path + .join("src/l1/OnChainProposer.sol") + .to_str() + .ok_or(DeployError::FailedToGetStringFromPath)?, + ) + .arg("-o") + .arg( + contracts_path + .join("solc_out") + .to_str() + .ok_or(DeployError::FailedToGetStringFromPath)?, + ) + .arg("--overwrite") + .arg("--allow-paths") + .arg( + contracts_path + .to_str() + .ok_or(DeployError::FailedToGetStringFromPath)?, + ) + .spawn() + .map_err(|err| DeployError::CompilationError(format!("Failed to spawn solc: {err}")))? + .wait() + .map_err(|err| DeployError::CompilationError(format!("Failed to wait for solc: {err}")))? + .success() + { + return Err(DeployError::CompilationError( + "Failed to compile OnChainProposer.sol".to_owned(), + )); + } - assert!( - Command::new("solc") - .arg("--bin") - .arg( - contracts_path - .join("src/l1/CommonBridge.sol") - .to_str() - .ok_or(DeployError::FailedToGetStringFromPath)?, - ) - .arg("-o") - .arg( - contracts_path - .join("solc_out") - .to_str() - .ok_or(DeployError::FailedToGetStringFromPath)?, - ) - .arg("--overwrite") - .arg("--allow-paths") - .arg( - contracts_path - .to_str() - .ok_or(DeployError::FailedToGetStringFromPath)?, - ) - .spawn() - .expect("Failed to spawn solc") - .wait() - .expect("Failed to wait for solc") - .success(), - "Failed to compile CommonBridge.sol" - ); + if !Command::new("solc") + .arg("--bin") + .arg( + contracts_path + .join("src/l1/CommonBridge.sol") + .to_str() + .ok_or(DeployError::FailedToGetStringFromPath)?, + ) + .arg("-o") + .arg( + contracts_path + .join("solc_out") + .to_str() + .ok_or(DeployError::FailedToGetStringFromPath)?, + ) + .arg("--overwrite") + .arg("--allow-paths") + .arg( + contracts_path + .to_str() + .ok_or(DeployError::FailedToGetStringFromPath)?, + ) + .spawn() + .map_err(|err| DeployError::CompilationError(format!("Failed to spawn solc: {err}")))? + .wait() + .map_err(|err| DeployError::CompilationError(format!("Failed to wait for solc: {err}")))? + .success() + { + return Err(DeployError::CompilationError( + "Failed to compile CommonBridge.sol".to_owned(), + )); + } Ok(()) } @@ -302,10 +344,19 @@ async fn deploy_on_chain_proposer( contracts_path: &Path, ) -> Result<(H256, Address), DeployError> { let on_chain_proposer_init_code = hex::decode( - std::fs::read_to_string(contracts_path.join("solc_out/OnChainProposer.bin")) - .expect("Failed to read on_chain_proposer_init_code"), + std::fs::read_to_string(contracts_path.join("solc_out/OnChainProposer.bin")).map_err( + |err| { + DeployError::DecodingError(format!( + "Failed to read on_chain_proposer_init_code: {err}" + )) + }, + )?, ) - .expect("Failed to decode on_chain_proposer_init_code") + .map_err(|err| { + DeployError::DecodingError(format!( + "Failed to decode on_chain_proposer_init_code: {err}" + )) + })? .into(); let (deploy_tx_hash, on_chain_proposer) = create2_deploy( @@ -314,7 +365,8 @@ async fn deploy_on_chain_proposer( &on_chain_proposer_init_code, eth_client, ) - .await?; + .await + .map_err(DeployError::from)?; Ok((deploy_tx_hash, on_chain_proposer)) } @@ -326,10 +378,13 @@ async fn deploy_bridge( contracts_path: &Path, ) -> Result<(H256, Address), DeployError> { let mut bridge_init_code = hex::decode( - std::fs::read_to_string(contracts_path.join("solc_out/CommonBridge.bin")) - .expect("Failed to read bridge_init_code"), + std::fs::read_to_string(contracts_path.join("solc_out/CommonBridge.bin")).map_err( + |err| DeployError::DecodingError(format!("Failed to read bridge_init_code: {err}")), + )?, ) - .expect("Failed to decode bridge_init_code"); + .map_err(|err| { + DeployError::DecodingError(format!("Failed to decode bridge_init_code: {err}")) + })?; let encoded_owner = { let offset = 32 - deployer.as_bytes().len() % 32; @@ -372,15 +427,15 @@ async fn create2_deploy( Overrides::default(), 10, ) - .await - .expect("Failed to build create2 deploy tx"); + .await?; let deploy_tx_hash = eth_client .send_eip1559_transaction(&deploy_tx, &deployer_private_key) - .await - .expect("Failed to send create2 deploy tx"); + .await?; - wait_for_transaction_receipt(deploy_tx_hash, eth_client).await; + wait_for_transaction_receipt(deploy_tx_hash, eth_client) + .await + .map_err(DeployError::from)?; let deployed_address = create2_address(keccak(init_code))?; @@ -388,7 +443,7 @@ async fn create2_deploy( } fn create2_address(init_code_hash: H256) -> Result { - Ok(Address::from_slice( + let addr = Address::from_slice( keccak( [ &[0xff], @@ -402,8 +457,11 @@ fn create2_address(init_code_hash: H256) -> Result { ) .as_bytes() .get(12..) - .expect("Failed to get create2 address"), - )) + .ok_or(DeployError::DecodingError( + "Failed to get create2 address".to_owned(), + ))?, + ); + Ok(addr) } #[allow(clippy::too_many_arguments)] @@ -416,7 +474,7 @@ async fn initialize_contracts( bridge: Address, contract_verifier_address: Address, eth_client: &EthClient, -) { +) -> Result<(), DeployError> { let initialize_frames = spinner!(["🪄❱❱", "❱🪄❱", "❱❱🪄"], 200); let mut spinner = Spinner::new( @@ -435,7 +493,8 @@ async fn initialize_contracts( verifier, eth_client, ) - .await; + .await + .map_err(DeployError::from)?; let msg = format!( "OnChainProposer:\n\tInitialized with tx hash {}", format!("{initialize_tx_hash:#x}").bright_cyan() @@ -454,12 +513,14 @@ async fn initialize_contracts( deployer_private_key, eth_client, ) - .await; + .await + .map_err(DeployError::from)?; let msg = format!( "CommonBridge:\n\tInitialized with tx hash {}", format!("{initialize_tx_hash:#x}").bright_cyan() ); spinner.success(&msg); + Ok(()) } #[allow(clippy::too_many_arguments)] @@ -472,11 +533,13 @@ async fn initialize_on_chain_proposer( committer: Address, verifier: Address, eth_client: &EthClient, -) -> H256 { +) -> Result { let on_chain_proposer_initialize_selector = keccak(b"initialize(address,address,address[])") .as_bytes() .get(..4) - .expect("Failed to get initialize selector") + .ok_or(DeployError::DecodingError( + "Failed to get initialize selector".to_owned(), + ))? .to_vec(); let encoded_bridge = { let offset = 32 - bridge.as_bytes().len() % 32; @@ -518,16 +581,14 @@ async fn initialize_on_chain_proposer( Overrides::default(), 10, ) - .await - .expect("Failed to build initialize transaction"); + .await?; let initialize_tx_hash = eth_client .send_eip1559_transaction(&initialize_tx, &deployer_private_key) - .await - .expect("Failed to send initialize transaction"); + .await?; - wait_for_transaction_receipt(initialize_tx_hash, eth_client).await; + wait_for_transaction_receipt(initialize_tx_hash, eth_client).await?; - initialize_tx_hash + Ok(initialize_tx_hash) } async fn initialize_bridge( @@ -536,11 +597,13 @@ async fn initialize_bridge( deployer: Address, deployer_private_key: SecretKey, eth_client: &EthClient, -) -> H256 { +) -> Result { let bridge_initialize_selector = keccak(b"initialize(address)") .as_bytes() .get(..4) - .expect("Failed to get initialize selector") + .ok_or(DeployError::DecodingError( + "Failed to get initialize selector".to_owned(), + ))? .to_vec(); let encoded_on_chain_proposer = { let offset = 32 - on_chain_proposer.as_bytes().len() % 32; @@ -562,29 +625,31 @@ async fn initialize_bridge( 10, ) .await - .expect("Failed to build initialize transaction"); + .map_err(DeployError::from)?; let initialize_tx_hash = eth_client .send_eip1559_transaction(&initialize_tx, &deployer_private_key) .await - .expect("Failed to send initialize transaction"); + .map_err(DeployError::from)?; - wait_for_transaction_receipt(initialize_tx_hash, eth_client).await; + wait_for_transaction_receipt(initialize_tx_hash, eth_client) + .await + .map_err(DeployError::from)?; - initialize_tx_hash + Ok(initialize_tx_hash) } -async fn wait_for_transaction_receipt(tx_hash: H256, eth_client: &EthClient) { - while eth_client - .get_transaction_receipt(tx_hash) - .await - .expect("Failed to get transaction receipt") - .is_none() - { +async fn wait_for_transaction_receipt( + tx_hash: H256, + eth_client: &EthClient, +) -> Result<(), EthClientError> { + while eth_client.get_transaction_receipt(tx_hash).await?.is_none() { tokio::time::sleep(std::time::Duration::from_secs(1)).await; } + Ok(()) } #[allow(clippy::unwrap_used)] +#[allow(clippy::expect_used)] #[cfg(test)] mod test { use crate::{compile_contracts, download_contract_deps}; diff --git a/crates/l2/proposer/l1_committer.rs b/crates/l2/proposer/l1_committer.rs index 084eaad942..27b6e3bbfa 100644 --- a/crates/l2/proposer/l1_committer.rs +++ b/crates/l2/proposer/l1_committer.rs @@ -4,7 +4,7 @@ use crate::{ state_diff::{AccountStateDiff, DepositLog, StateDiff, WithdrawalLog}, }, utils::{ - config::{committer::CommitterConfig, eth::EthConfig}, + config::{committer::CommitterConfig, errors::ConfigError, eth::EthConfig}, eth_client::{eth_sender::Overrides, EthClient, WrappedTransaction}, merkle_tree::merkelize, }, @@ -36,11 +36,12 @@ pub struct Committer { interval_ms: u64, } -pub async fn start_l1_commiter(store: Store) { - let eth_config = EthConfig::from_env().expect("EthConfig::from_env()"); - let committer_config = CommitterConfig::from_env().expect("CommitterConfig::from_env"); +pub async fn start_l1_commiter(store: Store) -> Result<(), ConfigError> { + let eth_config = EthConfig::from_env()?; + let committer_config = CommitterConfig::from_env()?; let committer = Committer::new_from_config(&committer_config, eth_config, store); committer.run().await; + Ok(()) } impl Committer { diff --git a/crates/l2/proposer/l1_watcher.rs b/crates/l2/proposer/l1_watcher.rs index 1c86026c61..771af10df6 100644 --- a/crates/l2/proposer/l1_watcher.rs +++ b/crates/l2/proposer/l1_watcher.rs @@ -1,7 +1,7 @@ use crate::{ proposer::errors::L1WatcherError, utils::{ - config::{eth::EthConfig, l1_watcher::L1WatcherConfig}, + config::{errors::ConfigError, eth::EthConfig, l1_watcher::L1WatcherConfig}, eth_client::{eth_sender::Overrides, EthClient}, }, }; @@ -18,11 +18,12 @@ use std::{cmp::min, ops::Mul, time::Duration}; use tokio::time::sleep; use tracing::{debug, error, info, warn}; -pub async fn start_l1_watcher(store: Store) { - let eth_config = EthConfig::from_env().expect("EthConfig::from_env()"); - let watcher_config = L1WatcherConfig::from_env().expect("L1WatcherConfig::from_env()"); +pub async fn start_l1_watcher(store: Store) -> Result<(), ConfigError> { + let eth_config = EthConfig::from_env()?; + let watcher_config = L1WatcherConfig::from_env()?; let mut l1_watcher = L1Watcher::new_from_config(watcher_config, eth_config); l1_watcher.run(&store).await; + Ok(()) } pub struct L1Watcher { diff --git a/crates/l2/proposer/mod.rs b/crates/l2/proposer/mod.rs index 777d4af47d..99e7f1ec34 100644 --- a/crates/l2/proposer/mod.rs +++ b/crates/l2/proposer/mod.rs @@ -1,10 +1,11 @@ use std::time::Duration; -use crate::utils::config::{proposer::ProposerConfig, read_env_file}; +use crate::utils::config::{errors::ConfigError, proposer::ProposerConfig, read_env_file}; use errors::ProposerError; use ethereum_types::Address; use ethrex_dev::utils::engine_client::config::EngineApiConfig; use ethrex_storage::Store; +use tokio::task::JoinSet; use tokio::time::sleep; use tracing::{error, info}; @@ -28,18 +29,33 @@ pub async fn start_proposer(store: Store) { panic!("Failed to read .env file: {e}"); } - let l1_watcher = tokio::spawn(l1_watcher::start_l1_watcher(store.clone())); - let l1_committer = tokio::spawn(l1_committer::start_l1_commiter(store.clone())); - let prover_server = tokio::spawn(prover_server::start_prover_server(store.clone())); - let proposer = tokio::spawn(async move { - let proposer_config = ProposerConfig::from_env().expect("ProposerConfig::from_env"); - let engine_config = EngineApiConfig::from_env().expect("EngineApiConfig::from_env"); - let proposer = Proposer::new_from_config(&proposer_config, engine_config) - .expect("Proposer::new_from_config"); - - proposer.run(store.clone()).await; - }); - tokio::try_join!(l1_watcher, l1_committer, prover_server, proposer).expect("tokio::try_join"); + let mut task_set = JoinSet::new(); + task_set.spawn(l1_watcher::start_l1_watcher(store.clone())); + task_set.spawn(l1_committer::start_l1_commiter(store.clone())); + task_set.spawn(prover_server::start_prover_server(store.clone())); + task_set.spawn(start_proposer_server(store.clone())); + + while let Some(res) = task_set.join_next().await { + match res { + Ok(Ok(_)) => {} + Ok(Err(err)) => { + panic!("Error starting Proposer: {err}"); + } + Err(err) => { + panic!("Error starting Proposer: {err}"); + } + }; + } +} + +async fn start_proposer_server(store: Store) -> Result<(), ConfigError> { + let proposer_config = ProposerConfig::from_env()?; + let engine_config = EngineApiConfig::from_env().map_err(ConfigError::from)?; + let proposer = + Proposer::new_from_config(&proposer_config, engine_config).map_err(ConfigError::from)?; + + proposer.run(store.clone()).await; + Ok(()) } impl Proposer { diff --git a/crates/l2/proposer/prover_server.rs b/crates/l2/proposer/prover_server.rs index 0846e2c298..f599fb7157 100644 --- a/crates/l2/proposer/prover_server.rs +++ b/crates/l2/proposer/prover_server.rs @@ -1,6 +1,9 @@ use super::errors::{ProverServerError, SigIntError}; use crate::utils::{ - config::{committer::CommitterConfig, eth::EthConfig, prover_server::ProverServerConfig}, + config::{ + committer::CommitterConfig, errors::ConfigError, eth::EthConfig, + prover_server::ProverServerConfig, + }, eth_client::{errors::EthClientError, eth_sender::Overrides, EthClient, WrappedTransaction}, }; use ethrex_core::{ @@ -75,15 +78,16 @@ pub enum ProofData { SubmitAck { block_number: u64 }, } -pub async fn start_prover_server(store: Store) { - let server_config = ProverServerConfig::from_env().expect("ProverServerConfig::from_env()"); - let eth_config = EthConfig::from_env().expect("EthConfig::from_env()"); - let proposer_config = CommitterConfig::from_env().expect("CommitterConfig::from_env()"); +pub async fn start_prover_server(store: Store) -> Result<(), ConfigError> { + let server_config = ProverServerConfig::from_env()?; + let eth_config = EthConfig::from_env()?; + let proposer_config = CommitterConfig::from_env()?; let mut prover_server = ProverServer::new_from_config(server_config.clone(), &proposer_config, eth_config, store) .await - .expect("ProverServer::new_from_config"); + .map_err(ConfigError::from)?; prover_server.run(&server_config).await; + Ok(()) } impl ProverServer { diff --git a/crates/l2/prover/Cargo.toml b/crates/l2/prover/Cargo.toml index 54fcc40953..ec9d06c9b0 100644 --- a/crates/l2/prover/Cargo.toml +++ b/crates/l2/prover/Cargo.toml @@ -53,3 +53,4 @@ gpu = ["risc0-zkvm/cuda"] [lints.clippy] unwrap_used = "deny" +expect_used = "deny" diff --git a/crates/l2/prover/src/main.rs b/crates/l2/prover/src/main.rs index d026309899..6f3feb9277 100644 --- a/crates/l2/prover/src/main.rs +++ b/crates/l2/prover/src/main.rs @@ -1,7 +1,7 @@ use ethrex_l2::utils::config::{prover_client::ProverClientConfig, read_env_file}; use ethrex_prover_lib::init_client; -use tracing::{self, debug, warn, Level}; +use tracing::{self, debug, error, warn, Level}; #[tokio::main] async fn main() { @@ -9,7 +9,9 @@ async fn main() { // Hiding debug!() logs. .with_max_level(Level::INFO) .finish(); - tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); + if let Err(e) = tracing::subscriber::set_global_default(subscriber) { + error!("setting default subscriber failed: {e}"); + } if let Err(e) = read_env_file() { warn!("Failed to read .env file: {e}"); diff --git a/crates/l2/prover/tests/perf_zkvm.rs b/crates/l2/prover/tests/perf_zkvm.rs index abcec2e421..0ce7740edb 100644 --- a/crates/l2/prover/tests/perf_zkvm.rs +++ b/crates/l2/prover/tests/perf_zkvm.rs @@ -1,4 +1,4 @@ -#![allow(clippy::unwrap_used)] +#![allow(clippy::expect_used)] use std::path::Path; use tracing::info; diff --git a/crates/l2/sdk/Cargo.toml b/crates/l2/sdk/Cargo.toml index a1d8a044b3..a23c311ce3 100644 --- a/crates/l2/sdk/Cargo.toml +++ b/crates/l2/sdk/Cargo.toml @@ -22,3 +22,4 @@ path = "src/sdk.rs" [lints.clippy] unwrap_used = "deny" +expect_used = "deny" diff --git a/crates/l2/sdk/src/sdk.rs b/crates/l2/sdk/src/sdk.rs index e149e9a64c..1113b82760 100644 --- a/crates/l2/sdk/src/sdk.rs +++ b/crates/l2/sdk/src/sdk.rs @@ -37,11 +37,8 @@ pub async fn wait_for_transaction_receipt( tx_hash: H256, client: &EthClient, max_retries: u64, -) -> Option { - let mut receipt = client - .get_transaction_receipt(tx_hash) - .await - .expect("Failed to get transaction receipt"); +) -> Result { + let mut receipt = client.get_transaction_receipt(tx_hash).await?; let mut r#try = 1; while receipt.is_none() { println!("[{try}/{max_retries}] Retrying to get transaction receipt for {tx_hash:#x}"); @@ -53,12 +50,11 @@ pub async fn wait_for_transaction_receipt( tokio::time::sleep(std::time::Duration::from_secs(2)).await; - receipt = client - .get_transaction_receipt(tx_hash) - .await - .expect("Failed to get transaction receipt"); + receipt = client.get_transaction_receipt(tx_hash).await?; } - receipt + receipt.ok_or(EthClientError::Custom( + "Transaction receipt is None".to_owned(), + )) } pub async fn transfer( diff --git a/crates/l2/tests/tests.rs b/crates/l2/tests/tests.rs index a7794833d5..97b15823c6 100644 --- a/crates/l2/tests/tests.rs +++ b/crates/l2/tests/tests.rs @@ -1,4 +1,5 @@ #![allow(clippy::unwrap_used)] +#![allow(clippy::expect_used)] use bytes::Bytes; use ethereum_types::{Address, H160, U256}; use ethrex_l2::utils::eth_client::{eth_sender::Overrides, EthClient}; @@ -192,7 +193,7 @@ async fn testito() { let withdraw_tx_receipt = ethrex_l2_sdk::wait_for_transaction_receipt(withdraw_tx, &proposer_client, 30) .await - .unwrap(); + .expect("Withdraw tx receipt not found"); // 7. Check balances on L1 and L2 diff --git a/crates/l2/utils/config/errors.rs b/crates/l2/utils/config/errors.rs index 932323dc5f..95bd3479c9 100644 --- a/crates/l2/utils/config/errors.rs +++ b/crates/l2/utils/config/errors.rs @@ -1,7 +1,17 @@ +use crate::proposer::errors::ProposerError; +use crate::utils::eth_client::errors::EthClientError; +use ethrex_dev::utils::engine_client; + #[derive(Debug, thiserror::Error)] pub enum ConfigError { #[error("Error deserializing config from env: {0}")] ConfigDeserializationError(#[from] envy::Error), #[error("Error reading env file: {0}")] EnvFileError(#[from] std::io::Error), + #[error("Error building Proposer from config: {0}")] + BuildProposerFromConfigError(#[from] ProposerError), + #[error("Error building Proposer Engine from config: {0}")] + BuildProposerEngineServerFromConfigError(#[from] engine_client::errors::ConfigError), + #[error("Error building Prover server from config: {0}")] + BuildProverServerFromConfigError(#[from] EthClientError), } diff --git a/crates/l2/utils/test_data_io.rs b/crates/l2/utils/test_data_io.rs index 1e8cd9fc1d..d24e759172 100644 --- a/crates/l2/utils/test_data_io.rs +++ b/crates/l2/utils/test_data_io.rs @@ -1,4 +1,5 @@ #![allow(clippy::unwrap_used)] +#![allow(clippy::expect_used)] use ethrex_core::types::{Block, Genesis}; use ethrex_rlp::{decode::RLPDecode, encode::RLPEncode}; use ethrex_storage::Store; From 4a837c0a67cbbed7573abbe8f5fb4a21f419d794 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Arjovsky?= Date: Thu, 28 Nov 2024 14:51:48 +0100 Subject: [PATCH 11/23] fix(l1): daily hive coverage report slack publish path (#1331) The script was moved and the reference in the actions code didn't. --- .github/workflows/hive_coverage.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/hive_coverage.yaml b/.github/workflows/hive_coverage.yaml index 6485fb0d3c..5aa8cf8390 100644 --- a/.github/workflows/hive_coverage.yaml +++ b/.github/workflows/hive_coverage.yaml @@ -82,12 +82,12 @@ jobs: - name: Post results to ethrex L1 slack channel env: url: ${{ secrets.ETHREX_L1_SLACK_WEBHOOK }} - run: sh publish.sh + run: sh .github/scripts/publish.sh - name: Post results to ethrex L2 slack channel env: url: ${{ secrets.ETHREX_L2_SLACK_WEBHOOK }} - run: sh publish.sh + run: sh .github/scripts/publish.sh - name: Post results to levm slack channel env: From f6d9f830777d36dbf6a82f29f5130e48a1919af0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Rodr=C3=ADguez=20Chatruc?= <49622509+jrchatruc@users.noreply.github.com> Date: Thu, 28 Nov 2024 10:56:18 -0300 Subject: [PATCH 12/23] feat(core): cache the sender address on mempool transactions (#1282) **Motivation** Depends on #1273 When doing flamegraphs of load tests, the node spends around 50% of its time on the `fill_transactions` function, and in there spends most of its time just calling the `sender()` method to recover the sender's address. By caching it when it's first added to the mempool, the time spent on `fill_transactions` goes down to around 8%, and block execution becomes roughly ~7x faster Before the change: ![flamegraph_original](https://github.com/user-attachments/assets/d9e443f5-c151-4dfd-81c5-eef339ec8ca7) After the change: ![flamegraph_cached_sender](https://github.com/user-attachments/assets/076a79d7-6c46-4ec7-9456-8c819ddcb4f3) **Description** --- crates/blockchain/mempool.rs | 31 ++++++++++++++++++------------ crates/common/types/transaction.rs | 18 +++++++++++++++-- crates/storage/store/storage.rs | 8 ++++++-- 3 files changed, 41 insertions(+), 16 deletions(-) diff --git a/crates/blockchain/mempool.rs b/crates/blockchain/mempool.rs index aa566f239c..91303d488f 100644 --- a/crates/blockchain/mempool.rs +++ b/crates/blockchain/mempool.rs @@ -27,13 +27,15 @@ pub fn add_blob_transaction( // Validate blobs bundle blobs_bundle.validate(&transaction)?; - // Validate transaction let transaction = Transaction::EIP4844Transaction(transaction); - validate_transaction(&transaction, store.clone())?; + let sender = transaction.sender(); + + // Validate transaction + validate_transaction(&transaction, sender, store.clone())?; // Add transaction and blobs bundle to storage let hash = transaction.compute_hash(); - store.add_transaction_to_pool(hash, MempoolTransaction::new(transaction))?; + store.add_transaction_to_pool(hash, MempoolTransaction::new(transaction, sender))?; store.add_blobs_bundle_to_pool(hash, blobs_bundle)?; Ok(hash) } @@ -44,13 +46,14 @@ pub fn add_transaction(transaction: Transaction, store: Store) -> Result Result<(), MempoolError> { +fn validate_transaction( + tx: &Transaction, + sender: Address, + store: Store, +) -> Result<(), MempoolError> { // TODO: Add validations here let header_no = store @@ -207,7 +214,7 @@ fn validate_transaction(tx: &Transaction, store: Store) -> Result<(), MempoolErr } }; - let maybe_sender_acc_info = store.get_account_info(header_no, tx.sender())?; + let maybe_sender_acc_info = store.get_account_info(header_no, sender)?; if let Some(sender_acc_info) = maybe_sender_acc_info { if tx.nonce() < sender_acc_info.nonce { @@ -541,7 +548,7 @@ mod tests { }; let tx = Transaction::EIP1559Transaction(tx); - let validation = validate_transaction(&tx, store); + let validation = validate_transaction(&tx, Address::random(), store); assert!(matches!( validation, Err(MempoolError::TxMaxInitCodeSizeError) @@ -567,7 +574,7 @@ mod tests { }; let tx = Transaction::EIP1559Transaction(tx); - let validation = validate_transaction(&tx, store); + let validation = validate_transaction(&tx, Address::random(), store); assert!(matches!( validation, Err(MempoolError::TxGasLimitExceededError) @@ -593,7 +600,7 @@ mod tests { }; let tx = Transaction::EIP1559Transaction(tx); - let validation = validate_transaction(&tx, store); + let validation = validate_transaction(&tx, Address::random(), store); assert!(matches!( validation, Err(MempoolError::TxTipAboveFeeCapError) @@ -620,7 +627,7 @@ mod tests { }; let tx = Transaction::EIP1559Transaction(tx); - let validation = validate_transaction(&tx, store); + let validation = validate_transaction(&tx, Address::random(), store); assert!(matches!( validation, Err(MempoolError::TxIntrinsicGasCostAboveLimitError) @@ -646,7 +653,7 @@ mod tests { }; let tx = Transaction::EIP4844Transaction(tx); - let validation = validate_transaction(&tx, store); + let validation = validate_transaction(&tx, Address::random(), store); assert!(matches!( validation, Err(MempoolError::TxBlobBaseFeeTooLowError) diff --git a/crates/common/types/transaction.rs b/crates/common/types/transaction.rs index 95fea476f0..0a81f34f7a 100644 --- a/crates/common/types/transaction.rs +++ b/crates/common/types/transaction.rs @@ -2122,22 +2122,28 @@ mod mempool { pub struct MempoolTransaction { // Unix timestamp (in microseconds) created once the transaction reached the MemPool timestamp: u128, + sender: Address, inner: Transaction, } impl MempoolTransaction { - pub fn new(tx: Transaction) -> Self { + pub fn new(tx: Transaction, sender: Address) -> Self { Self { timestamp: SystemTime::now() .duration_since(UNIX_EPOCH) .expect("Invalid system time") .as_micros(), + sender, inner: tx, } } pub fn time(&self) -> u128 { self.timestamp } + + pub fn sender(&self) -> Address { + self.sender + } } impl RLPEncode for MempoolTransaction { @@ -2153,8 +2159,16 @@ mod mempool { fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> { let decoder = Decoder::new(rlp)?; let (timestamp, decoder) = decoder.decode_field("timestamp")?; + let (sender, decoder) = decoder.decode_field("sender")?; let (inner, decoder) = decoder.decode_field("inner")?; - Ok((Self { timestamp, inner }, decoder.finish()?)) + Ok(( + Self { + timestamp, + sender, + inner, + }, + decoder.finish()?, + )) } } diff --git a/crates/storage/store/storage.rs b/crates/storage/store/storage.rs index c500093e40..d6fbbe736b 100644 --- a/crates/storage/store/storage.rs +++ b/crates/storage/store/storage.rs @@ -1213,8 +1213,12 @@ mod tests { use hex_literal::hex; fn test_filter_mempool_transactions(store: Store) { - let plain_tx = MempoolTransaction::new(Transaction::decode_canonical(&hex!("f86d80843baa0c4082f618946177843db3138ae69679a54b95cf345ed759450d870aa87bee538000808360306ba0151ccc02146b9b11adf516e6787b59acae3e76544fdcd75e77e67c6b598ce65da064c5dd5aae2fbb535830ebbdad0234975cd7ece3562013b63ea18cc0df6c97d4")).unwrap()); - let blob_tx = MempoolTransaction::new(Transaction::decode_canonical(&hex!("03f88f0780843b9aca008506fc23ac00830186a09400000000000000000000000000000000000001008080c001e1a0010657f37554c781402a22917dee2f75def7ab966d7b770905398eba3c44401401a0840650aa8f74d2b07f40067dc33b715078d73422f01da17abdbd11e02bbdfda9a04b2260f6022bf53eadb337b3e59514936f7317d872defb891a708ee279bdca90")).unwrap()); + let plain_tx_decoded = Transaction::decode_canonical(&hex!("f86d80843baa0c4082f618946177843db3138ae69679a54b95cf345ed759450d870aa87bee538000808360306ba0151ccc02146b9b11adf516e6787b59acae3e76544fdcd75e77e67c6b598ce65da064c5dd5aae2fbb535830ebbdad0234975cd7ece3562013b63ea18cc0df6c97d4")).unwrap(); + let plain_tx_sender = plain_tx_decoded.sender(); + let plain_tx = MempoolTransaction::new(plain_tx_decoded, plain_tx_sender); + let blob_tx_decoded = Transaction::decode_canonical(&hex!("03f88f0780843b9aca008506fc23ac00830186a09400000000000000000000000000000000000001008080c001e1a0010657f37554c781402a22917dee2f75def7ab966d7b770905398eba3c44401401a0840650aa8f74d2b07f40067dc33b715078d73422f01da17abdbd11e02bbdfda9a04b2260f6022bf53eadb337b3e59514936f7317d872defb891a708ee279bdca90")).unwrap(); + let blob_tx_sender = blob_tx_decoded.sender(); + let blob_tx = MempoolTransaction::new(blob_tx_decoded, blob_tx_sender); let plain_tx_hash = plain_tx.compute_hash(); let blob_tx_hash = blob_tx.compute_hash(); let filter = From 3f223f8104e1ce5278873eb9fe08be09c9139bba Mon Sep 17 00:00:00 2001 From: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Date: Thu, 28 Nov 2024 10:58:15 -0300 Subject: [PATCH 13/23] feat(l1, l2, levm): improve loc reporter (#1326) - Improves slack message - Generate different messages for slack and for github - Ignore tests dir in loc counting --- .github/scripts/publish_loc.sh | 7 ++++-- .github/workflows/loc.yaml | 2 +- .gitignore | 4 +++- cmd/loc/src/main.rs | 43 +++++++++++++++++++++++++--------- 4 files changed, 41 insertions(+), 15 deletions(-) diff --git a/.github/scripts/publish_loc.sh b/.github/scripts/publish_loc.sh index 4348fc3355..3e57cfd6c9 100644 --- a/.github/scripts/publish_loc.sh +++ b/.github/scripts/publish_loc.sh @@ -1,15 +1,18 @@ curl -X POST $url \ -H 'Content-Type: application/json; charset=utf-8' \ --data @- <> $GITHUB_STEP_SUMMARY - cat loc_report.md >> $GITHUB_STEP_SUMMARY + cat loc_report_github.txt >> $GITHUB_STEP_SUMMARY - name: Post results to ethrex L1 slack channel env: diff --git a/.gitignore b/.gitignore index 87bbcdadd4..172ae1bd74 100644 --- a/.gitignore +++ b/.gitignore @@ -51,4 +51,6 @@ levm_ef_tests_report.txt levm_ef_tests_summary_slack.txt levm_ef_tests_summary_github.txt levm_ef_tests_summary.txt -loc_report.md + +loc_report_slack.txt +loc_report_github.txt diff --git a/cmd/loc/src/main.rs b/cmd/loc/src/main.rs index 0b180b3803..3eb15c0747 100644 --- a/cmd/loc/src/main.rs +++ b/cmd/loc/src/main.rs @@ -11,18 +11,41 @@ fn main() { let config = Config::default(); let mut languages = Languages::new(); - languages.get_statistics(&[ethrex.clone()], &[], &config); + languages.get_statistics(&[ethrex.clone()], &["tests"], &config); let ethrex_loc = &languages.get(&LanguageType::Rust).unwrap(); let mut languages = Languages::new(); - languages.get_statistics(&[levm], &[], &config); + languages.get_statistics(&[levm], &["tests"], &config); let levm_loc = &languages.get(&LanguageType::Rust).unwrap(); let mut languages = Languages::new(); - languages.get_statistics(&[ethrex_l2], &[], &config); + languages.get_statistics(&[ethrex_l2], &["tests"], &config); let ethrex_l2_loc = &languages.get(&LanguageType::Rust).unwrap(); - let report = format!( + std::fs::write( + "loc_report_slack.txt", + slack_message(ethrex_loc.code, ethrex_l2_loc.code, levm_loc.code), + ) + .unwrap(); + std::fs::write( + "loc_report_github.txt", + github_step_summary(ethrex_loc.code, ethrex_l2_loc.code, levm_loc.code), + ) + .unwrap(); +} + +fn slack_message(ethrex_loc: usize, ethrex_l2_loc: usize, levm_loc: usize) -> String { + format!( + r#"*ethrex L1:* {}\n*ethrex L2:* {}\n*levm:* {}\n*ethrex (total):* {}"#, + ethrex_loc - ethrex_l2_loc - levm_loc, + ethrex_l2_loc, + levm_loc, + ethrex_loc, + ) +} + +fn github_step_summary(ethrex_loc: usize, ethrex_l2_loc: usize, levm_loc: usize) -> String { + format!( r#"``` ethrex loc summary ==================== @@ -31,11 +54,9 @@ ethrex L2: {} levm: {} ethrex (total): {} ```"#, - ethrex_loc.code - ethrex_l2_loc.code - levm_loc.code, - ethrex_l2_loc.code, - levm_loc.code, - ethrex_loc.code, - ); - - std::fs::write("loc_report.md", report).unwrap(); + ethrex_loc - ethrex_l2_loc - levm_loc, + ethrex_l2_loc, + levm_loc, + ethrex_loc, + ) } From ec08ee4079d45a78b4d79830e177e500aa559a2b Mon Sep 17 00:00:00 2001 From: Federico Borello <156438142+fborello-lambda@users.noreply.github.com> Date: Thu, 28 Nov 2024 11:15:12 -0300 Subject: [PATCH 14/23] feat(l2): estimate blob_gas when sending the commitment (#1216) **Motivation** The gas_price for blobs may differ from block to block. A way to estimate the gas and set it dynamically is beneficial. **Description** - Create new env variables to configure the estimation. - New `estimate_gas()` function based on the latest block. - If the Gas is too high (based on the `COMMITTER_MAX_BLOB_GAS_PRICE` envar, retry in order to wait for the blob gas to come down) Closes #1162 --- crates/l2/.env.example | 2 + crates/l2/proposer/errors.rs | 12 ++ crates/l2/proposer/l1_committer.rs | 220 ++++++++++++++++++--------- crates/l2/utils/config/committer.rs | 1 + crates/l2/utils/eth_client/errors.rs | 10 ++ crates/l2/utils/eth_client/mod.rs | 44 +++++- crates/networking/rpc/types/block.rs | 2 +- 7 files changed, 217 insertions(+), 74 deletions(-) diff --git a/crates/l2/.env.example b/crates/l2/.env.example index 0cc6969721..90bf2d80fb 100644 --- a/crates/l2/.env.example +++ b/crates/l2/.env.example @@ -31,6 +31,8 @@ COMMITTER_ON_CHAIN_PROPOSER_ADDRESS=0xe9927d77c931f8648da4cc6751ef4e5e2ce74608 COMMITTER_L1_ADDRESS=0x3d1e15a1a55578f7c920884a9943b3b35d0d885b COMMITTER_L1_PRIVATE_KEY=0x385c546456b6a603a1cfcaa9ec9494ba4832da08dd6bcf4de9a71e4a01b74924 COMMITTER_INTERVAL_MS=1000 +# 1 Gwei +COMMITTER_ARBITRARY_BASE_BLOB_GAS_PRICE=1000000000 PROPOSER_INTERVAL_MS=5000 PROPOSER_COINBASE_ADDRESS=0x0007a881CD95B1484fca47615B64803dad620C8d # https://dev.risczero.com/api/generating-proofs/dev-mode diff --git a/crates/l2/proposer/errors.rs b/crates/l2/proposer/errors.rs index aa1f7eec08..d33cbe7319 100644 --- a/crates/l2/proposer/errors.rs +++ b/crates/l2/proposer/errors.rs @@ -94,6 +94,18 @@ pub enum CommitterError { FailedToSendCommitment(String), #[error("Withdrawal transaction was invalid")] InvalidWithdrawalTransaction, + #[error("Blob estimation failed: {0}")] + BlobEstimationError(#[from] BlobEstimationError), +} + +#[derive(Debug, thiserror::Error)] +pub enum BlobEstimationError { + #[error("Overflow error while estimating blob gas")] + OverflowError, + #[error("Failed to calculate blob gas due to invalid parameters")] + CalculationError, + #[error("Blob gas estimation resulted in an infinite or undefined value. Outside valid or expected ranges")] + NonFiniteResult, } #[derive(Debug, thiserror::Error)] diff --git a/crates/l2/proposer/l1_committer.rs b/crates/l2/proposer/l1_committer.rs index 27b6e3bbfa..3633d15cf5 100644 --- a/crates/l2/proposer/l1_committer.rs +++ b/crates/l2/proposer/l1_committer.rs @@ -5,7 +5,7 @@ use crate::{ }, utils::{ config::{committer::CommitterConfig, errors::ConfigError, eth::EthConfig}, - eth_client::{eth_sender::Overrides, EthClient, WrappedTransaction}, + eth_client::{eth_sender::Overrides, BlockByNumber, EthClient, WrappedTransaction}, merkle_tree::merkelize, }, }; @@ -13,7 +13,7 @@ use bytes::Bytes; use ethrex_core::{ types::{ blobs_bundle, BlobsBundle, Block, PrivilegedL2Transaction, PrivilegedTxType, Transaction, - TxKind, + TxKind, BLOB_BASE_FEE_UPDATE_FRACTION, }, Address, H256, U256, }; @@ -21,10 +21,13 @@ use ethrex_storage::Store; use ethrex_vm::{evm_state, execute_block, get_state_transitions}; use keccak_hash::keccak; use secp256k1::SecretKey; +use std::f64::consts::E; use std::{collections::HashMap, time::Duration}; use tokio::time::sleep; use tracing::{error, info}; +use super::errors::BlobEstimationError; + const COMMIT_FUNCTION_SELECTOR: [u8; 4] = [132, 97, 12, 179]; pub struct Committer { @@ -34,6 +37,7 @@ pub struct Committer { l1_address: Address, l1_private_key: SecretKey, interval_ms: u64, + arbitrary_base_blob_gas_price: u64, } pub async fn start_l1_commiter(store: Store) -> Result<(), ConfigError> { @@ -57,6 +61,7 @@ impl Committer { l1_address: committer_config.l1_address, l1_private_key: committer_config.l1_private_key, interval_ms: committer_config.interval_ms, + arbitrary_base_blob_gas_price: committer_config.arbitrary_base_blob_gas_price, } } @@ -71,82 +76,87 @@ impl Committer { } async fn main_logic(&self) -> Result<(), CommitterError> { - let last_committed_block = - EthClient::get_last_committed_block(&self.eth_client, self.on_chain_proposer_address) - .await?; + loop { + let last_committed_block = EthClient::get_last_committed_block( + &self.eth_client, + self.on_chain_proposer_address, + ) + .await?; - let block_number_to_fetch = if last_committed_block == u64::MAX { - 0 - } else { - last_committed_block + 1 - }; + let block_number_to_fetch = if last_committed_block == u64::MAX { + 0 + } else { + last_committed_block + 1 + }; - if let Some(block_to_commit_body) = self - .store - .get_block_body(block_number_to_fetch) - .map_err(CommitterError::from)? - { - let block_to_commit_header = self + if let Some(block_to_commit_body) = self .store - .get_block_header(block_number_to_fetch) + .get_block_body(block_number_to_fetch) .map_err(CommitterError::from)? - .ok_or(CommitterError::FailedToGetInformationFromStorage( - "Failed to get_block_header() after get_block_body()".to_owned(), - ))?; - - let block_to_commit = Block::new(block_to_commit_header, block_to_commit_body); - - let withdrawals = self.get_block_withdrawals(&block_to_commit)?; - let deposits = self.get_block_deposits(&block_to_commit); - - let mut withdrawal_hashes = vec![]; - - for (_, tx) in &withdrawals { - let hash = tx - .get_withdrawal_hash() - .ok_or(CommitterError::InvalidWithdrawalTransaction)?; - withdrawal_hashes.push(hash); - } - - let withdrawal_logs_merkle_root = self.get_withdrawals_merkle_root(withdrawal_hashes); - let deposit_logs_hash = self.get_deposit_hash( - deposits - .iter() - .filter_map(|tx| tx.get_deposit_hash()) - .collect(), - ); - - let state_diff = self.prepare_state_diff( - &block_to_commit, - self.store.clone(), - withdrawals, - deposits, - )?; - - let blobs_bundle = self.generate_blobs_bundle(state_diff.clone())?; - - let head_block_hash = block_to_commit.hash(); - match self - .send_commitment( - block_to_commit.header.number, - withdrawal_logs_merkle_root, - deposit_logs_hash, - blobs_bundle, - ) - .await { - Ok(commit_tx_hash) => { - info!("Sent commitment to block {head_block_hash:#x}, with transaction hash {commit_tx_hash:#x}"); + let block_to_commit_header = self + .store + .get_block_header(block_number_to_fetch) + .map_err(CommitterError::from)? + .ok_or(CommitterError::FailedToGetInformationFromStorage( + "Failed to get_block_header() after get_block_body()".to_owned(), + ))?; + + let block_to_commit = Block::new(block_to_commit_header, block_to_commit_body); + + let withdrawals = self.get_block_withdrawals(&block_to_commit)?; + let deposits = self.get_block_deposits(&block_to_commit); + + let mut withdrawal_hashes = vec![]; + + for (_, tx) in &withdrawals { + let hash = tx + .get_withdrawal_hash() + .ok_or(CommitterError::InvalidWithdrawalTransaction)?; + withdrawal_hashes.push(hash); } - Err(error) => { - return Err(CommitterError::FailedToSendCommitment(format!( - "Failed to send commitment to block {head_block_hash:#x}: {error}" - ))); + + let withdrawal_logs_merkle_root = + self.get_withdrawals_merkle_root(withdrawal_hashes); + let deposit_logs_hash = self.get_deposit_hash( + deposits + .iter() + .filter_map(|tx| tx.get_deposit_hash()) + .collect(), + ); + + let state_diff = self.prepare_state_diff( + &block_to_commit, + self.store.clone(), + withdrawals, + deposits, + )?; + + let blobs_bundle = self.generate_blobs_bundle(state_diff.clone())?; + + let head_block_hash = block_to_commit.hash(); + match self + .send_commitment( + block_to_commit.header.number, + withdrawal_logs_merkle_root, + deposit_logs_hash, + blobs_bundle, + ) + .await + { + Ok(commit_tx_hash) => { + info!("Sent commitment to block {head_block_hash:#x}, with transaction hash {commit_tx_hash:#x}"); + } + Err(error) => { + return Err(CommitterError::FailedToSendCommitment(format!( + "Failed to send commitment to block {head_block_hash:#x}: {error}" + ))); + } } } - } - Ok(()) + sleep(Duration::from_millis(self.interval_ms)).await; + } } pub fn get_block_withdrawals( @@ -311,6 +321,16 @@ impl Committer { calldata.extend(withdrawal_logs_merkle_root.0); calldata.extend(deposit_logs_hash.0); + let le_bytes = estimate_blob_gas( + &self.eth_client, + self.arbitrary_base_blob_gas_price, + 1.2, // 20% of headroom + ) + .await? + .to_le_bytes(); + + let gas_price_per_blob = Some(U256::from_little_endian(&le_bytes)); + let wrapped_tx = self .eth_client .build_eip4844_transaction( @@ -319,7 +339,7 @@ impl Committer { Bytes::from(calldata), Overrides { from: Some(self.l1_address), - gas_price_per_blob: Some(U256::from(100000000000_i64)), + gas_price_per_blob, ..Default::default() }, blobs_bundle, @@ -344,3 +364,63 @@ impl Committer { Ok(commit_tx_hash) } } + +/// Estimates the gas price for blob transactions based on the current state of the blockchain. +/// +/// # Parameters: +/// - `eth_client`: The Ethereum client used to fetch the latest block. +/// - `arbitrary_base_blob_gas_price`: The base gas price that serves as the minimum price for blob transactions. +/// - `headroom`: A multiplier (as a float) applied to the estimated gas price to provide a buffer against fluctuations. +/// +/// # Formula: +/// The gas price is estimated using an exponential function based on the blob gas used in the latest block and the +/// excess blob gas from the block header, following the formula from EIP-4844: +/// ```txt +/// blob_gas = arbitrary_base_blob_gas_price + (excess_blob_gas + blob_gas_used) * headroom +/// ``` +async fn estimate_blob_gas( + eth_client: &EthClient, + arbitrary_base_blob_gas_price: u64, + headroom: f64, +) -> Result { + let latest_block = eth_client + .get_block_by_number(BlockByNumber::Latest) + .await?; + + let blob_gas_used = latest_block.header.blob_gas_used.unwrap_or(0); + let excess_blob_gas = latest_block.header.excess_blob_gas.unwrap_or(0); + + // Using the formula from the EIP-4844 + // https://eips.ethereum.org/EIPS/eip-4844 + // def get_base_fee_per_blob_gas(header: Header) -> int: + // return fake_exponential( + // MIN_BASE_FEE_PER_BLOB_GAS, + // header.excess_blob_gas, + // BLOB_BASE_FEE_UPDATE_FRACTION + // ) + // + // factor * e ** (numerator / denominator) + // def fake_exponential(factor: int, numerator: int, denominator: int) -> int: + + // Check if adding the blob gas used and excess blob gas would overflow + let total_blob_gas = match excess_blob_gas.checked_add(blob_gas_used) { + Some(total) => total, + None => return Err(BlobEstimationError::OverflowError.into()), + }; + + // If the blob's market is in high demand, the equation may give a really big number. + let exponent = (total_blob_gas as f64) / (BLOB_BASE_FEE_UPDATE_FRACTION as f64); + let blob_gas = match E.powf(exponent) { + result if result.is_finite() => result, + _ => return Err(BlobEstimationError::NonFiniteResult.into()), + }; + + // Check if we have an overflow when we take the headroom into account. + let gas_with_headroom = blob_gas * headroom; + let blob_gas = match arbitrary_base_blob_gas_price.checked_add(gas_with_headroom as u64) { + Some(gas) => gas, + None => return Err(BlobEstimationError::OverflowError.into()), + }; + + Ok(blob_gas) +} diff --git a/crates/l2/utils/config/committer.rs b/crates/l2/utils/config/committer.rs index 2cd653b3fd..bfe00fa1a0 100644 --- a/crates/l2/utils/config/committer.rs +++ b/crates/l2/utils/config/committer.rs @@ -12,6 +12,7 @@ pub struct CommitterConfig { #[serde(deserialize_with = "secret_key_deserializer")] pub l1_private_key: SecretKey, pub interval_ms: u64, + pub arbitrary_base_blob_gas_price: u64, } impl CommitterConfig { diff --git a/crates/l2/utils/eth_client/errors.rs b/crates/l2/utils/eth_client/errors.rs index bc6ec3e6ae..8fba6f3b6e 100644 --- a/crates/l2/utils/eth_client/errors.rs +++ b/crates/l2/utils/eth_client/errors.rs @@ -20,6 +20,8 @@ pub enum EthClientError { GetBlockNumberError(#[from] GetBlockNumberError), #[error("eth_getBlockByHash request error: {0}")] GetBlockByHashError(#[from] GetBlockByHashError), + #[error("eth_getBlockByNumber request error: {0}")] + GetBlockByNumberError(#[from] GetBlockByNumberError), #[error("eth_getLogs request error: {0}")] GetLogsError(#[from] GetLogsError), #[error("eth_getTransactionReceipt request error: {0}")] @@ -122,6 +124,14 @@ pub enum GetBlockByHashError { ParseIntError(#[from] std::num::ParseIntError), } +#[derive(Debug, thiserror::Error)] +pub enum GetBlockByNumberError { + #[error("{0}")] + SerdeJSONError(#[from] serde_json::Error), + #[error("{0}")] + RPCError(String), +} + #[derive(Debug, thiserror::Error)] pub enum GetLogsError { #[error("{0}")] diff --git a/crates/l2/utils/eth_client/mod.rs b/crates/l2/utils/eth_client/mod.rs index 7fc95cb103..e327a98138 100644 --- a/crates/l2/utils/eth_client/mod.rs +++ b/crates/l2/utils/eth_client/mod.rs @@ -4,8 +4,8 @@ use crate::utils::config::eth::EthConfig; use bytes::Bytes; use errors::{ EstimateGasPriceError, EthClientError, GetBalanceError, GetBlockByHashError, - GetBlockNumberError, GetGasPriceError, GetLogsError, GetNonceError, GetTransactionByHashError, - GetTransactionReceiptError, SendRawTransactionError, + GetBlockByNumberError, GetBlockNumberError, GetGasPriceError, GetLogsError, GetNonceError, + GetTransactionByHashError, GetTransactionReceiptError, SendRawTransactionError, }; use eth_sender::Overrides; use ethereum_types::{Address, H256, U256}; @@ -59,6 +59,13 @@ pub enum WrappedTransaction { L2(PrivilegedL2Transaction), } +pub enum BlockByNumber { + Number(u64), + Latest, + Earliest, + Pending, +} + // 0x08c379a0 == Error(String) pub const ERROR_FUNCTION_SELECTOR: [u8; 4] = [0x08, 0xc3, 0x79, 0xa0]; @@ -479,7 +486,7 @@ impl EthClient { id: RpcRequestId::Number(1), jsonrpc: "2.0".to_string(), method: "eth_getBlockByHash".to_string(), - params: Some(vec![json!(format!("{block_hash:#x}")), json!(true)]), + params: Some(vec![json!(block_hash), json!(true)]), }; match self.send_request(request).await { @@ -493,6 +500,37 @@ impl EthClient { } } + /// Fetches a block from the Ethereum blockchain by its number or the latest/earliest/pending block. + /// If no `block_number` is provided, get the latest. + pub async fn get_block_by_number( + &self, + block: BlockByNumber, + ) -> Result { + let r = match block { + BlockByNumber::Number(n) => format!("{n:#x}"), + BlockByNumber::Latest => "latest".to_owned(), + BlockByNumber::Earliest => "earliest".to_owned(), + BlockByNumber::Pending => "pending".to_owned(), + }; + let request = RpcRequest { + id: RpcRequestId::Number(1), + jsonrpc: "2.0".to_string(), + method: "eth_getBlockByNumber".to_string(), + // With false it just returns the hash of the transactions. + params: Some(vec![json!(r), json!(false)]), + }; + + match self.send_request(request).await { + Ok(RpcResponse::Success(result)) => serde_json::from_value(result.result) + .map_err(GetBlockByNumberError::SerdeJSONError) + .map_err(EthClientError::from), + Ok(RpcResponse::Error(error_response)) => { + Err(GetBlockByNumberError::RPCError(error_response.error.message).into()) + } + Err(error) => Err(error), + } + } + pub async fn get_logs( &self, from_block: U256, diff --git a/crates/networking/rpc/types/block.rs b/crates/networking/rpc/types/block.rs index 25ec4a78e3..8dca34d8b0 100644 --- a/crates/networking/rpc/types/block.rs +++ b/crates/networking/rpc/types/block.rs @@ -17,7 +17,7 @@ pub struct RpcBlock { // TODO (#307): Remove TotalDifficulty. total_difficulty: U256, #[serde(flatten)] - header: BlockHeader, + pub header: BlockHeader, #[serde(flatten)] pub body: BlockBodyWrapper, } From 13f1e05e0e5a3d87c0105c7932f3d9199878a5fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Arjovsky?= Date: Thu, 28 Nov 2024 16:13:04 +0100 Subject: [PATCH 15/23] feat(l1): add sync suite (#1335) Add sync suite to the daily hive report. --- .github/workflows/hive_coverage.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/hive_coverage.yaml b/.github/workflows/hive_coverage.yaml index 5aa8cf8390..9c9751aa73 100644 --- a/.github/workflows/hive_coverage.yaml +++ b/.github/workflows/hive_coverage.yaml @@ -25,6 +25,9 @@ jobs: - simulation: engine name: "Cancun Engine tests" run_command: make run-hive-on-latest SIMULATION=ethereum/engine HIVE_EXTRA_ARGS="--sim.parallelism 4" + - simulation: sync + name: "Sync tests" + run_command: make run-hive-on-latest SIMULATION=ethereum/sync HIVE_EXTRA_ARGS="--sim.parallelism 4" steps: - name: Pull image From 94b36b5903a55cf982186302eae171815af7ad5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Arjovsky?= Date: Thu, 28 Nov 2024 16:38:14 +0100 Subject: [PATCH 16/23] feat(l1): add transactions to the mempool when receiving them via p2p (#1304) Closes #1129 --- .github/workflows/integration.yaml | 2 +- crates/blockchain/mempool.rs | 2 +- crates/l2/proposer/l1_watcher.rs | 2 +- crates/networking/p2p/Cargo.toml | 1 + crates/networking/p2p/rlpx/connection.rs | 8 ++++++-- crates/networking/p2p/rlpx/error.rs | 3 +++ crates/networking/rpc/eth/transaction.rs | 2 +- 7 files changed, 14 insertions(+), 6 deletions(-) diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml index 398a0898e1..40e1da3efb 100644 --- a/.github/workflows/integration.yaml +++ b/.github/workflows/integration.yaml @@ -84,7 +84,7 @@ jobs: run_command: make run-hive-on-latest SIMULATION=devp2p TEST_PATTERN="/AccountRange|StorageRanges|ByteCodes|TrieNodes" - simulation: eth name: "Devp2p eth tests" - run_command: make run-hive-on-latest SIMULATION=devp2p TEST_PATTERN="eth/Status|GetBlockHeaders|SimultaneousRequests|SameRequestID|ZeroRequestID|GetBlockBodies|MaliciousHandshake|MaliciousStatus|Transaction" + run_command: make run-hive-on-latest SIMULATION=devp2p TEST_PATTERN="eth/Status|GetBlockHeaders|SimultaneousRequests|SameRequestID|ZeroRequestID|GetBlockBodies|MaliciousHandshake|MaliciousStatus|Transaction|InvalidTxs" - simulation: engine name: "Engine Auth and EC tests" run_command: make run-hive-on-latest SIMULATION=ethereum/engine TEST_PATTERN="engine-(auth|exchange-capabilities)/" diff --git a/crates/blockchain/mempool.rs b/crates/blockchain/mempool.rs index 91303d488f..9680f37b2c 100644 --- a/crates/blockchain/mempool.rs +++ b/crates/blockchain/mempool.rs @@ -41,7 +41,7 @@ pub fn add_blob_transaction( } /// Add a transaction to the mempool -pub fn add_transaction(transaction: Transaction, store: Store) -> Result { +pub fn add_transaction(transaction: Transaction, store: &Store) -> Result { // Blob transactions should be submitted via add_blob_transaction along with the corresponding blobs bundle if matches!(transaction, Transaction::EIP4844Transaction(_)) { return Err(MempoolError::BlobTxNoBlobsBundle); diff --git a/crates/l2/proposer/l1_watcher.rs b/crates/l2/proposer/l1_watcher.rs index 771af10df6..a29e188eb4 100644 --- a/crates/l2/proposer/l1_watcher.rs +++ b/crates/l2/proposer/l1_watcher.rs @@ -216,7 +216,7 @@ impl L1Watcher { match mempool::add_transaction( Transaction::PrivilegedL2Transaction(mint_transaction), - store.clone(), + store, ) { Ok(hash) => { info!("Mint transaction added to mempool {hash:#x}",); diff --git a/crates/networking/p2p/Cargo.toml b/crates/networking/p2p/Cargo.toml index f3db303f97..d0d5e27e15 100644 --- a/crates/networking/p2p/Cargo.toml +++ b/crates/networking/p2p/Cargo.toml @@ -9,6 +9,7 @@ edition = "2021" ethrex-core.workspace = true ethrex-rlp.workspace = true ethrex-storage.workspace = true +ethrex-blockchain.workspace = true tracing.workspace = true tokio.workspace = true diff --git a/crates/networking/p2p/rlpx/connection.rs b/crates/networking/p2p/rlpx/connection.rs index ca22000300..7e2962f01e 100644 --- a/crates/networking/p2p/rlpx/connection.rs +++ b/crates/networking/p2p/rlpx/connection.rs @@ -28,6 +28,7 @@ use super::{ utils::{ecdh_xchng, pubkey2id}, }; use aes::cipher::KeyIvInit; +use ethrex_blockchain::mempool; use ethrex_core::{H256, H512}; use ethrex_rlp::decode::RLPDecode; use ethrex_storage::Store; @@ -332,8 +333,11 @@ impl RLPxConnection { self.send(Message::AccountRange(response)).await? } // TODO(#1129) Add the transaction to the mempool once received. - txs_msg @ Message::Transactions(_) if peer_supports_eth => { - self.broadcast_message(txs_msg).await?; + Message::Transactions(txs) if peer_supports_eth => { + for tx in &txs.transactions { + mempool::add_transaction(tx.clone(), &self.storage)?; + } + self.broadcast_message(Message::Transactions(txs)).await?; } Message::GetBlockHeaders(msg_data) if peer_supports_eth => { let response = BlockHeaders { diff --git a/crates/networking/p2p/rlpx/error.rs b/crates/networking/p2p/rlpx/error.rs index bc8ca31b47..c7b5abae89 100644 --- a/crates/networking/p2p/rlpx/error.rs +++ b/crates/networking/p2p/rlpx/error.rs @@ -1,3 +1,4 @@ +use ethrex_blockchain::error::MempoolError; use ethrex_rlp::error::{RLPDecodeError, RLPEncodeError}; use ethrex_storage::error::StoreError; use thiserror::Error; @@ -38,6 +39,8 @@ pub(crate) enum RLPxError { BroadcastError(String), #[error(transparent)] RecvError(#[from] RecvError), + #[error("Error when inserting transaction in the mempool: {0}")] + MempoolError(#[from] MempoolError), } // Grouping all cryptographic related errors in a single CryptographicError variant diff --git a/crates/networking/rpc/eth/transaction.rs b/crates/networking/rpc/eth/transaction.rs index efa7ec8e79..2e3d0b076a 100644 --- a/crates/networking/rpc/eth/transaction.rs +++ b/crates/networking/rpc/eth/transaction.rs @@ -595,7 +595,7 @@ impl RpcHandler for SendRawTransactionRequest { context.storage, ) } else { - mempool::add_transaction(self.to_transaction(), context.storage) + mempool::add_transaction(self.to_transaction(), &context.storage) }?; serde_json::to_value(format!("{:#x}", hash)) .map_err(|error| RpcErr::Internal(error.to_string())) From f3f5214d54184eeb70327ea17f3e63111698aca8 Mon Sep 17 00:00:00 2001 From: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Date: Thu, 28 Nov 2024 12:50:05 -0300 Subject: [PATCH 17/23] fix(levm): ef tests reporter (#1336) --- cmd/ef_tests/levm/report.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/ef_tests/levm/report.rs b/cmd/ef_tests/levm/report.rs index 335c5e6791..7ab2b5d70c 100644 --- a/cmd/ef_tests/levm/report.rs +++ b/cmd/ef_tests/levm/report.rs @@ -18,7 +18,7 @@ use std::{ }; pub const LEVM_EF_TESTS_SUMMARY_SLACK_FILE_PATH: &str = "./levm_ef_tests_summary_slack.txt"; -pub const LEVM_EF_TESTS_SUMMARY_GITHUB_FILE_PATH: &str = "./levm_ef_tests_summary_slack.txt"; +pub const LEVM_EF_TESTS_SUMMARY_GITHUB_FILE_PATH: &str = "./levm_ef_tests_summary_github.txt"; pub const EF_TESTS_CACHE_FILE_PATH: &str = "./levm_ef_tests_cache.json"; pub type TestVector = (usize, usize, usize); From f496c7cc87be3837394b8b324488a6670d65a7d7 Mon Sep 17 00:00:00 2001 From: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Date: Thu, 28 Nov 2024 13:28:01 -0300 Subject: [PATCH 18/23] feat(levm): cache refactor (#1314) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Motivation** **Description** - A set `touched_accounts` is introduced to track read accounts. - Cache is no longer used as a tracker of read accounts. It is now only used to track updated accounts. All the account update logic is encapsulated in individual functions. - Cache struct is removed and a `cache` module is introduced with the cache's behavior. - Gas cost functions where the access cost takes place were refactored. **Leftovers** - https://github.com/lambdaclass/ethrex/issues/1316 **EF Test Report** 8 more tests pass without any further changes ```sh ✓ Ethereum Foundation Tests: 714 passed 3387 failed 4101 total run - 12:08 ✓ Summary: 714/4101 Cancun: 627/3578 Shanghai: 33/221 Homestead: 0/17 Istanbul: 0/34 London: 17/39 Byzantium: 0/33 Berlin: 17/35 Constantinople: 0/66 Merge: 20/62 Frontier: 0/16 ``` --- cmd/ef_tests/levm/runner/levm_runner.rs | 4 +- crates/vm/levm/src/account.rs | 29 +- crates/vm/levm/src/constants.rs | 8 +- crates/vm/levm/src/db/cache.rs | 35 ++ crates/vm/levm/src/{db.rs => db/mod.rs} | 71 +-- crates/vm/levm/src/errors.rs | 10 +- crates/vm/levm/src/gas_cost.rs | 539 +++++++++++------- crates/vm/levm/src/memory.rs | 40 +- crates/vm/levm/src/opcode_handlers/block.rs | 2 +- .../levm/src/opcode_handlers/environment.rs | 96 ++-- .../stack_memory_storage_flow.rs | 65 +-- crates/vm/levm/src/opcode_handlers/system.rs | 297 +++++----- crates/vm/levm/src/utils.rs | 14 +- crates/vm/levm/src/vm.rs | 385 +++++++------ crates/vm/levm/tests/tests.rs | 164 +++--- crates/vm/vm.rs | 4 +- 16 files changed, 966 insertions(+), 797 deletions(-) create mode 100644 crates/vm/levm/src/db/cache.rs rename crates/vm/levm/src/{db.rs => db/mod.rs} (51%) diff --git a/cmd/ef_tests/levm/runner/levm_runner.rs b/cmd/ef_tests/levm/runner/levm_runner.rs index 44406b02d4..2c5a5779b1 100644 --- a/cmd/ef_tests/levm/runner/levm_runner.rs +++ b/cmd/ef_tests/levm/runner/levm_runner.rs @@ -9,7 +9,7 @@ use ethrex_core::{ H256, U256, }; use ethrex_levm::{ - db::Cache, + db::CacheDB, errors::{TransactionReport, VMError}, vm::VM, Environment, @@ -98,7 +98,7 @@ pub fn prepare_vm_for_tx(vector: &TestVector, test: &EFTest) -> Result bool { self.balance.is_zero() && self.nonce == 0 && self.bytecode.is_empty() } + + pub fn has_code(&self) -> bool { + !(self.bytecode.is_empty() || self.bytecode_hash() == EMPTY_CODE_HASH) + } + + pub fn bytecode_hash(&self) -> H256 { + keccak(self.bytecode.as_ref()).0.into() + } } #[derive(Clone, Default, Debug, PartialEq, Eq, Serialize, Deserialize)] @@ -59,12 +64,12 @@ impl Account { } } - pub fn has_code(&self) -> Result { - Ok(!(self.info.bytecode.is_empty() || self.bytecode_hash() == EMPTY_CODE_HASH)) + pub fn has_code(&self) -> bool { + self.info.has_code() } pub fn bytecode_hash(&self) -> H256 { - keccak(self.info.bytecode.as_ref()).0.into() + self.info.bytecode_hash() } pub fn is_empty(&self) -> bool { @@ -90,14 +95,4 @@ impl Account { self.info.nonce = nonce; self } - - // TODO: Replace nonce increments with this or cache's analog (currently does not have senders) - pub fn increment_nonce(&mut self) -> Result<(), VMError> { - self.info.nonce = self - .info - .nonce - .checked_add(1) - .ok_or(VMError::Internal(InternalError::NonceOverflowed))?; - Ok(()) - } } diff --git a/crates/vm/levm/src/constants.rs b/crates/vm/levm/src/constants.rs index 6b07e6e874..ba91f928fd 100644 --- a/crates/vm/levm/src/constants.rs +++ b/crates/vm/levm/src/constants.rs @@ -1,5 +1,8 @@ use ethrex_core::{H256, U256}; +pub const WORD_SIZE_IN_BYTES: U256 = U256([32, 0, 0, 0]); +pub const WORD_SIZE_IN_BYTES_USIZE: usize = 32; + pub const SUCCESS_FOR_CALL: i32 = 1; pub const REVERT_FOR_CALL: i32 = 0; pub const HALT_FOR_CALL: i32 = 2; @@ -42,11 +45,6 @@ pub const TARGET_BLOB_GAS_PER_BLOCK: U256 = U256([393216, 0, 0, 0]); // TARGET_B 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]); -// Storage constants -pub const COLD_STORAGE_ACCESS_COST: U256 = U256([2100, 0, 0, 0]); -pub const WARM_ADDRESS_ACCESS_COST: U256 = U256([100, 0, 0, 0]); -pub const BALANCE_COLD_ADDRESS_ACCESS_COST: U256 = U256([2600, 0, 0, 0]); - // Block constants pub const LAST_AVAILABLE_BLOCK_LIMIT: U256 = U256([256, 0, 0, 0]); pub const MAX_BLOCK_GAS_LIMIT: U256 = U256([30_000_000, 0, 0, 0]); diff --git a/crates/vm/levm/src/db/cache.rs b/crates/vm/levm/src/db/cache.rs new file mode 100644 index 0000000000..dc58470030 --- /dev/null +++ b/crates/vm/levm/src/db/cache.rs @@ -0,0 +1,35 @@ +use crate::Account; +use ethrex_core::Address; +use std::collections::HashMap; + +pub type CacheDB = HashMap; + +pub fn get_account<'cache>( + cached_accounts: &'cache CacheDB, + address: &Address, +) -> Option<&'cache Account> { + cached_accounts.get(address) +} + +pub fn get_account_mut<'cache>( + cached_accounts: &'cache mut CacheDB, + address: &Address, +) -> Option<&'cache mut Account> { + cached_accounts.get_mut(address) +} + +pub fn insert_account( + cached_accounts: &mut CacheDB, + address: Address, + account: Account, +) -> Option { + cached_accounts.insert(address, account) +} + +pub fn remove_account(cached_accounts: &mut CacheDB, address: &Address) -> Option { + cached_accounts.remove(address) +} + +pub fn is_account_cached(cached_accounts: &CacheDB, address: &Address) -> bool { + cached_accounts.contains_key(address) +} diff --git a/crates/vm/levm/src/db.rs b/crates/vm/levm/src/db/mod.rs similarity index 51% rename from crates/vm/levm/src/db.rs rename to crates/vm/levm/src/db/mod.rs index d9abe76813..f3bdfb65fa 100644 --- a/crates/vm/levm/src/db.rs +++ b/crates/vm/levm/src/db/mod.rs @@ -1,10 +1,10 @@ -use crate::{ - account::{Account, AccountInfo, StorageSlot}, - errors::{InternalError, VMError}, -}; +use crate::account::{Account, AccountInfo, StorageSlot}; use ethrex_core::{Address, H256, U256}; use std::collections::HashMap; +pub mod cache; +pub use cache::CacheDB; + pub trait Database { fn get_account_info(&self, address: Address) -> AccountInfo; fn get_storage_slot(&self, address: Address, key: H256) -> U256; @@ -73,66 +73,3 @@ impl Database for Db { self.block_hashes.get(&block_number).cloned() } } - -#[derive(Debug, Default, Clone, Eq, PartialEq)] -pub struct Cache { - pub accounts: HashMap, -} - -impl Cache { - pub fn get_account(&self, address: Address) -> Option<&Account> { - self.accounts.get(&address) - } - - pub fn get_mut_account(&mut self, address: Address) -> Option<&mut Account> { - self.accounts.get_mut(&address) - } - - pub fn get_storage_slot(&self, address: Address, key: H256) -> Option { - self.get_account(address)?.storage.get(&key).cloned() - } - - pub fn add_account(&mut self, address: &Address, account: &Account) { - self.accounts.insert(*address, account.clone()); - } - - pub fn write_account_storage( - &mut self, - address: &Address, - key: H256, - slot: StorageSlot, - ) -> Result<(), VMError> { - self.accounts - .get_mut(address) - .ok_or(VMError::Internal( - InternalError::AccountShouldHaveBeenCached, - ))? - .storage - .insert(key, slot); - Ok(()) - } - - // TODO: Replace nonce increments with this (currently does not have senders) - pub fn increment_account_nonce(&mut self, address: &Address) -> Result<(), VMError> { - if let Some(account) = self.accounts.get_mut(address) { - account.info.nonce = account - .info - .nonce - .checked_add(1) - .ok_or(VMError::Internal(InternalError::NonceOverflowed))?; - } - Ok(()) - } - - pub fn is_account_cached(&self, address: &Address) -> bool { - self.accounts.contains_key(address) - } - - pub fn is_slot_cached(&self, address: &Address, key: H256) -> bool { - self.is_account_cached(address) - && self - .get_account(*address) - .map(|account| account.storage.contains_key(&key)) - .unwrap_or(false) - } -} diff --git a/crates/vm/levm/src/errors.rs b/crates/vm/levm/src/errors.rs index 943f3c55b8..3a5970dd42 100644 --- a/crates/vm/levm/src/errors.rs +++ b/crates/vm/levm/src/errors.rs @@ -66,6 +66,10 @@ pub enum VMError { GasRefundsOverflow, #[error("Memory size overflows")] MemorySizeOverflow, + #[error("Nonce overflowed")] + NonceOverflow, + #[error("Nonce underflowed")] + NonceUnderflow, // OutOfGas #[error("Out Of Gas")] OutOfGas(#[from] OutOfGasError), @@ -88,14 +92,12 @@ pub enum OutOfGasError { MaxGasLimitExceeded, #[error("Arithmetic operation divided by zero in gas calculation")] ArithmeticOperationDividedByZero, + #[error("Memory Expansion Cost Overflow")] + MemoryExpansionCostOverflow, } #[derive(Debug, Clone, PartialEq, Eq, thiserror::Error, Serialize, Deserialize)] pub enum InternalError { - #[error("Overflowed when incrementing nonce")] - NonceOverflowed, - #[error("Underflowed when incrementing nonce")] - NonceUnderflowed, #[error("Overflowed when incrementing program counter")] PCOverflowed, #[error("Underflowed when decrementing program counter")] diff --git a/crates/vm/levm/src/gas_cost.rs b/crates/vm/levm/src/gas_cost.rs index 75cdc321e7..f73dcb1dfa 100644 --- a/crates/vm/levm/src/gas_cost.rs +++ b/crates/vm/levm/src/gas_cost.rs @@ -1,8 +1,8 @@ use crate::{ call_frame::CallFrame, - constants::{COLD_STORAGE_ACCESS_COST, WORD_SIZE}, - errors::OutOfGasError, - StorageSlot, + constants::{WORD_SIZE, WORD_SIZE_IN_BYTES}, + errors::{InternalError, OutOfGasError, VMError}, + memory, StorageSlot, }; use bytes::Bytes; /// Contains the gas costs of the EVM instructions (in wei) @@ -84,10 +84,61 @@ pub const CODESIZE: U256 = U256([2, 0, 0, 0]); pub const CODECOPY_STATIC: U256 = U256([3, 0, 0, 0]); 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 DEFAULT_STATIC: U256 = U256::zero(); +pub const DEFAULT_COLD_DYNAMIC: U256 = U256([2600, 0, 0, 0]); +pub const DEFAULT_WARM_DYNAMIC: U256 = U256([100, 0, 0, 0]); + +pub const SLOAD_STATIC: U256 = U256::zero(); +pub const SLOAD_COLD_DYNAMIC: U256 = U256([2100, 0, 0, 0]); +pub const SLOAD_WARM_DYNAMIC: U256 = U256([100, 0, 0, 0]); + +pub const SSTORE_STATIC: U256 = U256::zero(); +pub const SSTORE_COLD_DYNAMIC: U256 = U256([2100, 0, 0, 0]); +pub const SSTORE_DEFAULT_DYNAMIC: U256 = U256([100, 0, 0, 0]); +pub const SSTORE_STORAGE_CREATION: U256 = U256([20000, 0, 0, 0]); +pub const SSTORE_STORAGE_MODIFICATION: U256 = U256([5000, 0, 0, 0]); + +pub const BALANCE_STATIC: U256 = DEFAULT_STATIC; +pub const BALANCE_COLD_DYNAMIC: U256 = DEFAULT_COLD_DYNAMIC; +pub const BALANCE_WARM_DYNAMIC: U256 = DEFAULT_WARM_DYNAMIC; + +pub const EXTCODESIZE_STATIC: U256 = DEFAULT_STATIC; +pub const EXTCODESIZE_COLD_DYNAMIC: U256 = DEFAULT_COLD_DYNAMIC; +pub const EXTCODESIZE_WARM_DYNAMIC: U256 = DEFAULT_WARM_DYNAMIC; + +pub const EXTCODEHASH_STATIC: U256 = DEFAULT_STATIC; +pub const EXTCODEHASH_COLD_DYNAMIC: U256 = DEFAULT_COLD_DYNAMIC; +pub const EXTCODEHASH_WARM_DYNAMIC: U256 = DEFAULT_WARM_DYNAMIC; + +pub const EXTCODECOPY_STATIC: U256 = U256::zero(); +pub const EXTCODECOPY_DYNAMIC_BASE: U256 = U256([3, 0, 0, 0]); +pub const EXTCODECOPY_COLD_DYNAMIC: U256 = DEFAULT_COLD_DYNAMIC; +pub const EXTCODECOPY_WARM_DYNAMIC: U256 = DEFAULT_WARM_DYNAMIC; + +pub const CALL_STATIC: U256 = DEFAULT_STATIC; +pub const CALL_COLD_DYNAMIC: U256 = DEFAULT_COLD_DYNAMIC; +pub const CALL_WARM_DYNAMIC: U256 = DEFAULT_WARM_DYNAMIC; +pub const CALL_POSITIVE_VALUE: U256 = U256([9000, 0, 0, 0]); +pub const CALL_POSITIVE_VALUE_STIPEND: U256 = U256([2300, 0, 0, 0]); +pub const CALL_TO_EMPTY_ACCOUNT: U256 = U256([25000, 0, 0, 0]); + +pub const CALLCODE_STATIC: U256 = DEFAULT_STATIC; +pub const CALLCODE_COLD_DYNAMIC: U256 = DEFAULT_COLD_DYNAMIC; +pub const CALLCODE_WARM_DYNAMIC: U256 = DEFAULT_WARM_DYNAMIC; +pub const CALLCODE_POSITIVE_VALUE: U256 = U256([9000, 0, 0, 0]); +pub const CALLCODE_POSITIVE_VALUE_STIPEND: U256 = U256([2300, 0, 0, 0]); + +pub const DELEGATECALL_STATIC: U256 = DEFAULT_STATIC; +pub const DELEGATECALL_COLD_DYNAMIC: U256 = DEFAULT_COLD_DYNAMIC; +pub const DELEGATECALL_WARM_DYNAMIC: U256 = DEFAULT_WARM_DYNAMIC; + +pub const STATICCALL_STATIC: U256 = DEFAULT_STATIC; +pub const STATICCALL_COLD_DYNAMIC: U256 = DEFAULT_COLD_DYNAMIC; +pub const STATICCALL_WARM_DYNAMIC: U256 = DEFAULT_WARM_DYNAMIC; + // Costs in gas for call opcodes (in wei) pub const WARM_ADDRESS_ACCESS_COST: U256 = U256([100, 0, 0, 0]); pub const COLD_ADDRESS_ACCESS_COST: U256 = U256([2600, 0, 0, 0]); @@ -114,7 +165,7 @@ pub fn exp(exponent_bits: u64) -> Result { } pub fn calldatacopy( - current_call_frame: &mut CallFrame, + current_call_frame: &CallFrame, size: usize, dest_offset: usize, ) -> Result { @@ -128,7 +179,7 @@ pub fn calldatacopy( } pub fn codecopy( - current_call_frame: &mut CallFrame, + current_call_frame: &CallFrame, size: usize, dest_offset: usize, ) -> Result { @@ -141,31 +192,8 @@ pub fn codecopy( ) } -pub fn extcodecopy( - current_call_frame: &mut CallFrame, - size: usize, - dest_offset: usize, - is_cached: bool, -) -> Result { - let address_access_cost = if is_cached { - WARM_ADDRESS_ACCESS_COST - } else { - COLD_ADDRESS_ACCESS_COST - }; - - // address_access_cost is not a static cost, but there's no static - // cost and there is the address_access_cost - copy_behavior( - EXTCODECOPY_DYNAMIC_BASE, - address_access_cost, - current_call_frame, - size, - dest_offset, - ) -} - pub fn returndatacopy( - current_call_frame: &mut CallFrame, + current_call_frame: &CallFrame, size: usize, dest_offset: usize, ) -> Result { @@ -181,7 +209,7 @@ pub fn returndatacopy( fn copy_behavior( dynamic_base: U256, static_cost: U256, - current_call_frame: &mut CallFrame, + current_call_frame: &CallFrame, size: usize, offset: usize, ) -> Result { @@ -208,7 +236,7 @@ fn copy_behavior( } pub fn keccak256( - current_call_frame: &mut CallFrame, + current_call_frame: &CallFrame, size: usize, offset: usize, ) -> Result { @@ -222,7 +250,7 @@ pub fn keccak256( } pub fn log( - current_call_frame: &mut CallFrame, + current_call_frame: &CallFrame, size: usize, offset: usize, number_of_topics: u8, @@ -248,20 +276,20 @@ pub fn log( .ok_or(OutOfGasError::GasCostOverflow) } -pub fn mload(current_call_frame: &mut CallFrame, offset: usize) -> Result { +pub fn mload(current_call_frame: &CallFrame, offset: usize) -> Result { mem_expansion_behavior(current_call_frame, offset, WORD_SIZE, MLOAD_STATIC) } -pub fn mstore(current_call_frame: &mut CallFrame, offset: usize) -> Result { +pub fn mstore(current_call_frame: &CallFrame, offset: usize) -> Result { mem_expansion_behavior(current_call_frame, offset, WORD_SIZE, MSTORE_STATIC) } -pub fn mstore8(current_call_frame: &mut CallFrame, offset: usize) -> Result { +pub fn mstore8(current_call_frame: &CallFrame, offset: usize) -> Result { mem_expansion_behavior(current_call_frame, offset, 1, MSTORE8_STATIC) } fn mem_expansion_behavior( - current_call_frame: &mut CallFrame, + current_call_frame: &CallFrame, offset: usize, offset_add: usize, static_cost: U256, @@ -276,49 +304,52 @@ fn mem_expansion_behavior( .ok_or(OutOfGasError::GasCostOverflow) } -pub fn sload(is_cached: bool) -> U256 { - if is_cached { - // If slot is warm (cached) add 100 to base_dynamic_gas - WARM_ADDRESS_ACCESS_COST +pub fn sload(storage_slot_was_cold: bool) -> Result { + let static_gas = SLOAD_STATIC; + + let dynamic_cost = if storage_slot_was_cold { + SLOAD_COLD_DYNAMIC } else { - // If slot is cold (not cached) add 2100 to base_dynamic_gas - COLD_STORAGE_ACCESS_COST - } + SLOAD_WARM_DYNAMIC + }; + + Ok(static_gas + .checked_add(dynamic_cost) + .ok_or(OutOfGasError::GasCostOverflow)?) } pub fn sstore( - value: U256, - is_cached: bool, storage_slot: &StorageSlot, -) -> Result { - let mut base_dynamic_gas: U256 = U256::zero(); - - if !is_cached { - // If slot is cold 2100 is added to base_dynamic_gas - base_dynamic_gas = base_dynamic_gas - .checked_add(U256::from(2100)) - .ok_or(OutOfGasError::GasCostOverflow)?; - }; + new_value: U256, + storage_slot_was_cold: bool, +) -> Result { + let static_gas = SSTORE_STATIC; - let sstore_gas_cost = if value == storage_slot.current_value { - U256::from(100) + let mut base_dynamic_gas = if new_value == storage_slot.current_value { + SSTORE_DEFAULT_DYNAMIC } else if storage_slot.current_value == storage_slot.original_value { - if storage_slot.original_value == U256::zero() { - U256::from(20000) + if storage_slot.original_value.is_zero() { + SSTORE_STORAGE_CREATION } else { - U256::from(2900) + SSTORE_STORAGE_MODIFICATION } } else { - U256::from(100) + SSTORE_DEFAULT_DYNAMIC }; - base_dynamic_gas - .checked_add(sstore_gas_cost) - .ok_or(OutOfGasError::GasCostOverflow) + if storage_slot_was_cold { + base_dynamic_gas = base_dynamic_gas + .checked_add(SSTORE_COLD_DYNAMIC) + .ok_or(OutOfGasError::GasCostOverflow)?; + } + + Ok(static_gas + .checked_add(base_dynamic_gas) + .ok_or(OutOfGasError::GasCostOverflow)?) } pub fn mcopy( - current_call_frame: &mut CallFrame, + current_call_frame: &CallFrame, size: usize, src_offset: usize, dest_offset: usize, @@ -349,152 +380,8 @@ pub fn mcopy( .ok_or(OutOfGasError::GasCostOverflow) } -#[allow(clippy::too_many_arguments)] -pub fn call( - current_call_frame: &mut CallFrame, - args_size: usize, - args_offset: usize, - ret_size: usize, - ret_offset: usize, - value: U256, - is_cached: bool, - account_is_empty: bool, -) -> Result { - let memory_byte_size = args_size - .checked_add(args_offset) - .ok_or(OutOfGasError::GasCostOverflow)? - .max( - ret_size - .checked_add(ret_offset) - .ok_or(OutOfGasError::GasCostOverflow)?, - ); - let memory_expansion_cost = current_call_frame.memory.expansion_cost(memory_byte_size)?; - - let positive_value_cost = if !value.is_zero() { - NON_ZERO_VALUE_COST - .checked_add(BASIC_FALLBACK_FUNCTION_STIPEND) - .ok_or(OutOfGasError::GasCostOverflow)? - } else { - U256::zero() - }; - - let address_access_cost = if !is_cached { - COLD_ADDRESS_ACCESS_COST - } else { - WARM_ADDRESS_ACCESS_COST - }; - - let value_to_empty_account_cost = if !value.is_zero() && account_is_empty { - VALUE_TO_EMPTY_ACCOUNT_COST - } else { - U256::zero() - }; - - memory_expansion_cost - .checked_add(address_access_cost) - .ok_or(OutOfGasError::GasCostOverflow)? - .checked_add(positive_value_cost) - .ok_or(OutOfGasError::GasCostOverflow)? - .checked_add(value_to_empty_account_cost) - .ok_or(OutOfGasError::GasCostOverflow) -} - -pub fn callcode( - current_call_frame: &mut CallFrame, - args_size: usize, - args_offset: usize, - ret_size: usize, - ret_offset: usize, - value: U256, - is_cached: bool, -) -> Result { - let transfer_cost = if value == U256::zero() { - U256::zero() - } else { - NON_ZERO_VALUE_COST - // Should also add BASIC_FALLBACK_FUNCTION_STIPEND?? - // See https://www.evm.codes/?fork=cancun#f2 and call impl - }; - - compute_gas_call( - current_call_frame, - args_size, - args_offset, - ret_size, - ret_offset, - is_cached, - )? - .checked_add(transfer_cost) - .ok_or(OutOfGasError::GasCostOverflow) -} - -pub fn delegatecall( - current_call_frame: &mut CallFrame, - args_size: usize, - args_offset: usize, - ret_size: usize, - ret_offset: usize, - is_cached: bool, -) -> Result { - compute_gas_call( - current_call_frame, - args_size, - args_offset, - ret_size, - ret_offset, - is_cached, - ) -} - -pub fn staticcall( - current_call_frame: &mut CallFrame, - args_size: usize, - args_offset: usize, - ret_size: usize, - ret_offset: usize, - is_cached: bool, -) -> Result { - compute_gas_call( - current_call_frame, - args_size, - args_offset, - ret_size, - ret_offset, - is_cached, - ) -} - -fn compute_gas_call( - current_call_frame: &mut CallFrame, - args_size: usize, - args_offset: usize, - ret_size: usize, - ret_offset: usize, - is_cached: bool, -) -> Result { - let memory_byte_size = args_offset - .checked_add(args_size) - .and_then(|src_sum| { - ret_offset - .checked_add(ret_size) - .map(|dest_sum| src_sum.max(dest_sum)) - }) - .ok_or(OutOfGasError::GasCostOverflow)?; - let memory_expansion_cost = current_call_frame.memory.expansion_cost(memory_byte_size)?; - - let access_cost = if is_cached { - WARM_ADDRESS_ACCESS_COST - } else { - COLD_ADDRESS_ACCESS_COST - }; - - memory_expansion_cost - .checked_add(access_cost) - .ok_or(OutOfGasError::GasCostOverflow) -} - pub fn create( - current_call_frame: &mut CallFrame, + current_call_frame: &CallFrame, code_offset_in_memory: U256, code_size_in_memory: U256, ) -> Result { @@ -507,7 +394,7 @@ pub fn create( } pub fn create_2( - current_call_frame: &mut CallFrame, + current_call_frame: &CallFrame, code_offset_in_memory: U256, code_size_in_memory: U256, ) -> Result { @@ -520,7 +407,7 @@ pub fn create_2( } fn compute_gas_create( - current_call_frame: &mut CallFrame, + current_call_frame: &CallFrame, code_offset_in_memory: U256, code_size_in_memory: U256, is_create_2: bool, @@ -566,10 +453,10 @@ fn compute_gas_create( .ok_or(OutOfGasError::CreationCostIsTooHigh) } -pub fn selfdestruct(is_cached: bool, account_is_empty: bool) -> Result { +pub fn selfdestruct(address_was_cold: bool, account_is_empty: bool) -> Result { let mut gas_cost = SELFDESTRUCT_STATIC; - if !is_cached { + if address_was_cold { gas_cost = gas_cost .checked_add(COLD_ADDRESS_ACCESS_COST) .ok_or(OutOfGasError::GasCostOverflow)?; @@ -618,3 +505,227 @@ pub fn tx_creation(code_length: u64, number_of_words: u64) -> Result Result { + let static_gas = static_cost; + let dynamic_cost: U256 = if address_was_cold { + cold_dynamic_cost + } else { + warm_dynamic_cost + }; + + Ok(static_gas + .checked_add(dynamic_cost) + .ok_or(OutOfGasError::GasCostOverflow)?) +} + +fn memory_access_cost( + new_memory_size: U256, + current_memory_size: U256, + static_cost: U256, + dynamic_base_cost: U256, +) -> Result { + let minimum_word_size = new_memory_size + .checked_add( + WORD_SIZE_IN_BYTES + .checked_sub(U256::one()) + .ok_or(InternalError::ArithmeticOperationUnderflow)?, + ) + .ok_or(OutOfGasError::MemoryExpansionCostOverflow)? + .checked_div(WORD_SIZE_IN_BYTES) + .ok_or(OutOfGasError::MemoryExpansionCostOverflow)?; + + let static_gas = static_cost; + let dynamic_cost = dynamic_base_cost + .checked_mul(minimum_word_size) + .ok_or(OutOfGasError::MemoryExpansionCostOverflow)? + .checked_add(memory::expansion_cost( + new_memory_size, + current_memory_size, + )?) + .ok_or(OutOfGasError::MemoryExpansionCostOverflow)?; + + Ok(static_gas + .checked_add(dynamic_cost) + .ok_or(OutOfGasError::GasCostOverflow)?) +} + +pub fn balance(address_was_cold: bool) -> Result { + address_access_cost( + address_was_cold, + BALANCE_STATIC, + BALANCE_COLD_DYNAMIC, + BALANCE_WARM_DYNAMIC, + ) +} + +pub fn extcodesize(address_was_cold: bool) -> Result { + address_access_cost( + address_was_cold, + EXTCODESIZE_STATIC, + EXTCODESIZE_COLD_DYNAMIC, + EXTCODESIZE_WARM_DYNAMIC, + ) +} + +pub fn extcodecopy( + new_memory_size: U256, + current_memory_size: U256, + address_was_cold: bool, +) -> Result { + Ok(memory_access_cost( + new_memory_size, + current_memory_size, + EXTCODECOPY_STATIC, + EXTCODECOPY_DYNAMIC_BASE, + )? + .checked_add(address_access_cost( + address_was_cold, + EXTCODECOPY_STATIC, + EXTCODECOPY_COLD_DYNAMIC, + EXTCODECOPY_WARM_DYNAMIC, + )?) + .ok_or(OutOfGasError::GasCostOverflow)?) +} + +pub fn extcodehash(address_was_cold: bool) -> Result { + address_access_cost( + address_was_cold, + EXTCODEHASH_STATIC, + EXTCODEHASH_COLD_DYNAMIC, + EXTCODEHASH_WARM_DYNAMIC, + ) +} + +pub fn call( + new_memory_size: U256, + current_memory_size: U256, + address_was_cold: bool, + address_is_empty: bool, + value_to_transfer: U256, +) -> Result { + let static_gas = CALL_STATIC; + + let memory_expansion_cost = memory::expansion_cost(new_memory_size, current_memory_size)?; + let address_access_cost = address_access_cost( + address_was_cold, + CALL_STATIC, + CALL_COLD_DYNAMIC, + CALL_WARM_DYNAMIC, + )?; + let positive_value_cost = if !value_to_transfer.is_zero() { + CALL_POSITIVE_VALUE + .checked_add(CALL_POSITIVE_VALUE_STIPEND) + .ok_or(InternalError::ArithmeticOperationOverflow)? + } else { + U256::zero() + }; + let value_to_empty_account = if address_is_empty && !value_to_transfer.is_zero() { + CALL_TO_EMPTY_ACCOUNT + } else { + U256::zero() + }; + + // Note: code_execution_cost will be charged from the sub context post-state. + let dynamic_gas = memory_expansion_cost + .checked_add(address_access_cost) + .ok_or(OutOfGasError::GasCostOverflow)? + .checked_add(positive_value_cost) + .ok_or(OutOfGasError::GasCostOverflow)? + .checked_add(value_to_empty_account) + .ok_or(OutOfGasError::GasCostOverflow)?; + + Ok(static_gas + .checked_add(dynamic_gas) + .ok_or(OutOfGasError::GasCostOverflow)?) +} + +pub fn callcode( + new_memory_size: U256, + current_memory_size: U256, + address_was_cold: bool, + value_to_transfer: U256, +) -> Result { + let static_gas = CALLCODE_STATIC; + + let memory_expansion_cost = memory::expansion_cost(new_memory_size, current_memory_size)?; + let address_access_cost = address_access_cost( + address_was_cold, + CALLCODE_STATIC, + CALLCODE_COLD_DYNAMIC, + CALLCODE_WARM_DYNAMIC, + )?; + let positive_value_cost = if !value_to_transfer.is_zero() { + CALLCODE_POSITIVE_VALUE + .checked_add(CALLCODE_POSITIVE_VALUE_STIPEND) + .ok_or(InternalError::ArithmeticOperationOverflow)? + } else { + U256::zero() + }; + + // Note: code_execution_cost will be charged from the sub context post-state. + let dynamic_gas = memory_expansion_cost + .checked_add(address_access_cost) + .ok_or(OutOfGasError::GasCostOverflow)? + .checked_add(positive_value_cost) + .ok_or(OutOfGasError::GasCostOverflow)?; + + Ok(static_gas + .checked_add(dynamic_gas) + .ok_or(OutOfGasError::GasCostOverflow)?) +} + +pub fn delegatecall( + new_memory_size: U256, + current_memory_size: U256, + address_was_cold: bool, +) -> Result { + let static_gas = DELEGATECALL_STATIC; + + let memory_expansion_cost = memory::expansion_cost(new_memory_size, current_memory_size)?; + let address_access_cost = address_access_cost( + address_was_cold, + DELEGATECALL_STATIC, + DELEGATECALL_COLD_DYNAMIC, + DELEGATECALL_WARM_DYNAMIC, + )?; + + // Note: code_execution_cost will be charged from the sub context post-state. + let dynamic_gas = memory_expansion_cost + .checked_add(address_access_cost) + .ok_or(OutOfGasError::GasCostOverflow)?; + + Ok(static_gas + .checked_add(dynamic_gas) + .ok_or(OutOfGasError::GasCostOverflow)?) +} + +pub fn staticcall( + new_memory_size: U256, + current_memory_size: U256, + address_was_cold: bool, +) -> Result { + let static_gas = STATICCALL_STATIC; + + let memory_expansion_cost = memory::expansion_cost(new_memory_size, current_memory_size)?; + let address_access_cost = address_access_cost( + address_was_cold, + STATICCALL_STATIC, + STATICCALL_COLD_DYNAMIC, + STATICCALL_WARM_DYNAMIC, + )?; + + // Note: code_execution_cost will be charged from the sub context post-state. + let dynamic_gas = memory_expansion_cost + .checked_add(address_access_cost) + .ok_or(OutOfGasError::GasCostOverflow)?; + + Ok(static_gas + .checked_add(dynamic_gas) + .ok_or(OutOfGasError::GasCostOverflow)?) +} diff --git a/crates/vm/levm/src/memory.rs b/crates/vm/levm/src/memory.rs index 2eab4ada7c..995990ca82 100644 --- a/crates/vm/levm/src/memory.rs +++ b/crates/vm/levm/src/memory.rs @@ -1,5 +1,5 @@ use crate::{ - constants::{MEMORY_EXPANSION_QUOTIENT, WORD_SIZE}, + constants::{MEMORY_EXPANSION_QUOTIENT, WORD_SIZE, WORD_SIZE_IN_BYTES}, errors::{InternalError, OutOfGasError, VMError}, }; use ethrex_core::U256; @@ -194,3 +194,41 @@ impl Memory { .into()) } } + +/// The total cost for a given memory size. +pub fn cost(memory_size: U256) -> Result { + let memory_size_word = memory_size + .checked_add( + WORD_SIZE_IN_BYTES + .checked_sub(U256::one()) + .ok_or(InternalError::ArithmeticOperationUnderflow)?, + ) + .ok_or(OutOfGasError::MemoryExpansionCostOverflow)? + .checked_div(WORD_SIZE_IN_BYTES) + .ok_or(OutOfGasError::MemoryExpansionCostOverflow)?; + + Ok(memory_size_word + .checked_pow(U256::from(2)) + .ok_or(OutOfGasError::MemoryExpansionCostOverflow)? + .checked_div(U256::from(512)) + .ok_or(OutOfGasError::MemoryExpansionCostOverflow)? + .checked_add( + U256::from(3) + .checked_mul(memory_size_word) + .ok_or(OutOfGasError::MemoryExpansionCostOverflow)?, + ) + .ok_or(OutOfGasError::MemoryExpansionCostOverflow)?) +} + +/// When a memory expansion is triggered, only the additional bytes of memory +/// must be paid for. +pub fn expansion_cost(new_memory_size: U256, old_memory_size: U256) -> Result { + let cost = if new_memory_size <= old_memory_size { + U256::zero() + } else { + cost(new_memory_size)? + .checked_sub(cost(old_memory_size)?) + .ok_or(InternalError::ArithmeticOperationUnderflow)? + }; + Ok(cost) +} diff --git a/crates/vm/levm/src/opcode_handlers/block.rs b/crates/vm/levm/src/opcode_handlers/block.rs index d252b4d4a9..e9662f61e7 100644 --- a/crates/vm/levm/src/opcode_handlers/block.rs +++ b/crates/vm/levm/src/opcode_handlers/block.rs @@ -136,7 +136,7 @@ impl VM { // the current account should have been cached when the contract was called let balance = self - .get_account(¤t_call_frame.code_address) + .get_account(current_call_frame.code_address) .info .balance; diff --git a/crates/vm/levm/src/opcode_handlers/environment.rs b/crates/vm/levm/src/opcode_handlers/environment.rs index c1e9cfe142..e558a036cc 100644 --- a/crates/vm/levm/src/opcode_handlers/environment.rs +++ b/crates/vm/levm/src/opcode_handlers/environment.rs @@ -1,12 +1,12 @@ use crate::{ call_frame::CallFrame, - constants::{BALANCE_COLD_ADDRESS_ACCESS_COST, WARM_ADDRESS_ACCESS_COST, WORD_SIZE}, + constants::WORD_SIZE_IN_BYTES_USIZE, errors::{InternalError, OpcodeSuccess, OutOfGasError, VMError}, gas_cost, vm::{word_to_address, VM}, }; use ethrex_core::U256; -use sha3::{Digest, Keccak256}; +use keccak_hash::keccak; // Environmental Information (16) // Opcodes: ADDRESS, BALANCE, ORIGIN, CALLER, CALLVALUE, CALLDATALOAD, CALLDATASIZE, CALLDATACOPY, CODESIZE, CODECOPY, GASPRICE, EXTCODESIZE, EXTCODECOPY, RETURNDATASIZE, RETURNDATACOPY, EXTCODEHASH @@ -31,18 +31,14 @@ impl VM { &mut self, current_call_frame: &mut CallFrame, ) -> Result { - let address = &word_to_address(current_call_frame.stack.pop()?); + let address = word_to_address(current_call_frame.stack.pop()?); - if self.cache.is_account_cached(address) { - self.increase_consumed_gas(current_call_frame, WARM_ADDRESS_ACCESS_COST)?; - } else { - self.increase_consumed_gas(current_call_frame, BALANCE_COLD_ADDRESS_ACCESS_COST)?; - self.cache_from_db(address); - }; + let (account_info, address_was_cold) = self.access_account(address); + + self.increase_consumed_gas(current_call_frame, gas_cost::balance(address_was_cold)?)?; - let balance = self.get_account(address).info.balance; + current_call_frame.stack.push(account_info.balance)?; - current_call_frame.stack.push(balance)?; Ok(OpcodeSuccess::Continue) } @@ -276,16 +272,14 @@ impl VM { ) -> Result { let address = word_to_address(current_call_frame.stack.pop()?); - if self.cache.is_account_cached(&address) { - self.increase_consumed_gas(current_call_frame, WARM_ADDRESS_ACCESS_COST)?; - } else { - self.increase_consumed_gas(current_call_frame, BALANCE_COLD_ADDRESS_ACCESS_COST)?; - self.cache_from_db(&address); - }; + let (account_info, address_was_cold) = self.access_account(address); - let bytecode = self.get_account(&address).info.bytecode; + self.increase_consumed_gas(current_call_frame, gas_cost::extcodesize(address_was_cold)?)?; + + current_call_frame + .stack + .push(account_info.bytecode.len().into())?; - current_call_frame.stack.push(bytecode.len().into())?; Ok(OpcodeSuccess::Continue) } @@ -311,35 +305,42 @@ impl VM { .try_into() .map_err(|_| VMError::VeryLargeNumber)?; - let is_cached = self.cache.is_account_cached(&address); - - let gas_cost = gas_cost::extcodecopy(current_call_frame, size, dest_offset, is_cached) - .map_err(VMError::OutOfGas)?; - - self.increase_consumed_gas(current_call_frame, gas_cost)?; - - if size == 0 { - return Ok(OpcodeSuccess::Continue); - } - - if !is_cached { - self.cache_from_db(&address); - }; - - let bytecode = self.get_account(&address).info.bytecode; + let (account_info, address_was_cold) = self.access_account(address); let new_memory_size = dest_offset .checked_add(size) .ok_or(VMError::Internal( InternalError::ArithmeticOperationOverflow, ))? - .checked_next_multiple_of(WORD_SIZE) + .checked_next_multiple_of(WORD_SIZE_IN_BYTES_USIZE) .ok_or(VMError::Internal( InternalError::ArithmeticOperationOverflow, ))?; let current_memory_size = current_call_frame.memory.data.len(); + + self.increase_consumed_gas( + current_call_frame, + gas_cost::extcodecopy( + new_memory_size.into(), + current_memory_size.into(), + address_was_cold, + )?, + )?; + + if size == 0 { + return Ok(OpcodeSuccess::Continue); + } + if current_memory_size < new_memory_size { - current_call_frame.memory.data.resize(new_memory_size, 0); + current_call_frame + .memory + .data + .try_reserve(new_memory_size) + .map_err(|_err| VMError::MemorySizeOverflow)?; + current_call_frame + .memory + .data + .extend(std::iter::repeat(0).take(new_memory_size)); } for i in 0..size { @@ -351,7 +352,8 @@ impl VM { InternalError::ArithmeticOperationOverflow, ))?) { - *memory_byte = *bytecode + *memory_byte = *account_info + .bytecode .get(offset.checked_add(i).ok_or(VMError::Internal( InternalError::ArithmeticOperationOverflow, ))?) @@ -433,21 +435,13 @@ impl VM { ) -> Result { let address = word_to_address(current_call_frame.stack.pop()?); - if self.cache.is_account_cached(&address) { - self.increase_consumed_gas(current_call_frame, WARM_ADDRESS_ACCESS_COST)?; - } else { - self.increase_consumed_gas(current_call_frame, BALANCE_COLD_ADDRESS_ACCESS_COST)?; - self.cache_from_db(&address); - }; + let (account_info, address_was_cold) = self.access_account(address); - let bytecode = self.get_account(&address).info.bytecode; + self.increase_consumed_gas(current_call_frame, gas_cost::extcodehash(address_was_cold)?)?; - let mut hasher = Keccak256::new(); - hasher.update(bytecode); - let result = hasher.finalize(); - current_call_frame - .stack - .push(U256::from_big_endian(&result))?; + current_call_frame.stack.push(U256::from_big_endian( + keccak(account_info.bytecode).as_fixed_bytes(), + ))?; Ok(OpcodeSuccess::Continue) } 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 0226d059b2..11f968d345 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,7 +1,6 @@ use crate::{ - account::StorageSlot, call_frame::CallFrame, - constants::{COLD_STORAGE_ACCESS_COST, WARM_ADDRESS_ACCESS_COST, WORD_SIZE}, + constants::WORD_SIZE, errors::{InternalError, OpcodeSuccess, OutOfGasError, VMError}, gas_cost, vm::VM, @@ -132,29 +131,19 @@ impl VM { &mut self, current_call_frame: &mut CallFrame, ) -> Result { - let key = current_call_frame.stack.pop()?; - + let storage_slot_key = current_call_frame.stack.pop()?; let address = current_call_frame.to; let mut bytes = [0u8; 32]; - key.to_big_endian(&mut bytes); - let key = H256::from(bytes); - - let is_cached = self.cache.is_slot_cached(&address, key); + storage_slot_key.to_big_endian(&mut bytes); + let storage_slot_key = H256::from(bytes); - let gas_cost = if is_cached { - // If slot is warm (cached) add 100 to gas_cost - WARM_ADDRESS_ACCESS_COST - } else { - // If slot is cold (not cached) add 2100 to gas_cost - COLD_STORAGE_ACCESS_COST - }; - - let current_value = self.get_storage_slot(&address, key).current_value; + let (storage_slot, storage_slot_was_cold) = + self.access_storage_slot(address, storage_slot_key); - self.increase_consumed_gas(current_call_frame, gas_cost)?; + self.increase_consumed_gas(current_call_frame, gas_cost::sload(storage_slot_was_cold)?)?; - current_call_frame.stack.push(current_value)?; + current_call_frame.stack.push(storage_slot.current_value)?; Ok(OpcodeSuccess::Continue) } @@ -168,31 +157,28 @@ impl VM { return Err(VMError::OpcodeNotAllowedInStaticContext); } - let key = current_call_frame.stack.pop()?; - let value = current_call_frame.stack.pop()?; + let storage_slot_key = current_call_frame.stack.pop()?; + let new_storage_slot_value = current_call_frame.stack.pop()?; // Convert key from U256 to H256 let mut bytes = [0u8; 32]; - key.to_big_endian(&mut bytes); + storage_slot_key.to_big_endian(&mut bytes); let key = H256::from(bytes); - let address = current_call_frame.to; - - let is_cached = self.cache.is_slot_cached(&address, key); - - let storage_slot = self.get_storage_slot(&address, key); + let (storage_slot, storage_slot_was_cold) = + self.access_storage_slot(current_call_frame.to, key); - let gas_cost = - gas_cost::sstore(value, is_cached, &storage_slot).map_err(VMError::OutOfGas)?; - - self.increase_consumed_gas(current_call_frame, gas_cost)?; + self.increase_consumed_gas( + current_call_frame, + gas_cost::sstore(&storage_slot, new_storage_slot_value, storage_slot_was_cold)?, + )?; // Gas Refunds // TODO: Think about what to do in case of underflow of gas refunds (when we try to substract from it if the value is low) let mut gas_refunds = U256::zero(); - if value != storage_slot.current_value { + if new_storage_slot_value != storage_slot.current_value { if storage_slot.current_value == storage_slot.original_value { - if storage_slot.original_value.is_zero() && value.is_zero() { + if storage_slot.original_value.is_zero() && new_storage_slot_value.is_zero() { gas_refunds = gas_refunds .checked_add(U256::from(4800)) .ok_or(VMError::GasRefundsOverflow)?; @@ -202,12 +188,12 @@ impl VM { gas_refunds = gas_refunds .checked_sub(U256::from(4800)) .ok_or(VMError::GasRefundsUnderflow)?; - } else if value.is_zero() { + } else if new_storage_slot_value.is_zero() { gas_refunds = gas_refunds .checked_add(U256::from(4800)) .ok_or(VMError::GasRefundsOverflow)?; } - } else if value == storage_slot.original_value { + } else if new_storage_slot_value == storage_slot.original_value { if storage_slot.original_value.is_zero() { gas_refunds = gas_refunds .checked_add(U256::from(19900)) @@ -226,14 +212,7 @@ impl VM { .checked_add(gas_refunds) .ok_or(VMError::GasLimitPriceProductOverflow)?; - self.cache.write_account_storage( - &address, - key, - StorageSlot { - original_value: storage_slot.original_value, - current_value: value, - }, - )?; + self.update_account_storage(current_call_frame.to, key, new_storage_slot_value)?; Ok(OpcodeSuccess::Continue) } diff --git a/crates/vm/levm/src/opcode_handlers/system.rs b/crates/vm/levm/src/opcode_handlers/system.rs index d2a8833477..f03090ceb3 100644 --- a/crates/vm/levm/src/opcode_handlers/system.rs +++ b/crates/vm/levm/src/opcode_handlers/system.rs @@ -1,10 +1,11 @@ use crate::{ call_frame::CallFrame, + constants::WORD_SIZE_IN_BYTES_USIZE, errors::{InternalError, OpcodeSuccess, ResultReason, VMError}, gas_cost, vm::{word_to_address, VM}, }; -use ethrex_core::{types::TxKind, U256}; +use ethrex_core::{types::TxKind, Address, U256}; // System Operations (10) // Opcodes: CREATE, CALL, CALLCODE, RETURN, DELEGATECALL, CREATE2, STATICCALL, REVERT, INVALID, SELFDESTRUCT @@ -15,10 +16,15 @@ impl VM { &mut self, current_call_frame: &mut CallFrame, ) -> Result { - let gas = current_call_frame.stack.pop()?; - let code_address = word_to_address(current_call_frame.stack.pop()?); - let value = current_call_frame.stack.pop()?; - let args_offset: usize = current_call_frame + let gas_for_call = current_call_frame.stack.pop()?; + let callee: Address = word_to_address(current_call_frame.stack.pop()?); + let value_to_transfer: U256 = current_call_frame.stack.pop()?; + + if current_call_frame.is_static && !value_to_transfer.is_zero() { + return Err(VMError::OpcodeNotAllowedInStaticContext); + } + + let args_start_offset: usize = current_call_frame .stack .pop()? .try_into() @@ -28,60 +34,64 @@ impl VM { .pop()? .try_into() .map_err(|_| VMError::VeryLargeNumber)?; - let ret_offset: usize = current_call_frame + let return_data_start_offset: usize = current_call_frame .stack .pop()? .try_into() .map_err(|_| VMError::VeryLargeNumber)?; - let ret_size: usize = current_call_frame + let return_data_size: usize = current_call_frame .stack .pop()? .try_into() .map_err(|_| VMError::VeryLargeNumber)?; - if current_call_frame.is_static && !value.is_zero() { - return Err(VMError::OpcodeNotAllowedInStaticContext); - } - - let is_cached = self.cache.is_account_cached(&code_address); - - if !is_cached { - self.cache_from_db(&code_address); - } - - let account_is_empty = self.get_account(&code_address).clone().is_empty(); - - let gas_cost = gas_cost::call( + let new_memory_size_for_args = (args_start_offset + .checked_add(args_size) + .ok_or(InternalError::ArithmeticOperationOverflow)?) + .checked_next_multiple_of(WORD_SIZE_IN_BYTES_USIZE) + .ok_or(VMError::Internal( + InternalError::ArithmeticOperationOverflow, + ))?; + let new_memory_size_for_return_data = (return_data_start_offset + .checked_add(return_data_size) + .ok_or(InternalError::ArithmeticOperationOverflow)?) + .checked_next_multiple_of(WORD_SIZE_IN_BYTES_USIZE) + .ok_or(VMError::Internal( + InternalError::ArithmeticOperationOverflow, + ))?; + let new_memory_size = new_memory_size_for_args.max(new_memory_size_for_return_data); + let current_memory_size = current_call_frame.memory.data.len(); + + let (account_info, address_was_cold) = self.access_account(callee); + + self.increase_consumed_gas( current_call_frame, - args_size, - args_offset, - ret_size, - ret_offset, - value, - is_cached, - account_is_empty, - ) - .map_err(VMError::OutOfGas)?; - - self.increase_consumed_gas(current_call_frame, gas_cost)?; + gas_cost::call( + new_memory_size.into(), + current_memory_size.into(), + address_was_cold, + account_info.is_empty(), + value_to_transfer, + )?, + )?; let msg_sender = current_call_frame.to; // The new sender will be the current contract. - let to = code_address; // In this case code_address and the sub-context account are the same. Unlike CALLCODE or DELEGATECODE. + let to = callee; // In this case code_address and the sub-context account are the same. Unlike CALLCODE or DELEGATECODE. let is_static = current_call_frame.is_static; self.generic_call( current_call_frame, - gas, - value, + gas_for_call, + value_to_transfer, msg_sender, to, - code_address, + callee, false, is_static, - args_offset, + args_start_offset, args_size, - ret_offset, - ret_size, + return_data_start_offset, + return_data_size, ) } @@ -93,8 +103,8 @@ impl VM { ) -> Result { let gas = current_call_frame.stack.pop()?; let code_address = word_to_address(current_call_frame.stack.pop()?); - let value = current_call_frame.stack.pop()?; - let args_offset: usize = current_call_frame + let value_to_transfer = current_call_frame.stack.pop()?; + let args_start_offset: usize = current_call_frame .stack .pop()? .try_into() @@ -104,36 +114,45 @@ impl VM { .pop()? .try_into() .map_err(|_err| VMError::VeryLargeNumber)?; - let ret_offset: usize = current_call_frame + let return_data_start_offset: usize = current_call_frame .stack .pop()? .try_into() .map_err(|_err| VMError::VeryLargeNumber)?; - let ret_size = current_call_frame + let return_data_size = current_call_frame .stack .pop()? .try_into() .map_err(|_err| VMError::VeryLargeNumber)?; - // Gas consumed - let is_cached = self.cache.is_account_cached(&code_address); - - if !is_cached { - self.cache_from_db(&code_address); - }; - - let gas_cost = gas_cost::callcode( + let new_memory_size_for_args = (args_start_offset + .checked_add(args_size) + .ok_or(InternalError::ArithmeticOperationOverflow)?) + .checked_next_multiple_of(WORD_SIZE_IN_BYTES_USIZE) + .ok_or(VMError::Internal( + InternalError::ArithmeticOperationOverflow, + ))?; + let new_memory_size_for_return_data = (return_data_start_offset + .checked_add(return_data_size) + .ok_or(InternalError::ArithmeticOperationOverflow)?) + .checked_next_multiple_of(WORD_SIZE_IN_BYTES_USIZE) + .ok_or(VMError::Internal( + InternalError::ArithmeticOperationOverflow, + ))?; + let new_memory_size = new_memory_size_for_args.max(new_memory_size_for_return_data); + let current_memory_size = current_call_frame.memory.data.len(); + + let (_account_info, address_was_cold) = self.access_account(code_address); + + self.increase_consumed_gas( current_call_frame, - args_size, - args_offset, - ret_size, - ret_offset, - value, - is_cached, - ) - .map_err(VMError::OutOfGas)?; - - self.increase_consumed_gas(current_call_frame, gas_cost)?; + gas_cost::callcode( + new_memory_size.into(), + current_memory_size.into(), + address_was_cold, + value_to_transfer, + )?, + )?; // Sender and recipient are the same in this case. But the code executed is from another account. let msg_sender = current_call_frame.to; @@ -143,16 +162,16 @@ impl VM { self.generic_call( current_call_frame, gas, - value, + value_to_transfer, msg_sender, to, code_address, false, is_static, - args_offset, + args_start_offset, args_size, - ret_offset, - ret_size, + return_data_start_offset, + return_data_size, ) } @@ -195,7 +214,7 @@ impl VM { ) -> Result { let gas = current_call_frame.stack.pop()?; let code_address = word_to_address(current_call_frame.stack.pop()?); - let args_offset: usize = current_call_frame + let args_start_offset: usize = current_call_frame .stack .pop()? .try_into() @@ -205,12 +224,12 @@ impl VM { .pop()? .try_into() .map_err(|_err| VMError::VeryLargeNumber)?; - let ret_offset: usize = current_call_frame + let return_data_start_offset: usize = current_call_frame .stack .pop()? .try_into() .map_err(|_err| VMError::VeryLargeNumber)?; - let ret_size = current_call_frame + let return_data_size = current_call_frame .stack .pop()? .try_into() @@ -221,23 +240,33 @@ impl VM { let to = current_call_frame.to; let is_static = current_call_frame.is_static; - // Gas consumed - let is_cached = self.cache.is_account_cached(&code_address); - if !is_cached { - self.cache_from_db(&code_address); - }; - - let gas_cost = gas_cost::delegatecall( + let (_account_info, address_was_cold) = self.access_account(code_address); + + let new_memory_size_for_args = (args_start_offset + .checked_add(args_size) + .ok_or(InternalError::ArithmeticOperationOverflow)?) + .checked_next_multiple_of(WORD_SIZE_IN_BYTES_USIZE) + .ok_or(VMError::Internal( + InternalError::ArithmeticOperationOverflow, + ))?; + let new_memory_size_for_return_data = (return_data_start_offset + .checked_add(return_data_size) + .ok_or(InternalError::ArithmeticOperationOverflow)?) + .checked_next_multiple_of(WORD_SIZE_IN_BYTES_USIZE) + .ok_or(VMError::Internal( + InternalError::ArithmeticOperationOverflow, + ))?; + let new_memory_size = new_memory_size_for_args.max(new_memory_size_for_return_data); + let current_memory_size = current_call_frame.memory.data.len(); + + self.increase_consumed_gas( current_call_frame, - args_size, - args_offset, - ret_size, - ret_offset, - is_cached, - ) - .map_err(VMError::OutOfGas)?; - - self.increase_consumed_gas(current_call_frame, gas_cost)?; + gas_cost::delegatecall( + new_memory_size.into(), + current_memory_size.into(), + address_was_cold, + )?, + )?; self.generic_call( current_call_frame, @@ -248,10 +277,10 @@ impl VM { code_address, false, is_static, - args_offset, + args_start_offset, args_size, - ret_offset, - ret_size, + return_data_start_offset, + return_data_size, ) } @@ -263,7 +292,7 @@ impl VM { ) -> Result { let gas = current_call_frame.stack.pop()?; let code_address = word_to_address(current_call_frame.stack.pop()?); - let args_offset: usize = current_call_frame + let args_start_offset: usize = current_call_frame .stack .pop()? .try_into() @@ -273,40 +302,49 @@ impl VM { .pop()? .try_into() .map_err(|_err| VMError::VeryLargeNumber)?; - let ret_offset: usize = current_call_frame + let return_data_start_offset: usize = current_call_frame .stack .pop()? .try_into() .map_err(|_err| VMError::VeryLargeNumber)?; - let ret_size = current_call_frame + let return_data_size = current_call_frame .stack .pop()? .try_into() .map_err(|_err| VMError::VeryLargeNumber)?; + let (_account_info, address_was_cold) = self.access_account(code_address); + + let new_memory_size_for_args = (args_start_offset + .checked_add(args_size) + .ok_or(InternalError::ArithmeticOperationOverflow)?) + .checked_next_multiple_of(WORD_SIZE_IN_BYTES_USIZE) + .ok_or(VMError::Internal( + InternalError::ArithmeticOperationOverflow, + ))?; + let new_memory_size_for_return_data = (return_data_start_offset + .checked_add(return_data_size) + .ok_or(InternalError::ArithmeticOperationOverflow)?) + .checked_next_multiple_of(WORD_SIZE_IN_BYTES_USIZE) + .ok_or(VMError::Internal( + InternalError::ArithmeticOperationOverflow, + ))?; + let new_memory_size = new_memory_size_for_args.max(new_memory_size_for_return_data); + let current_memory_size = current_call_frame.memory.data.len(); + + self.increase_consumed_gas( + current_call_frame, + gas_cost::staticcall( + new_memory_size.into(), + current_memory_size.into(), + address_was_cold, + )?, + )?; + let value = U256::zero(); let msg_sender = current_call_frame.to; // The new sender will be the current contract. let to = code_address; // In this case code_address and the sub-context account are the same. Unlike CALLCODE or DELEGATECODE. - // Gas consumed - let is_cached = self.cache.is_account_cached(&code_address); - - if !is_cached { - self.cache_from_db(&code_address); - }; - - let gas_cost = gas_cost::staticcall( - current_call_frame, - args_size, - args_offset, - ret_size, - ret_offset, - is_cached, - ) - .map_err(VMError::OutOfGas)?; - - self.increase_consumed_gas(current_call_frame, gas_cost)?; - self.generic_call( current_call_frame, gas, @@ -316,10 +354,10 @@ impl VM { code_address, false, true, - args_offset, + args_start_offset, args_size, - ret_offset, - ret_size, + return_data_start_offset, + return_data_size, ) } @@ -444,44 +482,27 @@ impl VM { return Err(VMError::OpcodeNotAllowedInStaticContext); } - // 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(); - - let is_cached = self.cache.is_account_cached(&target_address); + let (target_account_info, target_account_is_cold) = self.access_account(target_address); - // 3 & 4. Get target account and add the balance of the current account to it - let mut target_account = self.get_account(&target_address); - let account_is_empty = target_account.is_empty(); + self.increase_consumed_gas( + current_call_frame, + gas_cost::selfdestruct(target_account_is_cold, target_account_info.is_empty()) + .map_err(VMError::OutOfGas)?, + )?; - let gas_cost = - gas_cost::selfdestruct(is_cached, account_is_empty).map_err(VMError::OutOfGas)?; + let (current_account_info, _current_account_is_cold) = + self.access_account(current_call_frame.to); - target_account.info.balance = target_account - .info - .balance - .checked_add(current_account_balance) - .ok_or(VMError::BalanceOverflow)?; + self.increase_account_balance(target_address, current_account_info.balance)?; + self.decrease_account_balance(current_call_frame.to, current_account_info.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/utils.rs b/crates/vm/levm/src/utils.rs index 56dc716fe4..02fcb9177f 100644 --- a/crates/vm/levm/src/utils.rs +++ b/crates/vm/levm/src/utils.rs @@ -1,6 +1,6 @@ use crate::{ account::{Account, AccountInfo}, - db::{Cache, Db}, + db::{cache, CacheDB, Db}, environment::Environment, errors::{InternalError, VMError}, operations::Operation, @@ -27,7 +27,7 @@ pub fn new_vm_with_bytecode(bytecode: Bytes) -> Result { Address::from_low_u64_be(100), U256::MAX, Db::new(), - Cache::default(), + CacheDB::default(), ) } @@ -38,7 +38,7 @@ pub fn new_vm_with_ops(operations: &[Operation]) -> Result { Address::from_low_u64_be(100), U256::MAX, Db::new(), - Cache::default(), + CacheDB::default(), ) } @@ -49,7 +49,7 @@ pub fn new_vm_with_ops_db(operations: &[Operation], db: Db) -> Result Result { let accounts = [ // This is the contract account that is going to be executed @@ -91,8 +91,8 @@ pub fn new_vm_with_ops_addr_bal_db( db.add_accounts(accounts.to_vec()); // add to cache accounts from list accounts - cache.add_account(&accounts[0].0, &accounts[0].1); - cache.add_account(&accounts[1].0, &accounts[1].1); + cache::insert_account(&mut cache, accounts[0].0, accounts[0].1.clone()); + cache::insert_account(&mut cache, accounts[1].0, accounts[1].1.clone()); let env = Environment::default_from_address(sender_address); diff --git a/crates/vm/levm/src/vm.rs b/crates/vm/levm/src/vm.rs index dd0fcf7dce..0bc2b752ed 100644 --- a/crates/vm/levm/src/vm.rs +++ b/crates/vm/levm/src/vm.rs @@ -2,7 +2,7 @@ use crate::{ account::{Account, StorageSlot}, call_frame::CallFrame, constants::*, - db::{Cache, Database}, + db::{cache, CacheDB, Database}, environment::Environment, errors::{ InternalError, OpcodeSuccess, OutOfGasError, ResultReason, TransactionReport, TxResult, @@ -10,6 +10,7 @@ use crate::{ }, gas_cost, opcodes::Opcode, + AccountInfo, }; use bytes::Bytes; use ethrex_core::{types::TxKind, Address, H256, U256}; @@ -42,8 +43,11 @@ pub struct VM { /// Mapping between addresses (160-bit identifiers) and account /// states. pub db: Arc, - pub cache: Cache, + pub cache: CacheDB, pub tx_kind: TxKind, + + pub touched_accounts: HashSet
, + pub touched_storage_slots: HashMap>, } pub fn address_to_word(address: Address) -> U256 { @@ -72,22 +76,25 @@ impl VM { value: U256, calldata: Bytes, db: Arc, - mut cache: Cache, + mut cache: CacheDB, ) -> Result { // Maybe this decision should be made in an upper layer // Add sender, coinbase and recipient (in the case of a Call) to cache [https://www.evm.codes/about#access_list] - let sender_account_info = db.get_account_info(env.origin); - cache.add_account(&env.origin, &Account::from(sender_account_info.clone())); - - let coinbase_account_info = db.get_account_info(env.coinbase); - cache.add_account(&env.coinbase, &Account::from(coinbase_account_info)); + let mut default_touched_accounts = + HashSet::from_iter([env.origin, env.coinbase].iter().cloned()); match to { TxKind::Call(address_to) => { + default_touched_accounts.insert(address_to); + // add address_to to cache let recipient_account_info = db.get_account_info(address_to); - cache.add_account(&address_to, &Account::from(recipient_account_info.clone())); + cache::insert_account( + &mut cache, + address_to, + Account::from(recipient_account_info.clone()), + ); // CALL tx let initial_call_frame = CallFrame::new( @@ -110,6 +117,8 @@ impl VM { accrued_substate: Substate::default(), cache, tx_kind: to, + touched_accounts: HashSet::new(), + touched_storage_slots: HashMap::new(), }) } TxKind::Create => { @@ -117,13 +126,16 @@ impl VM { // (2) let new_contract_address = - VM::calculate_create_address(env.origin, sender_account_info.nonce).map_err( - |_| VMError::Internal(InternalError::CouldNotComputeCreateAddress), - )?; // TODO: Remove after merging the PR that removes unwraps. + VM::calculate_create_address(env.origin, db.get_account_info(env.origin).nonce) + .map_err(|_| { + VMError::Internal(InternalError::CouldNotComputeCreateAddress) + })?; + + default_touched_accounts.insert(new_contract_address); // (3) let created_contract = Account::new(value, calldata.clone(), 1, HashMap::new()); - cache.add_account(&new_contract_address, &created_contract); + cache::insert_account(&mut cache, new_contract_address, created_contract); // (5) let code: Bytes = calldata.clone(); @@ -148,6 +160,8 @@ impl VM { accrued_substate: Substate::default(), cache, tx_kind: TxKind::Create, + touched_accounts: default_touched_accounts, + touched_storage_slots: HashMap::new(), }) } } @@ -168,7 +182,7 @@ impl VM { Err(e) => { return TransactionReport { result: TxResult::Revert(e), - new_state: self.cache.accounts.clone(), + new_state: self.cache.clone(), gas_used: current_call_frame.gas_used.low_u64(), gas_refunded: self.env.refunded_gas.low_u64(), output: current_call_frame.returndata.clone(), // Bytes::new() if error is not RevertOpcode @@ -289,7 +303,7 @@ impl VM { self.call_frames.push(current_call_frame.clone()); return TransactionReport { result: TxResult::Success, - new_state: self.cache.accounts.clone(), + new_state: self.cache.clone(), gas_used: current_call_frame.gas_used.low_u64(), gas_refunded: self.env.refunded_gas.low_u64(), output: current_call_frame.returndata.clone(), @@ -314,7 +328,7 @@ impl VM { return TransactionReport { result: TxResult::Revert(error), - new_state: self.cache.accounts.clone(), + new_state: self.cache.clone(), gas_used: current_call_frame.gas_used.low_u64(), gas_refunded: self.env.refunded_gas.low_u64(), output: current_call_frame.returndata.clone(), // Bytes::new() if error is not RevertOpcode @@ -328,7 +342,7 @@ impl VM { fn restore_state( &mut self, - backup_cache: Cache, + backup_cache: CacheDB, backup_substate: Substate, backup_refunded_gas: U256, ) { @@ -374,29 +388,20 @@ impl VM { } } - let origin = self.env.origin; + let (sender_account_info, _address_was_cold) = self.access_account(self.env.origin); - let mut sender_account = self.get_account(&origin); - - // See if it's raised in upper layers - sender_account.info.nonce = sender_account - .info - .nonce - .checked_add(1) - .ok_or(VMError::Internal(InternalError::NonceOverflowed))?; + self.increment_account_nonce(self.env.origin)?; // (4) - if sender_account.has_code()? { + if sender_account_info.has_code() { return Err(VMError::SenderAccountShouldNotHaveBytecode); } // (6) - if sender_account.info.balance < call_frame.msg_value { + if sender_account_info.balance < call_frame.msg_value { return Err(VMError::SenderBalanceShouldContainTransferValue); } - self.cache.add_account(&origin, &sender_account); - // (7) if self.env.gas_price < self.env.base_fee_per_gas { return Err(VMError::GasPriceIsLowerThanBaseFee); @@ -418,18 +423,10 @@ impl VM { ))? .clone(); - let sender = call_frame.msg_sender; - let mut sender_account = self.get_account(&sender); - - sender_account.info.nonce = sender_account - .info - .nonce - .checked_sub(1) - .ok_or(VMError::Internal(InternalError::NonceUnderflowed))?; + self.decrement_account_nonce(call_frame.msg_sender)?; let new_contract_address = call_frame.to; - - if self.cache.accounts.remove(&new_contract_address).is_none() { + if cache::remove_account(&mut self.cache, &new_contract_address).is_none() { return Err(VMError::AddressDoesNotMatchAnAccount); // Should not be this error } @@ -463,14 +460,6 @@ impl VM { let sender = initial_call_frame.msg_sender; - let initial_call_frame = self - .call_frames - .last() - .ok_or(VMError::Internal( - InternalError::CouldNotAccessLastCallframe, - ))? - .clone(); - let calldata_cost = gas_cost::tx_calldata(&initial_call_frame.calldata).map_err(VMError::OutOfGas)?; @@ -527,49 +516,26 @@ impl VM { let contract_address = initial_call_frame.to; - let mut created_contract = self.get_account(&contract_address); - - created_contract.info.bytecode = contract_code; - - self.cache.add_account(&contract_address, &created_contract); + self.update_account_bytecode(contract_address, contract_code)?; } - let mut sender_account = self.get_account(&sender); let coinbase_address = self.env.coinbase; - sender_account.info.balance = sender_account - .info - .balance - .checked_sub( - U256::from(report.gas_used) - .checked_mul(self.env.gas_price) - .ok_or(VMError::GasLimitPriceProductOverflow)?, - ) - .ok_or(VMError::BalanceUnderflow)?; + self.decrease_account_balance( + sender, + U256::from(report.gas_used) + .checked_mul(self.env.gas_price) + .ok_or(VMError::GasLimitPriceProductOverflow)?, + )?; let receiver_address = initial_call_frame.to; - let mut receiver_account = self.get_account(&receiver_address); // If execution was successful we want to transfer value from sender to receiver if report.is_success() { // Subtract to the caller the gas sent - sender_account.info.balance = sender_account - .info - .balance - .checked_sub(initial_call_frame.msg_value) - .ok_or(VMError::BalanceUnderflow)?; - receiver_account.info.balance = receiver_account - .info - .balance - .checked_add(initial_call_frame.msg_value) - .ok_or(VMError::BalanceUnderflow)?; + self.decrease_account_balance(sender, initial_call_frame.msg_value)?; + self.increase_account_balance(receiver_address, initial_call_frame.msg_value)?; } - // Note: This is commented because it's used for debugging purposes in development. - // dbg!(&report.gas_refunded); - - self.cache.add_account(&sender, &sender_account); - self.cache.add_account(&receiver_address, &receiver_account); - // Send coinbase fee let priority_fee_per_gas = self .env @@ -580,16 +546,9 @@ impl VM { .checked_mul(priority_fee_per_gas) .ok_or(VMError::BalanceOverflow)?; - let mut coinbase_account = self.get_account(&coinbase_address); - coinbase_account.info.balance = coinbase_account - .info - .balance - .checked_add(coinbase_fee) - .ok_or(VMError::BalanceOverflow)?; - - self.cache.add_account(&coinbase_address, &coinbase_account); + self.increase_account_balance(coinbase_address, coinbase_fee)?; - report.new_state.clone_from(&self.cache.accounts); + report.new_state.clone_from(&self.cache); Ok(report) } @@ -617,30 +576,20 @@ impl VM { ret_offset: usize, ret_size: usize, ) -> Result { - let mut sender_account = self.get_account(¤t_call_frame.msg_sender); + let (sender_account_info, _address_was_cold) = + self.access_account(current_call_frame.msg_sender); - if sender_account.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); + self.decrease_account_balance(current_call_frame.msg_sender, value)?; + self.increase_account_balance(to, value)?; - // transfer value - sender_account.info.balance = sender_account - .info - .balance - .checked_sub(value) - .ok_or(VMError::BalanceUnderflow)?; - recipient_account.info.balance = recipient_account - .info - .balance - .checked_add(value) - .ok_or(VMError::BalanceOverflow)?; - - let code_address_bytecode = self.get_account(&code_address).info.bytecode; + let (code_account_info, _address_was_cold) = self.access_account(code_address); - if code_address_bytecode.is_empty() { + if code_account_info.bytecode.is_empty() { current_call_frame .stack .push(U256::from(SUCCESS_FOR_CALL))?; @@ -675,7 +624,7 @@ impl VM { msg_sender, to, code_address, - code_address_bytecode, + code_account_info.bytecode, value, calldata, is_static, @@ -694,12 +643,6 @@ 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); // Add gas used by the sub-context to the current one after it's execution. @@ -808,29 +751,25 @@ impl VM { return Ok(OpcodeSuccess::Result(ResultReason::Revert)); } - if !self.cache.is_account_cached(¤t_call_frame.msg_sender) { - self.cache_from_db(¤t_call_frame.msg_sender); - }; - - let sender_account = self - .cache - .get_mut_account(current_call_frame.msg_sender) - .ok_or(VMError::Internal(InternalError::AccountNotFound))?; + let (sender_account_info, _sender_address_was_cold) = + self.access_account(current_call_frame.msg_sender); - if sender_account.info.balance < value_in_wei_to_send { + if sender_account_info.balance < value_in_wei_to_send { current_call_frame .stack .push(U256::from(REVERT_FOR_CREATE))?; return Ok(OpcodeSuccess::Result(ResultReason::Revert)); } - let Some(new_nonce) = sender_account.info.nonce.checked_add(1) else { - current_call_frame - .stack - .push(U256::from(REVERT_FOR_CREATE))?; - return Ok(OpcodeSuccess::Result(ResultReason::Revert)); + let new_nonce = match self.increment_account_nonce(current_call_frame.msg_sender) { + Ok(nonce) => nonce, + Err(_) => { + current_call_frame + .stack + .push(U256::from(REVERT_FOR_CREATE))?; + return Ok(OpcodeSuccess::Result(ResultReason::Revert)); + } }; - sender_account.info.nonce = new_nonce; let code_offset_in_memory = code_offset_in_memory .try_into() @@ -844,13 +783,11 @@ impl VM { let new_address = match salt { Some(salt) => Self::calculate_create2_address(current_call_frame.to, &code, salt)?, - None => Self::calculate_create_address( - current_call_frame.msg_sender, - sender_account.info.nonce, - )?, + None => Self::calculate_create_address(current_call_frame.msg_sender, new_nonce)?, }; - if self.cache.accounts.contains_key(&new_address) { + // FIXME: Shouldn't we check against the db? + if cache::is_account_cached(&self.cache, &new_address) { current_call_frame .stack .push(U256::from(REVERT_FOR_CREATE))?; @@ -858,7 +795,7 @@ impl VM { } let new_account = Account::new(U256::zero(), code.clone(), 0, Default::default()); - self.cache.add_account(&new_address, &new_account); + cache::insert_account(&mut self.cache, new_address, new_account); current_call_frame .stack @@ -866,7 +803,7 @@ impl VM { self.generic_call( current_call_frame, - U256::MAX, + U256::MAX, // FIXME: Why we send U256::MAX here? value_in_wei_to_send, current_call_frame.msg_sender, new_address, @@ -897,7 +834,7 @@ impl VM { let potential_consumed_gas = current_call_frame .gas_used .checked_add(gas) - .ok_or(VMError::OutOfGas(OutOfGasError::ConsumedGasOverflow))?; + .ok_or(OutOfGasError::ConsumedGasOverflow)?; if potential_consumed_gas > current_call_frame.gas_limit { return Err(VMError::OutOfGas(OutOfGasError::MaxGasLimitExceeded)); } @@ -907,52 +844,172 @@ impl VM { .env .consumed_gas .checked_add(gas) - .ok_or(VMError::OutOfGas(OutOfGasError::ConsumedGasOverflow))?; + .ok_or(OutOfGasError::ConsumedGasOverflow)?; Ok(()) } - pub fn cache_from_db(&mut self, address: &Address) { - let acc_info = self.db.get_account_info(*address); - self.cache.add_account( + pub fn cache_from_db(&mut self, address: Address) { + let acc_info = self.db.get_account_info(address); + cache::insert_account( + &mut self.cache, address, - &Account { + Account { info: acc_info.clone(), storage: HashMap::new(), }, ); } - /// Gets account, first checking the cache and then the database (caching in the second case) - pub fn get_account(&mut self, address: &Address) -> Account { - match self.cache.get_account(*address) { - Some(acc) => acc.clone(), + /// Accesses to an account's information. + /// + /// Accessed accounts are stored in the `touched_accounts` set. + /// Accessed accounts take place in some gas cost computation. + #[must_use] + pub fn access_account(&mut self, address: Address) -> (AccountInfo, bool) { + let address_was_cold = self.touched_accounts.insert(address); + let account = match cache::get_account(&self.cache, &address) { + Some(account) => account.info.clone(), + None => self.db.get_account_info(address), + }; + (account, address_was_cold) + } + + /// Accesses to an account's storage slot. + /// + /// Accessed storage slots are stored in the `touched_storage_slots` set. + /// Accessed storage slots take place in some gas cost computation. + #[must_use] + pub fn access_storage_slot(&mut self, address: Address, key: H256) -> (StorageSlot, bool) { + let storage_slot_was_cold = self + .touched_storage_slots + .entry(address) + .or_default() + .insert(key); + let storage_slot = match cache::get_account(&self.cache, &address) { + Some(account) => match account.storage.get(&key) { + Some(storage_slot) => storage_slot.clone(), + None => { + let value = self.db.get_storage_slot(address, key); + StorageSlot { + original_value: value, + current_value: value, + } + } + }, None => { - let acc_info = self.db.get_account_info(*address); - let acc = Account { - info: acc_info, - storage: HashMap::new(), - }; - self.cache.add_account(address, &acc); - acc + let value = self.db.get_storage_slot(address, key); + StorageSlot { + original_value: value, + current_value: value, + } } + }; + (storage_slot, storage_slot_was_cold) + } + + pub fn increase_account_balance( + &mut self, + address: Address, + increase: U256, + ) -> Result<(), VMError> { + let account = self.get_account_mut(address)?; + account.info.balance = account + .info + .balance + .checked_add(increase) + .ok_or(VMError::BalanceOverflow)?; + Ok(()) + } + + pub fn decrease_account_balance( + &mut self, + address: Address, + decrease: U256, + ) -> Result<(), VMError> { + let account = self.get_account_mut(address)?; + account.info.balance = account + .info + .balance + .checked_sub(decrease) + .ok_or(VMError::BalanceUnderflow)?; + Ok(()) + } + + pub fn increment_account_nonce(&mut self, address: Address) -> Result { + let account = self.get_account_mut(address)?; + account.info.nonce = account + .info + .nonce + .checked_add(1) + .ok_or(VMError::NonceOverflow)?; + Ok(account.info.nonce) + } + + pub fn decrement_account_nonce(&mut self, address: Address) -> Result<(), VMError> { + let account = self.get_account_mut(address)?; + account.info.nonce = account + .info + .nonce + .checked_sub(1) + .ok_or(VMError::NonceUnderflow)?; + Ok(()) + } + + pub fn update_account_bytecode( + &mut self, + address: Address, + new_bytecode: Bytes, + ) -> Result<(), VMError> { + let account = self.get_account_mut(address)?; + account.info.bytecode = new_bytecode; + Ok(()) + } + + pub fn update_account_storage( + &mut self, + address: Address, + key: H256, + new_value: U256, + ) -> Result<(), VMError> { + let account = self.get_account_mut(address)?; + let account_original_storage_slot_value = account + .storage + .get(&key) + .map_or(U256::zero(), |slot| slot.original_value); + let slot = account.storage.entry(key).or_insert(StorageSlot { + original_value: account_original_storage_slot_value, + current_value: new_value, + }); + slot.current_value = new_value; + Ok(()) + } + + pub fn get_account_mut(&mut self, address: Address) -> Result<&mut Account, VMError> { + if !cache::is_account_cached(&self.cache, &address) { + let account_info = self.db.get_account_info(address); + let account = Account { + info: account_info, + storage: HashMap::new(), + }; + cache::insert_account(&mut self.cache, address, account.clone()); } + cache::get_account_mut(&mut self.cache, &address) + .ok_or(VMError::Internal(InternalError::AccountNotFound)) } - /// Gets storage slot, first checking the cache and then the database (caching in the second case) - pub fn get_storage_slot(&mut self, address: &Address, key: H256) -> StorageSlot { - match self.cache.get_storage_slot(*address, key) { - Some(slot) => slot, + /// Gets account, first checking the cache and then the database (caching in the second case) + pub fn get_account(&mut self, address: Address) -> Account { + match cache::get_account(&self.cache, &address) { + Some(acc) => acc.clone(), None => { - let value = self.db.get_storage_slot(*address, key); - let slot = StorageSlot { - original_value: value, - current_value: value, + let account_info = self.db.get_account_info(address); + let account = Account { + info: account_info, + storage: HashMap::new(), }; - let mut acc = self.get_account(address); - acc.storage.insert(key, slot.clone()); - self.cache.add_account(address, &acc); - slot + cache::insert_account(&mut self.cache, address, account.clone()); + account } } } diff --git a/crates/vm/levm/tests/tests.rs b/crates/vm/levm/tests/tests.rs index 24b81cd649..8e6dc78889 100644 --- a/crates/vm/levm/tests/tests.rs +++ b/crates/vm/levm/tests/tests.rs @@ -6,7 +6,7 @@ use ethrex_core::{types::TxKind, Address, H256, U256}; use ethrex_levm::{ account::Account, constants::*, - db::{Cache, Db}, + db::{cache, CacheDB, Db}, errors::{TxResult, VMError}, gas_cost, operations::Operation, @@ -1720,8 +1720,8 @@ fn call_returns_if_bytecode_empty() { let mut db = Db::new(); db.add_accounts(vec![(callee_address, callee_account.clone())]); - let mut cache = Cache::default(); - cache.add_account(&callee_address, &callee_account); + let mut cache = CacheDB::default(); + cache::insert_account(&mut cache, callee_address, callee_account); let mut vm = new_vm_with_ops_addr_bal_db( ops_to_bytecode(&caller_ops).unwrap(), @@ -1764,8 +1764,8 @@ fn call_changes_callframe_and_stores() { let mut db = Db::new(); db.add_accounts(vec![(callee_address, callee_account.clone())]); - let mut cache = Cache::default(); - cache.add_account(&callee_address, &callee_account); + let mut cache = CacheDB::default(); + cache::insert_account(&mut cache, callee_address, callee_account); let mut vm = new_vm_with_ops_addr_bal_db( ops_to_bytecode(&caller_ops).unwrap(), @@ -1865,9 +1865,9 @@ fn nested_calls() { (callee3_address, callee3_account.clone()), ]); - let mut cache = Cache::default(); - cache.add_account(&callee2_address, &callee2_account); - cache.add_account(&callee3_address, &callee3_account); + let mut cache = CacheDB::default(); + cache::insert_account(&mut cache, callee2_address, callee2_account); + cache::insert_account(&mut cache, callee3_address, callee3_account); let mut vm = new_vm_with_ops_addr_bal_db( ops_to_bytecode(&caller_ops).unwrap(), @@ -1938,8 +1938,8 @@ fn staticcall_changes_callframe_is_static() { let mut db = Db::new(); db.add_accounts(vec![(callee_address, callee_account.clone())]); - let mut cache = Cache::default(); - cache.add_account(&callee_address, &callee_account); + let mut cache = CacheDB::default(); + cache::insert_account(&mut cache, callee_address, callee_account); let mut vm = new_vm_with_ops_addr_bal_db( ops_to_bytecode(&caller_ops).unwrap(), @@ -2054,8 +2054,8 @@ fn pc_op_with_push_offset() { // let mut db = Db::new(); // db.add_accounts(vec![(callee_address, callee_account.clone())]); -// let mut cache = Cache::default(); -// cache.add_account(&callee_address, &callee_account); +// let mut cache = CacheDB::default(); +// cache::insert_account(&mut cache, callee_address, callee_account); // let mut vm = new_vm_with_ops_addr_bal_db( // ops_to_bytecode(&caller_ops).unwrap(), @@ -2118,8 +2118,8 @@ fn pc_op_with_push_offset() { // let mut db = Db::new(); // db.add_accounts(vec![(callee_address, callee_account.clone())]); -// let mut cache = Cache::default(); -// cache.add_account(&callee_address, &callee_account); +// let mut cache = CacheDB::default(); +// cache::insert_account(&mut cache, callee_address, callee_account); // let mut vm = new_vm_with_ops_addr_bal_db( // ops_to_bytecode(&caller_ops).unwrap(), @@ -2180,8 +2180,8 @@ fn pc_op_with_push_offset() { // let mut db = Db::new(); // db.add_accounts(vec![(callee_address, callee_account.clone())]); -// let mut cache = Cache::default(); -// cache.add_account(&callee_address, &callee_account); +// let mut cache = CacheDB::default(); +// cache::insert_account(&mut cache, callee_address, callee_account); // let mut vm = new_vm_with_ops_addr_bal_db( // ops_to_bytecode(&caller_ops).unwrap(), @@ -2241,8 +2241,8 @@ fn pc_op_with_push_offset() { // let mut db = Db::new(); // db.add_accounts(vec![(callee_address, callee_account.clone())]); -// let mut cache = Cache::default(); -// cache.add_account(&callee_address, &callee_account); +// let mut cache = CacheDB::default(); +// cache::insert_account(&mut cache, callee_address, callee_account); // let mut vm = new_vm_with_ops_addr_bal_db( // ops_to_bytecode(&caller_ops).unwrap(), @@ -2464,8 +2464,8 @@ fn calldataload_being_set_by_parent() { let mut db = Db::new(); db.add_accounts(vec![(callee_address, callee_account.clone())]); - let mut cache = Cache::default(); - cache.add_account(&callee_address, &callee_account); + let mut cache = CacheDB::default(); + cache::insert_account(&mut cache, callee_address, callee_account); let mut vm = new_vm_with_ops_addr_bal_db( ops_to_bytecode(&caller_ops).unwrap(), @@ -2601,8 +2601,8 @@ fn returndatacopy_being_set_by_parent() { let mut db = Db::new(); db.add_accounts(vec![(callee_address, callee_account.clone())]); - let mut cache = Cache::default(); - cache.add_account(&callee_address, &callee_account); + let mut cache = CacheDB::default(); + cache::insert_account(&mut cache, callee_address, callee_account); let mut vm = new_vm_with_ops_addr_bal_db( ops_to_bytecode(&caller_ops).unwrap(), @@ -2644,7 +2644,7 @@ fn blockhash_op() { Address::default(), U256::MAX, db, - Cache::default(), + CacheDB::default(), ) .unwrap(); @@ -2712,7 +2712,7 @@ fn blockhash_block_number_not_from_recent_256() { Address::default(), U256::MAX, db, - Cache::default(), + CacheDB::default(), ) .unwrap(); @@ -2991,9 +2991,9 @@ fn sstore_op() { key.to_big_endian(&mut bytes); let key = H256::from(bytes); - let stored_value = vm.cache.get_storage_slot(sender_address, key).unwrap(); + let (storage_slot, _storage_slot_was_cold) = vm.access_storage_slot(sender_address, key); - assert_eq!(value, stored_value.current_value); + assert_eq!(value, storage_slot.current_value); } #[test] @@ -3415,8 +3415,8 @@ fn logs_from_multiple_callers() { let mut db = Db::new(); db.add_accounts(vec![(callee_address, callee_account.clone())]); - let mut cache = Cache::default(); - cache.add_account(&callee_address, &callee_account); + let mut cache = CacheDB::default(); + cache::insert_account(&mut cache, callee_address, callee_account); let mut vm = new_vm_with_ops_addr_bal_db( ops_to_bytecode(&caller_ops).unwrap(), @@ -3788,7 +3788,7 @@ fn create_happy_path() { sender_addr, sender_balance, Db::new(), - Cache::default(), + CacheDB::default(), ) .unwrap(); vm.current_call_frame_mut().unwrap().msg_sender = sender_addr; @@ -3803,15 +3803,12 @@ fn create_happy_path() { assert_eq!(word_to_address(returned_address), expected_address); // check the created account is correct - let new_account = vm - .cache - .get_account(word_to_address(returned_address)) - .unwrap(); + let new_account = cache::get_account(&vm.cache, &word_to_address(returned_address)).unwrap(); assert_eq!(new_account.info.balance, U256::from(value_to_transfer)); 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(); + let sender_account = cache::get_account(&vm.cache, &sender_addr).unwrap(); assert_eq!(sender_account.info.nonce, sender_nonce + 1); assert_eq!( sender_account.info.balance, @@ -3835,7 +3832,7 @@ fn cant_create_with_size_longer_than_max_code_size() { sender_addr, sender_balance, Db::new(), - Cache::default(), + CacheDB::default(), ) .unwrap(); vm.current_call_frame_mut().unwrap().msg_sender = sender_addr; @@ -3848,7 +3845,7 @@ fn cant_create_with_size_longer_than_max_code_size() { assert_eq!(create_return_value, U256::from(REVERT_FOR_CREATE)); // Check that the sender account is updated - let sender_account = vm.cache.get_account(sender_addr).unwrap(); + let sender_account = cache::get_account(&vm.cache, &sender_addr).unwrap(); assert_eq!(sender_account.info.nonce, sender_nonce); assert_eq!(sender_account.info.balance, sender_balance); } @@ -3869,7 +3866,7 @@ fn cant_create_on_static_contexts() { sender_addr, sender_balance, Db::new(), - Cache::default(), + CacheDB::default(), ) .unwrap(); vm.current_call_frame_mut().unwrap().msg_sender = sender_addr; @@ -3883,7 +3880,7 @@ fn cant_create_on_static_contexts() { assert_eq!(create_return_value, U256::from(REVERT_FOR_CREATE)); // Check that the sender account is updated - let sender_account = vm.cache.get_account(sender_addr).unwrap(); + let sender_account = cache::get_account(&vm.cache, &sender_addr).unwrap(); assert_eq!(sender_account.info.nonce, sender_nonce); assert_eq!(sender_account.info.balance, sender_balance); } @@ -3904,7 +3901,7 @@ fn cant_create_if_transfer_value_bigger_than_balance() { sender_addr, sender_balance, Db::new(), - Cache::default(), + CacheDB::default(), ) .unwrap(); vm.current_call_frame_mut().unwrap().msg_sender = sender_addr; @@ -3917,7 +3914,7 @@ fn cant_create_if_transfer_value_bigger_than_balance() { assert_eq!(create_return_value, U256::from(REVERT_FOR_CREATE)); // Check that the sender account is updated - let sender_account = vm.cache.get_account(sender_addr).unwrap(); + let sender_account = cache::get_account(&vm.cache, &sender_addr).unwrap(); assert_eq!(sender_account.info.nonce, sender_nonce); assert_eq!(sender_account.info.balance, sender_balance); } @@ -3951,7 +3948,7 @@ fn cant_create_if_sender_nonce_would_overflow() { assert_eq!(create_return_value, U256::from(REVERT_FOR_CREATE)); // Check that the sender account is updated - let sender_account = vm.cache.get_account(sender_addr).unwrap(); + let sender_account = cache::get_account(&vm.cache, &sender_addr).unwrap(); assert_eq!(sender_account.info.nonce, sender_nonce); assert_eq!(sender_account.info.balance, sender_balance); } @@ -4060,7 +4057,7 @@ fn create2_happy_path() { sender_addr, sender_balance, Db::new(), - Cache::default(), + CacheDB::default(), ) .unwrap(); vm.current_call_frame_mut().unwrap().msg_sender = sender_addr; @@ -4072,15 +4069,12 @@ fn create2_happy_path() { let returned_address = call_frame.stack.pop().unwrap(); assert_eq!(word_to_address(returned_address), expected_address); // check the created account is correct - let new_account = vm - .cache - .get_account(word_to_address(returned_address)) - .unwrap(); + let new_account = cache::get_account(&vm.cache, &word_to_address(returned_address)).unwrap(); assert_eq!(new_account.info.balance, U256::from(value)); 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(); + let sender_account = cache::get_account(&vm.cache, &sender_addr).unwrap(); assert_eq!(sender_account.info.nonce, sender_nonce + 1); assert_eq!(sender_account.info.balance, sender_balance - value); } @@ -4128,10 +4122,11 @@ fn caller_op() { Account::default().with_bytecode(ops_to_bytecode(&operations).unwrap()), )]); - let mut cache = Cache::default(); - cache.add_account( - &address_that_has_the_code, - &Account::default().with_bytecode(ops_to_bytecode(&operations).unwrap()), + let mut cache = CacheDB::default(); + cache::insert_account( + &mut cache, + address_that_has_the_code, + Account::default().with_bytecode(ops_to_bytecode(&operations).unwrap()), ); let env = Environment::default_from_address(caller); @@ -4169,10 +4164,11 @@ fn origin_op() { Account::default().with_bytecode(ops_to_bytecode(&operations).unwrap()), )]); - let mut cache = Cache::default(); - cache.add_account( - &msg_sender, - &Account::default().with_bytecode(ops_to_bytecode(&operations).unwrap()), + let mut cache = CacheDB::default(); + cache::insert_account( + &mut cache, + msg_sender, + Account::default().with_bytecode(ops_to_bytecode(&operations).unwrap()), ); let env = Environment::default_from_address(msg_sender); @@ -4212,7 +4208,7 @@ fn balance_op() { Address::from_low_u64_be(address), U256::from(1234), Db::new(), - Cache::default(), + CacheDB::default(), ) .unwrap(); @@ -4237,10 +4233,11 @@ fn address_op() { Account::default().with_bytecode(ops_to_bytecode(&operations).unwrap()), )]); - let mut cache = Cache::default(); - cache.add_account( - &address_that_has_the_code, - &Account::default().with_bytecode(ops_to_bytecode(&operations).unwrap()), + let mut cache = CacheDB::default(); + cache::insert_account( + &mut cache, + address_that_has_the_code, + Account::default().with_bytecode(ops_to_bytecode(&operations).unwrap()), ); let env = Environment::default_from_address(Address::from_low_u64_be(42)); @@ -4280,10 +4277,11 @@ fn selfbalance_op() { .with_balance(balance), )]); - let mut cache = Cache::default(); - cache.add_account( - &address_that_has_the_code, - &Account::default() + let mut cache = CacheDB::default(); + cache::insert_account( + &mut cache, + address_that_has_the_code, + Account::default() .with_bytecode(ops_to_bytecode(&operations).unwrap()) .with_balance(balance), ); @@ -4324,10 +4322,11 @@ fn callvalue_op() { Account::default().with_bytecode(ops_to_bytecode(&operations).unwrap()), )]); - let mut cache = Cache::default(); - cache.add_account( - &address_that_has_the_code, - &Account::default().with_bytecode(ops_to_bytecode(&operations).unwrap()), + let mut cache = CacheDB::default(); + cache::insert_account( + &mut cache, + address_that_has_the_code, + Account::default().with_bytecode(ops_to_bytecode(&operations).unwrap()), ); let env = Environment::default_from_address(Address::from_low_u64_be(42)); @@ -4365,10 +4364,11 @@ fn codesize_op() { Account::default().with_bytecode(ops_to_bytecode(&operations).unwrap()), )]); - let mut cache = Cache::default(); - cache.add_account( - &address_that_has_the_code, - &Account::default().with_bytecode(ops_to_bytecode(&operations).unwrap()), + let mut cache = CacheDB::default(); + cache::insert_account( + &mut cache, + address_that_has_the_code, + Account::default().with_bytecode(ops_to_bytecode(&operations).unwrap()), ); let env = Environment::default_from_address(Address::from_low_u64_be(42)); @@ -4404,10 +4404,11 @@ fn gasprice_op() { Account::default().with_bytecode(ops_to_bytecode(&operations).unwrap()), )]); - let mut cache = Cache::default(); - cache.add_account( - &address_that_has_the_code, - &Account::default().with_bytecode(ops_to_bytecode(&operations).unwrap()), + let mut cache = CacheDB::default(); + cache::insert_account( + &mut cache, + address_that_has_the_code, + Account::default().with_bytecode(ops_to_bytecode(&operations).unwrap()), ); let mut env = Environment::default_from_address(Address::from_low_u64_be(42)); @@ -4461,10 +4462,11 @@ fn codecopy_op() { Account::default().with_bytecode(ops_to_bytecode(&operations).unwrap()), )]); - let mut cache = Cache::default(); - cache.add_account( - &address_that_has_the_code, - &Account::default().with_bytecode(ops_to_bytecode(&operations).unwrap()), + let mut cache = CacheDB::default(); + cache::insert_account( + &mut cache, + address_that_has_the_code, + Account::default().with_bytecode(ops_to_bytecode(&operations).unwrap()), ); let env = Environment::default_from_address(Address::from_low_u64_be(42)); @@ -4699,7 +4701,7 @@ fn revert_sstore() { let mut vm = new_vm_with_ops(&operations).unwrap(); vm.current_call_frame_mut().unwrap().code_address = sender_address; - vm.cache.add_account(&sender_address, &Account::default()); + cache::insert_account(&mut vm.cache, sender_address, Account::default()); let mut current_call_frame = vm.call_frames.pop().unwrap(); diff --git a/crates/vm/vm.rs b/crates/vm/vm.rs index 78f58d251f..c45d809bd6 100644 --- a/crates/vm/vm.rs +++ b/crates/vm/vm.rs @@ -80,7 +80,7 @@ impl From for EvmState { cfg_if::cfg_if! { if #[cfg(feature = "levm")] { use ethrex_levm::{ - db::{Cache, Database as LevmDatabase}, + db::{CacheDB, Database as LevmDatabase}, errors::{TransactionReport, TxResult, VMError}, vm::VM, Environment, @@ -190,7 +190,7 @@ cfg_if::cfg_if! { tx.value(), tx.data().clone(), db, - Cache::default(), + CacheDB::default(), )?; vm.transact() From 2da9af5a93a3d5d47c96c8be0cddffe6543cc6e8 Mon Sep 17 00:00:00 2001 From: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Date: Thu, 28 Nov 2024 14:03:30 -0300 Subject: [PATCH 19/23] refactor(levm): safe `mod` implementation (#1338) --- crates/vm/levm/src/opcode_handlers/arithmetic.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/crates/vm/levm/src/opcode_handlers/arithmetic.rs b/crates/vm/levm/src/opcode_handlers/arithmetic.rs index 0b05f8ba24..9fd8c6c82f 100644 --- a/crates/vm/levm/src/opcode_handlers/arithmetic.rs +++ b/crates/vm/levm/src/opcode_handlers/arithmetic.rs @@ -115,13 +115,9 @@ impl VM { let dividend = current_call_frame.stack.pop()?; let divisor = current_call_frame.stack.pop()?; - if divisor.is_zero() { - current_call_frame.stack.push(U256::zero())?; - return Ok(OpcodeSuccess::Continue); - } - let remainder = dividend.checked_rem(divisor).ok_or(VMError::Internal( - InternalError::ArithmeticOperationDividedByZero, - ))?; // Cannot be zero bc if above; + + let remainder = dividend.checked_rem(divisor).unwrap_or_default(); + current_call_frame.stack.push(remainder)?; Ok(OpcodeSuccess::Continue) From e99e3a28dc793d6033aded65347109620ee40a47 Mon Sep 17 00:00:00 2001 From: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Date: Thu, 28 Nov 2024 14:08:14 -0300 Subject: [PATCH 20/23] feat(levm): add test dir summary (#1332) After the fork summary, a dir summary is added. *Example* image --- cmd/ef_tests/levm/Cargo.toml | 2 +- cmd/ef_tests/levm/deserialize.rs | 1 + cmd/ef_tests/levm/parser.rs | 7 +++-- cmd/ef_tests/levm/report.rs | 37 +++++++++++++++++++++++-- cmd/ef_tests/levm/runner/levm_runner.rs | 1 + cmd/ef_tests/levm/runner/revm_runner.rs | 1 + cmd/ef_tests/levm/types.rs | 1 + 7 files changed, 45 insertions(+), 5 deletions(-) diff --git a/cmd/ef_tests/levm/Cargo.toml b/cmd/ef_tests/levm/Cargo.toml index 6a553046ed..568128c8ae 100644 --- a/cmd/ef_tests/levm/Cargo.toml +++ b/cmd/ef_tests/levm/Cargo.toml @@ -20,7 +20,7 @@ spinoff = "0.8.0" thiserror = "2.0.3" clap = { version = "4.3", features = ["derive"] } clap_complete = "4.5.17" - +itertools = "0.13.0" revm = { version = "14.0.3", features = [ "serde", "std", diff --git a/cmd/ef_tests/levm/deserialize.rs b/cmd/ef_tests/levm/deserialize.rs index a1acfee2be..06c26bd053 100644 --- a/cmd/ef_tests/levm/deserialize.rs +++ b/cmd/ef_tests/levm/deserialize.rs @@ -230,6 +230,7 @@ impl<'de> Deserialize<'de> for EFTests { let ef_test = EFTest { name: test_name.to_owned().to_owned(), + dir: String::default(), _info: serde_json::from_value( test_data .get("_info") diff --git a/cmd/ef_tests/levm/parser.rs b/cmd/ef_tests/levm/parser.rs index 6cfb23f51b..f119581fc6 100644 --- a/cmd/ef_tests/levm/parser.rs +++ b/cmd/ef_tests/levm/parser.rs @@ -103,10 +103,13 @@ pub fn parse_ef_test_dir( let test_file = std::fs::File::open(test.path()).map_err(|err| { EFTestParseError::FailedToReadFile(format!("{:?}: {err}", test.path())) })?; - let test: EFTests = serde_json::from_reader(test_file).map_err(|err| { + let mut tests: EFTests = serde_json::from_reader(test_file).map_err(|err| { EFTestParseError::FailedToParseTestFile(format!("{:?} parse error: {err}", test.path())) })?; - directory_tests.extend(test.0); + for test in tests.0.iter_mut() { + test.dir = test_dir.file_name().into_string().unwrap(); + } + directory_tests.extend(tests.0); } Ok(directory_tests) } diff --git a/cmd/ef_tests/levm/report.rs b/cmd/ef_tests/levm/report.rs index 7ab2b5d70c..f205855826 100644 --- a/cmd/ef_tests/levm/report.rs +++ b/cmd/ef_tests/levm/report.rs @@ -7,6 +7,7 @@ use ethrex_levm::{ }; use ethrex_storage::{error::StoreError, AccountUpdate}; use ethrex_vm::SpecId; +use itertools::Itertools; use revm::primitives::{EVMError, ExecutionResult as RevmExecutionResult}; use serde::{Deserialize, Serialize}; use spinoff::{spinners::Dots, Color, Spinner}; @@ -189,7 +190,7 @@ pub fn summary_for_shell(reports: &[EFTestReport]) -> String { let total_run = reports.len(); let success_percentage = (total_passed as f64 / total_run as f64) * 100.0; format!( - "{} {}/{total_run} ({success_percentage:.2})\n\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n", + "{} {}/{total_run} ({success_percentage:.2})\n\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n\n\n{}\n", "Summary:".bold(), if total_passed == total_run { format!("{}", total_passed).green() @@ -208,6 +209,7 @@ pub fn summary_for_shell(reports: &[EFTestReport]) -> String { fork_summary_shell(reports, SpecId::CONSTANTINOPLE), fork_summary_shell(reports, SpecId::MERGE), fork_summary_shell(reports, SpecId::FRONTIER), + test_dir_summary_for_shell(reports), ) } @@ -237,6 +239,34 @@ fn fork_statistics(reports: &[EFTestReport], fork: SpecId) -> (usize, usize, f64 (fork_tests, fork_passed_tests, fork_success_percentage) } +pub fn test_dir_summary_for_shell(reports: &[EFTestReport]) -> String { + let mut test_dirs_summary = String::new(); + reports + .iter() + .into_group_map_by(|report| report.dir.clone()) + .iter() + .for_each(|(dir, reports)| { + let total_passed = reports.iter().filter(|report| report.passed()).count(); + let total_run = reports.len(); + let success_percentage = (total_passed as f64 / total_run as f64) * 100.0; + let test_dir_summary = format!( + "{}: {}/{} ({:.2}%)\n", + dir.bold(), + if total_passed == total_run { + format!("{}", total_passed).green() + } else if total_passed > 0 { + format!("{}", total_passed).yellow() + } else { + format!("{}", total_passed).red() + }, + total_run, + success_percentage + ); + test_dirs_summary.push_str(&test_dir_summary); + }); + test_dirs_summary +} + #[derive(Debug, Default, Clone)] pub struct EFTestsReport(pub Vec); @@ -270,6 +300,7 @@ impl Display for EFTestsReport { writeln!(f)?; writeln!(f, "{}", "Failed tests:".bold())?; writeln!(f)?; + writeln!(f, "{}", test_dir_summary_for_shell(&self.0))?; for report in self.0.iter() { if report.failed_vectors.is_empty() { continue; @@ -347,6 +378,7 @@ impl Display for EFTestsReport { #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct EFTestReport { pub name: String, + pub dir: String, pub test_hash: H256, pub fork: SpecId, pub skipped: bool, @@ -355,9 +387,10 @@ pub struct EFTestReport { } impl EFTestReport { - pub fn new(name: String, test_hash: H256, fork: SpecId) -> Self { + pub fn new(name: String, dir: String, test_hash: H256, fork: SpecId) -> Self { EFTestReport { name, + dir, test_hash, fork, ..Default::default() diff --git a/cmd/ef_tests/levm/runner/levm_runner.rs b/cmd/ef_tests/levm/runner/levm_runner.rs index 2c5a5779b1..fb0273b7e5 100644 --- a/cmd/ef_tests/levm/runner/levm_runner.rs +++ b/cmd/ef_tests/levm/runner/levm_runner.rs @@ -22,6 +22,7 @@ use std::{collections::HashMap, sync::Arc}; pub fn run_ef_test(test: &EFTest) -> Result { let mut ef_test_report = EFTestReport::new( test.name.clone(), + test.dir.clone(), test._info.generated_test_hash, test.fork(), ); diff --git a/cmd/ef_tests/levm/runner/revm_runner.rs b/cmd/ef_tests/levm/runner/revm_runner.rs index 73c2e61598..ebff7b3c31 100644 --- a/cmd/ef_tests/levm/runner/revm_runner.rs +++ b/cmd/ef_tests/levm/runner/revm_runner.rs @@ -365,6 +365,7 @@ pub fn _run_ef_test_revm(test: &EFTest) -> Result); #[derive(Debug)] pub struct EFTest { pub name: String, + pub dir: String, pub _info: EFTestInfo, pub env: EFTestEnv, pub post: EFTestPost, From 0303d4d80bf00741c2e8b278ccc7e95a2ed2ff1b Mon Sep 17 00:00:00 2001 From: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Date: Thu, 28 Nov 2024 14:16:33 -0300 Subject: [PATCH 21/23] fix(levm): minor fix in BYTE opcode handling (#1340) It is not right to default to `usize::MAX` when the incoming `U256` does not fit in `usize`. --- crates/vm/levm/src/opcode_handlers/bitwise_comparison.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/crates/vm/levm/src/opcode_handlers/bitwise_comparison.rs b/crates/vm/levm/src/opcode_handlers/bitwise_comparison.rs index c808124303..265432a7fb 100644 --- a/crates/vm/levm/src/opcode_handlers/bitwise_comparison.rs +++ b/crates/vm/levm/src/opcode_handlers/bitwise_comparison.rs @@ -167,7 +167,14 @@ impl VM { self.increase_consumed_gas(current_call_frame, gas_cost::BYTE)?; let op1 = current_call_frame.stack.pop()?; let op2 = current_call_frame.stack.pop()?; - let byte_index = op1.try_into().unwrap_or(usize::MAX); + let byte_index = match op1.try_into() { + Ok(byte_index) => byte_index, + Err(_) => { + // Index is out of bounds, then push 0 + current_call_frame.stack.push(U256::zero())?; + return Ok(OpcodeSuccess::Continue); + } + }; if byte_index < WORD_SIZE { let byte_to_push = WORD_SIZE From c9f6694dc280d0169faca3cbf866e8fd0249b786 Mon Sep 17 00:00:00 2001 From: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Date: Thu, 28 Nov 2024 14:18:37 -0300 Subject: [PATCH 22/23] fix(levm): `mulmod` implementation (#1337) - `mulmod` implementation is refactored to a less error-prone version. - The modular arithmetic was not being done correctly. --- .../vm/levm/src/opcode_handlers/arithmetic.rs | 35 +++++++------------ 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/crates/vm/levm/src/opcode_handlers/arithmetic.rs b/crates/vm/levm/src/opcode_handlers/arithmetic.rs index 9fd8c6c82f..4aaecec32d 100644 --- a/crates/vm/levm/src/opcode_handlers/arithmetic.rs +++ b/crates/vm/levm/src/opcode_handlers/arithmetic.rs @@ -5,7 +5,7 @@ use crate::{ opcode_handlers::bitwise_comparison::checked_shift_left, vm::VM, }; -use ethrex_core::{U256, U512}; +use ethrex_core::U256; // Arithmetic Operations (11) // Opcodes: ADD, SUB, MUL, DIV, SDIV, MOD, SMOD, ADDMOD, MULMOD, EXP, SIGNEXTEND @@ -190,31 +190,22 @@ impl VM { ) -> Result { self.increase_consumed_gas(current_call_frame, gas_cost::MULMOD)?; - let multiplicand = U512::from(current_call_frame.stack.pop()?); - let multiplier = U512::from(current_call_frame.stack.pop()?); - let divisor = U512::from(current_call_frame.stack.pop()?); - if divisor.is_zero() { + let multiplicand = current_call_frame.stack.pop()?; + let multiplier = current_call_frame.stack.pop()?; + let modulus = current_call_frame.stack.pop()?; + + if modulus.is_zero() { current_call_frame.stack.push(U256::zero())?; return Ok(OpcodeSuccess::Continue); } - let (product, overflow) = multiplicand.overflowing_mul(multiplier); - let mut remainder = product.checked_rem(divisor).ok_or(VMError::Internal( - InternalError::ArithmeticOperationDividedByZero, - ))?; // Cannot be zero bc if above - if overflow || remainder > divisor { - remainder = remainder.overflowing_sub(divisor).0; - } - let mut result = Vec::new(); - for byte in remainder.0.iter().take(4) { - let bytes = byte.to_le_bytes(); - result.extend_from_slice(&bytes); - } - // before reverse we have something like [120, 255, 0, 0....] - // after reverse we get the [0, 0, ...., 255, 120] which is the correct order for the little endian u256 - result.reverse(); - let remainder = U256::from(result.as_slice()); - current_call_frame.stack.push(remainder)?; + let multiplicand = multiplicand.checked_rem(modulus).unwrap_or_default(); + let multiplier = multiplier.checked_rem(modulus).unwrap_or_default(); + + let (product, _overflowed) = multiplicand.overflowing_mul(multiplier); + let product_mod = product.checked_rem(modulus).unwrap_or_default(); + + current_call_frame.stack.push(product_mod)?; Ok(OpcodeSuccess::Continue) } From 0941ec3a926564d82f7e201d5aa7582937126db2 Mon Sep 17 00:00:00 2001 From: Ivan Litteri <67517699+ilitteri@users.noreply.github.com> Date: Thu, 28 Nov 2024 14:19:32 -0300 Subject: [PATCH 23/23] fix(levm): `log`x non-compliance memory allocation (#1330) Resolves #1323 --- crates/vm/levm/src/memory.rs | 4 ++++ crates/vm/levm/tests/edge_case_tests.rs | 11 +++++++++++ 2 files changed, 15 insertions(+) diff --git a/crates/vm/levm/src/memory.rs b/crates/vm/levm/src/memory.rs index 995990ca82..0baf3ba216 100644 --- a/crates/vm/levm/src/memory.rs +++ b/crates/vm/levm/src/memory.rs @@ -66,6 +66,10 @@ impl Memory { } pub fn load_range(&mut self, offset: usize, size: usize) -> Result, VMError> { + if size == 0 { + return Ok(Vec::new()); + } + let size_to_load = offset.checked_add(size).ok_or(VMError::Internal( InternalError::ArithmeticOperationOverflow, ))?; diff --git a/crates/vm/levm/tests/edge_case_tests.rs b/crates/vm/levm/tests/edge_case_tests.rs index 50a5bdade6..57198efc18 100644 --- a/crates/vm/levm/tests/edge_case_tests.rs +++ b/crates/vm/levm/tests/edge_case_tests.rs @@ -251,3 +251,14 @@ fn test_non_compliance_extcodecopy_size_and_destoffset() { &U256::from(64) ); } + +#[test] +fn test_non_compliance_log() { + let mut vm = new_vm_with_bytecode(Bytes::copy_from_slice(&[95, 97, 89, 0, 160, 89])).unwrap(); + let mut current_call_frame = vm.call_frames.pop().unwrap(); + vm.execute(&mut current_call_frame); + assert_eq!( + current_call_frame.stack.stack.first().unwrap(), + &U256::zero() + ); +}