diff --git a/test/blockchaintest/blockchaintest.cpp b/test/blockchaintest/blockchaintest.cpp index 0df9373de9..bf795491e9 100644 --- a/test/blockchaintest/blockchaintest.cpp +++ b/test/blockchaintest/blockchaintest.cpp @@ -32,8 +32,8 @@ bool collect_tests(std::vector& cases, const fs::path& root, cases.push_back( {file.path.string(), [path = file.path, &vm](evmone::test::TestReport& report) { std::ifstream f{path}; - evmone::test::run_blockchain_tests( - evmone::test::load_blockchain_tests(f), vm, report); + for (const auto& test : evmone::test::load_blockchain_tests(f)) + evmone::test::run_blockchain_test(test, vm, report); }}); } } @@ -67,7 +67,7 @@ bool collect_tests(std::vector& cases, const fs::path& root, { cases.push_back( {root.string() + "::" + test.name, [test, &vm](evmone::test::TestReport& report) { - evmone::test::run_blockchain_tests({&test, 1}, vm, report); + evmone::test::run_blockchain_test(test, vm, report); }}); } } diff --git a/test/integration/blockchaintest/CMakeLists.txt b/test/integration/blockchaintest/CMakeLists.txt index 10662377a8..031ba8e0ae 100644 --- a/test/integration/blockchaintest/CMakeLists.txt +++ b/test/integration/blockchaintest/CMakeLists.txt @@ -63,6 +63,18 @@ set_tests_properties( PASS_REGULAR_EXPRESSION "SKIPPED [^\n]*unsupported_rlp\\.json - tests with invalidly" ) +# A test which gives up gives up only itself. Collected from a directory the whole file is one +# test, so the test after the one which stopped has to report too. +add_test( + NAME ${PREFIX}/stops_at_one_test + COMMAND evmone-blockchaintest ${TESTS1} +) +set_tests_properties( + ${PREFIX}/stops_at_one_test PROPERTIES + PASS_REGULAR_EXPRESSION + "a_stops_early:.*block validity.*b_still_runs:.*post state root" +) + # --collect-only lists what would run instead of running it, and --ignore drops a path from that # list. Over a directory a test is listed as its file. add_test( diff --git a/test/integration/blockchaintest/stops_at_one_test.json b/test/integration/blockchaintest/stops_at_one_test.json new file mode 100644 index 0000000000..4a41b2c558 --- /dev/null +++ b/test/integration/blockchaintest/stops_at_one_test.json @@ -0,0 +1,128 @@ +{ + "a_stops_early": { + "network": "Amsterdam", + "genesisBlockHeader": { + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "uncleHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "coinbase": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionsTrie": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "receiptTrie": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "bloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "difficulty": "0x00", + "number": "0x00", + "gasLimit": "0x01000000", + "gasUsed": "0x00", + "timestamp": "0x00", + "extraData": "0x00", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "baseFeePerGas": "0x10", + "withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "blobGasUsed": "0x00", + "excessBlobGas": "0x00", + "parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "requestsHash": "0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "slotNumber": "0x00", + "hash": "0x0000000000000000000000000000000000000000000000000000000000000001" + }, + "pre": { + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "code": "0x", + "nonce": "0x00", + "balance": "0x02540be400" + } + }, + "blocks": [ + { + "rlp": "0xf90285f90259a00000000000000000000000000000000000000000000000000000000000000001a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794000000000000000000000000000000000000c014a09906207bb91a552f2c9036ff75ffebd15d6262fe1f1dc40afa6e047158ee25e5a06e1d1e299aa4bca4b32c05a8bd8cf631d97c7d9b54b1401c373860db01a38efea0c220348cfca03fd761c800a2af15752cee5ba04f836e61f798c4c6222c0cab02b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800184010000008265960c80a000000000000000000000000000000000000000000000000000000000000000008800000000000000000ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b4218080a00000000000000000000000000000000000000000000000000000000000000000a0e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855e6a502e30180800e830186a094000000000000000000000000000000000000c0de8080c0800101c0c0", + "blockHeader": { + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000001", + "uncleHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "coinbase": "0x000000000000000000000000000000000000c014", + "stateRoot": "0x9906207bb91a552f2c9036ff75ffebd15d6262fe1f1dc40afa6e047158ee25e5", + "transactionsTrie": "0x6e1d1e299aa4bca4b32c05a8bd8cf631d97c7d9b54b1401c373860db01a38efe", + "receiptTrie": "0xc220348cfca03fd761c800a2af15752cee5ba04f836e61f798c4c6222c0cab02", + "bloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "difficulty": "0x00", + "number": "0x01", + "gasLimit": "0x01000000", + "gasUsed": "0x6596", + "timestamp": "0x00", + "extraData": "0x", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "baseFeePerGas": "0x0e", + "withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "blobGasUsed": "0x00", + "excessBlobGas": "0x00", + "parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "requestsHash": "0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "slotNumber": "0x00", + "hash": "0x0000000000000000000000000000000000000000000000000000000000000002" + }, + "transactions": [], + "withdrawals": [], + "uncleHeaders": [] + } + ], + "lastblockhash": "0x0000000000000000000000000000000000000000000000000000000000000002", + "postState": {}, + "config": { + "network": "Amsterdam", + "chainid": "0x01" + }, + "genesisRLP": "0x", + "sealEngine": "NoProof", + "_info": { + "comment": "Its block does not advance the timestamp, so it is invalid where the test says it is valid, and the test stops there." + } + }, + "b_still_runs": { + "_info": { + "comment": "Runs only if the test before it stopped by itself rather than taking the rest of the file with it. Its post state is wrong, so reaching it is visible." + }, + "network": "Amsterdam", + "genesisBlockHeader": { + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "uncleHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "coinbase": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionsTrie": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "receiptTrie": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "bloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "difficulty": "0x00", + "number": "0x00", + "gasLimit": "0x01000000", + "gasUsed": "0x00", + "timestamp": "0x00", + "extraData": "0x00", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "baseFeePerGas": "0x10", + "withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "blobGasUsed": "0x00", + "excessBlobGas": "0x00", + "parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "requestsHash": "0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "slotNumber": "0x00", + "hash": "0x0000000000000000000000000000000000000000000000000000000000000001" + }, + "pre": { + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "code": "0x", + "nonce": "0x00", + "balance": "0x02540be400" + } + }, + "blocks": [], + "lastblockhash": "0x0000000000000000000000000000000000000000000000000000000000000001", + "postState": {}, + "config": { + "network": "Amsterdam", + "chainid": "0x01" + }, + "genesisRLP": "0x", + "sealEngine": "NoProof" + } +} diff --git a/test/unittests/blockchaintest_runner_test.cpp b/test/unittests/blockchaintest_runner_test.cpp index f16abd5ca9..87797bb3d6 100644 --- a/test/unittests/blockchaintest_runner_test.cpp +++ b/test/unittests/blockchaintest_runner_test.cpp @@ -56,7 +56,7 @@ std::vector run(const BlockchainTest& t) std::vector failures; TestReport report{[&](const Failure& failure) { failures.push_back(failure); }}; evmc::VM vm{evmc_create_evmone()}; - run_blockchain_tests({&t, 1}, vm, report); + run_blockchain_test(t, vm, report); return failures; } } // namespace diff --git a/test/utils/blockchaintest.hpp b/test/utils/blockchaintest.hpp index 20321fd6ad..bcb922ef01 100644 --- a/test/utils/blockchaintest.hpp +++ b/test/utils/blockchaintest.hpp @@ -80,6 +80,6 @@ std::vector load_blockchain_tests(std::istream& input); /// Builds the test named @p name in a fixture file from its JSON value @p j. BlockchainTest make_blockchain_test(const std::string& name, const json::json& j); -/// Execute the blockchain @p tests using the @p vm, recording what does not match into @p report. -void run_blockchain_tests(std::span tests, evmc::VM& vm, TestReport& report); +/// Execute the blockchain @p test using the @p vm, recording what does not match into @p report. +void run_blockchain_test(const BlockchainTest& test, evmc::VM& vm, TestReport& report); } // namespace evmone::test diff --git a/test/utils/blockchaintest_runner.cpp b/test/utils/blockchaintest_runner.cpp index 6cc6c65834..d97c7c36f6 100644 --- a/test/utils/blockchaintest_runner.cpp +++ b/test/utils/blockchaintest_runner.cpp @@ -212,300 +212,291 @@ std::string print_state(const TestState& s) } } // namespace -void run_blockchain_tests(std::span tests, evmc::VM& vm, TestReport& report) +void run_blockchain_test(const BlockchainTest& test, evmc::VM& vm, TestReport& report) { - for (size_t case_index = 0; case_index != tests.size(); ++case_index) + const auto rev_schedule = to_rev_schedule(test.network); + report.start_case(test.name); + // The network names the whole schedule, so a fork transition shows at the test level and a + // block needs only its index. The block number would not do: an invalid block does not + // advance it, so two of them can share one. + const auto in_test = report.at(test.network); + + // Validate the genesis block header. + report.check_eq("genesis block number", test.genesis_block_header.block_number, 0); + report.check_eq("genesis gas used", test.genesis_block_header.gas_used, 0); + report.check_eq("genesis transactions root", test.genesis_block_header.transactions_root, + state::EMPTY_MPT_HASH); + report.check_eq( + "genesis receipts root", test.genesis_block_header.receipts_root, state::EMPTY_MPT_HASH); + report.check_eq("genesis withdrawals root", test.genesis_block_header.withdrawal_root, + rev_schedule.get_revision(test.genesis_block_header.timestamp) >= EVMC_SHANGHAI ? + state::EMPTY_MPT_HASH : + bytes32{}); + report.check_eq("genesis logs bloom", bytes_view{test.genesis_block_header.logs_bloom}, + bytes_view{state::BloomFilter{}}); + + TestBlockHashes block_hashes{ + {test.genesis_block_header.block_number, test.genesis_block_header.hash}}; + + struct BlockData { - const auto& c = tests[case_index]; - const auto rev_schedule = to_rev_schedule(c.network); - report.start_case(c.name); - // The network names the whole schedule, so a fork transition shows at the case level and - // a block needs only its index. The block number would not do: an invalid block does not - // advance it, so two of them can share one. - const auto in_case = report.at(c.network, '/', case_index); - - // Validate the genesis block header. - report.check_eq("genesis block number", c.genesis_block_header.block_number, 0); - report.check_eq("genesis gas used", c.genesis_block_header.gas_used, 0); - report.check_eq("genesis transactions root", c.genesis_block_header.transactions_root, - state::EMPTY_MPT_HASH); - report.check_eq( - "genesis receipts root", c.genesis_block_header.receipts_root, state::EMPTY_MPT_HASH); - report.check_eq("genesis withdrawals root", c.genesis_block_header.withdrawal_root, - rev_schedule.get_revision(c.genesis_block_header.timestamp) >= EVMC_SHANGHAI ? - state::EMPTY_MPT_HASH : - bytes32{}); - report.check_eq("genesis logs bloom", bytes_view{c.genesis_block_header.logs_bloom}, - bytes_view{state::BloomFilter{}}); - - TestBlockHashes block_hashes{ - {c.genesis_block_header.block_number, c.genesis_block_header.hash}}; - - struct BlockData - { - const BlockHeader* header; - bool has_ommers = false; - TestState post_state; - intx::uint256 total_difficulty; - }; - std::unordered_map block_data{{{c.genesis_block_header.hash, - {&c.genesis_block_header, false, c.pre_state, c.genesis_block_header.difficulty}}}}; - const auto* canonical_state = &c.pre_state; - hash256 canonical_state_root; // Skip pre-state root hash computation (maybe not needed). - auto canonical_tip_hash = c.genesis_block_header.hash; - intx::uint256 max_total_difficulty = c.genesis_block_header.difficulty; - - for (size_t i = 0; i < c.test_blocks.size(); ++i) - { - const auto& test_block = c.test_blocks[i]; - const auto& bi = test_block.block_info; + const BlockHeader* header; + bool has_ommers = false; + TestState post_state; + intx::uint256 total_difficulty; + }; + std::unordered_map block_data{ + {{test.genesis_block_header.hash, {&test.genesis_block_header, false, test.pre_state, + test.genesis_block_header.difficulty}}}}; + const auto* canonical_state = &test.pre_state; + hash256 canonical_state_root; // Skip pre-state root hash computation (maybe not needed). + auto canonical_tip_hash = test.genesis_block_header.hash; + intx::uint256 max_total_difficulty = test.genesis_block_header.difficulty; + + for (size_t i = 0; i < test.test_blocks.size(); ++i) + { + const auto& test_block = test.test_blocks[i]; + const auto& bi = test_block.block_info; - const auto parent_data_it = block_data.find(test_block.block_info.parent_hash); - const auto* parent_header = - parent_data_it != block_data.end() ? parent_data_it->second.header : nullptr; - const auto parent_has_ommers = - parent_data_it != block_data.end() && parent_data_it->second.has_ommers; + const auto parent_data_it = block_data.find(test_block.block_info.parent_hash); + const auto* parent_header = + parent_data_it != block_data.end() ? parent_data_it->second.header : nullptr; + const auto parent_has_ommers = + parent_data_it != block_data.end() && parent_data_it->second.has_ommers; - const auto rev = rev_schedule.get_revision(bi.timestamp); - const auto blob_params = get_blob_params(c.network, c.blob_schedule, bi.timestamp); - const auto blob_gas_limit = - static_cast(state::max_blob_gas_per_block(blob_params)); + const auto rev = rev_schedule.get_revision(bi.timestamp); + const auto blob_params = get_blob_params(test.network, test.blob_schedule, bi.timestamp); + const auto blob_gas_limit = + static_cast(state::max_blob_gas_per_block(blob_params)); - const auto in_block = report.at(i); + const auto in_block = report.at(i); - // Invalid blocks are skipped: they may carry transactions that do not even decode. - if (test_block.expected_exception.empty()) - check_transactions_round_trip(test_block.rlp, report); + // Invalid blocks are skipped: they may carry transactions that do not even decode. + if (test_block.expected_exception.empty()) + check_transactions_round_trip(test_block.rlp, report); - const auto block_error = - validate_block(rev, blob_params, test_block, parent_header, parent_has_ommers); + const auto block_error = + validate_block(rev, blob_params, test_block, parent_header, parent_has_ommers); - if (test_block.expected_exception.empty()) + if (test_block.expected_exception.empty()) + { + if (block_error) { - if (block_error) - { - report.fail("block validity", - "expected the block to be valid: " + block_error.message()); - // TODO: This and the requests failure below abandon the whole file, not - // just this case. Give each case its own run so only that case stops. - return; - } + report.fail( + "block validity", "expected the block to be valid: " + block_error.message()); + return; + } - // Block being valid guarantees its parent was found. - assert(parent_data_it != block_data.end()); - const auto& pre_state = parent_data_it->second.post_state; + // Block being valid guarantees its parent was found. + assert(parent_data_it != block_data.end()); + const auto& pre_state = parent_data_it->second.post_state; - auto res = apply_block(pre_state, vm, bi, block_hashes, test_block.transactions, - rev, blob_gas_limit, {.block_reward = mining_reward(rev)}); + auto res = apply_block(pre_state, vm, bi, block_hashes, test_block.transactions, rev, + blob_gas_limit, {.block_reward = mining_reward(rev)}); - if (res.requests_error) - { - report.fail("requests", res.requests_error.message()); - return; - } + if (res.requests_error) + { + report.fail("requests", res.requests_error.message()); + return; + } - block_hashes[test_block.expected_block_header.block_number] = - test_block.expected_block_header.hash; - const auto [inserted_it, _] = block_data.insert({test_block.block_info.hash, - { - .header = &test_block.expected_block_header, - .has_ommers = !test_block.block_info.ommers.empty(), - .post_state = std::move(res.block_state), - .total_difficulty = parent_data_it->second.total_difficulty + - test_block.block_info.difficulty, - }}); + block_hashes[test_block.expected_block_header.block_number] = + test_block.expected_block_header.hash; + const auto [inserted_it, _] = block_data.insert({test_block.block_info.hash, + { + .header = &test_block.expected_block_header, + .has_ommers = !test_block.block_info.ommers.empty(), + .post_state = std::move(res.block_state), + .total_difficulty = + parent_data_it->second.total_difficulty + test_block.block_info.difficulty, + }}); - const auto state_root = state::mpt_hash(inserted_it->second.post_state); + const auto state_root = state::mpt_hash(inserted_it->second.post_state); - if (inserted_it->second.total_difficulty >= max_total_difficulty) - { - canonical_state = &inserted_it->second.post_state; - canonical_state_root = state_root; - canonical_tip_hash = test_block.expected_block_header.hash; - max_total_difficulty = inserted_it->second.total_difficulty; - } + if (inserted_it->second.total_difficulty >= max_total_difficulty) + { + canonical_state = &inserted_it->second.post_state; + canonical_state_root = state_root; + canonical_tip_hash = test_block.expected_block_header.hash; + max_total_difficulty = inserted_it->second.total_difficulty; + } - if (!res.rejected.empty()) - { - report.fail("transactions in a valid block", - "invalid transaction: " + res.rejected.front().error.message()); - } + if (!res.rejected.empty()) + { + report.fail("transactions in a valid block", + "invalid transaction: " + res.rejected.front().error.message()); + } - report.check_eq("blob gas used", blob_gas_limit - res.blob_gas_left, - static_cast(bi.blob_gas_used.value_or(0))); - report.check_eq( - "state root", state_root, test_block.expected_block_header.state_root); + report.check_eq("blob gas used", blob_gas_limit - res.blob_gas_left, + static_cast(bi.blob_gas_used.value_or(0))); + report.check_eq("state root", state_root, test_block.expected_block_header.state_root); - if (rev >= EVMC_SHANGHAI) - { - report.check_eq("withdrawals root", - state::mpt_hash(test_block.block_info.withdrawals), - test_block.expected_block_header.withdrawal_root); - } + if (rev >= EVMC_SHANGHAI) + { + report.check_eq("withdrawals root", + state::mpt_hash(test_block.block_info.withdrawals), + test_block.expected_block_header.withdrawal_root); + } - report.check_eq("transactions root", state::mpt_hash(test_block.transactions), - test_block.expected_block_header.transactions_root); - report.check_eq("receipts root", state::mpt_hash(res.receipts), - test_block.expected_block_header.receipts_root); - if (rev >= EVMC_PRAGUE) - { - report.check_eq("requests hash", calculate_requests_hash(res.requests), - test_block.expected_block_header.requests_hash); - } - report.check_eq( - "gas used", res.gas_used, test_block.expected_block_header.gas_used); - report.check_eq("logs bloom", bytes_view{res.bloom}, - bytes_view{test_block.expected_block_header.logs_bloom}); + report.check_eq("transactions root", state::mpt_hash(test_block.transactions), + test_block.expected_block_header.transactions_root); + report.check_eq("receipts root", state::mpt_hash(res.receipts), + test_block.expected_block_header.receipts_root); + if (rev >= EVMC_PRAGUE) + { + report.check_eq("requests hash", calculate_requests_hash(res.requests), + test_block.expected_block_header.requests_hash); } - else + report.check_eq("gas used", res.gas_used, test_block.expected_block_header.gas_used); + report.check_eq("logs bloom", bytes_view{res.bloom}, + bytes_view{test_block.expected_block_header.logs_bloom}); + } + else + { + if (block_error) { - if (block_error) - { - // Block correctly rejected at validation; verify the reason matches the - // fixture's expected exception. - report.check( - is_expected_block_exception(block_error, test_block.expected_exception), - "block rejection reason", block_error, test_block.expected_exception); - continue; - } + // Block correctly rejected at validation; verify the reason matches the + // fixture's expected exception. + report.check( + is_expected_block_exception(block_error, test_block.expected_exception), + "block rejection reason", block_error, test_block.expected_exception); + continue; + } - // Block being valid guarantees its parent was found. - assert(parent_data_it != block_data.end()); - const auto& pre_state = parent_data_it->second.post_state; - - // Legacy fixtures name the broken rule in vocabulary evmone does not speak - // (InvalidStateRoot, TooManyUncles); only the spec names can be compared. - const auto names_spec_exception = - test_block.expected_exception.find("Exception.") != std::string::npos; - - // TODO: The transaction senders come from the fixture instead of being recovered - // from the signatures, so evmone never sees the signature the test broke. Such a - // transaction executes as the sender the fixture names and the block is rejected - // by whatever rule that sender happens to break, or by its state root alone. - const auto sender_not_recovered = contains_any( - test_block.expected_exception, "TransactionException.INVALID_SIGNATURE_VRS"); - - const auto res = - apply_block(pre_state, vm, bi, block_hashes, test_block.transactions, rev, - blob_gas_limit, {.block_reward = mining_reward(rev)}); - if (!res.rejected.empty()) - { - // A transaction was rejected: the fixture must name that reason, not merely - // some rejection. - const auto& rejected = res.rejected.front(); - if (names_spec_exception && !sender_not_recovered) - { - report.check( - is_expected_tx_exception(rejected.error, test_block.expected_exception), - "transaction rejection reason", rejected.error, - test_block.expected_exception); - } - continue; - } - if (res.requests_error) + // Block being valid guarantees its parent was found. + assert(parent_data_it != block_data.end()); + const auto& pre_state = parent_data_it->second.post_state; + + // Legacy fixtures name the broken rule in vocabulary evmone does not speak + // (InvalidStateRoot, TooManyUncles); only the spec names can be compared. + const auto names_spec_exception = + test_block.expected_exception.find("Exception.") != std::string::npos; + + // TODO: The transaction senders come from the fixture instead of being recovered + // from the signatures, so evmone never sees the signature the test broke. Such a + // transaction executes as the sender the fixture names and the block is rejected + // by whatever rule that sender happens to break, or by its state root alone. + const auto sender_not_recovered = contains_any( + test_block.expected_exception, "TransactionException.INVALID_SIGNATURE_VRS"); + + const auto res = apply_block(pre_state, vm, bi, block_hashes, test_block.transactions, + rev, blob_gas_limit, {.block_reward = mining_reward(rev)}); + if (!res.rejected.empty()) + { + // A transaction was rejected: the fixture must name that reason, not merely + // some rejection. + const auto& rejected = res.rejected.front(); + if (names_spec_exception && !sender_not_recovered) { - if (!sender_not_recovered) - { - report.check(is_expected_block_exception( - res.requests_error, test_block.expected_exception), - "block rejection reason", res.requests_error, - test_block.expected_exception); - } - continue; + report.check( + is_expected_tx_exception(rejected.error, test_block.expected_exception), + "transaction rejection reason", rejected.error, + test_block.expected_exception); } - // The block executed, so it is invalid only if it computes something other than - // its header claims. Each difference below is the symptom of one BlockException: - // a block failing a check other than the one the fixture names breaks a different - // rule than the test is about. - // TODO: Of the ommers only the count and the distance to their nephew are - // validated, not the ommer headers themselves, so a fixture that breaks an - // ommer's gas limit, number or timestamp reaches execution and lands here. - const auto ommers_not_validated = !test_block.block_info.ommers.empty(); - - // Asserts the fixture names one of @p names, the exceptions the check that just - // fired is the symptom of. Silent where the reason cannot be compared. - const auto expect_fixture_names = [&](std::string_view names) { - if (!names_spec_exception || ommers_not_validated || sender_not_recovered) - return; - report.check(contains_any(test_block.expected_exception, names), - "block rejection reason", names, test_block.expected_exception); - }; - - if (blob_gas_limit - res.blob_gas_left != - static_cast(bi.blob_gas_used.value_or(0))) + continue; + } + if (res.requests_error) + { + if (!sender_not_recovered) { - expect_fixture_names( - "BlockException.INCORRECT_BLOB_GAS_USED|" - "BlockException.BLOB_GAS_USED_ABOVE_LIMIT"); - continue; + report.check(is_expected_block_exception( + res.requests_error, test_block.expected_exception), + "block rejection reason", res.requests_error, + test_block.expected_exception); } + continue; + } + // The block executed, so it is invalid only if it computes something other than + // its header claims. Each difference below is the symptom of one BlockException: + // a block failing a check other than the one the fixture names breaks a different + // rule than the test is about. + // TODO: Of the ommers only the count and the distance to their nephew are + // validated, not the ommer headers themselves, so a fixture that breaks an + // ommer's gas limit, number or timestamp reaches execution and lands here. + const auto ommers_not_validated = !test_block.block_info.ommers.empty(); + + // Asserts the fixture names one of @p names, the exceptions the check that just + // fired is the symptom of. Silent where the reason cannot be compared. + const auto expect_fixture_names = [&](std::string_view names) { + if (!names_spec_exception || ommers_not_validated || sender_not_recovered) + return; + report.check(contains_any(test_block.expected_exception, names), + "block rejection reason", names, test_block.expected_exception); + }; - if (state::mpt_hash(res.block_state) != test_block.expected_block_header.state_root) - { - expect_fixture_names("BlockException.INVALID_STATE_ROOT"); - continue; - } + if (blob_gas_limit - res.blob_gas_left != + static_cast(bi.blob_gas_used.value_or(0))) + { + expect_fixture_names( + "BlockException.INCORRECT_BLOB_GAS_USED|" + "BlockException.BLOB_GAS_USED_ABOVE_LIMIT"); + continue; + } - if (rev >= EVMC_SHANGHAI && state::mpt_hash(test_block.block_info.withdrawals) != - test_block.expected_block_header.withdrawal_root) - { - expect_fixture_names("BlockException.INVALID_WITHDRAWALS_ROOT"); - continue; - } - if (state::mpt_hash(test_block.transactions) != - test_block.expected_block_header.transactions_root) - { - expect_fixture_names("BlockException.INVALID_TRANSACTIONS_ROOT"); - continue; - } - if (state::mpt_hash(res.receipts) != test_block.expected_block_header.receipts_root) - { - expect_fixture_names("BlockException.INVALID_RECEIPTS_ROOT"); - continue; - } - if (rev >= EVMC_PRAGUE && calculate_requests_hash(res.requests) != - test_block.expected_block_header.requests_hash) - { - expect_fixture_names("BlockException.INVALID_REQUESTS"); - continue; - } - if (res.gas_used != test_block.expected_block_header.gas_used) - { - expect_fixture_names( - "BlockException.INVALID_GAS_USED|" - "BlockException.GAS_USED_OVERFLOW"); - continue; - } - if (bytes_view{res.bloom} != - bytes_view{test_block.expected_block_header.logs_bloom}) - { - expect_fixture_names("BlockException.INVALID_LOG_BLOOM"); - continue; - } + if (state::mpt_hash(res.block_state) != test_block.expected_block_header.state_root) + { + expect_fixture_names("BlockException.INVALID_STATE_ROOT"); + continue; + } - report.fail("block validity", "expected the block to be invalid"); + if (rev >= EVMC_SHANGHAI && state::mpt_hash(test_block.block_info.withdrawals) != + test_block.expected_block_header.withdrawal_root) + { + expect_fixture_names("BlockException.INVALID_WITHDRAWALS_ROOT"); + continue; + } + if (state::mpt_hash(test_block.transactions) != + test_block.expected_block_header.transactions_root) + { + expect_fixture_names("BlockException.INVALID_TRANSACTIONS_ROOT"); + continue; } + if (state::mpt_hash(res.receipts) != test_block.expected_block_header.receipts_root) + { + expect_fixture_names("BlockException.INVALID_RECEIPTS_ROOT"); + continue; + } + if (rev >= EVMC_PRAGUE && calculate_requests_hash(res.requests) != + test_block.expected_block_header.requests_hash) + { + expect_fixture_names("BlockException.INVALID_REQUESTS"); + continue; + } + if (res.gas_used != test_block.expected_block_header.gas_used) + { + expect_fixture_names( + "BlockException.INVALID_GAS_USED|" + "BlockException.GAS_USED_OVERFLOW"); + continue; + } + if (bytes_view{res.bloom} != bytes_view{test_block.expected_block_header.logs_bloom}) + { + expect_fixture_names("BlockException.INVALID_LOG_BLOOM"); + continue; + } + + report.fail("block validity", "expected the block to be invalid"); } - report.check_eq("canonical chain tip", canonical_tip_hash, c.expectation.last_block_hash); - - const auto expected_post_hash = - std::holds_alternative(c.expectation.post_state) ? - state::mpt_hash(std::get(c.expectation.post_state)) : - std::get(c.expectation.post_state); - - // Get the final state hash. In case none blocks have been applied, compute genesis one. - const auto canonical_post_hash = - canonical_state_root ? canonical_state_root : state::mpt_hash(c.pre_state); - // The state dumps are a callable so that formatting the whole state only happens once - // the roots are already known to differ. - report.check_eq("post state root", canonical_post_hash, expected_post_hash, [&] { - return "Result state:\n" + print_state(*canonical_state) + - (std::holds_alternative(c.expectation.post_state) ? - "\n\nExpected state:\n" + - print_state(std::get(c.expectation.post_state)) : - ""); - }); } + report.check_eq("canonical chain tip", canonical_tip_hash, test.expectation.last_block_hash); + + const auto expected_post_hash = + std::holds_alternative(test.expectation.post_state) ? + state::mpt_hash(std::get(test.expectation.post_state)) : + std::get(test.expectation.post_state); + + // Get the final state hash. In case none blocks have been applied, compute genesis one. + const auto canonical_post_hash = + canonical_state_root ? canonical_state_root : state::mpt_hash(test.pre_state); + // The state dumps are a callable so that formatting the whole state only happens once + // the roots are already known to differ. + report.check_eq("post state root", canonical_post_hash, expected_post_hash, [&] { + return "Result state:\n" + print_state(*canonical_state) + + (std::holds_alternative(test.expectation.post_state) ? + "\n\nExpected state:\n" + + print_state(std::get(test.expectation.post_state)) : + ""); + }); } } // namespace evmone::test