Skip to content

Commit

Permalink
core, rpcdaemon: move gas+funds bailout mechanism into evm (#2354)
Browse files Browse the repository at this point in the history
  • Loading branch information
bzawisto authored Sep 18, 2024
1 parent c0d6ded commit 6cdb957
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 46 deletions.
55 changes: 47 additions & 8 deletions silkworm/core/execution/evm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<evmone::VM*>(evmc_create_evmone())} // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast)
{}

Expand Down Expand Up @@ -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<intx::uint256>(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_) {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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<intx::uint256>(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;
}
Expand All @@ -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_);
}
}

Expand Down Expand Up @@ -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) {
Expand Down
9 changes: 7 additions & 2 deletions silkworm/core/execution/evm.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,16 @@
#include <silkworm/core/state/intra_block_state.hpp>
#include <silkworm/core/types/block.hpp>

#include "silkworm/core/types/address.hpp"

namespace silkworm {

struct CallResult {
evmc_status_code status{EVMC_SUCCESS};
uint64_t gas_left{0};
uint64_t gas_refund{0};
Bytes data;
std::string error_message;
};

class EvmTracer {
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -120,6 +123,8 @@ class EVM {

gsl::not_null<TransferFunc*> transfer{standard_transfer};

CallResult deduct_entry_fees(const Transaction& txn) const;

private:
friend class EvmHost;

Expand All @@ -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<evmc::bytes32> block_hashes_{};
EvmTracers tracers_;
Expand Down
40 changes: 4 additions & 36 deletions silkworm/rpc/core/evm_executor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<uint64_t>(g0) << " start";
result = evm.execute(txn, txn.gas_limit - static_cast<uint64_t>(g0));
Expand Down

0 comments on commit 6cdb957

Please sign in to comment.