Skip to content

Commit

Permalink
Handle validation & execution by processor
Browse files Browse the repository at this point in the history
  • Loading branch information
Bartosz Zawistowski committed Oct 1, 2024
1 parent 4378224 commit 95e8c9c
Show file tree
Hide file tree
Showing 21 changed files with 257 additions and 209 deletions.
39 changes: 0 additions & 39 deletions silkworm/core/execution/evm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -270,45 +270,6 @@ 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
2 changes: 0 additions & 2 deletions silkworm/core/execution/evm.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,6 @@ class EVM {

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

CallResult deduct_entry_fees(const Transaction& txn) const;

private:
friend class EvmHost;

Expand Down
3 changes: 1 addition & 2 deletions silkworm/core/execution/execution.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,7 @@ namespace silkworm {
if (!rule_set) {
return ValidationResult::kUnknownProtocolRuleSet;
}
constexpr auto kBailout = false;
ExecutionProcessor processor{block, *rule_set, state, chain_config, kBailout};
ExecutionProcessor processor{block, *rule_set, state, chain_config};

if (const ValidationResult res = processor.execute_block(receipts); res != ValidationResult::kOk) {
return res;
Expand Down
3 changes: 1 addition & 2 deletions silkworm/core/execution/execution_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ namespace silkworm {

static constexpr auto kMiner{0x5a0b54d5dc17e0aadc383d2db43b0a0d3e029c4c_address};
static constexpr auto kSender{0xb685342b8c54347aad148e1f22eff3eb3eb29391_address};
static constexpr auto kBailout = false;

TEST_CASE("Execute two blocks") {
// ---------------------------------------
Expand Down Expand Up @@ -166,7 +165,7 @@ TEST_CASE("Execute block with tracing") {
std::vector<Receipt> receipts;
const auto rule_set{protocol::rule_set_factory(chain_config)};
REQUIRE(rule_set);
ExecutionProcessor processor{block, *rule_set, state, chain_config, kBailout};
ExecutionProcessor processor{block, *rule_set, state, chain_config};

BlockTracer block_tracer{};
processor.evm().add_tracer(block_tracer);
Expand Down
70 changes: 64 additions & 6 deletions silkworm/core/execution/processor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@
namespace silkworm {

ExecutionProcessor::ExecutionProcessor(const Block& block, protocol::RuleSet& rule_set, State& state,
const ChainConfig& config, bool bailout)
: state_{state}, rule_set_{rule_set}, evm_{block, state_, config, bailout} {
const ChainConfig& config)
: state_{state}, rule_set_{rule_set}, evm_{block, state_, config} {
evm_.beneficiary = rule_set.get_beneficiary(block.header);
evm_.transfer = rule_set.transfer_func();
}
Expand Down Expand Up @@ -81,7 +81,7 @@ void ExecutionProcessor::execute_transaction(const Transaction& txn, Receipt& re

const CallResult vm_res{evm_.execute(txn, txn.gas_limit - static_cast<uint64_t>(g0))};

const uint64_t gas_used{txn.gas_limit - refund_gas(txn, vm_res.gas_left, vm_res.gas_refund)};
const uint64_t gas_used{txn.gas_limit - refund_gas(txn, effective_gas_price, vm_res.gas_left, vm_res.gas_refund)};

// award the fee recipient
const intx::uint256 amount{txn.priority_fee_per_gas(base_fee_per_gas) * gas_used};
Expand Down Expand Up @@ -110,11 +110,70 @@ void ExecutionProcessor::execute_transaction(const Transaction& txn, Receipt& re
std::swap(receipt.logs, state_.logs());
}

CallResult ExecutionProcessor::call_with_evm(const Transaction& txn, EVM& call_evm, bool bailout, bool refund) noexcept {
const std::optional sender{txn.sender()};

SILKWORM_ASSERT(protocol::validate_call_precheck(txn, call_evm) == ValidationResult::kOk);
SILKWORM_ASSERT(protocol::validate_call_funds(txn, call_evm, state_.get_balance(*txn.sender()), bailout) == ValidationResult::kOk);

const BlockHeader& header{call_evm.block().header};
const intx::uint256 base_fee_per_gas{header.base_fee_per_gas.value_or(0)};

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};
state_.access_account(*sender);

if (txn.to) {
state_.access_account(*txn.to);
// EVM itself increments the nonce for contract creation
state_.set_nonce(*sender, state_.get_nonce(*txn.sender()) + 1);
}

for (const AccessListEntry& ae : txn.access_list) {
state_.access_account(ae.account);
for (const evmc::bytes32& key : ae.storage_keys) {
state_.access_storage(ae.account, key);
}
}

if (!bailout) {
const intx::uint256 required_funds = protocol::compute_call_cost(txn, effective_gas_price, call_evm);
state_.subtract_from_balance(*txn.sender(), required_funds);
}
const intx::uint128 g0{protocol::intrinsic_gas(txn, call_evm.revision())};
const auto result = call_evm.execute(txn, txn.gas_limit - static_cast<uint64_t>(g0));

uint64_t gas_left{result.gas_left};
uint64_t gas_used{txn.gas_limit - result.gas_left};

if (refund && !bailout) {
gas_used = txn.gas_limit - refund_gas(txn, effective_gas_price, result.gas_left, result.gas_refund);
gas_left = txn.gas_limit - gas_used;
}

// Reward the fee recipient
const intx::uint256 priority_fee_per_gas{txn.max_fee_per_gas >= base_fee_per_gas ? txn.priority_fee_per_gas(base_fee_per_gas)
: txn.max_priority_fee_per_gas};

state_.add_to_balance(call_evm.beneficiary, priority_fee_per_gas * gas_used);

for (auto& tracer : call_evm.tracers()) {
tracer.get().on_reward_granted(result, state_);
}
state_.finalize_transaction(call_evm.revision());

return {result.status, gas_left, gas_used, result.data, result.error_message};
}

void ExecutionProcessor::reset() {
state_.clear_journal_and_substate();
}

uint64_t ExecutionProcessor::available_gas() const noexcept {
return evm_.block().header.gas_limit - cumulative_gas_used_;
}

uint64_t ExecutionProcessor::refund_gas(const Transaction& txn, uint64_t gas_left, uint64_t gas_refund) noexcept {
uint64_t ExecutionProcessor::refund_gas(const Transaction& txn, const intx::uint256& effective_gas_price, uint64_t gas_left, uint64_t gas_refund) noexcept {
const evmc_revision rev{evm_.revision()};

const uint64_t max_refund_quotient{rev >= EVMC_LONDON ? protocol::kMaxRefundQuotientLondon
Expand All @@ -123,8 +182,6 @@ uint64_t ExecutionProcessor::refund_gas(const Transaction& txn, uint64_t gas_lef
uint64_t refund = std::min(gas_refund, max_refund);
gas_left += refund;

const intx::uint256 base_fee_per_gas{evm_.block().header.base_fee_per_gas.value_or(0)};
const intx::uint256 effective_gas_price{txn.effective_gas_price(base_fee_per_gas)};
state_.add_to_balance(*txn.sender(), gas_left * effective_gas_price);

return gas_left;
Expand All @@ -142,6 +199,7 @@ ValidationResult ExecutionProcessor::execute_block_no_post_validation(std::vecto

receipts.resize(block.transactions.size());
auto receipt_it{receipts.begin()};

for (const auto& txn : block.transactions) {
const ValidationResult err{protocol::validate_transaction(txn, state_, available_gas())};
if (err != ValidationResult::kOk) {
Expand Down
8 changes: 6 additions & 2 deletions silkworm/core/execution/processor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,16 @@ class ExecutionProcessor {
ExecutionProcessor(const ExecutionProcessor&) = delete;
ExecutionProcessor& operator=(const ExecutionProcessor&) = delete;

ExecutionProcessor(const Block& block, protocol::RuleSet& rule_set, State& state, const ChainConfig& config, bool bailout);
ExecutionProcessor(const Block& block, protocol::RuleSet& rule_set, State& state, const ChainConfig& config);

/**
* Execute a transaction, but do not write to the DB yet.
* Precondition: transaction must be valid.
*/
void execute_transaction(const Transaction& txn, Receipt& receipt) noexcept;

CallResult call_with_evm(const Transaction& txn, EVM& evm, bool bailout, bool refund) noexcept;

//! \brief Execute the block.
//! \remarks Warning: This method does not verify state root; pre-Byzantium receipt root isn't validated either.
//! \pre RuleSet's validate_block_header & pre_validate_block_body must return kOk.
Expand All @@ -53,6 +55,8 @@ class ExecutionProcessor {

EVM& evm() noexcept { return evm_; }
const EVM& evm() const noexcept { return evm_; }
IntraBlockState& get_ibs_state() { return state_; }
void reset();

private:
/**
Expand All @@ -68,7 +72,7 @@ class ExecutionProcessor {
//! \brief Notify the registered tracers at the end of block execution.
void notify_block_execution_end(const Block& block);

uint64_t refund_gas(const Transaction& txn, uint64_t gas_left, uint64_t gas_refund) noexcept;
uint64_t refund_gas(const Transaction& txn, const intx::uint256& effective_gas_price, uint64_t gas_left, uint64_t gas_refund) noexcept;

uint64_t cumulative_gas_used_{0};
IntraBlockState state_;
Expand Down
12 changes: 5 additions & 7 deletions silkworm/core/execution/processor_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@

namespace silkworm {

static constexpr auto kBailout = false;

TEST_CASE("Zero gas price") {
Block block{};
block.header.number = 2'687'232;
Expand All @@ -47,7 +45,7 @@ TEST_CASE("Zero gas price") {

InMemoryState state;
auto rule_set{protocol::rule_set_factory(kMainnetConfig)};
ExecutionProcessor processor{block, *rule_set, state, kMainnetConfig, kBailout};
ExecutionProcessor processor{block, *rule_set, state, kMainnetConfig};

Receipt receipt;
processor.execute_transaction(txn, receipt);
Expand Down Expand Up @@ -87,7 +85,7 @@ TEST_CASE("No refund on error") {

InMemoryState state;
auto rule_set{protocol::rule_set_factory(kMainnetConfig)};
ExecutionProcessor processor{block, *rule_set, state, kMainnetConfig, kBailout};
ExecutionProcessor processor{block, *rule_set, state, kMainnetConfig};

Transaction txn{};
txn.nonce = nonce;
Expand Down Expand Up @@ -181,7 +179,7 @@ TEST_CASE("Self-destruct") {

InMemoryState state;
auto rule_set{protocol::rule_set_factory(kMainnetConfig)};
ExecutionProcessor processor{block, *rule_set, state, kMainnetConfig, kBailout};
ExecutionProcessor processor{block, *rule_set, state, kMainnetConfig};

processor.evm().state().add_to_balance(originator, kEther);
processor.evm().state().set_code(caller_address, caller_code);
Expand Down Expand Up @@ -329,7 +327,7 @@ TEST_CASE("Out of Gas during account re-creation") {
txn.set_sender(caller);

auto rule_set{protocol::rule_set_factory(kMainnetConfig)};
ExecutionProcessor processor{block, *rule_set, state, kMainnetConfig, kBailout};
ExecutionProcessor processor{block, *rule_set, state, kMainnetConfig};
processor.evm().state().add_to_balance(caller, kEther);

Receipt receipt;
Expand Down Expand Up @@ -372,7 +370,7 @@ TEST_CASE("Empty suicide beneficiary") {
InMemoryState state;

auto rule_set{protocol::rule_set_factory(kMainnetConfig)};
ExecutionProcessor processor{block, *rule_set, state, kMainnetConfig, kBailout};
ExecutionProcessor processor{block, *rule_set, state, kMainnetConfig};
processor.evm().state().add_to_balance(caller, kEther);

Receipt receipt;
Expand Down
3 changes: 1 addition & 2 deletions silkworm/core/protocol/blockchain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,7 @@ ValidationResult Blockchain::insert_block(Block& block, bool check_state_root) {
}

ValidationResult Blockchain::execute_block(const Block& block, bool check_state_root) {
constexpr auto kBailout = false;
ExecutionProcessor processor{block, *rule_set_, state_, config_, kBailout};
ExecutionProcessor processor{block, *rule_set_, state_, config_};
processor.evm().state_pool = state_pool;
processor.evm().exo_evm = exo_evm;

Expand Down
76 changes: 76 additions & 0 deletions silkworm/core/protocol/validation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,82 @@ ValidationResult pre_validate_transactions(const Block& block, const ChainConfig
return ValidationResult::kOk;
}

ValidationResult validate_call_precheck(const Transaction& txn, const EVM& evm) noexcept {
const std::optional sender{txn.sender()};
if (!sender) {
return ValidationResult::kInvalidSignature;
}

if (evm.revision() >= EVMC_LONDON) {
if (txn.max_fee_per_gas > 0 || txn.max_priority_fee_per_gas > 0) {
if (txn.max_fee_per_gas < txn.max_priority_fee_per_gas) {
return ValidationResult::kMaxPriorityFeeGreaterThanMax;
}

if (txn.max_fee_per_gas < evm.block().header.base_fee_per_gas) {
return ValidationResult::kMaxFeeLessThanBase;
}
}
} else {
if (txn.type != silkworm::TransactionType::kLegacy && txn.type != silkworm::TransactionType::kAccessList) {
return ValidationResult::kUnsupportedTransactionType;
}
}

if (evm.revision() >= EVMC_CANCUN) {
if (!evm.block().header.excess_blob_gas) {
return ValidationResult::kWrongBlobGasUsed;
}
}

const intx::uint128 g0{protocol::intrinsic_gas(txn, evm.revision())};
assert(g0 <= UINT64_MAX); // true due to the precondition (transaction must be valid)

if (txn.gas_limit < g0) {
return ValidationResult::kIntrinsicGas;
}

return ValidationResult::kOk;
}

ValidationResult validate_call_funds(const Transaction& txn, const EVM& evm, const intx::uint256& owned_funds, bool bailout) noexcept {
if (!bailout) {
const intx::uint256 base_fee{evm.block().header.base_fee_per_gas.value_or(0)};
const intx::uint256 effective_gas_price{txn.max_fee_per_gas >= evm.block().header.base_fee_per_gas ? txn.effective_gas_price(base_fee)
: txn.max_priority_fee_per_gas};

const auto required_funds = compute_call_cost(txn, effective_gas_price, evm);
intx::uint512 maximum_cost = required_funds;
if (txn.type != TransactionType::kLegacy && txn.type != TransactionType::kAccessList) {
maximum_cost = txn.maximum_gas_cost();
}
if (owned_funds < maximum_cost + txn.value) {
return ValidationResult::kInsufficientFunds;
}
}
return ValidationResult::kOk;
}

intx::uint256 compute_call_cost(const Transaction& txn, const intx::uint256& effective_gas_price, const EVM& evm) {
// 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
required_funds = txn.gas_limit * effective_gas_price;
} else {
required_funds = 0;
}

// EIP-4844 blob gas cost (calc_data_fee)
if (evm.block().header.blob_gas_used && evm.revision() >= 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)};
required_funds += txn.total_blob_gas() * blob_gas_price;
}

return required_funds;
}

intx::uint256 expected_base_fee_per_gas(const BlockHeader& parent) {
if (!parent.base_fee_per_gas) {
return kInitialBaseFee;
Expand Down
7 changes: 7 additions & 0 deletions silkworm/core/protocol/validation.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

#include <evmc/evmc.h>

#include <silkworm/core/execution/evm.hpp>
#include <silkworm/core/state/intra_block_state.hpp>
#include <silkworm/core/types/block.hpp>
#include <silkworm/core/types/transaction.hpp>
Expand Down Expand Up @@ -130,6 +131,12 @@ namespace protocol {
ValidationResult validate_transaction(const Transaction& txn, const IntraBlockState& state,
uint64_t available_gas) noexcept;

ValidationResult validate_call_precheck(const Transaction& txn, const EVM& evm) noexcept;

ValidationResult validate_call_funds(const Transaction& txn, const EVM& evm, const intx::uint256& owned_funds, bool bailout) noexcept;

intx::uint256 compute_call_cost(const Transaction& txn, const intx::uint256& effective_gas_price, const EVM& evm);

//! \see EIP-1559: Fee market change for ETH 1.0 chain
intx::uint256 expected_base_fee_per_gas(const BlockHeader& parent);

Expand Down
Loading

0 comments on commit 95e8c9c

Please sign in to comment.