Skip to content

Commit 503ec3c

Browse files
authored
Blockchain tests support: Introduce difficulty calculation in t8n (#682)
- Add ommer block reward - Difficulty calculation added - Use it in t8n interface implementation
2 parents 07a371b + ab77bf9 commit 503ec3c

18 files changed

+317
-25
lines changed

test/state/CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ target_sources(
1212
bloom_filter.hpp
1313
bloom_filter.cpp
1414
errors.hpp
15+
ethash_difficulty.hpp
16+
ethash_difficulty.cpp
1517
hash_utils.hpp
1618
hash_utils.cpp
1719
host.hpp

test/state/ethash_difficulty.cpp

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// evmone: Fast Ethereum Virtual Machine implementation
2+
// Copyright 2023 The evmone Authors.
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
#include "ethash_difficulty.hpp"
6+
#include <algorithm>
7+
#include <cassert>
8+
9+
namespace evmone::state
10+
{
11+
namespace
12+
{
13+
int64_t get_bomb_delay(evmc_revision rev) noexcept
14+
{
15+
switch (rev)
16+
{
17+
default:
18+
return 0;
19+
case EVMC_BYZANTIUM:
20+
return 3'000'000;
21+
case EVMC_CONSTANTINOPLE:
22+
case EVMC_PETERSBURG:
23+
case EVMC_ISTANBUL:
24+
return 5'000'000;
25+
case EVMC_BERLIN:
26+
return 9'000'000;
27+
case EVMC_LONDON:
28+
return 9'700'000;
29+
}
30+
}
31+
} // namespace
32+
33+
int64_t calculate_difficulty(int64_t parent_difficulty, bool parent_has_ommers,
34+
int64_t parent_timestamp, int64_t current_timestamp, int64_t block_number,
35+
evmc_revision rev) noexcept
36+
{
37+
// The calculation follows Ethereum Yellow Paper section 4.3.4. "Block Header Validity".
38+
39+
if (rev >= EVMC_PARIS)
40+
return 0; // No difficulty after the Merge.
41+
42+
// TODO: Implement for older revisions
43+
if (rev < EVMC_BYZANTIUM)
44+
return 0x020000;
45+
46+
static constexpr auto min_difficulty = int64_t{1} << 17;
47+
48+
const auto delay = get_bomb_delay(rev);
49+
const auto fake_block_number = std::max(int64_t{0}, block_number - delay);
50+
const auto p = (fake_block_number / 100'000) - 2;
51+
assert(p < 63);
52+
const auto epsilon = p < 0 ? 0 : int64_t{1} << p;
53+
const auto y = parent_has_ommers ? 2 : 1;
54+
55+
const auto timestamp_diff = current_timestamp - parent_timestamp;
56+
assert(timestamp_diff > 0);
57+
const auto sigma_2 = std::max(y - timestamp_diff / 9, int64_t{-99});
58+
const auto x = parent_difficulty / 2048;
59+
const auto difficulty = parent_difficulty + x * sigma_2 + epsilon;
60+
return std::max(min_difficulty, difficulty);
61+
}
62+
} // namespace evmone::state

test/state/ethash_difficulty.hpp

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// evmone: Fast Ethereum Virtual Machine implementation
2+
// Copyright 2023 The evmone Authors.
3+
// SPDX-License-Identifier: Apache-2.0
4+
#pragma once
5+
6+
#include <evmc/evmc.h>
7+
8+
namespace evmone::state
9+
{
10+
int64_t calculate_difficulty(int64_t parent_difficulty, bool parent_has_ommers,
11+
int64_t parent_timestamp, int64_t current_timestamp, int64_t block_number,
12+
evmc_revision rev) noexcept;
13+
} // namespace evmone::state

test/state/hash_utils.hpp

+4
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ using namespace evmc::literals;
2222
/// Better than ethash::hash256 because has some additional handy constructors.
2323
using hash256 = bytes32;
2424

25+
/// The hash of the empty RLP list, i.e. keccak256({0xc0}).
26+
static constexpr auto EmptyListHash =
27+
0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347_bytes32;
28+
2529
/// Computes Keccak hash out of input bytes (wrapper of ethash::keccak256).
2630
inline hash256 keccak256(bytes_view data) noexcept
2731
{

test/state/state.cpp

+15-2
Original file line numberDiff line numberDiff line change
@@ -136,11 +136,24 @@ std::variant<int64_t, std::error_code> validate_transaction(const Account& sende
136136
}
137137

138138
void finalize(State& state, evmc_revision rev, const address& coinbase,
139-
std::optional<uint64_t> block_reward, std::span<Withdrawal> withdrawals)
139+
std::optional<uint64_t> block_reward, std::span<Ommer> ommers,
140+
std::span<Withdrawal> withdrawals)
140141
{
141142
// TODO: The block reward can be represented as a withdrawal.
142143
if (block_reward.has_value())
143-
state.touch(coinbase).balance += *block_reward;
144+
{
145+
const auto reward = *block_reward;
146+
assert(reward % 32 == 0); // Assume block reward is divisible by 32.
147+
const auto reward_by_32 = reward / 32;
148+
const auto reward_by_8 = reward / 8;
149+
150+
state.touch(coinbase).balance += reward + reward_by_32 * ommers.size();
151+
for (const auto& ommer : ommers)
152+
{
153+
assert(ommer.delta > 0 && ommer.delta < 8);
154+
state.touch(ommer.beneficiary).balance += reward_by_8 * (8 - ommer.delta);
155+
}
156+
}
144157

145158
for (const auto& withdrawal : withdrawals)
146159
state.touch(withdrawal.recipient).balance += withdrawal.get_amount();

test/state/state.hpp

+13-1
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ class State
6666
[[nodiscard]] const auto& get_accounts() const noexcept { return m_accounts; }
6767
};
6868

69+
struct Ommer
70+
{
71+
address beneficiary; ///< Ommer block beneficiary address.
72+
uint32_t delta = 0; ///< Difference between current and ommer block number.
73+
};
74+
6975
struct Withdrawal
7076
{
7177
uint64_t index = 0;
@@ -84,10 +90,15 @@ struct BlockInfo
8490
{
8591
int64_t number = 0;
8692
int64_t timestamp = 0;
93+
int64_t parent_timestamp = 0;
8794
int64_t gas_limit = 0;
8895
address coinbase;
96+
int64_t difficulty = 0;
97+
int64_t parent_difficulty = 0;
98+
hash256 parent_ommers_hash = {};
8999
bytes32 prev_randao;
90100
uint64_t base_fee = 0;
101+
std::vector<Ommer> ommers = {};
91102
std::vector<Withdrawal> withdrawals;
92103
std::unordered_map<int64_t, hash256> known_block_hashes = {};
93104
};
@@ -168,7 +179,8 @@ struct TransactionReceipt
168179
/// Applies block reward to coinbase, withdrawals (post Shanghai) and deletes empty touched accounts
169180
/// (post Spurious Dragon).
170181
void finalize(State& state, evmc_revision rev, const address& coinbase,
171-
std::optional<uint64_t> block_reward, std::span<Withdrawal> withdrawals);
182+
std::optional<uint64_t> block_reward, std::span<Ommer> ommers,
183+
std::span<Withdrawal> withdrawals);
172184

173185
[[nodiscard]] std::variant<TransactionReceipt, std::error_code> transition(State& state,
174186
const BlockInfo& block, const Transaction& tx, evmc_revision rev, evmc::VM& vm,

test/statetest/statetest.hpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ struct StateTransitionTest
5050
{
5151
TestMultiTransaction::Indexes indexes;
5252
hash256 state_hash;
53-
hash256 logs_hash;
53+
hash256 logs_hash = EmptyListHash;
5454
bool exception = false;
5555
};
5656

test/statetest/statetest_loader.cpp

+37-7
Original file line numberDiff line numberDiff line change
@@ -157,16 +157,30 @@ state::Withdrawal from_json<state::Withdrawal>(const json::json& j)
157157
template <>
158158
state::BlockInfo from_json<state::BlockInfo>(const json::json& j)
159159
{
160-
evmc::bytes32 difficulty;
160+
evmc::bytes32 prev_randao;
161+
int64_t current_difficulty = 0;
162+
int64_t parent_difficulty = 0;
161163
const auto prev_randao_it = j.find("currentRandom");
162164
const auto current_difficulty_it = j.find("currentDifficulty");
163165
const auto parent_difficulty_it = j.find("parentDifficulty");
166+
167+
if (current_difficulty_it != j.end())
168+
current_difficulty = from_json<int64_t>(*current_difficulty_it);
169+
if (parent_difficulty_it != j.end())
170+
parent_difficulty = from_json<int64_t>(*parent_difficulty_it);
171+
172+
// When it's not defined init it with difficulty value.
164173
if (prev_randao_it != j.end())
165-
difficulty = from_json<bytes32>(*prev_randao_it);
174+
prev_randao = from_json<bytes32>(*prev_randao_it);
166175
else if (current_difficulty_it != j.end())
167-
difficulty = from_json<bytes32>(*current_difficulty_it);
176+
prev_randao = from_json<bytes32>(*current_difficulty_it);
168177
else if (parent_difficulty_it != j.end())
169-
difficulty = from_json<bytes32>(*parent_difficulty_it);
178+
prev_randao = from_json<bytes32>(*parent_difficulty_it);
179+
180+
hash256 parent_uncle_hash;
181+
const auto parent_uncle_hash_it = j.find("parentUncleHash");
182+
if (parent_uncle_hash_it != j.end())
183+
parent_uncle_hash = from_json<hash256>(*parent_uncle_hash_it);
170184

171185
uint64_t base_fee = 0;
172186
if (j.contains("currentBaseFee"))
@@ -192,10 +206,26 @@ state::BlockInfo from_json<state::BlockInfo>(const json::json& j)
192206
block_hashes[from_json<int64_t>(j_num)] = from_json<hash256>(j_hash);
193207
}
194208

209+
std::vector<state::Ommer> ommers;
210+
if (const auto ommers_it = j.find("ommers"); ommers_it != j.end())
211+
{
212+
for (const auto& ommer : *ommers_it)
213+
{
214+
ommers.push_back(
215+
{from_json<evmc::address>(ommer.at("address")), ommer.at("delta").get<uint32_t>()});
216+
}
217+
}
218+
219+
int64_t parent_timestamp = 0;
220+
auto parent_timestamp_it = j.find("parentTimestamp");
221+
if (parent_timestamp_it != j.end())
222+
parent_timestamp = from_json<int64_t>(*parent_timestamp_it);
223+
195224
return {from_json<int64_t>(j.at("currentNumber")), from_json<int64_t>(j.at("currentTimestamp")),
196-
from_json<int64_t>(j.at("currentGasLimit")),
197-
from_json<evmc::address>(j.at("currentCoinbase")), difficulty, base_fee,
198-
std::move(withdrawals), std::move(block_hashes)};
225+
parent_timestamp, from_json<int64_t>(j.at("currentGasLimit")),
226+
from_json<evmc::address>(j.at("currentCoinbase")), current_difficulty, parent_difficulty,
227+
parent_uncle_hash, prev_randao, base_fee, std::move(ommers), std::move(withdrawals),
228+
std::move(block_hashes)};
199229
}
200230

201231
template <>

test/statetest/statetest_runner.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ void run_state_test(const StateTransitionTest& test, evmc::VM& vm)
3131
state::transition(state, test.block, tx, rev, vm, test.block.gas_limit);
3232

3333
// Finalize block with reward 0.
34-
state::finalize(state, rev, test.block.coinbase, 0, {});
34+
state::finalize(state, rev, test.block.coinbase, 0, {}, {});
3535

3636
if (holds_alternative<state::TransactionReceipt>(res))
3737
EXPECT_EQ(logs_hash(get<state::TransactionReceipt>(res).logs), expected.logs_hash);

test/t8n/t8n.cpp

+21-3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// SPDX-License-Identifier: Apache-2.0
44

55
#include "../state/errors.hpp"
6+
#include "../state/ethash_difficulty.hpp"
67
#include "../state/mpt_hash.hpp"
78
#include "../state/rlp.hpp"
89
#include "../statetest/statetest.hpp"
@@ -84,8 +85,24 @@ int main(int argc, const char* argv[])
8485
}
8586

8687
json::json j_result;
87-
// FIXME: Calculate difficulty properly
88-
j_result["currentDifficulty"] = "0x20000";
88+
89+
// Difficulty was received from upstream. No need to calc
90+
// TODO: Check if it's needed by the blockchain test. If not remove if statement true branch
91+
if (block.difficulty != 0)
92+
j_result["currentDifficulty"] = hex0x(block.difficulty);
93+
else
94+
{
95+
const auto current_difficulty = state::calculate_difficulty(block.parent_difficulty,
96+
block.parent_ommers_hash != EmptyListHash, block.parent_timestamp, block.timestamp,
97+
block.number, rev);
98+
99+
j_result["currentDifficulty"] = hex0x(current_difficulty);
100+
block.difficulty = current_difficulty;
101+
102+
if (rev < EVMC_PARIS) // Override prev_randao with difficulty pre-Merge
103+
block.prev_randao = intx::be::store<bytes32>(intx::uint256{current_difficulty});
104+
}
105+
89106
j_result["currentBaseFee"] = hex0x(block.base_fee);
90107

91108
int64_t cumulative_gas_used = 0;
@@ -193,7 +210,8 @@ int main(int argc, const char* argv[])
193210
}
194211
}
195212

196-
state::finalize(state, rev, block.coinbase, block_reward, block.withdrawals);
213+
state::finalize(
214+
state, rev, block.coinbase, block_reward, block.ommers, block.withdrawals);
197215

198216
j_result["logsHash"] = hex0x(logs_hash(txs_logs));
199217
j_result["stateRoot"] = hex0x(state::mpt_hash(state.get_accounts()));

test/unittests/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ target_sources(
3434
execution_state_test.cpp
3535
instructions_test.cpp
3636
state_bloom_filter_test.cpp
37+
state_difficulty_test.cpp
3738
state_mpt_hash_test.cpp
3839
state_mpt_test.cpp
3940
state_new_account_address_test.cpp
+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// evmone: Fast Ethereum Virtual Machine implementation
2+
// Copyright 2023 The evmone Authors.
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
#include <gmock/gmock.h>
6+
#include <test/state/ethash_difficulty.hpp>
7+
8+
using namespace evmone::state;
9+
10+
struct DifficultyTest // NOLINT(clang-analyzer-optin.performance.Padding)
11+
{
12+
evmc_revision rev;
13+
const char* name;
14+
int64_t block_number;
15+
int64_t difficulty;
16+
int64_t timestamp;
17+
int64_t parent_difficulty;
18+
int64_t parent_timestamp;
19+
bool parent_has_ommers;
20+
};
21+
22+
/// Example difficulty tests from
23+
/// https://github.com/ethereum/tests/blob/develop/DifficultyTests.
24+
static constexpr DifficultyTest tests[] = {
25+
{
26+
EVMC_BYZANTIUM,
27+
"DifficultyTest1",
28+
0x0186a0,
29+
0x69702c7f2c9fad14,
30+
0x28d214819,
31+
0x6963001f28ba95c2,
32+
0x28d214818,
33+
false,
34+
},
35+
{
36+
EVMC_BYZANTIUM,
37+
"DifficultyTest1038",
38+
0x0dbba0,
39+
0x79f2cbb8c97579b0,
40+
0x72e440371,
41+
0x79f2cbb8c97579b0,
42+
0x72e44035d,
43+
true,
44+
},
45+
{
46+
EVMC_BERLIN,
47+
"DifficultyTest1",
48+
0x186a0,
49+
0x56c67d1e106966c3,
50+
0x63ed689e9,
51+
0x56bba5a95b3dff04,
52+
0x63ed689e8,
53+
false,
54+
},
55+
{
56+
EVMC_BERLIN,
57+
"DifficultyTest1040",
58+
0x10c8e0,
59+
0x68f7512123928555,
60+
0x617ec9fcc,
61+
0x68f7512123928555,
62+
0x617ec9fb8,
63+
true,
64+
},
65+
};
66+
67+
TEST(state_difficulty, tests)
68+
{
69+
for (const auto& t : tests)
70+
{
71+
const auto difficulty = calculate_difficulty(t.parent_difficulty, t.parent_has_ommers,
72+
t.parent_timestamp, t.timestamp, t.block_number, t.rev);
73+
EXPECT_EQ(difficulty, t.difficulty) << t.rev << "/" << t.name;
74+
}
75+
}

test/unittests/state_rlp_test.cpp

+6
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ TEST(state_rlp, empty_bytes_hash)
2525
EXPECT_EQ(keccak256({}), emptyBytesHash);
2626
}
2727

28+
TEST(state_rlp, empty_list_hash)
29+
{
30+
EXPECT_EQ(keccak256(bytes{0xc0}), EmptyListHash); // Hash of empty RLP list: 0xc0.
31+
EXPECT_EQ(keccak256(rlp::encode(std::vector<uint64_t>{})), EmptyListHash);
32+
}
33+
2834
TEST(state_rlp, empty_mpt_hash)
2935
{
3036
const auto rlp_null = rlp::encode(0);

test/unittests/state_transition.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ void state_transition::TearDown()
4141
ASSERT_TRUE(holds_alternative<TransactionReceipt>(res))
4242
<< std::get<std::error_code>(res).message();
4343
const auto& receipt = std::get<TransactionReceipt>(res);
44-
evmone::state::finalize(state, rev, block.coinbase, 0, block.withdrawals);
44+
evmone::state::finalize(
45+
state, rev, block.coinbase, block_reward, block.ommers, block.withdrawals);
4546

4647
EXPECT_EQ(receipt.status, expect.status);
4748
if (expect.gas_used.has_value())

0 commit comments

Comments
 (0)