Skip to content

Commit c49bc0a

Browse files
authored
EIP-4844 - pass blockchain tests (#1077)
1 parent 7a96b8e commit c49bc0a

15 files changed

+121
-40
lines changed

circle.yml

+3-10
Original file line numberDiff line numberDiff line change
@@ -362,11 +362,8 @@ jobs:
362362
- run:
363363
name: "Execution spec tests (blockchain_tests)"
364364
working_directory: ~/build
365-
# 4844 and 4788 temporarily turned off b/c they rely on blob features.
366365
command: >
367-
bin/evmone-blockchaintest
368-
--gtest_filter='*:-*eip4844*:*eip4788*'
369-
~/spec-tests/fixtures/blockchain_tests
366+
bin/evmone-blockchaintest ~/spec-tests/fixtures/blockchain_tests
370367
- download_execution_spec_tests:
371368
372369
fixtures_suffix: pectra-devnet-4
@@ -428,10 +425,8 @@ jobs:
428425
- run:
429426
name: "EOF pre-release execution spec tests (blockchain_tests)"
430427
working_directory: ~/build
431-
# 4844 and 4788 temporarily turned off b/c they rely on blob features.
432428
command: >
433429
bin/evmone-blockchaintest ~/spec-tests/fixtures/blockchain_tests/osaka
434-
--gtest_filter='*:-*stEIP4844*'
435430
- run:
436431
name: "EOF pre-release execution spec tests (eof_tests)"
437432
working_directory: ~/build
@@ -466,18 +461,16 @@ jobs:
466461
- run:
467462
name: "Blockchain tests (GeneralStateTests)"
468463
working_directory: ~/build
469-
# 4844 and 4788 temporarily turned off b/c they rely on blob features.
470464
command: >
471465
bin/evmone-blockchaintest
472-
--gtest_filter='*:-VMTests/vmPerformance.*:*.*Call50000_sha256:*.CALLBlake2f_MaxRounds:*eip4844*:*stEIP4844*:*eip4788*'
466+
--gtest_filter='*:-VMTests/vmPerformance.*:*.*Call50000_sha256:*.CALLBlake2f_MaxRounds'
473467
~/tests/BlockchainTests/GeneralStateTests
474468
- run:
475469
name: "Blockchain tests (ValidBlocks)"
476470
working_directory: ~/build
477-
# 4844 and 4788 temporarily turned off b/c they rely on blob features.
478471
command: >
479472
bin/evmone-blockchaintest
480-
--gtest_filter='*:-bcMultiChainTest.*:bcTotalDifficultyTest.*:bcForkStressTest.ForkStressTest:bcGasPricerTest.RPC_API_Test:bcValidBlockTest.SimpleTx3LowS:*bcEIP4844*'
473+
--gtest_filter='*:-bcMultiChainTest.*:bcTotalDifficultyTest.*:bcForkStressTest.ForkStressTest:bcGasPricerTest.RPC_API_Test:bcValidBlockTest.SimpleTx3LowS'
481474
~/tests/BlockchainTests/ValidBlocks
482475
~/tests/LegacyTests/Cancun/BlockchainTests/ValidBlocks
483476
- collect_coverage_gcc

test/blockchaintest/blockchaintest.hpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ struct BlockHeader
3939
hash256 transactions_root;
4040
hash256 withdrawal_root;
4141
hash256 parent_beacon_block_root;
42-
uint64_t excess_blob_gas;
42+
std::optional<uint64_t> blob_gas_used;
43+
std::optional<uint64_t> excess_blob_gas;
4344
hash256 requests_hash;
4445
};
4546

test/blockchaintest/blockchaintest_loader.cpp

+15-2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ T load_if_exists(const json::json& j, std::string_view key)
1818
return from_json<T>(*it);
1919
return {};
2020
}
21+
template <typename T>
22+
std::optional<T> load_optional(const json::json& j, std::string_view key)
23+
{
24+
if (const auto it = j.find(key); it != j.end())
25+
return from_json<T>(*it);
26+
return std::nullopt;
27+
}
2128
} // namespace
2229

2330
template <>
@@ -41,7 +48,8 @@ BlockHeader from_json<BlockHeader>(const json::json& j)
4148
.transactions_root = from_json<hash256>(j.at("transactionsTrie")),
4249
.withdrawal_root = load_if_exists<hash256>(j, "withdrawalsRoot"),
4350
.parent_beacon_block_root = load_if_exists<hash256>(j, "parentBeaconBlockRoot"),
44-
.excess_blob_gas = load_if_exists<uint64_t>(j, "excessBlobGas"),
51+
.blob_gas_used = load_optional<uint64_t>(j, "blobGasUsed"),
52+
.excess_blob_gas = load_optional<uint64_t>(j, "excessBlobGas"),
4553
.requests_hash = load_if_exists<hash256>(j, "requestsHash"),
4654
};
4755
}
@@ -62,8 +70,13 @@ static TestBlock load_test_block(const json::json& j, evmc_revision rev)
6270
tb.block_info.prev_randao = tb.expected_block_header.prev_randao;
6371
tb.block_info.base_fee = tb.expected_block_header.base_fee_per_gas;
6472
tb.block_info.parent_beacon_block_root = tb.expected_block_header.parent_beacon_block_root;
73+
tb.block_info.blob_gas_used = tb.expected_block_header.blob_gas_used;
74+
tb.block_info.excess_blob_gas = tb.expected_block_header.excess_blob_gas;
75+
6576
tb.block_info.blob_base_fee =
66-
compute_blob_gas_price(tb.expected_block_header.excess_blob_gas);
77+
tb.block_info.excess_blob_gas.has_value() ?
78+
std::optional(state::compute_blob_gas_price(*tb.block_info.excess_blob_gas)) :
79+
std::nullopt;
6780

6881
// Override prev_randao with difficulty pre-Merge
6982
if (rev < EVMC_PARIS)

test/blockchaintest/blockchaintest_runner.cpp

+27-3
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ TransitionResult apply_block(TestState& state, evmc::VM& vm, const state::BlockI
4242

4343
std::vector<state::Log> txs_logs;
4444
int64_t block_gas_left = block.gas_limit;
45-
int64_t blob_gas_left = 0;
45+
auto blob_gas_left = static_cast<int64_t>(block.blob_gas_used.value_or(0));
4646

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

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

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

103+
if (rev >= EVMC_CANCUN)
104+
{
105+
// `excess_blob_gas` and `blob_gas_used` mandatory after Cancun and invalid before.
106+
if (!test_block.block_info.excess_blob_gas.has_value() ||
107+
!test_block.block_info.blob_gas_used.has_value())
108+
return false;
109+
110+
// Check that the excess blob gas was updated correctly.
111+
if (*test_block.block_info.excess_blob_gas !=
112+
state::calc_excess_blob_gas(
113+
parent_header.blob_gas_used.value_or(0), parent_header.excess_blob_gas.value_or(0)))
114+
return false;
115+
116+
// Ensure the total blob gas spent is at most equal to the limit
117+
if (*test_block.block_info.blob_gas_used > state::BlockInfo::MAX_BLOB_GAS_PER_BLOCK)
118+
return false;
119+
}
120+
else
121+
{
122+
if (test_block.block_info.excess_blob_gas.has_value() ||
123+
test_block.block_info.blob_gas_used.has_value())
124+
return false;
125+
}
102126
return true;
103127
}
104128

test/state/block.cpp

+18-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
namespace evmone::state
99
{
10+
static constexpr uint64_t TARGET_BLOB_GAS_PER_BLOCK = 393216;
11+
1012
intx::uint256 compute_blob_gas_price(uint64_t excess_blob_gas) noexcept
1113
{
1214
/// A helper function approximating `factor * e ** (numerator / denominator)`.
@@ -16,10 +18,16 @@ intx::uint256 compute_blob_gas_price(uint64_t excess_blob_gas) noexcept
1618
intx::uint256 i = 1;
1719
intx::uint256 output = 0;
1820
intx::uint256 numerator_accum = factor * denominator;
21+
const intx::uint256 numerator256 = numerator;
1922
while (numerator_accum > 0)
2023
{
2124
output += numerator_accum;
22-
numerator_accum = (numerator_accum * numerator) / (denominator * i);
25+
// Ensure the multiplication won't overflow 256 bits.
26+
if (const auto p = intx::umul(numerator_accum, numerator256);
27+
p <= std::numeric_limits<intx::uint256>::max())
28+
numerator_accum = intx::uint256(p) / (denominator * i);
29+
else
30+
return std::numeric_limits<intx::uint256>::max();
2331
i += 1;
2432
}
2533
return output / denominator;
@@ -30,6 +38,15 @@ intx::uint256 compute_blob_gas_price(uint64_t excess_blob_gas) noexcept
3038
return fake_exponential(MIN_BLOB_GASPRICE, excess_blob_gas, BLOB_GASPRICE_UPDATE_FRACTION);
3139
}
3240

41+
uint64_t calc_excess_blob_gas(
42+
uint64_t parent_blob_gas_used, uint64_t parent_excess_blob_gas) noexcept
43+
{
44+
if (parent_excess_blob_gas + parent_blob_gas_used < TARGET_BLOB_GAS_PER_BLOCK)
45+
return 0;
46+
else
47+
return parent_excess_blob_gas + parent_blob_gas_used - TARGET_BLOB_GAS_PER_BLOCK;
48+
}
49+
3350
[[nodiscard]] bytes rlp_encode(const Withdrawal& withdrawal)
3451
{
3552
return rlp::encode_tuple(withdrawal.index, withdrawal.validator_index, withdrawal.recipient,

test/state/block.hpp

+11-5
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ struct Withdrawal
3333
struct BlockInfo
3434
{
3535
/// Max amount of blob gas allowed in block. It's constant now but can be dynamic in the future.
36-
static constexpr int64_t MAX_BLOB_GAS_PER_BLOCK = 786432;
36+
static constexpr uint64_t MAX_BLOB_GAS_PER_BLOCK = 786432;
3737

3838
int64_t number = 0;
3939
int64_t timestamp = 0;
@@ -49,13 +49,15 @@ struct BlockInfo
4949
/// The EIP-1559 base fee, since London.
5050
uint64_t base_fee = 0;
5151

52+
/// The "blob gas used" parameter from EIP-4844
53+
std::optional<uint64_t> blob_gas_used;
54+
5255
/// The "excess blob gas" parameter from EIP-4844
5356
/// for computing the blob gas price in the current block.
54-
uint64_t excess_blob_gas = 0;
57+
std::optional<uint64_t> excess_blob_gas;
5558

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

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

69+
/// Computes the current excess blob gas based on parameters of the parent block.
70+
uint64_t calc_excess_blob_gas(
71+
uint64_t parent_blob_gas_used, uint64_t parent_excess_blob_gas) noexcept;
72+
6773
/// Defines how to RLP-encode a Withdrawal.
6874
[[nodiscard]] bytes rlp_encode(const Withdrawal& withdrawal);
6975
} // namespace evmone::state

test/state/host.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -493,7 +493,7 @@ evmc_tx_context Host::get_tx_context() const noexcept
493493
m_block.prev_randao,
494494
0x01_bytes32, // Chain ID is expected to be 1.
495495
uint256be{m_block.base_fee},
496-
intx::be::store<uint256be>(m_block.blob_base_fee),
496+
intx::be::store<uint256be>(m_block.blob_base_fee.value_or(0)),
497497
m_tx.blob_hashes.data(),
498498
m_tx.blob_hashes.size(),
499499
};

test/state/state.cpp

+8-3
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,8 @@ std::variant<TransactionProperties, std::error_code> validate_transaction(
311311
if (tx.blob_hashes.empty())
312312
return make_error_code(EMPTY_BLOB_HASHES_LIST);
313313

314-
if (tx.max_blob_gas_price < block.blob_base_fee)
314+
assert(block.blob_base_fee.has_value());
315+
if (tx.max_blob_gas_price < *block.blob_base_fee)
315316
return make_error_code(FEE_CAP_LESS_THEN_BLOCKS);
316317

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

452453
if (tx.type == Transaction::Type::blob)
453454
{
454-
const auto blob_fee = tx.blob_gas_used() * block.blob_base_fee;
455+
// This uint64 * uint256 cannot overflow, because tx.blob_gas_used has limits enforced
456+
// before this stage.
457+
assert(block.blob_base_fee.has_value());
458+
const auto blob_fee = intx::umul(intx::uint256(tx.blob_gas_used()), *block.blob_base_fee);
459+
assert(blob_fee <= std::numeric_limits<intx::uint256>::max());
455460
assert(sender_acc.balance >= blob_fee); // Required for valid tx.
456-
sender_acc.balance -= blob_fee;
461+
sender_acc.balance -= intx::uint256(blob_fee);
457462
}
458463

459464
Host host{rev, vm, state, block, block_hashes, tx};

test/state/transaction.hpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,10 @@ struct Transaction
4343
};
4444

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

5252
Type type = Type::legacy;

test/statetest/statetest_loader.cpp

+2-4
Original file line numberDiff line numberDiff line change
@@ -232,10 +232,7 @@ state::BlockInfo from_json<state::BlockInfo>(const json::json& j)
232232
{
233233
const auto parent_excess_blob_gas = from_json<uint64_t>(*it);
234234
const auto parent_blob_gas_used = from_json<uint64_t>(j.at("parentBlobGasUsed"));
235-
static constexpr uint64_t TARGET_BLOB_GAS_PER_BLOCK = 0x60000;
236-
excess_blob_gas =
237-
std::max(parent_excess_blob_gas + parent_blob_gas_used, TARGET_BLOB_GAS_PER_BLOCK) -
238-
TARGET_BLOB_GAS_PER_BLOCK;
235+
excess_blob_gas = state::calc_excess_blob_gas(parent_blob_gas_used, parent_excess_blob_gas);
239236
}
240237
else if (const auto it2 = j.find("currentExcessBlobGas"); it2 != j.end())
241238
{
@@ -254,6 +251,7 @@ state::BlockInfo from_json<state::BlockInfo>(const json::json& j)
254251
.prev_randao = prev_randao,
255252
.parent_beacon_block_root = load_if_exists<hash256>(j, "parentBeaconBlockRoot"),
256253
.base_fee = base_fee,
254+
.blob_gas_used = load_if_exists<uint64_t>(j, "blobGasUsed"),
257255
.excess_blob_gas = excess_blob_gas,
258256
.blob_base_fee = state::compute_blob_gas_price(excess_blob_gas),
259257
.ommers = std::move(ommers),

test/t8n/t8n.cpp

+6-5
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ int main(int argc, const char* argv[])
115115
j_result["currentBaseFee"] = hex0x(block.base_fee);
116116

117117
int64_t cumulative_gas_used = 0;
118-
int64_t blob_gas_left = state::BlockInfo::MAX_BLOB_GAS_PER_BLOCK;
118+
auto blob_gas_left = static_cast<int64_t>(state::BlockInfo::MAX_BLOB_GAS_PER_BLOCK);
119119
std::vector<state::Transaction> transactions;
120120
std::vector<state::TransactionReceipt> receipts;
121121
int64_t block_gas_left = block.gas_limit;
@@ -209,7 +209,7 @@ int main(int argc, const char* argv[])
209209
j_receipt["root"] = "";
210210
j_receipt["status"] = "0x1";
211211
j_receipt["transactionIndex"] = hex0x(i);
212-
blob_gas_left -= tx.blob_gas_used();
212+
blob_gas_left -= static_cast<int64_t>(tx.blob_gas_used());
213213
transactions.emplace_back(std::move(tx));
214214
block_gas_left -= receipt.gas_used;
215215
receipts.emplace_back(std::move(receipt));
@@ -246,9 +246,10 @@ int main(int argc, const char* argv[])
246246
j_result["gasUsed"] = hex0x(cumulative_gas_used);
247247
if (rev >= EVMC_CANCUN)
248248
{
249-
j_result["blobGasUsed"] =
250-
hex0x(state::BlockInfo::MAX_BLOB_GAS_PER_BLOCK - blob_gas_left);
251-
j_result["currentExcessBlobGas"] = hex0x(block.excess_blob_gas);
249+
j_result["blobGasUsed"] = hex0x(
250+
static_cast<int64_t>(state::BlockInfo::MAX_BLOB_GAS_PER_BLOCK) - blob_gas_left);
251+
if (block.excess_blob_gas.has_value())
252+
j_result["currentExcessBlobGas"] = hex0x(*block.excess_blob_gas);
252253
}
253254
if (rev >= EVMC_PRAGUE)
254255
{

test/unittests/state_transition.cpp

+10
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,16 @@ void state_transition::export_state_test(
222222
}
223223
}
224224

225+
if (tx.type == Transaction::Type::blob)
226+
{
227+
jtx["maxFeePerBlobGas"] = hex0x(tx.max_blob_gas_price);
228+
jtx["blobVersionedHashes"] = json::json::array();
229+
for (const auto& blob_hash : tx.blob_hashes)
230+
{
231+
jtx["blobVersionedHashes"].emplace_back(hex0x(blob_hash));
232+
}
233+
}
234+
225235
auto& jpost = jt["post"][to_test_fork_name(rev)][0];
226236
jpost["indexes"] = {{"data", 0}, {"gas", 0}, {"value", 0}};
227237
jpost["hash"] = hex0x(mpt_hash(post));

test/unittests/state_transition_block_test.cpp

+7-2
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,14 @@ TEST_F(state_transition, eip7516_blob_base_fee)
6666
{
6767
rev = EVMC_CANCUN;
6868

69-
block.blob_base_fee = 0xabcd;
69+
block.excess_blob_gas = 0xabcd00;
70+
// 0x1d is the result of ref implementation in EIP-4844
71+
static constexpr auto price = 0x1d;
72+
block.blob_base_fee = price;
73+
assert(state::compute_blob_gas_price(*block.excess_blob_gas) == *block.blob_base_fee);
74+
7075
tx.to = To;
7176
pre.insert(*tx.to, {.code = sstore(0x4a, OP_BLOBBASEFEE)});
7277

73-
expect.post[To].storage[0x4a_bytes32] = 0xabcd_bytes32;
78+
expect.post[To].storage[0x4a_bytes32] = bytes32(price);
7479
}

test/unittests/state_transition_tx_test.cpp

+4
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ TEST_F(state_transition, tx_blob_gas_price)
6262
0x0100000000000000000000000000000000000000000000000000000000000000_bytes32);
6363
tx.max_blob_gas_price = 1;
6464

65+
block.excess_blob_gas = 0;
66+
block.blob_base_fee = 1;
67+
block.blob_gas_used = 786432;
68+
6569
pre.get(tx.sender).balance = 0x20000 + tx.gas_limit * tx.max_gas_price;
6670

6771
expect.post[Coinbase].exists = false; // all gas is burned, Coinbase gets nothing

test/unittests/state_tx_test.cpp

+5-1
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,11 @@ TEST(state_tx, validate_sender)
8888

8989
TEST(state_tx, validate_blob_tx)
9090
{
91-
const BlockInfo bi{.gas_limit = 0x989680, .base_fee = 1, .blob_base_fee = 1};
91+
const BlockInfo bi{.gas_limit = 0x989680,
92+
.base_fee = 1,
93+
.blob_gas_used = 786432,
94+
.excess_blob_gas = 0,
95+
.blob_base_fee = 1};
9296
Transaction tx{
9397
.type = Transaction::Type::blob,
9498
.gas_limit = 60000,

0 commit comments

Comments
 (0)