From 6cdb95791bc5f6f875435387773fe0171f700306 Mon Sep 17 00:00:00 2001 From: Bartosz Zawistowski <39065214+bzawisto@users.noreply.github.com> Date: Wed, 18 Sep 2024 11:23:33 +0200 Subject: [PATCH] core, rpcdaemon: move gas+funds bailout mechanism into evm (#2354) --- silkworm/core/execution/evm.cpp | 55 +++++++++++++++++++++++++----- silkworm/core/execution/evm.hpp | 9 +++-- silkworm/rpc/core/evm_executor.cpp | 40 +++------------------- 3 files changed, 58 insertions(+), 46 deletions(-) diff --git a/silkworm/core/execution/evm.cpp b/silkworm/core/execution/evm.cpp index 073f71bbb7..19ecd11a50 100644 --- a/silkworm/core/execution/evm.cpp +++ b/silkworm/core/execution/evm.cpp @@ -59,12 +59,12 @@ class DelegatingTracer : public evmone::Tracer { IntraBlockState& intra_block_state_; }; -EVM::EVM(const Block& block, IntraBlockState& state, const ChainConfig& config, bool gas_bailout) noexcept +EVM::EVM(const Block& block, IntraBlockState& state, const ChainConfig& config, bool bailout) noexcept : beneficiary{block.header.beneficiary}, block_{block}, state_{state}, config_{config}, - gas_bailout_{gas_bailout}, + bailout_{bailout}, evm1_{static_cast(evmc_create_evmone())} // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) {} @@ -100,8 +100,8 @@ evmc::Result EVM::create(const evmc_message& message) noexcept { evmc::Result res{EVMC_SUCCESS, message.gas, 0}; auto value{intx::be::load(message.value)}; - const auto have = state_.get_balance(message.sender); - if (!gas_bailout_ && have < value) { + const auto owned_funds = state_.get_balance(message.sender); + if (!bailout_ && owned_funds < value) { res.status_code = EVMC_INSUFFICIENT_BALANCE; for (auto tracer : tracers_) { @@ -150,7 +150,7 @@ evmc::Result EVM::create(const evmc_message& message) noexcept { state_.set_nonce(contract_addr, 1); } - transfer(state_, message.sender, contract_addr, value, gas_bailout_); + transfer(state_, message.sender, contract_addr, value, bailout_); const evmc_message deploy_message{ .kind = message.depth > 0 ? message.kind : EVMC_CALL, @@ -205,8 +205,8 @@ evmc::Result EVM::call(const evmc_message& message) noexcept { evmc::Result res{EVMC_SUCCESS, message.gas}; const auto value{intx::be::load(message.value)}; - const auto have = state_.get_balance(message.sender); - if (!gas_bailout_ && message.kind != EVMC_DELEGATECALL && have < value) { + const auto owned_funds = state_.get_balance(message.sender); + if (!bailout_ && message.kind != EVMC_DELEGATECALL && owned_funds < value) { res.status_code = EVMC_INSUFFICIENT_BALANCE; return res; } @@ -219,7 +219,7 @@ evmc::Result EVM::call(const evmc_message& message) noexcept { // https://github.com/ethereum/go-ethereum/blob/v1.9.25/core/vm/evm.go#L391 state_.touch(message.recipient); } else { - transfer(state_, message.sender, message.recipient, value, gas_bailout_); + transfer(state_, message.sender, message.recipient, value, bailout_); } } @@ -270,6 +270,45 @@ evmc::Result EVM::call(const evmc_message& message) noexcept { return res; } +CallResult EVM::deduct_entry_fees(const Transaction& txn) const { + if (!bailout_) { + const evmc_revision rev{revision()}; + const intx::uint256 base_fee_per_gas{block().header.base_fee_per_gas.value_or(0)}; + + // EIP-1559 normal gas cost + intx::uint256 required_funds; + if (txn.max_fee_per_gas > 0 || txn.max_priority_fee_per_gas > 0) { + // This method should be called after check (max_fee and base_fee) present in pre_check() method + const intx::uint256 effective_gas_price{txn.max_fee_per_gas >= base_fee_per_gas ? txn.effective_gas_price(base_fee_per_gas) + : txn.max_priority_fee_per_gas}; + required_funds = txn.gas_limit * effective_gas_price; + } else { + required_funds = 0; + } + + // EIP-4844 blob gas cost (calc_data_fee) + if (block().header.blob_gas_used && rev >= EVMC_CANCUN) { + // compute blob fee for eip-4844 data blobs if any + const intx::uint256 blob_gas_price{block().header.blob_gas_price().value_or(0)}; + required_funds += txn.total_blob_gas() * blob_gas_price; + } + + intx::uint512 maximum_cost = required_funds; + if (txn.type != TransactionType::kLegacy && txn.type != TransactionType::kAccessList) { + maximum_cost = txn.maximum_gas_cost(); + } + + const auto owned_funds = state_.get_balance(*txn.sender()); + if (owned_funds < maximum_cost + txn.value) { + std::string from = address_to_hex(*txn.sender()); + std::string msg = "insufficient funds for gas * price + value: address " + from + " have " + intx::to_string(owned_funds) + " want " + intx::to_string(maximum_cost + txn.value); + return {.status = EVMC_INSUFFICIENT_BALANCE, .error_message = msg}; + } + state_.subtract_from_balance(*txn.sender(), required_funds); + } + return {.status = EVMC_SUCCESS}; +} + evmc_result EVM::execute(const evmc_message& message, ByteView code, const evmc::bytes32* code_hash) noexcept { const evmc_revision rev{revision()}; if (exo_evm) { diff --git a/silkworm/core/execution/evm.hpp b/silkworm/core/execution/evm.hpp index 86db1c950c..8ee304afe6 100644 --- a/silkworm/core/execution/evm.hpp +++ b/silkworm/core/execution/evm.hpp @@ -33,6 +33,8 @@ #include #include +#include "silkworm/core/types/address.hpp" + namespace silkworm { struct CallResult { @@ -40,6 +42,7 @@ struct CallResult { uint64_t gas_left{0}; uint64_t gas_refund{0}; Bytes data; + std::string error_message; }; class EvmTracer { @@ -92,7 +95,7 @@ class EVM { EVM(const EVM&) = delete; EVM& operator=(const EVM&) = delete; - EVM(const Block& block, IntraBlockState& state, const ChainConfig& config, bool gas_bailout = false) noexcept; + EVM(const Block& block, IntraBlockState& state, const ChainConfig& config, bool bailout = false) noexcept; ~EVM(); @@ -120,6 +123,8 @@ class EVM { gsl::not_null transfer{standard_transfer}; + CallResult deduct_entry_fees(const Transaction& txn) const; + private: friend class EvmHost; @@ -138,7 +143,7 @@ class EVM { const Block& block_; IntraBlockState& state_; const ChainConfig& config_; - bool gas_bailout_; + bool bailout_; const Transaction* txn_{nullptr}; std::vector block_hashes_{}; EvmTracers tracers_; diff --git a/silkworm/rpc/core/evm_executor.cpp b/silkworm/rpc/core/evm_executor.cpp index 2b9c9d5c5d..b60052fdc9 100644 --- a/silkworm/rpc/core/evm_executor.cpp +++ b/silkworm/rpc/core/evm_executor.cpp @@ -260,46 +260,14 @@ ExecutionResult EVMExecutor::call( const intx::uint128 g0{protocol::intrinsic_gas(txn, rev)}; SILKWORM_ASSERT(g0 <= UINT64_MAX); // true due to the precondition (transaction must be valid) - const auto pre_check_result = pre_check(evm, txn, base_fee_per_gas, g0); - if (pre_check_result) { + if (const auto pre_check_result = pre_check(evm, txn, base_fee_per_gas, g0)) { Bytes data{}; return {std::nullopt, txn.gas_limit, data, pre_check_result->pre_check_error, pre_check_result->pre_check_error_code}; } - if (!gas_bailout) { - // EIP-1559 normal gas cost - intx::uint256 want; - if (txn.max_fee_per_gas > 0 || txn.max_priority_fee_per_gas > 0) { - // This method should be called after check (max_fee and base_fee) present in pre_check() method - const intx::uint256 effective_gas_price{txn.max_fee_per_gas >= base_fee_per_gas ? txn.effective_gas_price(base_fee_per_gas) - : txn.max_priority_fee_per_gas}; - want = txn.gas_limit * effective_gas_price; - } else { - want = 0; - } - - // EIP-4844 blob gas cost (calc_data_fee) - if (evm.block().header.blob_gas_used && rev >= EVMC_CANCUN) { - // compute blob fee for eip-4844 data blobs if any - const intx::uint256 blob_gas_price{evm.block().header.blob_gas_price().value_or(0)}; - want += txn.total_blob_gas() * blob_gas_price; - } - - intx::uint512 max_want = want; - if (txn.type != silkworm::TransactionType::kLegacy && txn.type != silkworm::TransactionType::kAccessList) { - max_want = txn.maximum_gas_cost(); - } - - const auto have = ibs_state_.get_balance(*txn.sender()); - if (have < max_want + txn.value) { - Bytes data{}; - std::string from = address_to_hex(*txn.sender()); - std::string msg = "insufficient funds for gas * price + value: address " + from + " have " + intx::to_string(have) + " want " + intx::to_string(max_want + txn.value); - return {std::nullopt, txn.gas_limit, data, msg, PreCheckErrorCode::kInsufficientFunds}; - } - ibs_state_.subtract_from_balance(*txn.sender(), want); + if (const auto result = evm.deduct_entry_fees(txn); result.status != EVMC_SUCCESS) { + return {std::nullopt, txn.gas_limit, {}, result.error_message, PreCheckErrorCode::kInsufficientFunds}; } - if (txn.to.has_value()) { ibs_state_.access_account(*txn.to); // EVM itself increments the nonce for contract creation @@ -312,7 +280,7 @@ ExecutionResult EVMExecutor::call( } } - silkworm::CallResult result; + CallResult result; try { SILK_DEBUG << "EVMExecutor::call execute on EVM txn: " << &txn << " g0: " << static_cast(g0) << " start"; result = evm.execute(txn, txn.gas_limit - static_cast(g0));