Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use evmone APIv2 on the side #2561

Merged
merged 7 commits into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions cmd/test/ethereum.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,27 @@ static const fs::path kBlockchainDir{"BlockchainTests"};

static const fs::path kTransactionDir{"TransactionTests"};

static const std::vector<fs::path> kSlowTests{
static const std::array kSlowTests{
kBlockchainDir / "GeneralStateTests" / "stTimeConsuming",
kBlockchainDir / "GeneralStateTests" / "VMTests" / "vmPerformance",
};

static const std::vector<fs::path> kFailingTests{};
static const std::array kFailingTests{
// Tests related to create address collision. Silkworm and evmone implement this scenario
// differently:
// Silkworm follows the older EIP-684 and clears the created account storage if not empty,
// evmone tries to follow the newer EIP-7610 to revert the creation, however Silkworm
// is not able to provide enough information to evmone to identify non-empty storage,
// in the result the non-empty storage remains unchanged.
// This scenarion don't happen in real networks. The desired behavior for implementations
// is still being discussed.
kBlockchainDir / "GeneralStateTests" / "stCreate2" / "RevertInCreateInInitCreate2.json",
kBlockchainDir / "GeneralStateTests" / "stCreate2" / "RevertInCreateInInitCreate2Paris.json",
kBlockchainDir / "GeneralStateTests" / "stRevertTest" / "RevertInCreateInInit.json",
kBlockchainDir / "GeneralStateTests" / "stRevertTest" / "RevertInCreateInInit_Paris.json",
kBlockchainDir / "GeneralStateTests" / "stSStoreTest" / "InitCollision.json",
kBlockchainDir / "GeneralStateTests" / "stSStoreTest" / "InitCollisionParis.json",
};

static constexpr size_t kColumnWidth{80};

Expand Down
159 changes: 159 additions & 0 deletions silkworm/core/execution/processor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,29 +16,186 @@

#include "processor.hpp"

#include <evmone/test/state/state.hpp>

#include <silkworm/core/common/assert.hpp>
#include <silkworm/core/protocol/intrinsic_gas.hpp>
#include <silkworm/core/protocol/param.hpp>
#include <silkworm/core/trie/vector_root.hpp>

namespace silkworm {
class StateView final : public evmone::state::StateView {
IntraBlockState& state_;

public:
explicit StateView(IntraBlockState& state) noexcept : state_{state} {}

std::optional<Account> get_account(const evmc::address& addr) const noexcept override {
const auto* obj = state_.get_object(addr);
if (obj == nullptr || !obj->current.has_value())
return std::nullopt;

const auto& cur = *obj->current;
return Account{
.nonce = cur.nonce,
.balance = cur.balance,
.code_hash = cur.code_hash,

// This information is only needed to implement EIP-7610 (create address collision).
// Proper way of doing so is to inspect the account's storage root hash,
// but this information is currently unavailable to EVM.
// The false value is safe "do nothing" option.
.has_storage = false,
};
}

evmone::bytes get_account_code(const evmc::address& addr) const noexcept override {
return evmone::bytes{state_.get_code(addr)};
}

evmc::bytes32 get_storage(const evmc::address& addr, const evmc::bytes32& key) const noexcept override {
return state_.get_original_storage(addr, key);
}
};

namespace {
class BlockHashes final : public evmone::state::BlockHashes {
EVM& evm_;

public:
explicit BlockHashes(EVM& evm) noexcept : evm_{evm} {}
evmc::bytes32 get_block_hash(int64_t block_number) const noexcept override {
return evm_.get_block_hash(block_number);
}
};

/// Checks the result of the transaction execution in evmone (APIv2)
/// against the result produced by Silkworm.
void check_evm1_execution_result(const evmone::state::TransactionReceipt& evm1_receipt,
const Receipt& receipt, const CallResult& vm_res,
uint64_t gas_used, const IntraBlockState& state) {
if (static_cast<uint64_t>(evm1_receipt.gas_used) != gas_used) {
std::cerr << "g: " << evm1_receipt.gas_used << ", silkworm: " << gas_used << "\n";
SILKWORM_ASSERT(static_cast<uint64_t>(evm1_receipt.gas_used) == gas_used);
}

SILKWORM_ASSERT(receipt.logs.size() == evm1_receipt.logs.size());
for (size_t i = 0; i < receipt.logs.size(); ++i) {
const auto& e1l = evm1_receipt.logs[i];
const auto& exp = receipt.logs[i];
SILKWORM_ASSERT(e1l.addr == exp.address);
SILKWORM_ASSERT(e1l.topics.size() == exp.topics.size());
for (size_t j = 0; j < exp.topics.size(); ++j) {
SILKWORM_ASSERT(e1l.topics[j] == exp.topics[j]);
}
SILKWORM_ASSERT(e1l.data == exp.data);
}

const auto& state_diff = evm1_receipt.state_diff;
for (const auto& entry : state_diff.modified_accounts) {
if (std::ranges::find(state_diff.deleted_accounts, entry.addr) != state_diff.deleted_accounts.end()) {
continue;
}

for (const auto& [k, v] : entry.modified_storage) {
auto expected = state.get_current_storage(entry.addr, k);
if (v != expected) {
std::cerr << "k: " << hex(k) << "e1: " << hex(v) << ", silkworm: " << hex(expected) << "\n";
}
}
}
for (const auto& a : state_diff.deleted_accounts) {
SILKWORM_ASSERT(!state.exists(a));
}
for (const auto& m : state_diff.modified_accounts) {
if (std::ranges::find(state_diff.deleted_accounts, m.addr) != state_diff.deleted_accounts.end()) {
continue;
}

SILKWORM_ASSERT(state.get_nonce(m.addr) == m.nonce);
if (m.balance != state.get_balance(m.addr)) {
std::cerr << "b: " << hex(m.addr) << " " << to_string(m.balance) << ", silkworm: " << to_string(state.get_balance(m.addr)) << "\n";
SILKWORM_ASSERT(state.get_balance(m.addr) == m.balance);
}
if (!m.code.empty()) {
SILKWORM_ASSERT(state.get_code(m.addr) == m.code);
}
}

if (evm1_receipt.status == EVMC_FAILURE) { // imprecise error code
SILKWORM_ASSERT(!receipt.success);
} else if (evm1_receipt.status != EVMC_OUT_OF_GAS && vm_res.status != EVMC_PRECOMPILE_FAILURE) {
if (evm1_receipt.status != vm_res.status) {
std::cerr << "e1: " << evm1_receipt.status << ", silkworm: " << vm_res.status << "\n";
}
SILKWORM_ASSERT(evm1_receipt.status == vm_res.status);
}
}
} // namespace

ExecutionProcessor::ExecutionProcessor(const Block& block, protocol::RuleSet& rule_set, State& state,
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();

evm1_block_ = {
.number = static_cast<int64_t>(block.header.number),
.timestamp = static_cast<int64_t>(block.header.timestamp),
.gas_limit = static_cast<int64_t>(block.header.gas_limit),
.coinbase = block.header.beneficiary,
.difficulty = static_cast<int64_t>(block.header.difficulty),
.prev_randao = block.header.difficulty == 0 ? block.header.prev_randao : intx::be::store<evmone::state::bytes32>(intx::uint256{block.header.difficulty}),
.base_fee = static_cast<uint64_t>(block.header.base_fee_per_gas.value_or(0)),
.excess_blob_gas = block.header.excess_blob_gas.value_or(0),
.blob_base_fee = block.header.blob_gas_price().value_or(0),
};
for (const auto& o : block.ommers)
evm1_block_.ommers.emplace_back(evmone::state::Ommer{o.beneficiary, static_cast<uint32_t>(block.header.number - o.number)});
if (block.withdrawals) {
for (const auto& w : *block.withdrawals)
evm1_block_.withdrawals.emplace_back(evmone::state::Withdrawal{w.index, w.validator_index, w.address, w.amount});
}
}

void ExecutionProcessor::execute_transaction(const Transaction& txn, Receipt& receipt) noexcept {
// Plain debug assertion instead of SILKWORM_ASSERT not to validate txn twice (see execute_block_no_post_validation)
assert(protocol::validate_transaction(txn, state_, available_gas()) == ValidationResult::kOk);

StateView evm1_state_view{state_};
BlockHashes evm1_block_hashes{evm_};

evmone::state::Transaction evm1_txn{
.type = static_cast<evmone::state::Transaction::Type>(txn.type),
.data = txn.data,
.gas_limit = static_cast<int64_t>(txn.gas_limit),
.max_gas_price = txn.max_fee_per_gas,
.max_priority_gas_price = txn.max_priority_fee_per_gas,
.max_blob_gas_price = txn.max_fee_per_blob_gas,
.sender = *txn.sender(),
.to = txn.to,
.value = txn.value,
// access_list
// blob_hashes
.chain_id = static_cast<uint64_t>(txn.chain_id.value_or(0)),
.nonce = txn.nonce};
for (const auto& [account, storage_keys] : txn.access_list)
evm1_txn.access_list.emplace_back(account, storage_keys);
for (const evmc::bytes32& h : txn.blob_versioned_hashes)
evm1_txn.blob_hashes.emplace_back(h);

const auto rev = evm_.revision();
const auto g0 = protocol::intrinsic_gas(txn, rev);
SILKWORM_ASSERT(g0 <= INT64_MAX); // true due to the precondition (transaction must be valid)
const auto execution_gas_limit = txn.gas_limit - static_cast<uint64_t>(g0);

// Execute transaction with evmone APIv2.
// This must be done before the Silkworm execution so that the state is unmodified.
// evmone will not modify the state itself: state is read-only and the state modifications
// are provided as the state diff in the returned receipt.
const auto evm1_receipt = evmone::state::transition(
evm1_state_view, evm1_block_, evm1_block_hashes, evm1_txn, rev, evm_.vm(), static_cast<int64_t>(execution_gas_limit));

// Optimization: since receipt.logs might have some capacity, let's reuse it.
std::swap(receipt.logs, state_.logs());

Expand Down Expand Up @@ -97,6 +254,8 @@ void ExecutionProcessor::execute_transaction(const Transaction& txn, Receipt& re
receipt.cumulative_gas_used = cumulative_gas_used_;
receipt.bloom = logs_bloom(state_.logs());
std::swap(receipt.logs, state_.logs());

check_evm1_execution_result(evm1_receipt, receipt, vm_res, gas_used, state_);
}

CallResult ExecutionProcessor::call(const Transaction& txn, const std::vector<std::shared_ptr<EvmTracer>>& tracers, bool bailout, bool refund) noexcept {
Expand Down
3 changes: 3 additions & 0 deletions silkworm/core/execution/processor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
#include <cstdint>
#include <vector>

#include <evmone/test/state/block.hpp>

#include <silkworm/core/execution/evm.hpp>
#include <silkworm/core/protocol/rule_set.hpp>
#include <silkworm/core/state/state.hpp>
Expand Down Expand Up @@ -83,6 +85,7 @@ class ExecutionProcessor {
IntraBlockState state_;
protocol::RuleSet& rule_set_;
EVM evm_;
evmone::state::BlockInfo evm1_block_;
};

} // namespace silkworm
1 change: 1 addition & 0 deletions silkworm/core/state/intra_block_state.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ class IntraBlockState {
friend class state::StorageAccessDelta;
friend class state::AccountAccessDelta;
friend class state::TransientStorageChangeDelta;
friend class StateView;

evmc::bytes32 get_storage(const evmc::address& address, const evmc::bytes32& key, bool original) const noexcept;

Expand Down
22 changes: 20 additions & 2 deletions third_party/evmone/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,20 @@ add_library(
evmone/lib/evmone_precompiles/ripemd160.cpp
evmone/lib/evmone_precompiles/sha256.cpp
evmone/lib/evmone_precompiles/kzg.cpp
evmone/lib/evmone_precompiles/bn254.cpp
evmone/lib/evmone_precompiles/secp256k1.cpp
evmone/lib/evmone_precompiles/bls.cpp
evmone/test/state/host.cpp
evmone/test/state/precompiles.cpp
evmone/test/state/state.cpp
)
set_source_files_properties(evmone/lib/evmone/vm.cpp PROPERTIES COMPILE_DEFINITIONS PROJECT_VERSION="0.11.0")
target_include_directories(
evmone
PUBLIC evmone/include evmone/lib
INTERFACE .
PRIVATE ../..
)
set_source_files_properties(evmone/lib/evmone/vm.cpp PROPERTIES COMPILE_DEFINITIONS PROJECT_VERSION="0.12.0")
target_include_directories(evmone PUBLIC evmone/include evmone/lib)
target_link_libraries(
evmone
PUBLIC evmc intx::intx blst::blst
Expand All @@ -70,3 +81,10 @@ else()
$<$<CXX_COMPILER_ID:Clang,AppleClang>:-Wno-unknown-attributes>
)
endif()

if(SILKWORM_CORE_USE_ABSEIL)
# Propagate SILKWORM_CORE_USE_ABSEIL to allow using Silkworm's preferred FlatHashMap.
find_package(absl REQUIRED)
target_compile_definitions(evmone PRIVATE SILKWORM_CORE_USE_ABSEIL)
target_link_libraries(evmone PRIVATE absl::flat_hash_map)
endif()
Loading