Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Blockchain tests support: Introduce difficulty calculation in t8n #682

Merged
merged 4 commits into from
Sep 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions test/state/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ target_sources(
bloom_filter.hpp
bloom_filter.cpp
errors.hpp
ethash_difficulty.hpp
ethash_difficulty.cpp
hash_utils.hpp
hash_utils.cpp
host.hpp
Expand Down
62 changes: 62 additions & 0 deletions test/state/ethash_difficulty.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// evmone: Fast Ethereum Virtual Machine implementation
// Copyright 2023 The evmone Authors.
// SPDX-License-Identifier: Apache-2.0

#include "ethash_difficulty.hpp"
#include <algorithm>
#include <cassert>

namespace evmone::state
{
namespace
{
int64_t get_bomb_delay(evmc_revision rev) noexcept
{
switch (rev)
{
default:
return 0;

Check warning on line 18 in test/state/ethash_difficulty.cpp

View check run for this annotation

Codecov / codecov/patch

test/state/ethash_difficulty.cpp#L17-L18

Added lines #L17 - L18 were not covered by tests
case EVMC_BYZANTIUM:
return 3'000'000;
case EVMC_CONSTANTINOPLE:

Check warning on line 21 in test/state/ethash_difficulty.cpp

View check run for this annotation

Codecov / codecov/patch

test/state/ethash_difficulty.cpp#L21

Added line #L21 was not covered by tests
case EVMC_PETERSBURG:
case EVMC_ISTANBUL:
return 5'000'000;

Check warning on line 24 in test/state/ethash_difficulty.cpp

View check run for this annotation

Codecov / codecov/patch

test/state/ethash_difficulty.cpp#L24

Added line #L24 was not covered by tests
case EVMC_BERLIN:
return 9'000'000;
case EVMC_LONDON:
return 9'700'000;

Check warning on line 28 in test/state/ethash_difficulty.cpp

View check run for this annotation

Codecov / codecov/patch

test/state/ethash_difficulty.cpp#L27-L28

Added lines #L27 - L28 were not covered by tests
}
}
} // namespace

int64_t calculate_difficulty(int64_t parent_difficulty, bool parent_has_ommers,
int64_t parent_timestamp, int64_t current_timestamp, int64_t block_number,
evmc_revision rev) noexcept
{
// The calculation follows Ethereum Yellow Paper section 4.3.4. "Block Header Validity".

if (rev >= EVMC_PARIS)
return 0; // No difficulty after the Merge.

Check warning on line 40 in test/state/ethash_difficulty.cpp

View check run for this annotation

Codecov / codecov/patch

test/state/ethash_difficulty.cpp#L40

Added line #L40 was not covered by tests

// TODO: Implement for older revisions
if (rev < EVMC_BYZANTIUM)
return 0x020000;

Check warning on line 44 in test/state/ethash_difficulty.cpp

View check run for this annotation

Codecov / codecov/patch

test/state/ethash_difficulty.cpp#L44

Added line #L44 was not covered by tests

static constexpr auto min_difficulty = int64_t{1} << 17;

const auto delay = get_bomb_delay(rev);
const auto fake_block_number = std::max(int64_t{0}, block_number - delay);
const auto p = (fake_block_number / 100'000) - 2;
assert(p < 63);
const auto epsilon = p < 0 ? 0 : int64_t{1} << p;
const auto y = parent_has_ommers ? 2 : 1;

const auto timestamp_diff = current_timestamp - parent_timestamp;
assert(timestamp_diff > 0);
const auto sigma_2 = std::max(y - timestamp_diff / 9, int64_t{-99});
const auto x = parent_difficulty / 2048;
const auto difficulty = parent_difficulty + x * sigma_2 + epsilon;
return std::max(min_difficulty, difficulty);
}
} // namespace evmone::state
13 changes: 13 additions & 0 deletions test/state/ethash_difficulty.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// evmone: Fast Ethereum Virtual Machine implementation
// Copyright 2023 The evmone Authors.
// SPDX-License-Identifier: Apache-2.0
#pragma once

#include <evmc/evmc.h>

namespace evmone::state
{
int64_t calculate_difficulty(int64_t parent_difficulty, bool parent_has_ommers,
int64_t parent_timestamp, int64_t current_timestamp, int64_t block_number,
evmc_revision rev) noexcept;
} // namespace evmone::state
4 changes: 4 additions & 0 deletions test/state/hash_utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ using namespace evmc::literals;
/// Better than ethash::hash256 because has some additional handy constructors.
using hash256 = bytes32;

/// The hash of the empty RLP list, i.e. keccak256({0xc0}).
static constexpr auto EmptyListHash =
0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347_bytes32;

/// Computes Keccak hash out of input bytes (wrapper of ethash::keccak256).
inline hash256 keccak256(bytes_view data) noexcept
{
Expand Down
17 changes: 15 additions & 2 deletions test/state/state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,11 +136,24 @@ std::variant<int64_t, std::error_code> validate_transaction(const Account& sende
}

void finalize(State& state, evmc_revision rev, const address& coinbase,
std::optional<uint64_t> block_reward, std::span<Withdrawal> withdrawals)
std::optional<uint64_t> block_reward, std::span<Ommer> ommers,
std::span<Withdrawal> withdrawals)
{
// TODO: The block reward can be represented as a withdrawal.
if (block_reward.has_value())
state.touch(coinbase).balance += *block_reward;
{
const auto reward = *block_reward;
assert(reward % 32 == 0); // Assume block reward is divisible by 32.
const auto reward_by_32 = reward / 32;
const auto reward_by_8 = reward / 8;

state.touch(coinbase).balance += reward + reward_by_32 * ommers.size();
for (const auto& ommer : ommers)
{
assert(ommer.delta > 0 && ommer.delta < 8);
state.touch(ommer.beneficiary).balance += reward_by_8 * (8 - ommer.delta);
}
}

for (const auto& withdrawal : withdrawals)
state.touch(withdrawal.recipient).balance += withdrawal.get_amount();
Expand Down
14 changes: 13 additions & 1 deletion test/state/state.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ class State
[[nodiscard]] const auto& get_accounts() const noexcept { return m_accounts; }
};

struct Ommer
{
address beneficiary; ///< Ommer block beneficiary address.
uint32_t delta = 0; ///< Difference between current and ommer block number.
};

struct Withdrawal
{
uint64_t index = 0;
Expand All @@ -84,10 +90,15 @@ struct BlockInfo
{
int64_t number = 0;
int64_t timestamp = 0;
int64_t parent_timestamp = 0;
int64_t gas_limit = 0;
address coinbase;
int64_t difficulty = 0;
int64_t parent_difficulty = 0;
hash256 parent_ommers_hash = {};
bytes32 prev_randao;
uint64_t base_fee = 0;
std::vector<Ommer> ommers = {};
chfast marked this conversation as resolved.
Show resolved Hide resolved
std::vector<Withdrawal> withdrawals;
std::unordered_map<int64_t, hash256> known_block_hashes = {};
};
Expand Down Expand Up @@ -168,7 +179,8 @@ struct TransactionReceipt
/// Applies block reward to coinbase, withdrawals (post Shanghai) and deletes empty touched accounts
/// (post Spurious Dragon).
void finalize(State& state, evmc_revision rev, const address& coinbase,
std::optional<uint64_t> block_reward, std::span<Withdrawal> withdrawals);
std::optional<uint64_t> block_reward, std::span<Ommer> ommers,
std::span<Withdrawal> withdrawals);

[[nodiscard]] std::variant<TransactionReceipt, std::error_code> transition(State& state,
const BlockInfo& block, const Transaction& tx, evmc_revision rev, evmc::VM& vm,
Expand Down
2 changes: 1 addition & 1 deletion test/statetest/statetest.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ struct StateTransitionTest
{
TestMultiTransaction::Indexes indexes;
hash256 state_hash;
hash256 logs_hash;
hash256 logs_hash = EmptyListHash;
bool exception = false;
};

Expand Down
44 changes: 37 additions & 7 deletions test/statetest/statetest_loader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -157,16 +157,30 @@ state::Withdrawal from_json<state::Withdrawal>(const json::json& j)
template <>
state::BlockInfo from_json<state::BlockInfo>(const json::json& j)
{
evmc::bytes32 difficulty;
evmc::bytes32 prev_randao;
int64_t current_difficulty = 0;
int64_t parent_difficulty = 0;
const auto prev_randao_it = j.find("currentRandom");
const auto current_difficulty_it = j.find("currentDifficulty");
const auto parent_difficulty_it = j.find("parentDifficulty");

if (current_difficulty_it != j.end())
current_difficulty = from_json<int64_t>(*current_difficulty_it);
if (parent_difficulty_it != j.end())
parent_difficulty = from_json<int64_t>(*parent_difficulty_it);

// When it's not defined init it with difficulty value.
if (prev_randao_it != j.end())
difficulty = from_json<bytes32>(*prev_randao_it);
prev_randao = from_json<bytes32>(*prev_randao_it);
else if (current_difficulty_it != j.end())
difficulty = from_json<bytes32>(*current_difficulty_it);
prev_randao = from_json<bytes32>(*current_difficulty_it);
else if (parent_difficulty_it != j.end())
difficulty = from_json<bytes32>(*parent_difficulty_it);
prev_randao = from_json<bytes32>(*parent_difficulty_it);

hash256 parent_uncle_hash;
const auto parent_uncle_hash_it = j.find("parentUncleHash");
if (parent_uncle_hash_it != j.end())
parent_uncle_hash = from_json<hash256>(*parent_uncle_hash_it);

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

std::vector<state::Ommer> ommers;
if (const auto ommers_it = j.find("ommers"); ommers_it != j.end())
{
for (const auto& ommer : *ommers_it)
{
ommers.push_back(
{from_json<evmc::address>(ommer.at("address")), ommer.at("delta").get<uint32_t>()});
}
}

int64_t parent_timestamp = 0;
auto parent_timestamp_it = j.find("parentTimestamp");
if (parent_timestamp_it != j.end())
parent_timestamp = from_json<int64_t>(*parent_timestamp_it);

return {from_json<int64_t>(j.at("currentNumber")), from_json<int64_t>(j.at("currentTimestamp")),
from_json<int64_t>(j.at("currentGasLimit")),
from_json<evmc::address>(j.at("currentCoinbase")), difficulty, base_fee,
std::move(withdrawals), std::move(block_hashes)};
parent_timestamp, from_json<int64_t>(j.at("currentGasLimit")),
from_json<evmc::address>(j.at("currentCoinbase")), current_difficulty, parent_difficulty,
parent_uncle_hash, prev_randao, base_fee, std::move(ommers), std::move(withdrawals),
std::move(block_hashes)};
}

template <>
Expand Down
2 changes: 1 addition & 1 deletion test/statetest/statetest_runner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ void run_state_test(const StateTransitionTest& test, evmc::VM& vm)
state::transition(state, test.block, tx, rev, vm, test.block.gas_limit);

// Finalize block with reward 0.
state::finalize(state, rev, test.block.coinbase, 0, {});
state::finalize(state, rev, test.block.coinbase, 0, {}, {});

if (holds_alternative<state::TransactionReceipt>(res))
EXPECT_EQ(logs_hash(get<state::TransactionReceipt>(res).logs), expected.logs_hash);
Expand Down
24 changes: 21 additions & 3 deletions test/t8n/t8n.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// SPDX-License-Identifier: Apache-2.0

#include "../state/errors.hpp"
#include "../state/ethash_difficulty.hpp"
#include "../state/mpt_hash.hpp"
#include "../state/rlp.hpp"
#include "../statetest/statetest.hpp"
Expand Down Expand Up @@ -84,8 +85,24 @@ int main(int argc, const char* argv[])
}

json::json j_result;
// FIXME: Calculate difficulty properly
j_result["currentDifficulty"] = "0x20000";

// Difficulty was received from upstream. No need to calc
// TODO: Check if it's needed by the blockchain test. If not remove if statement true branch
if (block.difficulty != 0)
j_result["currentDifficulty"] = hex0x(block.difficulty);
else
{
const auto current_difficulty = state::calculate_difficulty(block.parent_difficulty,
block.parent_ommers_hash != EmptyListHash, block.parent_timestamp, block.timestamp,
block.number, rev);

j_result["currentDifficulty"] = hex0x(current_difficulty);
block.difficulty = current_difficulty;

if (rev < EVMC_PARIS) // Override prev_randao with difficulty pre-Merge
block.prev_randao = intx::be::store<bytes32>(intx::uint256{current_difficulty});
}

j_result["currentBaseFee"] = hex0x(block.base_fee);

int64_t cumulative_gas_used = 0;
Expand Down Expand Up @@ -193,7 +210,8 @@ int main(int argc, const char* argv[])
}
}

state::finalize(state, rev, block.coinbase, block_reward, block.withdrawals);
state::finalize(
state, rev, block.coinbase, block_reward, block.ommers, block.withdrawals);

j_result["logsHash"] = hex0x(logs_hash(txs_logs));
j_result["stateRoot"] = hex0x(state::mpt_hash(state.get_accounts()));
Expand Down
1 change: 1 addition & 0 deletions test/unittests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ target_sources(
execution_state_test.cpp
instructions_test.cpp
state_bloom_filter_test.cpp
state_difficulty_test.cpp
state_mpt_hash_test.cpp
state_mpt_test.cpp
state_new_account_address_test.cpp
Expand Down
75 changes: 75 additions & 0 deletions test/unittests/state_difficulty_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// evmone: Fast Ethereum Virtual Machine implementation
// Copyright 2023 The evmone Authors.
// SPDX-License-Identifier: Apache-2.0

#include <gmock/gmock.h>
#include <test/state/ethash_difficulty.hpp>

using namespace evmone::state;

struct DifficultyTest // NOLINT(clang-analyzer-optin.performance.Padding)
{
evmc_revision rev;
const char* name;
int64_t block_number;
int64_t difficulty;
int64_t timestamp;
int64_t parent_difficulty;
int64_t parent_timestamp;
bool parent_has_ommers;
};

/// Example difficulty tests from
/// https://github.com/ethereum/tests/blob/develop/DifficultyTests.
static constexpr DifficultyTest tests[] = {
{
EVMC_BYZANTIUM,
"DifficultyTest1",
0x0186a0,
0x69702c7f2c9fad14,
0x28d214819,
0x6963001f28ba95c2,
0x28d214818,
false,
},
{
EVMC_BYZANTIUM,
"DifficultyTest1038",
0x0dbba0,
0x79f2cbb8c97579b0,
0x72e440371,
0x79f2cbb8c97579b0,
0x72e44035d,
true,
},
{
EVMC_BERLIN,
"DifficultyTest1",
0x186a0,
0x56c67d1e106966c3,
0x63ed689e9,
0x56bba5a95b3dff04,
0x63ed689e8,
false,
},
{
EVMC_BERLIN,
"DifficultyTest1040",
0x10c8e0,
0x68f7512123928555,
0x617ec9fcc,
0x68f7512123928555,
0x617ec9fb8,
true,
},
};

TEST(state_difficulty, tests)
{
for (const auto& t : tests)
{
const auto difficulty = calculate_difficulty(t.parent_difficulty, t.parent_has_ommers,
t.parent_timestamp, t.timestamp, t.block_number, t.rev);
EXPECT_EQ(difficulty, t.difficulty) << t.rev << "/" << t.name;
}
}
6 changes: 6 additions & 0 deletions test/unittests/state_rlp_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ TEST(state_rlp, empty_bytes_hash)
EXPECT_EQ(keccak256({}), emptyBytesHash);
}

TEST(state_rlp, empty_list_hash)
{
EXPECT_EQ(keccak256(bytes{0xc0}), EmptyListHash); // Hash of empty RLP list: 0xc0.
EXPECT_EQ(keccak256(rlp::encode(std::vector<uint64_t>{})), EmptyListHash);
}

TEST(state_rlp, empty_mpt_hash)
{
const auto rlp_null = rlp::encode(0);
Expand Down
3 changes: 2 additions & 1 deletion test/unittests/state_transition.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@
ASSERT_TRUE(holds_alternative<TransactionReceipt>(res))
<< std::get<std::error_code>(res).message();
const auto& receipt = std::get<TransactionReceipt>(res);
evmone::state::finalize(state, rev, block.coinbase, 0, block.withdrawals);
evmone::state::finalize(

Check warning on line 44 in test/unittests/state_transition.cpp

View check run for this annotation

Codecov / codecov/patch

test/unittests/state_transition.cpp#L44

Added line #L44 was not covered by tests
state, rev, block.coinbase, block_reward, block.ommers, block.withdrawals);

EXPECT_EQ(receipt.status, expect.status);
if (expect.gas_used.has_value())
Expand Down
Loading