From 57e3d53df70455f616f3c0ea676d58b27733142e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Mon, 27 Jul 2026 00:46:12 +0200 Subject: [PATCH] test: Take the state test transaction from its encoding A state test's transaction is built from a shared multi-transaction template, which describes the transaction rather than being it: the template cannot express a malformed encoding, so a case whose defect lives in the bytes was executed as if it were well formed. In execution-specs tests@v20.0.1 that is the whole frontier/validation/bad_v_r_s family, 116 cases the runner reported as "unexpected valid transaction". Each post entry carries the serialization it was built from as "txbytes". Decode it and run that transaction instead; input that does not decode makes the transaction invalid (INVALID_ENCODING). The sender still comes from the template, so nothing about the signature is verified yet. --- test/state/errors.hpp | 3 +++ test/statetest/statetest_runner.cpp | 18 +++++++++++++++--- test/utils/statetest.hpp | 3 +++ test/utils/statetest_loader.cpp | 3 +++ 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/test/state/errors.hpp b/test/state/errors.hpp index 8c4afd64c2..5c71081019 100644 --- a/test/state/errors.hpp +++ b/test/state/errors.hpp @@ -32,6 +32,7 @@ enum ErrorCode : int // NOLINT(*-use-enum-class) EMPTY_AUTHORIZATION_LIST, MAX_GAS_LIMIT_EXCEEDED, INVALID_CHAIN_ID, + INVALID_ENCODING, UNKNOWN_ERROR, // Block-level validation. @@ -103,6 +104,8 @@ inline const std::error_category& evmone_category() noexcept return "max gas limit exceeded"; case INVALID_CHAIN_ID: return "invalid transaction chain id"; + case INVALID_ENCODING: + return "invalid transaction encoding"; case UNKNOWN_ERROR: return "Unknown error"; case INCORRECT_BLOCK_FORMAT: diff --git a/test/statetest/statetest_runner.cpp b/test/statetest/statetest_runner.cpp index 6c607c19f1..cb6765461e 100644 --- a/test/statetest/statetest_runner.cpp +++ b/test/statetest/statetest_runner.cpp @@ -24,12 +24,24 @@ void run_state_test(const StateTransitionTest& test, evmc::VM& vm, bool trace_su // continue; const auto& expected = cases[case_index]; - const auto tx = test.multi_tx.get(expected.indexes); auto state = test.pre_state; const auto blob_params = get_blob_params(rev, test.blob_schedule); - const auto res = transition(state, block, test.block_hashes, tx, rev, vm, - block.gas_limit, static_cast(state::max_blob_gas_per_block(blob_params))); + // Decode transaction from txbytes if available. + const auto template_tx = test.multi_tx.get(expected.indexes); + auto tx = std::optional{template_tx}; + if (expected.txbytes.has_value()) + { + tx = state::decode_transaction(*expected.txbytes); + if (tx.has_value()) + tx->sender = template_tx.sender; // No recovery yet, take sender from JSON. + } + + const auto res = + tx.has_value() ? + transition(state, block, test.block_hashes, *tx, rev, vm, block.gas_limit, + static_cast(state::max_blob_gas_per_block(blob_params))) : + make_error_code(state::INVALID_ENCODING); if (holds_alternative(res)) { diff --git a/test/utils/statetest.hpp b/test/utils/statetest.hpp index ea42821c7a..55c096832d 100644 --- a/test/utils/statetest.hpp +++ b/test/utils/statetest.hpp @@ -51,6 +51,9 @@ struct StateTransitionTest hash256 state_hash; hash256 logs_hash = EmptyListHash; bool exception = false; + + /// The full encoded transaction for this case. Not always available. + std::optional txbytes; }; evmc_revision rev; diff --git a/test/utils/statetest_loader.cpp b/test/utils/statetest_loader.cpp index 9b0a74879b..c17f1bf15f 100644 --- a/test/utils/statetest_loader.cpp +++ b/test/utils/statetest_loader.cpp @@ -494,6 +494,9 @@ static void from_json(const json::json& j, StateTransitionTest::Case::Expectatio o.state_hash = from_json(j.at("hash")); o.logs_hash = from_json(j.at("logs")); o.exception = j.contains("expectException"); + // Not load_if_exists(): it maps an absent key to bytes{}, which engages the optional. + if (const auto it = j.find("txbytes"); it != j.end()) + o.txbytes = from_json(*it); } static void from_json(const json::json& j_t, StateTransitionTest& o)