Etherlink: Remove maximum gas limit condition
TODO
-
add item in CHANGELOG -
fix remaining tests -
update commit messages if required
What
This MR removes the maximum execution gas limit validation that rejects transactions with over-estimated gas limit.
Part of L2-18
Why
Etherlink transactions have two distinct fee components:
- Inclusion fees (DA fees) - Cost based on transaction data size
- Execution fees - Cost based on computational work performed
The estimateGas RPC returns the sum of both fee types for any given transaction.
As demand increases and more execution gas is used in a shorter time, Etherlink increases the gas price to increase the execution fee but not the inclusion fee. https://docs.etherlink.com/network/fees/#execution-fee
Current issue: The validation logic in validate.ml valides execution fees as follows:
tx_execution_fees = gas_limit(tx) - inclusion_fees(tx)
validate (tx_execution_fees <= MAXIMUM_GAS_LIMIT)
where:
-
inclusion_fees(tx)= exact inclusion fees required (source) -
MAXIMUM_GAS_LIMIT= 30M (source) -
gas_limit(tx)= user-provided gas limit
Note: here for the sake of simplicity we use MAXIMUM_GAS_LIMIT but it would be MAXIMUM_GAS_PER_TRANSACTION if provided when configuring the kernel.
Users commonly over-estimate gas limits to ensure their transactions succeed. However, this creates an unintended consequence as over-estimation inflates the calculated inclusion fees, causing valid transactions to be rejected.
Notably, when deploying substantial contracts:
- Inclusion fees become significant
- Even modest percentage margins cause large absolute increases
Example
Here is an example where the user relies on a 150% over-approximation of the gas estimation and hits the maximum allowed execution fee.
| Component | Actual Need | User Estimate (150% over-approximation) |
|---|---|---|
| Gas limit | 60M | 90M |
| Inclusion fees | 55M | 55M (unchanged) |
| Execution fees | 5M | 35M |
How
- Remove execution fee maximum validation in evm node (
validate.ml) but keeps inclusion fee minimum validation. - Eliminate the same validation in kernel (
blocks.rs). - Update
can_fit_in_rebootin kernel (blocks.rs) to bound the gas limit provided using evmlimits. - Bound the gas limit in
apply.rsusing evmlimits.
Why I think it is safe
- Inclusion fee validation ensures gas covers minimum required data costs
- The
can_fit_in_rebootwill still be operating as expected: asking for a reboot in case of high gas limit, as it is currently done. - Applying a transaction is still capped at
MAXIMUM_GAS_LIMIT(ormaximum_gas_per_transactionif provided) viamin(gas, limits.maximum_gas_limit)(source)
This should ensure no transaction can actually consume more than 30M (or maximum_gas_per_transaction if provided) gas during execution, regardless of the user-provided gas limit.
Manual testing
1. Test relaxed maximum gas limit validation
- Build the kernel and start sandbox EVM node:
make -f etherlink.mk EVM_KERNEL_FEATURES=debug build && make octez-evm-node && \
TEZOS_EVENTS_CONFIG="file-descriptor-stdout://?section-prefix=evm_node:debug&colors=true&advertise-levels=true&format=pp-short" \
./octez-evm-node run sandbox --network testnet --init-from-snapshot \
--data-dir working-dir/sandbox --config-file working-dir/sandbox-config.json \
--fund 0x9b49c988b5817Be31DfB00F7a5a4671772dCce2B --kernel evm_kernel.wasm \
--kernel-verbosity debug -v &> debug.log
- Send transaction with excessive gas limit:
cast send --rpc-url 127.0.0.1:8545 --gas-limit 60000000 --private-key 0x9722f6cc9ff938e63f8ccb74c3daa6b45837e5c5e3835ac08c44c50ab5f39dc0 0x6ce4d79d4E77402e1ef3417Fdda433aA744C6e1c
blockHash 0x26a900d2288bb82a1eb657abef813503b70aa8bc99d13039222f1c1867bab324
blockNumber 19512291
contractAddress
cumulativeGasUsed 621000
effectiveGasPrice 1000000000
from 0x6ce4d79d4E77402e1ef3417Fdda433aA744C6e1c
gasUsed 621000
logs []
logsBloom 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
root
status 1 (success)
transactionHash 0x0f1be52921fbcb4a82db0ad6151f8e3d44be60f2dff961995630693662ecee60
transactionIndex 0
type 2
blobGasPrice
blobGasUsed
to 0x6ce4d79d4E77402e1ef3417Fdda433aA744C6e1c
- Transaction should be confirmed
$ grep -B1 confirmed debug.log | grep -A1 0f1be52921fbcb
May 31 14:07:56.146 DEBUG │ transaction 0f1be52921fbcb4a82db0ad6151f8e3d44be60f2dff961995630693662ecee60
May 31 14:07:56.146 DEBUG │ confirmed
2. Test out of gas still applying
- Deploy the following
SimpleComputeBurner.solcontract
$ solc --bin --abi SimpleComputeBurner.sol -o build/
Compiler run successful. Artifact(s) can be found in directory "build/".
$ cast send \
--private-key 0x9722f6cc9ff938e63f8ccb74c3daa6b45837e5c5e3835ac08c44c50ab5f39dc0 \
--create $(cat build/SimpleComputeBurner.bin)
...
contractAddress 0x04A606985CA06343bE8caD2C9EE3624aa16E698E
...
- Run the following highly gas-consuming transaction
$ cast send --private-key 0x9722f6cc9ff938e63f8ccb74c3daa6b45837e5c5e3835ac08c44c50ab5f39dc0 0xdF161529E1fcf74CFf3342790E6Ca536b05b0a95 "burnGas(uint256)" 100000
Error: Failed to estimate gas: Max retries exceeded server returned an error response: error code -32005: The transaction failed: Error(OutOfGas).
$ cast send --gas-limit 1000000000 --private-key 0x9722f6cc9ff938e63f8ccb74c3daa6b45837e5c5e3835ac08c44c50ab5f39dc0 0xdF161529E1fcf74CFf3342790E6Ca536b05b0a95 "burnGas(uint256)" 100000
blockHash 0xb540126fcc2618c69b95b5a1324282bde31a7819a84993b5e63ecb40dc38d4b4
blockNumber 19513901
contractAddress
cumulativeGasUsed 30744000
effectiveGasPrice 1000000000
from 0x6ce4d79d4E77402e1ef3417Fdda433aA744C6e1c
gasUsed 30744000
logs []
logsBloom 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
root
status 0 (failed)
transactionHash 0x3968e747a31c6285079bb3a5b5b05083d10711929c3cea15689b5bff684a17ca
transactionIndex 0
type 2
blobGasPrice
blobGasUsed
to 0xdF161529E1fcf74CFf3342790E6Ca536b05b0a95
- In the EVM node logs you can note the transaction has been executed and fails because of an
OutOfGaserror.
May 31 16:31:10.440 DEBUG │ {"jsonrpc":"2.0","result":{"type":"0x2","chainId":"0x1f47b","hash":"0x2c2e24a1546133a70452ba2a8950425f853f89cf3dbbd801202fc7fcee823cce","nonce":"0x4b4ea","blockHash":"0xb190fbbf64132acf78fe46a5ff1b18cdf3f5dc1372140460b845c449be6651bc","blockNumber":"0x129c249","transactionIndex":"0x0","from":"0x6ce4d79d4e77402e1ef3417fdda433aa744c6e1c","to":"0xdf161529e1fcf74cff3342790e6ca536b05b0a95","value":"0x0","gas":"0x3b9aca00","maxFeePerGas":"0x77359401","maxPriorityFeePerGas":"0x1","accessList":[],"input":"0x4ad5d16f00000000000000000000000000000000000000000000000000000000000186a0","v":"0x1","r":"0x0b41dc7edac47d9edeaaf318bb7778a7012093950469ee6bcfc4825a9298d219","s":"0x7e3ca0238542109615633a12d5884b5e9662aac8f6f554f976c9dcc5418c55f1"},"id":20}
...
May 31 16:31:10.829 DEBUG │ {"jsonrpc":"2.0","error":{"code":-32003,"message":"The transaction failed:
May 31 16:31:10.829 DEBUG │ Error(OutOfGas)."},"id":21}