Skip to content

Commit

Permalink
feat(levm): fix validation errors (part 2) and some extra things (#1345)
Browse files Browse the repository at this point in the history
**Motivation**
The main motivation is to fix validation related tests. Return a
validation exception everytime that is necessary (while being careful of
not returning them in cases in which they are not expected).
It also fixes/changes some things that I find along the way and I
consider appropriate changing.

<!-- Why does this pull request exist? What are its goals? -->

**Description**

<!-- A clear and concise general description of the changes this PR
introduces -->
- Adds function `add_intrinsic_gas()`
- Uses our gas_price as an effective gas price. So it works for
transactions Type 2 and 3.
- Implements blob gas cost
- Implements calculation of base fee per blob gas and it's associated
validation.
- Fixes `add_gas_with_max` for report
- It implements somewhat of a revert for create, but I'm not sure if it
is well done because cache's purpose has recently changed.

Diffs for `add_intrinsic_gas` and `validate_transaction` don't look very
good. I recommend to watch the code directly for those functions.

**End Result**
```bash
✓ Ethereum Foundation Tests: 1466 passed 2635 failed 4101 total run - 39:12
✓ Summary: 1466/4101 (35.75)

Cancun: 1356/3578 (37.90%)
Shanghai: 55/221 (24.89%)
Homestead: 0/17 (0.00%)
Istanbul: 0/34 (0.00%)
London: 19/39 (48.72%)
Byzantium: 0/33 (0.00%)
Berlin: 17/35 (48.57%)
Constantinople: 0/66 (0.00%)
Merge: 19/62 (30.65%)
Frontier: 0/16 (0.00%)
```

After mergin main into branch: 
```
Summary: 1471/4101 (35.87)
Cancun: 1361/3578 (38.04%)
```

<!-- Link to issues: Resolves #111, Resolves #222 -->

Closes #issue_number

---------

Co-authored-by: Ivan Litteri <[email protected]>
  • Loading branch information
JereSalo and ilitteri authored Dec 3, 2024
1 parent 94df083 commit c6acab4
Show file tree
Hide file tree
Showing 9 changed files with 294 additions and 147 deletions.
4 changes: 2 additions & 2 deletions cmd/ef_tests/levm/runner/levm_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::{
report::{EFTestReport, TestVector},
runner::{EFTestRunnerError, InternalError},
types::EFTest,
utils,
utils::{self, effective_gas_price},
};
use ethrex_core::{
types::{code_hash, AccountInfo},
Expand Down Expand Up @@ -94,7 +94,7 @@ pub fn prepare_vm_for_tx(vector: &TestVector, test: &EFTest) -> Result<VM, EFTes
prev_randao: test.env.current_random,
chain_id: U256::from(1729),
base_fee_per_gas: test.env.current_base_fee.unwrap_or_default(),
gas_price: tx.gas_price.unwrap_or_default(),
gas_price: effective_gas_price(test, &tx)?,
block_excess_blob_gas: test.env.current_excess_blob_gas,
block_blob_gas_used: None,
tx_blob_hashes: tx.blob_versioned_hashes.clone(),
Expand Down
21 changes: 4 additions & 17 deletions cmd/ef_tests/levm/runner/revm_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ use crate::{
levm_runner::{self, post_state_root},
EFTestRunnerError, InternalError,
},
types::{EFTest, EFTestTransaction},
utils::load_initial_state,
types::EFTest,
utils::{effective_gas_price, load_initial_state},
};
use bytes::Bytes;
use ethrex_core::{types::TxKind, Address, H256, U256};
use ethrex_core::{types::TxKind, Address, H256};
use ethrex_levm::{
errors::{TransactionReport, TxResult},
Account, StorageSlot,
Expand Down Expand Up @@ -90,19 +90,6 @@ 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,
Expand Down Expand Up @@ -148,7 +135,7 @@ pub fn prepare_revm_for_tx<'state>(
let tx_env = RevmTxEnv {
caller: tx.sender.0.into(),
gas_limit: tx.gas_limit.as_u64(),
gas_price: RevmU256::from_limbs(effective_gas_price(test, tx).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,
Expand Down
40 changes: 38 additions & 2 deletions cmd/ef_tests/levm/utils.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use crate::types::EFTest;
use ethrex_core::{types::Genesis, H256};
use crate::{
runner::{EFTestRunnerError, InternalError},
types::{EFTest, EFTestTransaction},
};
use ethrex_core::{types::Genesis, H256, U256};
use ethrex_storage::{EngineType, Store};
use ethrex_vm::{evm_state, EvmState};

Expand All @@ -17,3 +20,36 @@ pub fn load_initial_state(test: &EFTest) -> (EvmState, H256) {
genesis.get_block().header.compute_block_hash(),
)
}

// If gas price is not provided, calculate it with current base fee and priority fee
pub fn effective_gas_price(
test: &EFTest,
tx: &&EFTestTransaction,
) -> Result<U256, EFTestRunnerError> {
match tx.gas_price {
None => {
let current_base_fee = test
.env
.current_base_fee
.ok_or(EFTestRunnerError::Internal(
InternalError::FirstRunInternal("current_base_fee not found".to_string()),
))?;
let priority_fee = tx
.max_priority_fee_per_gas
.ok_or(EFTestRunnerError::Internal(
InternalError::FirstRunInternal(
"max_priority_fee_per_gas not found".to_string(),
),
))?;
let max_fee_per_gas = tx.max_fee_per_gas.ok_or(EFTestRunnerError::Internal(
InternalError::FirstRunInternal("max_fee_per_gas not found".to_string()),
))?;

Ok(std::cmp::min(
max_fee_per_gas,
current_base_fee + priority_fee,
))
}
Some(price) => Ok(price),
}
}
17 changes: 17 additions & 0 deletions crates/vm/levm/docs/validations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
## Transaction Validation

1. **GASLIMIT_PRICE_PRODUCT_OVERFLOW**: The product of gas limit and gas price is too high.
2. **INSUFFICIENT_ACCOUNT_FUNDS**: Sender does not have enough funds to pay for the gas.
3. **INSUFFICIENT_MAX_FEE_PER_GAS**: The max fee per gas is lower than the base fee per gas.
4. **INITCODE_SIZE_EXCEEDED**: The size of the initcode is too big.
5. **INTRINSIC_GAS_TOO_LOW**: The gas limit is lower than the intrinsic gas.
6. **NONCE_IS_MAX**: The nonce of the sender is at its maximum value.
7. **PRIORITY_GREATER_THAN_MAX_FEE_PER_GAS**: The priority fee is greater than the max fee per gas.
8. **SENDER_NOT_EOA**: The sender is not an EOA (it has code).
9. **GAS_ALLOWANCE_EXCEEDED**: The gas limit is higher than the block gas limit.
10. **INSUFFICIENT_MAX_FEE_PER_BLOB_GAS**: The max fee per blob gas is lower than the base fee per gas.
11. **TYPE_3_TX_ZERO_BLOBS**: The transaction has zero blobs.
12. **TYPE_3_TX_INVALID_BLOB_VERSIONED_HASH**: The blob versioned hash is invalid.
13. **TYPE_3_TX_PRE_FORK**: The transaction is a pre-cancun transaction.
14. **TYPE_3_TX_BLOB_COUNT_EXCEEDED**: The blob count is higher than the max allowed.
15. **TYPE_3_TX_CONTRACT_CREATION**: The type 3 transaction is a contract creation.
4 changes: 2 additions & 2 deletions crates/vm/levm/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ pub const MAX_BLOB_NUMBER_PER_BLOCK: usize = 6;

// Blob constants
pub const TARGET_BLOB_GAS_PER_BLOCK: U256 = U256([393216, 0, 0, 0]); // TARGET_BLOB_NUMBER_PER_BLOCK * GAS_PER_BLOB
pub const MIN_BASE_FEE_PER_BLOB_GAS: U256 = U256([1, 0, 0, 0]);
pub const BLOB_BASE_FEE_UPDATE_FRACTION: U256 = U256([3338477, 0, 0, 0]);
pub const MIN_BASE_FEE_PER_BLOB_GAS: u64 = 1;
pub const BLOB_BASE_FEE_UPDATE_FRACTION: u64 = 3338477;
pub const MAX_BLOB_COUNT: usize = 6;
pub const VALID_BLOB_PREFIXES: [u8; 2] = [0x01, 0x02];

Expand Down
14 changes: 12 additions & 2 deletions crates/vm/levm/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,8 +213,18 @@ pub struct TransactionReport {

impl TransactionReport {
/// Function to add gas to report without exceeding the maximum gas limit
pub fn add_gas_with_max(&mut self, gas: u64, max: u64) {
self.gas_used = self.gas_used.saturating_add(gas).min(max);
pub fn add_gas_with_max(&mut self, gas: u64, max: u64) -> Result<(), VMError> {
let new_gas_used = self
.gas_used
.checked_add(gas)
.ok_or(OutOfGasError::MaxGasLimitExceeded)?;

if new_gas_used > max {
return Err(VMError::OutOfGas(OutOfGasError::MaxGasLimitExceeded));
}

self.gas_used = new_gas_used;
Ok(())
}

pub fn is_success(&self) -> bool {
Expand Down
55 changes: 51 additions & 4 deletions crates/vm/levm/src/gas_cost.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,13 @@ pub const INIT_CODE_WORD_COST: U256 = U256([2, 0, 0, 0]);
pub const CODE_DEPOSIT_COST: U256 = U256([200, 0, 0, 0]);
pub const CREATE_BASE_COST: U256 = U256([32000, 0, 0, 0]);

// Calldata costs
pub const CALLDATA_COST_ZERO_BYTE: U256 = U256([4, 0, 0, 0]);
pub const CALLDATA_COST_NON_ZERO_BYTE: U256 = U256([16, 0, 0, 0]);

// Blob gas costs
pub const BLOB_GAS_PER_BLOB: U256 = U256([131072, 0, 0, 0]);

pub fn exp(exponent_bits: u64) -> Result<U256, OutOfGasError> {
let exponent_byte_size = (exponent_bits
.checked_add(7)
Expand Down Expand Up @@ -462,18 +469,18 @@ pub fn selfdestruct(address_was_cold: bool, account_is_empty: bool) -> Result<U2
Ok(gas_cost)
}

pub fn tx_calldata(calldata: &Bytes) -> Result<u64, OutOfGasError> {
pub fn tx_calldata(calldata: &Bytes) -> Result<U256, OutOfGasError> {
// This cost applies both for call and create
// 4 gas for each zero byte in the transaction data 16 gas for each non-zero byte in the transaction.
let mut calldata_cost: u64 = 0;
let mut calldata_cost: U256 = U256::zero();
for byte in calldata {
if *byte != 0 {
calldata_cost = calldata_cost
.checked_add(16)
.checked_add(CALLDATA_COST_NON_ZERO_BYTE)
.ok_or(OutOfGasError::GasUsedOverflow)?;
} else {
calldata_cost = calldata_cost
.checked_add(4)
.checked_add(CALLDATA_COST_ZERO_BYTE)
.ok_or(OutOfGasError::GasUsedOverflow)?;
}
}
Expand Down Expand Up @@ -720,3 +727,43 @@ pub fn staticcall(
.checked_add(dynamic_gas)
.ok_or(OutOfGasError::GasCostOverflow)?)
}

pub fn fake_exponential(factor: u64, numerator: u64, denominator: u64) -> Result<U256, VMError> {
let mut i = 1;
let mut output: u64 = 0;

// Initial multiplication: factor * denominator
let mut numerator_accum = factor
.checked_mul(denominator)
.ok_or(InternalError::ArithmeticOperationOverflow)?;

while numerator_accum > 0 {
// Safe addition to output
output = output
.checked_add(numerator_accum)
.ok_or(InternalError::ArithmeticOperationOverflow)?;

// Safe multiplication and division within loop
numerator_accum = numerator_accum
.checked_mul(numerator)
.ok_or(InternalError::ArithmeticOperationOverflow)?
.checked_div(
denominator
.checked_mul(i)
.ok_or(InternalError::ArithmeticOperationOverflow)?,
)
.ok_or(VMError::Internal(
InternalError::ArithmeticOperationOverflow,
))?;

i = i
.checked_add(1)
.ok_or(InternalError::ArithmeticOperationOverflow)?;
}

Ok(U256::from(
output
.checked_div(denominator)
.ok_or(InternalError::ArithmeticOperationOverflow)?,
))
}
Loading

0 comments on commit c6acab4

Please sign in to comment.