From 14ae9412071bf33a6e5c74fccd484876b2cf4c3f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pawe=C5=82=20Bylica?= <pawel@ethereum.org>
Date: Mon, 19 Aug 2024 17:31:13 +0200
Subject: [PATCH] blockchaintest: Optional partial state hash check

This is blockchain tests execution optimization: for listed test names
only check state root hash of first 5 blocks (to detect early problems)
and last 5 blocks (to do the final check of the chain of blocks).

The current implementation of the MPT hash of the state builds
the trie from scratch (no updates to the trie of the previous block).
For the tests will a long chain of blocks the performance degrades
significantly with 99% time spent in the keccak hash function.

This improves testing of EIP-2935
implemented in https://github.com/ethereum/evmone/pull/953.
---
 test/blockchaintest/blockchaintest_runner.cpp | 31 +++++++++++++++++--
 1 file changed, 28 insertions(+), 3 deletions(-)

diff --git a/test/blockchaintest/blockchaintest_runner.cpp b/test/blockchaintest/blockchaintest_runner.cpp
index b80f83e3b4..6f2e2529e6 100644
--- a/test/blockchaintest/blockchaintest_runner.cpp
+++ b/test/blockchaintest/blockchaintest_runner.cpp
@@ -116,6 +116,22 @@ std::string print_state(const TestState& s)
 
     return out.str();
 }
+
+/// How many first/last state hashes to check in the series of block for the "partial" check.
+constexpr size_t PARTIAL_STATE_HASH_CHECK_MARGIN = 5;
+
+/// List of test names for which only perform the partial state root hash checks.
+/// For tests on the list, only check state hashes for first M (early problems)
+/// and last M (final check of the chain) blocks,
+/// where M is defined by PARTIAL_STATE_HASH_CHECK_MARGIN.
+/// Otherwise, for very long blockchain test we spend a lot of time in MPT hashing
+/// because the implementation of it is very simplistic: the trie is not updated
+/// along the state changes but a new trie is build from scratch for every block.
+constexpr std::array PARTIAL_STATE_HASH_CHECK_TESTS{
+    "a test name",
+};
+
+
 }  // namespace
 
 void run_blockchain_tests(std::span<const BlockchainTest> tests, evmc::VM& vm)
@@ -123,6 +139,10 @@ void run_blockchain_tests(std::span<const BlockchainTest> tests, evmc::VM& vm)
     for (size_t case_index = 0; case_index != tests.size(); ++case_index)
     {
         const auto& c = tests[case_index];
+
+        const bool partial_state_hash_check = std::ranges::find(PARTIAL_STATE_HASH_CHECK_TESTS,
+                                                  c.name) != PARTIAL_STATE_HASH_CHECK_TESTS.end();
+
         SCOPED_TRACE(std::string{evmc::to_string(c.rev.get_revision(0))} + '/' +
                      std::to_string(case_index) + '/' + c.name);
 
@@ -142,8 +162,9 @@ void run_blockchain_tests(std::span<const BlockchainTest> tests, evmc::VM& vm)
         std::unordered_map<int64_t, hash256> known_block_hashes{
             {c.genesis_block_header.block_number, c.genesis_block_header.hash}};
 
-        for (const auto& test_block : c.test_blocks)
+        for (size_t block_idx = 0; block_idx != c.test_blocks.size(); ++block_idx)
         {
+            const auto& test_block = c.test_blocks[block_idx];
             auto bi = test_block.block_info;
             bi.known_block_hashes = known_block_hashes;
 
@@ -158,8 +179,12 @@ void run_blockchain_tests(std::span<const BlockchainTest> tests, evmc::VM& vm)
             SCOPED_TRACE(std::string{evmc::to_string(rev)} + '/' + std::to_string(case_index) +
                          '/' + c.name + '/' + std::to_string(test_block.block_info.number));
 
-            EXPECT_EQ(
-                state::mpt_hash(TestState{state}), test_block.expected_block_header.state_root);
+            if (!partial_state_hash_check || block_idx < PARTIAL_STATE_HASH_CHECK_MARGIN ||
+                block_idx >= c.test_blocks.size() - PARTIAL_STATE_HASH_CHECK_MARGIN)
+            {
+                EXPECT_EQ(
+                    state::mpt_hash(TestState{state}), test_block.expected_block_header.state_root);
+            }
 
             if (rev >= EVMC_SHANGHAI)
             {