|
4 | 4 |
|
5 | 5 | #include "blockchaintest_runner.hpp" |
6 | 6 | #include <gtest/gtest.h> |
| 7 | +#include <test/state/errors.hpp> |
7 | 8 | #include <test/state/ethash_difficulty.hpp> |
8 | 9 | #include <test/state/requests.hpp> |
9 | 10 | #include <test/utils/mpt_hash.hpp> |
@@ -125,102 +126,104 @@ TransitionResult apply_block(const TestState& state, evmc::VM& vm, const state:: |
125 | 126 | bloom, blob_gas_left, std::move(block_state)}; |
126 | 127 | } |
127 | 128 |
|
128 | | -bool validate_block(evmc_revision rev, state::BlobParams blob_params, const TestBlock& test_block, |
129 | | - const BlockHeader* parent_header, bool parent_has_ommers) noexcept |
| 129 | +/// Validates block-level validity unrelated to individual transactions. |
| 130 | +/// |
| 131 | +/// Returns an empty error_code if the block is valid, otherwise the specific validation error. |
| 132 | +std::error_code validate_block(evmc_revision rev, state::BlobParams blob_params, |
| 133 | + const TestBlock& test_block, const BlockHeader* parent_header, bool parent_has_ommers) noexcept |
130 | 134 | { |
131 | | - // NOTE: includes only block validity unrelated to individual txs. See `apply_block`. |
| 135 | + using namespace state; |
132 | 136 |
|
133 | | - // Fail if parent header was not found. |
| 137 | + // Fail if parent header was not found: the block references a parent that is neither the |
| 138 | + // genesis nor any previously-accepted block (an unknown or rejected parent). |
134 | 139 | if (parent_header == nullptr) |
135 | | - return false; |
| 140 | + return make_error_code(INVALID_BLOCK_PARENT); |
136 | 141 |
|
137 | 142 | if (test_block.block_info.number != parent_header->block_number + 1) |
138 | | - return false; |
| 143 | + return make_error_code(INVALID_BLOCK_NUMBER); |
139 | 144 |
|
140 | 145 | if (test_block.block_info.gas_used > test_block.block_info.gas_limit) |
141 | | - return false; |
| 146 | + return make_error_code(INCORRECT_BLOCK_FORMAT); |
142 | 147 |
|
143 | 148 | // Some tests have gas limit at INT64_MAX, so we cast to uint64_t to avoid overflow. |
144 | 149 | const auto parent_header_gas_limit_u64 = static_cast<uint64_t>(parent_header->gas_limit); |
145 | 150 | const auto test_block_gas_limit_u64 = static_cast<uint64_t>(test_block.block_info.gas_limit); |
146 | 151 | if (test_block_gas_limit_u64 >= |
147 | 152 | parent_header_gas_limit_u64 + parent_header_gas_limit_u64 / 1024) |
148 | | - return false; |
| 153 | + return make_error_code(INVALID_GASLIMIT); |
149 | 154 | if (test_block_gas_limit_u64 <= |
150 | 155 | parent_header_gas_limit_u64 - parent_header_gas_limit_u64 / 1024) |
151 | | - return false; |
| 156 | + return make_error_code(INVALID_GASLIMIT); |
152 | 157 |
|
153 | 158 | // Block gas limit minimum from Yellow Paper. |
154 | 159 | if (test_block.block_info.gas_limit < 5000) |
155 | | - return false; |
| 160 | + return make_error_code(INVALID_GASLIMIT); |
156 | 161 |
|
157 | 162 | // FIXME: Some tests have timestamp not fitting into int64_t, type has to be uint64_t. |
158 | 163 | if (static_cast<uint64_t>(test_block.block_info.timestamp) <= |
159 | 164 | static_cast<uint64_t>(parent_header->timestamp)) |
160 | | - return false; |
| 165 | + return make_error_code(INVALID_BLOCK_TIMESTAMP_OLDER_THAN_PARENT); |
161 | 166 |
|
162 | | - if (test_block.block_info.difficulty != state::calculate_difficulty(parent_header->difficulty, |
163 | | - parent_has_ommers, parent_header->timestamp, |
164 | | - test_block.block_info.timestamp, |
165 | | - test_block.block_info.number, rev)) |
166 | | - return false; |
| 167 | + if (test_block.block_info.difficulty != |
| 168 | + calculate_difficulty(parent_header->difficulty, parent_has_ommers, parent_header->timestamp, |
| 169 | + test_block.block_info.timestamp, test_block.block_info.number, rev)) |
| 170 | + return make_error_code(INCORRECT_BLOCK_FORMAT); |
167 | 171 |
|
168 | 172 | if (rev >= EVMC_PARIS && !test_block.block_info.ommers.empty()) |
169 | | - return false; |
170 | | - |
| 173 | + return make_error_code(INCORRECT_BLOCK_FORMAT); |
171 | 174 |
|
172 | 175 | for (const auto& ommer : test_block.block_info.ommers) |
173 | 176 | { |
174 | 177 | // Check that ommer block number difference with current block is within allowed range. |
175 | 178 | // https://github.com/ethereum/execution-specs/blob/ee73be5c4d83a2e3c358bd14990878002e52ba9e/src/ethereum/gray_glacier/fork.py#L623 |
176 | 179 | if (ommer.delta < 1 || ommer.delta > 6) |
177 | | - return false; |
| 180 | + return make_error_code(INCORRECT_BLOCK_FORMAT); |
178 | 181 | } |
179 | 182 |
|
180 | 183 | if (test_block.block_info.extra_data.size() > 32) |
181 | | - return false; |
| 184 | + return make_error_code(INCORRECT_BLOCK_FORMAT); |
182 | 185 |
|
183 | 186 | if (rev >= EVMC_LONDON) |
184 | 187 | { |
185 | | - const auto calculated_base_fee = state::calc_base_fee( |
| 188 | + const auto calculated_base_fee = calc_base_fee( |
186 | 189 | parent_header->gas_limit, parent_header->gas_used, parent_header->base_fee_per_gas); |
187 | 190 | if (test_block.block_info.base_fee != calculated_base_fee) |
188 | | - return false; |
| 191 | + return make_error_code(INVALID_BASEFEE_PER_GAS); |
189 | 192 | } |
190 | 193 |
|
191 | 194 | if (rev >= EVMC_CANCUN) |
192 | 195 | { |
193 | 196 | // `excess_blob_gas` and `blob_gas_used` mandatory after Cancun and invalid before. |
194 | 197 | if (!test_block.block_info.excess_blob_gas.has_value() || |
195 | 198 | !test_block.block_info.blob_gas_used.has_value()) |
196 | | - return false; |
| 199 | + return make_error_code(INCORRECT_BLOCK_FORMAT); |
197 | 200 |
|
198 | 201 | // Check that the excess blob gas was updated correctly. |
199 | 202 | // According to EIP-7918 current blocks params (`rev`) should be used for parent base fee |
200 | 203 | // calculation. |
201 | 204 | const auto parent_blob_base_fee = |
202 | | - state::compute_blob_gas_price(blob_params, parent_header->excess_blob_gas.value_or(0)); |
| 205 | + compute_blob_gas_price(blob_params, parent_header->excess_blob_gas.value_or(0)); |
203 | 206 | if (*test_block.block_info.excess_blob_gas != |
204 | | - state::calc_excess_blob_gas(rev, blob_params, parent_header->blob_gas_used.value_or(0), |
| 207 | + calc_excess_blob_gas(rev, blob_params, parent_header->blob_gas_used.value_or(0), |
205 | 208 | parent_header->excess_blob_gas.value_or(0), parent_header->base_fee_per_gas, |
206 | 209 | parent_blob_base_fee)) |
207 | | - return false; |
| 210 | + return make_error_code(INCORRECT_EXCESS_BLOB_GAS); |
208 | 211 | } |
209 | 212 | else |
210 | 213 | { |
211 | 214 | if (test_block.block_info.excess_blob_gas.has_value() || |
212 | 215 | test_block.block_info.blob_gas_used.has_value()) |
213 | | - return false; |
| 216 | + return make_error_code(INCORRECT_BLOCK_FORMAT); |
214 | 217 | } |
215 | 218 |
|
216 | 219 | // Block is invalid if some of the withdrawal fields failed to be parsed. |
217 | 220 | if (!test_block.withdrawals_parse_success) |
218 | | - return false; |
| 221 | + return make_error_code(INCORRECT_BLOCK_FORMAT); |
219 | 222 |
|
220 | 223 | if (rev >= EVMC_OSAKA && test_block.rlp_size > MAX_RLP_BLOCK_SIZE) |
221 | | - return false; |
| 224 | + return make_error_code(RLP_BLOCK_LIMIT_EXCEEDED); |
222 | 225 |
|
223 | | - return true; |
| 226 | + return {}; |
224 | 227 | } |
225 | 228 |
|
226 | 229 | std::optional<int64_t> mining_reward(evmc_revision rev) noexcept |
@@ -315,11 +318,13 @@ void run_blockchain_tests(std::span<const BlockchainTest> tests, evmc::VM& vm) |
315 | 318 | SCOPED_TRACE(std::string{evmc::to_string(rev)} + '/' + std::to_string(case_index) + |
316 | 319 | '/' + c.name + '/' + std::to_string(test_block.block_info.number)); |
317 | 320 |
|
318 | | - if (test_block.valid) |
| 321 | + const auto block_error = |
| 322 | + validate_block(rev, blob_params, test_block, parent_header, parent_has_ommers); |
| 323 | + |
| 324 | + if (test_block.expected_exception.empty()) |
319 | 325 | { |
320 | | - ASSERT_TRUE( |
321 | | - validate_block(rev, blob_params, test_block, parent_header, parent_has_ommers)) |
322 | | - << "Expected block to be valid (validate_block)"; |
| 326 | + ASSERT_FALSE(block_error) |
| 327 | + << "Expected block to be valid (validate_block): " << block_error.message(); |
323 | 328 |
|
324 | 329 | // Block being valid guarantees its parent was found. |
325 | 330 | assert(parent_data_it != block_data.end()); |
@@ -377,8 +382,19 @@ void run_blockchain_tests(std::span<const BlockchainTest> tests, evmc::VM& vm) |
377 | 382 | } |
378 | 383 | else |
379 | 384 | { |
380 | | - if (!validate_block(rev, blob_params, test_block, parent_header, parent_has_ommers)) |
| 385 | + if (block_error) |
| 386 | + { |
| 387 | + // Block correctly rejected at validation; verify the reason matches the |
| 388 | + // fixture's expected exception. The error message is the `BlockException` |
| 389 | + // constant; `expected_exception` may list `|`-separated alternatives, so a |
| 390 | + // substring search suffices as long as no constant name is a substring of |
| 391 | + // another (true for the constants evmone produces). |
| 392 | + EXPECT_NE(test_block.expected_exception.find(block_error.message()), |
| 393 | + std::string::npos) |
| 394 | + << "Block invalidity reason mismatch: got " << block_error.message() |
| 395 | + << ", expected " << test_block.expected_exception; |
381 | 396 | continue; |
| 397 | + } |
382 | 398 |
|
383 | 399 | // Block being valid guarantees its parent was found. |
384 | 400 | assert(parent_data_it != block_data.end()); |
|
0 commit comments