Skip to content

EIP-4844 - pass blockchain tests #1077

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

Merged
merged 6 commits into from
Jan 15, 2025
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
13 changes: 3 additions & 10 deletions circle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -362,11 +362,8 @@ jobs:
- run:
name: "Execution spec tests (blockchain_tests)"
working_directory: ~/build
# 4844 and 4788 temporarily turned off b/c they rely on blob features.
command: >
bin/evmone-blockchaintest
--gtest_filter='*:-*eip4844*:*eip4788*'
~/spec-tests/fixtures/blockchain_tests
bin/evmone-blockchaintest ~/spec-tests/fixtures/blockchain_tests
- download_execution_spec_tests:
release: [email protected]
fixtures_suffix: pectra-devnet-4
Expand Down Expand Up @@ -428,10 +425,8 @@ jobs:
- run:
name: "EOF pre-release execution spec tests (blockchain_tests)"
working_directory: ~/build
# 4844 and 4788 temporarily turned off b/c they rely on blob features.
command: >
bin/evmone-blockchaintest ~/spec-tests/fixtures/blockchain_tests/osaka
--gtest_filter='*:-*stEIP4844*'
- run:
name: "EOF pre-release execution spec tests (eof_tests)"
working_directory: ~/build
Expand Down Expand Up @@ -466,18 +461,16 @@ jobs:
- run:
name: "Blockchain tests (GeneralStateTests)"
working_directory: ~/build
# 4844 and 4788 temporarily turned off b/c they rely on blob features.
command: >
bin/evmone-blockchaintest
--gtest_filter='*:-VMTests/vmPerformance.*:*.*Call50000_sha256:*.CALLBlake2f_MaxRounds:*eip4844*:*stEIP4844*:*eip4788*'
--gtest_filter='*:-VMTests/vmPerformance.*:*.*Call50000_sha256:*.CALLBlake2f_MaxRounds'
~/tests/BlockchainTests/GeneralStateTests
- run:
name: "Blockchain tests (ValidBlocks)"
working_directory: ~/build
# 4844 and 4788 temporarily turned off b/c they rely on blob features.
command: >
bin/evmone-blockchaintest
--gtest_filter='*:-bcMultiChainTest.*:bcTotalDifficultyTest.*:bcForkStressTest.ForkStressTest:bcGasPricerTest.RPC_API_Test:bcValidBlockTest.SimpleTx3LowS:*bcEIP4844*'
--gtest_filter='*:-bcMultiChainTest.*:bcTotalDifficultyTest.*:bcForkStressTest.ForkStressTest:bcGasPricerTest.RPC_API_Test:bcValidBlockTest.SimpleTx3LowS'
~/tests/BlockchainTests/ValidBlocks
~/tests/LegacyTests/Cancun/BlockchainTests/ValidBlocks
- collect_coverage_gcc
Expand Down
3 changes: 2 additions & 1 deletion test/blockchaintest/blockchaintest.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ struct BlockHeader
hash256 transactions_root;
hash256 withdrawal_root;
hash256 parent_beacon_block_root;
uint64_t excess_blob_gas;
std::optional<uint64_t> blob_gas_used;
std::optional<uint64_t> excess_blob_gas;
hash256 requests_hash;
};

Expand Down
17 changes: 15 additions & 2 deletions test/blockchaintest/blockchaintest_loader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ T load_if_exists(const json::json& j, std::string_view key)
return from_json<T>(*it);
return {};
}
template <typename T>
std::optional<T> load_optional(const json::json& j, std::string_view key)
{
if (const auto it = j.find(key); it != j.end())
return from_json<T>(*it);
return std::nullopt;
}
} // namespace

template <>
Expand All @@ -41,7 +48,8 @@ BlockHeader from_json<BlockHeader>(const json::json& j)
.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"),
.excess_blob_gas = load_if_exists<uint64_t>(j, "excessBlobGas"),
.blob_gas_used = load_optional<uint64_t>(j, "blobGasUsed"),
.excess_blob_gas = load_optional<uint64_t>(j, "excessBlobGas"),
.requests_hash = load_if_exists<hash256>(j, "requestsHash"),
};
}
Expand All @@ -62,8 +70,13 @@ static TestBlock load_test_block(const json::json& j, evmc_revision rev)
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;
tb.block_info.blob_gas_used = tb.expected_block_header.blob_gas_used;
tb.block_info.excess_blob_gas = tb.expected_block_header.excess_blob_gas;

tb.block_info.blob_base_fee =
compute_blob_gas_price(tb.expected_block_header.excess_blob_gas);
tb.block_info.excess_blob_gas.has_value() ?
std::optional(state::compute_blob_gas_price(*tb.block_info.excess_blob_gas)) :
std::nullopt;

// Override prev_randao with difficulty pre-Merge
if (rev < EVMC_PARIS)
Expand Down
30 changes: 27 additions & 3 deletions test/blockchaintest/blockchaintest_runner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ TransitionResult apply_block(TestState& state, evmc::VM& vm, const state::BlockI

std::vector<state::Log> txs_logs;
int64_t block_gas_left = block.gas_limit;
int64_t blob_gas_left = 0;
auto blob_gas_left = static_cast<int64_t>(block.blob_gas_used.value_or(0));

std::vector<RejectedTransaction> rejected_txs;
std::vector<state::TransactionReceipt> receipts;
Expand Down Expand Up @@ -75,7 +75,7 @@ TransitionResult apply_block(TestState& state, evmc::VM& vm, const state::BlockI
receipt.post_state = state::mpt_hash(block_state);

block_gas_left -= receipt.gas_used;
blob_gas_left -= tx.blob_gas_used();
blob_gas_left -= static_cast<int64_t>(tx.blob_gas_used());
receipts.emplace_back(std::move(receipt));
}
}
Expand All @@ -95,10 +95,34 @@ TransitionResult apply_block(TestState& state, evmc::VM& vm, const state::BlockI
bloom, blob_gas_left, std::move(block_state)};
}

bool validate_block(evmc_revision, const TestBlock&, const BlockHeader&) noexcept
bool validate_block(
evmc_revision rev, const TestBlock& test_block, const BlockHeader& parent_header) noexcept
{
// NOTE: includes only block validity unrelated to individual txs. See `apply_block`.

if (rev >= EVMC_CANCUN)
{
// `excess_blob_gas` and `blob_gas_used` mandatory after Cancun and invalid before.
if (!test_block.block_info.excess_blob_gas.has_value() ||
!test_block.block_info.blob_gas_used.has_value())
return false;

// Check that the excess blob gas was updated correctly.
if (*test_block.block_info.excess_blob_gas !=
state::calc_excess_blob_gas(
parent_header.blob_gas_used.value_or(0), parent_header.excess_blob_gas.value_or(0)))
return false;

// Ensure the total blob gas spent is at most equal to the limit
if (*test_block.block_info.blob_gas_used > state::BlockInfo::MAX_BLOB_GAS_PER_BLOCK)
return false;
}
else
{
if (test_block.block_info.excess_blob_gas.has_value() ||
test_block.block_info.blob_gas_used.has_value())
return false;
}
return true;
}

Expand Down
19 changes: 18 additions & 1 deletion test/state/block.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

namespace evmone::state
{
static constexpr uint64_t TARGET_BLOB_GAS_PER_BLOCK = 393216;

intx::uint256 compute_blob_gas_price(uint64_t excess_blob_gas) noexcept
{
/// A helper function approximating `factor * e ** (numerator / denominator)`.
Expand All @@ -16,10 +18,16 @@ intx::uint256 compute_blob_gas_price(uint64_t excess_blob_gas) noexcept
intx::uint256 i = 1;
intx::uint256 output = 0;
intx::uint256 numerator_accum = factor * denominator;
const intx::uint256 numerator256 = numerator;
while (numerator_accum > 0)
{
output += numerator_accum;
numerator_accum = (numerator_accum * numerator) / (denominator * i);
// Ensure the multiplication won't overflow 256 bits.
if (const auto p = intx::umul(numerator_accum, numerator256);
p <= std::numeric_limits<intx::uint256>::max())
numerator_accum = intx::uint256(p) / (denominator * i);
else
return std::numeric_limits<intx::uint256>::max();
i += 1;
}
return output / denominator;
Expand All @@ -30,6 +38,15 @@ intx::uint256 compute_blob_gas_price(uint64_t excess_blob_gas) noexcept
return fake_exponential(MIN_BLOB_GASPRICE, excess_blob_gas, BLOB_GASPRICE_UPDATE_FRACTION);
}

uint64_t calc_excess_blob_gas(
uint64_t parent_blob_gas_used, uint64_t parent_excess_blob_gas) noexcept
{
if (parent_excess_blob_gas + parent_blob_gas_used < TARGET_BLOB_GAS_PER_BLOCK)
return 0;
else
return parent_excess_blob_gas + parent_blob_gas_used - TARGET_BLOB_GAS_PER_BLOCK;
}

[[nodiscard]] bytes rlp_encode(const Withdrawal& withdrawal)
{
return rlp::encode_tuple(withdrawal.index, withdrawal.validator_index, withdrawal.recipient,
Expand Down
16 changes: 11 additions & 5 deletions test/state/block.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ struct Withdrawal
struct BlockInfo
{
/// Max amount of blob gas allowed in block. It's constant now but can be dynamic in the future.
static constexpr int64_t MAX_BLOB_GAS_PER_BLOCK = 786432;
static constexpr uint64_t MAX_BLOB_GAS_PER_BLOCK = 786432;

int64_t number = 0;
int64_t timestamp = 0;
Expand All @@ -49,13 +49,15 @@ struct BlockInfo
/// The EIP-1559 base fee, since London.
uint64_t base_fee = 0;

/// The "blob gas used" parameter from EIP-4844
std::optional<uint64_t> blob_gas_used;

/// The "excess blob gas" parameter from EIP-4844
/// for computing the blob gas price in the current block.
uint64_t excess_blob_gas = 0;
std::optional<uint64_t> excess_blob_gas;

/// The blob gas price parameter from EIP-4844.
/// This value is not stored in block headers directly but computed from excess_blob_gas.
intx::uint256 blob_base_fee = 0;
/// Blob gas price from EIP-4844, computed from excess_blob_gas.
std::optional<intx::uint256> blob_base_fee;

std::vector<Ommer> ommers;
std::vector<Withdrawal> withdrawals;
Expand All @@ -64,6 +66,10 @@ struct BlockInfo
/// Computes the current blob gas price based on the excess blob gas.
intx::uint256 compute_blob_gas_price(uint64_t excess_blob_gas) noexcept;

/// Computes the current excess blob gas based on parameters of the parent block.
uint64_t calc_excess_blob_gas(
uint64_t parent_blob_gas_used, uint64_t parent_excess_blob_gas) noexcept;

/// Defines how to RLP-encode a Withdrawal.
[[nodiscard]] bytes rlp_encode(const Withdrawal& withdrawal);
} // namespace evmone::state
2 changes: 1 addition & 1 deletion test/state/host.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,7 @@ evmc_tx_context Host::get_tx_context() const noexcept
m_block.prev_randao,
0x01_bytes32, // Chain ID is expected to be 1.
uint256be{m_block.base_fee},
intx::be::store<uint256be>(m_block.blob_base_fee),
intx::be::store<uint256be>(m_block.blob_base_fee.value_or(0)),
m_tx.blob_hashes.data(),
m_tx.blob_hashes.size(),
};
Expand Down
11 changes: 8 additions & 3 deletions test/state/state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,8 @@ std::variant<TransactionProperties, std::error_code> validate_transaction(
if (tx.blob_hashes.empty())
return make_error_code(EMPTY_BLOB_HASHES_LIST);

if (tx.max_blob_gas_price < block.blob_base_fee)
assert(block.blob_base_fee.has_value());
if (tx.max_blob_gas_price < *block.blob_base_fee)
return make_error_code(FEE_CAP_LESS_THEN_BLOCKS);

if (std::ranges::any_of(tx.blob_hashes, [](const auto& h) { return h.bytes[0] != 0x01; }))
Expand Down Expand Up @@ -451,9 +452,13 @@ TransactionReceipt transition(const StateView& state_view, const BlockInfo& bloc

if (tx.type == Transaction::Type::blob)
{
const auto blob_fee = tx.blob_gas_used() * block.blob_base_fee;
// This uint64 * uint256 cannot overflow, because tx.blob_gas_used has limits enforced
// before this stage.
assert(block.blob_base_fee.has_value());
const auto blob_fee = intx::umul(intx::uint256(tx.blob_gas_used()), *block.blob_base_fee);
assert(blob_fee <= std::numeric_limits<intx::uint256>::max());
assert(sender_acc.balance >= blob_fee); // Required for valid tx.
sender_acc.balance -= blob_fee;
sender_acc.balance -= intx::uint256(blob_fee);
}

Host host{rev, vm, state, block, block_hashes, tx};
Expand Down
4 changes: 2 additions & 2 deletions test/state/transaction.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ struct Transaction
};

/// Returns amount of blob gas used by this transaction
[[nodiscard]] int64_t blob_gas_used() const
[[nodiscard]] uint64_t blob_gas_used() const
{
static constexpr auto GAS_PER_BLOB = 0x20000;
return GAS_PER_BLOB * static_cast<int64_t>(blob_hashes.size());
return GAS_PER_BLOB * blob_hashes.size();
}

Type type = Type::legacy;
Expand Down
6 changes: 2 additions & 4 deletions test/statetest/statetest_loader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -232,10 +232,7 @@ state::BlockInfo from_json<state::BlockInfo>(const json::json& j)
{
const auto parent_excess_blob_gas = from_json<uint64_t>(*it);
const auto parent_blob_gas_used = from_json<uint64_t>(j.at("parentBlobGasUsed"));
static constexpr uint64_t TARGET_BLOB_GAS_PER_BLOCK = 0x60000;
excess_blob_gas =
std::max(parent_excess_blob_gas + parent_blob_gas_used, TARGET_BLOB_GAS_PER_BLOCK) -
TARGET_BLOB_GAS_PER_BLOCK;
excess_blob_gas = state::calc_excess_blob_gas(parent_blob_gas_used, parent_excess_blob_gas);
}
else if (const auto it2 = j.find("currentExcessBlobGas"); it2 != j.end())
{
Expand All @@ -254,6 +251,7 @@ state::BlockInfo from_json<state::BlockInfo>(const json::json& j)
.prev_randao = prev_randao,
.parent_beacon_block_root = load_if_exists<hash256>(j, "parentBeaconBlockRoot"),
.base_fee = base_fee,
.blob_gas_used = load_if_exists<uint64_t>(j, "blobGasUsed"),
.excess_blob_gas = excess_blob_gas,
.blob_base_fee = state::compute_blob_gas_price(excess_blob_gas),
.ommers = std::move(ommers),
Expand Down
11 changes: 6 additions & 5 deletions test/t8n/t8n.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ int main(int argc, const char* argv[])
j_result["currentBaseFee"] = hex0x(block.base_fee);

int64_t cumulative_gas_used = 0;
int64_t blob_gas_left = state::BlockInfo::MAX_BLOB_GAS_PER_BLOCK;
auto blob_gas_left = static_cast<int64_t>(state::BlockInfo::MAX_BLOB_GAS_PER_BLOCK);
std::vector<state::Transaction> transactions;
std::vector<state::TransactionReceipt> receipts;
int64_t block_gas_left = block.gas_limit;
Expand Down Expand Up @@ -209,7 +209,7 @@ int main(int argc, const char* argv[])
j_receipt["root"] = "";
j_receipt["status"] = "0x1";
j_receipt["transactionIndex"] = hex0x(i);
blob_gas_left -= tx.blob_gas_used();
blob_gas_left -= static_cast<int64_t>(tx.blob_gas_used());
transactions.emplace_back(std::move(tx));
block_gas_left -= receipt.gas_used;
receipts.emplace_back(std::move(receipt));
Expand Down Expand Up @@ -246,9 +246,10 @@ int main(int argc, const char* argv[])
j_result["gasUsed"] = hex0x(cumulative_gas_used);
if (rev >= EVMC_CANCUN)
{
j_result["blobGasUsed"] =
hex0x(state::BlockInfo::MAX_BLOB_GAS_PER_BLOCK - blob_gas_left);
j_result["currentExcessBlobGas"] = hex0x(block.excess_blob_gas);
j_result["blobGasUsed"] = hex0x(
static_cast<int64_t>(state::BlockInfo::MAX_BLOB_GAS_PER_BLOCK) - blob_gas_left);
if (block.excess_blob_gas.has_value())
j_result["currentExcessBlobGas"] = hex0x(*block.excess_blob_gas);
}
if (rev >= EVMC_PRAGUE)
{
Expand Down
10 changes: 10 additions & 0 deletions test/unittests/state_transition.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,16 @@ void state_transition::export_state_test(
}
}

if (tx.type == Transaction::Type::blob)
{
jtx["maxFeePerBlobGas"] = hex0x(tx.max_blob_gas_price);
jtx["blobVersionedHashes"] = json::json::array();
for (const auto& blob_hash : tx.blob_hashes)
{
jtx["blobVersionedHashes"].emplace_back(hex0x(blob_hash));
}
}

auto& jpost = jt["post"][to_test_fork_name(rev)][0];
jpost["indexes"] = {{"data", 0}, {"gas", 0}, {"value", 0}};
jpost["hash"] = hex0x(mpt_hash(post));
Expand Down
9 changes: 7 additions & 2 deletions test/unittests/state_transition_block_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,14 @@ TEST_F(state_transition, eip7516_blob_base_fee)
{
rev = EVMC_CANCUN;

block.blob_base_fee = 0xabcd;
block.excess_blob_gas = 0xabcd00;
// 0x1d is the result of ref implementation in EIP-4844
static constexpr auto price = 0x1d;
block.blob_base_fee = price;
assert(state::compute_blob_gas_price(*block.excess_blob_gas) == *block.blob_base_fee);

tx.to = To;
pre.insert(*tx.to, {.code = sstore(0x4a, OP_BLOBBASEFEE)});

expect.post[To].storage[0x4a_bytes32] = 0xabcd_bytes32;
expect.post[To].storage[0x4a_bytes32] = bytes32(price);
}
4 changes: 4 additions & 0 deletions test/unittests/state_transition_tx_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ TEST_F(state_transition, tx_blob_gas_price)
0x0100000000000000000000000000000000000000000000000000000000000000_bytes32);
tx.max_blob_gas_price = 1;

block.excess_blob_gas = 0;
block.blob_base_fee = 1;
block.blob_gas_used = 786432;

pre.get(tx.sender).balance = 0x20000 + tx.gas_limit * tx.max_gas_price;

expect.post[Coinbase].exists = false; // all gas is burned, Coinbase gets nothing
Expand Down
6 changes: 5 additions & 1 deletion test/unittests/state_tx_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,11 @@ TEST(state_tx, validate_sender)

TEST(state_tx, validate_blob_tx)
{
const BlockInfo bi{.gas_limit = 0x989680, .base_fee = 1, .blob_base_fee = 1};
const BlockInfo bi{.gas_limit = 0x989680,
.base_fee = 1,
.blob_gas_used = 786432,
.excess_blob_gas = 0,
.blob_base_fee = 1};
Transaction tx{
.type = Transaction::Type::blob,
.gas_limit = 60000,
Expand Down