Skip to content

Commit

Permalink
Implement EIP-4788: Beacon block root in the EVM (#709)
Browse files Browse the repository at this point in the history
Implement the system call from the "Block processing" of the EIP-4788.
https://eips.ethereum.org/EIPS/eip-4788#block-processing

At the beginning of each block a gas-free call is invoked from
the _system_ account (`0xff..fe`) to the specific _system_ contract.
  • Loading branch information
chfast authored Nov 27, 2023
2 parents 2fb7cc1 + 87e8d9b commit b1c9637
Show file tree
Hide file tree
Showing 9 changed files with 91 additions and 1 deletion.
1 change: 1 addition & 0 deletions test/blockchaintest/blockchaintest.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ struct BlockHeader
hash256 hash;
hash256 transactions_root;
hash256 withdrawal_root;
hash256 parent_beacon_block_root;
};

struct TestBlock
Expand Down
2 changes: 2 additions & 0 deletions test/blockchaintest/blockchaintest_loader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ BlockHeader from_json<BlockHeader>(const json::json& j)
.hash = from_json<hash256>(j.at("hash")),
.transactions_root = from_json<hash256>(j.at("transactionsTrie")),
.withdrawal_root = load_if_exists<hash256>(j, "withdrawalsRoot"),
.parent_beacon_block_root = load_if_exists<hash256>(j, "parentBeaconBlockRoot"),
};
}

Expand All @@ -58,6 +59,7 @@ static TestBlock load_test_block(const json::json& j, evmc_revision rev)
tb.block_info.difficulty = tb.expected_block_header.difficulty;
tb.block_info.prev_randao = tb.expected_block_header.prev_randao;
tb.block_info.base_fee = tb.expected_block_header.base_fee_per_gas;
tb.block_info.parent_beacon_block_root = tb.expected_block_header.parent_beacon_block_root;

// Override prev_randao with difficulty pre-Merge
if (rev < EVMC_PARIS)
Expand Down
2 changes: 2 additions & 0 deletions test/blockchaintest/blockchaintest_runner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ TransitionResult apply_block(state::State& state, evmc::VM& vm, const state::Blo
const std::vector<state::Transaction>& txs, evmc_revision rev,
std::optional<int64_t> block_reward)
{
state::system_call(state, block, rev, vm);

std::vector<state::Log> txs_logs;
int64_t block_gas_left = block.gas_limit;

Expand Down
3 changes: 2 additions & 1 deletion test/state/host.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,8 @@ evmc::Result Host::call(const evmc_message& orig_msg) noexcept
evmc_tx_context Host::get_tx_context() const noexcept
{
// TODO: The effective gas price is already computed in transaction validation.
assert(m_tx.max_gas_price >= m_block.base_fee);
// TODO: The effective gas price calculation is broken for system calls (gas price 0).
assert(m_tx.max_gas_price >= m_block.base_fee || m_tx.max_gas_price == 0);
const auto priority_gas_price =
std::min(m_tx.max_priority_gas_price, m_tx.max_gas_price - m_block.base_fee);
const auto effective_gas_price = m_block.base_fee + priority_gas_price;
Expand Down
35 changes: 35 additions & 0 deletions test/state/state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,41 @@ void delete_empty_accounts(State& state)
}
} // namespace

void system_call(State& state, const BlockInfo& block, evmc_revision rev, evmc::VM& vm)
{
static constexpr auto SystemAddress = 0xfffffffffffffffffffffffffffffffffffffffe_address;
static constexpr auto BeaconRootsAddress = 0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02_address;

if (rev >= EVMC_CANCUN)
{
if (const auto acc = state.find(BeaconRootsAddress); acc != nullptr)
{
const evmc_message msg{
.kind = EVMC_CALL,
.gas = 30'000'000,
.recipient = BeaconRootsAddress,
.sender = SystemAddress,
.input_data = block.parent_beacon_block_root.bytes,
.input_size = sizeof(block.parent_beacon_block_root),
};

const Transaction empty_tx{};
Host host{rev, vm, state, block, empty_tx};
const auto& code = acc->code;
[[maybe_unused]] const auto res = vm.execute(host, rev, msg, code.data(), code.size());
assert(res.status_code == EVMC_SUCCESS);
assert(acc->access_status == EVMC_ACCESS_COLD);

// Reset storage status.
for (auto& [_, val] : acc->storage)
{
val.access_status = EVMC_ACCESS_COLD;
val.original = val.current;
}
}
}
}

void finalize(State& state, evmc_revision rev, const address& coinbase,
std::optional<uint64_t> block_reward, std::span<const Ommer> ommers,
std::span<const Withdrawal> withdrawals)
Expand Down
7 changes: 7 additions & 0 deletions test/state/state.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ struct BlockInfo
int64_t parent_difficulty = 0;
hash256 parent_ommers_hash;
bytes32 prev_randao;
hash256 parent_beacon_block_root;
uint64_t base_fee = 0;
std::vector<Ommer> ommers;
std::vector<Withdrawal> withdrawals;
Expand Down Expand Up @@ -190,6 +191,12 @@ std::variant<int64_t, std::error_code> validate_transaction(const Account& sende
const BlockInfo& block, const Transaction& tx, evmc_revision rev,
int64_t block_gas_left) noexcept;

/// Performs the system call.
///
/// Executes code at pre-defined accounts from the system sender (0xff...fe).
/// The sender's nonce is not increased.
void system_call(State& state, const BlockInfo& block, evmc_revision rev, evmc::VM& vm);

/// Defines how to RLP-encode a Transaction.
[[nodiscard]] bytes rlp_encode(const Transaction& tx);

Expand Down
1 change: 1 addition & 0 deletions test/statetest/statetest_loader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ state::BlockInfo from_json<state::BlockInfo>(const json::json& j)
.parent_difficulty = parent_difficulty,
.parent_ommers_hash = parent_uncle_hash,
.prev_randao = prev_randao,
.parent_beacon_block_root = {},
.base_fee = base_fee,
.ommers = std::move(ommers),
.withdrawals = std::move(withdrawals),
Expand Down
1 change: 1 addition & 0 deletions test/unittests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ target_sources(
state_new_account_address_test.cpp
state_precompiles_test.cpp
state_rlp_test.cpp
state_system_call_test.cpp
state_transition.hpp
state_transition.cpp
state_transition_block_test.cpp
Expand Down
40 changes: 40 additions & 0 deletions test/unittests/state_system_call_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// evmone: Fast Ethereum Virtual Machine implementation
// Copyright 2023 The evmone Authors.
// SPDX-License-Identifier: Apache-2.0

#include <evmone/evmone.h>
#include <gtest/gtest.h>
#include <test/state/state.hpp>
#include <test/utils/bytecode.hpp>

using namespace evmc;
using namespace evmone::state;

TEST(state_system_call, non_existient)
{
evmc::VM vm;
State state;

system_call(state, {}, EVMC_CANCUN, vm);

EXPECT_EQ(state.get_accounts().size(), 0);
}

TEST(state_system_call, sstore_timestamp)
{
static constexpr auto BeaconRootsAddress = 0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02_address;

evmc::VM vm{evmc_create_evmone()};
const BlockInfo block{.number = 1, .timestamp = 404};
State state;
state.insert(BeaconRootsAddress, {.code = sstore(OP_NUMBER, OP_TIMESTAMP)});

system_call(state, block, EVMC_CANCUN, vm);

ASSERT_EQ(state.get_accounts().size(), 1);
EXPECT_EQ(state.get(BeaconRootsAddress).nonce, 0);
EXPECT_EQ(state.get(BeaconRootsAddress).balance, 0);
const auto& storage = state.get(BeaconRootsAddress).storage;
ASSERT_EQ(storage.size(), 1);
EXPECT_EQ(storage.at(0x01_bytes32).current, 404);
}

0 comments on commit b1c9637

Please sign in to comment.