Skip to content

Commit

Permalink
Blockchain tests support: Withdrawals hashing (#690)
Browse files Browse the repository at this point in the history
- Support withdrawals root hash calculation
- Add `ArrowGlacier` fork name mapping to `evmc_revision` (cast to
London fork).
  • Loading branch information
rodiazet authored Aug 23, 2023
2 parents f5ca539 + 1ffe20f commit c9afe1f
Show file tree
Hide file tree
Showing 11 changed files with 175 additions and 23 deletions.
18 changes: 7 additions & 11 deletions test/state/mpt_hash.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,22 +35,18 @@ hash256 mpt_hash(const std::unordered_map<address, Account>& accounts)
return trie.hash();
}

hash256 mpt_hash(std::span<const Transaction> transactions)
template <typename T>
hash256 mpt_hash(std::span<const T> list)
{
MPT trie;
for (size_t i = 0; i < transactions.size(); ++i)
trie.insert(rlp::encode(i), rlp::encode(transactions[i]));
for (size_t i = 0; i < list.size(); ++i)
trie.insert(rlp::encode(i), rlp::encode(list[i]));

return trie.hash();
}

hash256 mpt_hash(std::span<const TransactionReceipt> receipts)
{
MPT trie;
for (size_t i = 0; i < receipts.size(); ++i)
trie.insert(rlp::encode(i), rlp::encode(receipts[i]));

return trie.hash();
}
template hash256 mpt_hash<Transaction>(std::span<const Transaction>);
template hash256 mpt_hash<TransactionReceipt>(std::span<const TransactionReceipt>);
template hash256 mpt_hash<Withdrawal>(std::span<const Withdrawal>);

} // namespace evmone::state
16 changes: 10 additions & 6 deletions test/state/mpt_hash.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,20 @@
namespace evmone::state
{
struct Account;
struct Transaction;
struct TransactionReceipt;

/// Computes Merkle Patricia Trie root hash for the given collection of state accounts.
hash256 mpt_hash(const std::unordered_map<address, Account>& accounts);

/// Computes Merkle Patricia Trie root hash for the given collection of transactions.
hash256 mpt_hash(std::span<const Transaction> transactions);
/// Computes Merkle Patricia Trie root hash for the given list of structures.
template <typename T>
hash256 mpt_hash(std::span<const T> list);

/// Computes Merkle Patricia Trie root hash for the given collection of transactions receipts.
hash256 mpt_hash(std::span<const TransactionReceipt> receipts);
/// A helper to automatically convert collections (e.g. vector, array) to span.
template <typename T>
inline hash256 mpt_hash(const T& list)
requires std::is_convertible_v<T, std::span<const typename T::value_type>>
{
return mpt_hash(std::span<const typename T::value_type>{list});
}

} // namespace evmone::state
6 changes: 6 additions & 0 deletions test/state/state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -295,4 +295,10 @@ std::variant<TransactionReceipt, std::error_code> transition(State& state, const
}
}

[[nodiscard]] bytes rlp_encode(const Withdrawal& withdrawal)
{
return rlp::encode_tuple(withdrawal.index, withdrawal.validator_index, withdrawal.recipient,
withdrawal.amount_in_gwei);
}

} // namespace evmone::state
5 changes: 5 additions & 0 deletions test/state/state.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ class State

struct Withdrawal
{
uint64_t index = 0;
uint64_t validator_index = 0;
address recipient;
uint64_t amount_in_gwei = 0; ///< The amount is denominated in gwei.

Expand Down Expand Up @@ -185,4 +187,7 @@ std::variant<int64_t, std::error_code> validate_transaction(const Account& sende
/// Defines how to RLP-encode a Log.
[[nodiscard]] bytes rlp_encode(const Log& log);

/// Defines how to RLP-encode a Withdrawal.
[[nodiscard]] bytes rlp_encode(const Withdrawal& withdrawal);

} // namespace evmone::state
3 changes: 3 additions & 0 deletions test/statetest/statetest.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ int64_t from_json<int64_t>(const json::json& j);
template <>
state::BlockInfo from_json<state::BlockInfo>(const json::json& j);

template <>
state::Withdrawal from_json<state::Withdrawal>(const json::json& j);

template <>
state::State from_json<state::State>(const json::json& j);

Expand Down
14 changes: 9 additions & 5 deletions test/statetest/statetest_loader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,13 @@ inline uint64_t calculate_current_base_fee_eip1559(
return base_fee;
}

template <>
state::Withdrawal from_json<state::Withdrawal>(const json::json& j)
{
return {from_json<uint64_t>(j.at("index")), from_json<uint64_t>(j.at("validatorIndex")),
from_json<evmc::address>(j.at("address")), from_json<uint64_t>(j.at("amount"))};
}

template <>
state::BlockInfo from_json<state::BlockInfo>(const json::json& j)
{
Expand Down Expand Up @@ -175,10 +182,7 @@ state::BlockInfo from_json<state::BlockInfo>(const json::json& j)
if (const auto withdrawals_it = j.find("withdrawals"); withdrawals_it != j.end())
{
for (const auto& withdrawal : *withdrawals_it)
{
withdrawals.push_back({from_json<evmc::address>(withdrawal.at("address")),
from_json<uint64_t>(withdrawal.at("amount"))});
}
withdrawals.push_back(from_json<state::Withdrawal>(withdrawal));
}

std::unordered_map<int64_t, hash256> block_hashes;
Expand Down Expand Up @@ -238,7 +242,7 @@ evmc_revision to_rev(std::string_view s)
return EVMC_ISTANBUL;
if (s == "Berlin")
return EVMC_BERLIN;
if (s == "London")
if (s == "London" || s == "ArrowGlacier")
return EVMC_LONDON;
if (s == "Merge")
return EVMC_PARIS;
Expand Down
3 changes: 3 additions & 0 deletions test/t8n/t8n.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,9 @@ int main(int argc, const char* argv[])

j_result["logsBloom"] = hex0x(compute_bloom_filter(receipts));
j_result["receiptsRoot"] = hex0x(state::mpt_hash(receipts));
if (rev >= EVMC_SHANGHAI)
j_result["withdrawalsRoot"] = hex0x(state::mpt_hash(block.withdrawals));

j_result["txRoot"] = hex0x(state::mpt_hash(transactions));
j_result["gasUsed"] = hex0x(cumulative_gas_used);

Expand Down
1 change: 1 addition & 0 deletions test/unittests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ target_sources(
statetest_loader_test.cpp
statetest_loader_tx_test.cpp
statetest_logs_hash_test.cpp
statetest_withdrawals_test.cpp
tracing_test.cpp
)
target_link_libraries(evmone-unittests PRIVATE evmone evmone::evmmax evmone::state evmone::statetestutils testutils evmc::instructions GTest::gtest GTest::gtest_main)
Expand Down
26 changes: 26 additions & 0 deletions test/unittests/state_mpt_hash_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -279,3 +279,29 @@ TEST(state_mpt_hash, pre_byzantium_receipt)
EXPECT_EQ(mpt_hash(std::array{receipt0, receipt1, receipt2}),
0x8a4fa43a95939b06ad13ce8cd08e026ae6e79ea3c5fc80c732d252e2769ce778_bytes32);
}

TEST(state_mpt_hash, mpt_hashing_test)
{
// The same data as in 'statetest_withdrawals.withdrawals_warmup_test_case' unit test.
MPT trie;

trie.insert("80"_hex, "db808094c000000000000000000000000000000000000001830186a0"_hex);
EXPECT_EQ(
trie.hash(), 0x4ae14e53d6bf2a9c73891aef9d2e6373ff06d400b6e7727b17a5eceb5e8dec9d_bytes32);

trie.insert("01"_hex, "db018094c000000000000000000000000000000000000002830186a0"_hex);
EXPECT_EQ(
trie.hash(), 0xc5128234c0282b256e2aa91ddc783ddb2c21556766f2e11e64159561b59f8ac7_bytes32);

trie.insert("0x02"_hex, "0xdb028094c000000000000000000000000000000000000003830186a0"_hex);
trie.insert("0x03"_hex, "0xdb038094c000000000000000000000000000000000000004830186a0"_hex);
trie.insert("0x04"_hex, "0xdb048094c000000000000000000000000000000000000005830186a0"_hex);
trie.insert("0x05"_hex, "0xdb058094c000000000000000000000000000000000000006830186a0"_hex);
trie.insert("0x06"_hex, "0xdb068094c000000000000000000000000000000000000007830186a0"_hex);
trie.insert("0x07"_hex, "0xdb078094c000000000000000000000000000000000000008830186a0"_hex);
trie.insert("0x08"_hex, "0xdb088094c000000000000000000000000000000000000009830186a0"_hex);
trie.insert("0x09"_hex, "0xdb098094c000000000000000000000000000000000000010830186a0"_hex);

EXPECT_EQ(
trie.hash(), 0xaa45c53e9f7d6a8362f80876029915da00b1441ef39eb9bbb74f98465ff433ad_bytes32);
}
2 changes: 1 addition & 1 deletion test/unittests/state_transition_block_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ TEST_F(state_transition, block_apply_withdrawal)
{
static constexpr auto withdrawal_address = 0x8888_address;

block.withdrawals = {{withdrawal_address, 3}};
block.withdrawals = {{0, 0, withdrawal_address, 3}};
tx.to = To;
expect.post[withdrawal_address].balance = intx::uint256{3} * 1'000'000'000;
}
Expand Down
104 changes: 104 additions & 0 deletions test/unittests/statetest_withdrawals_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// evmone: Fast Ethereum Virtual Machine implementation
// Copyright 2023 The evmone Authors.
// SPDX-License-Identifier: Apache-2.0

#include <gtest/gtest.h>
#include <test/state/mpt_hash.hpp>
#include <test/statetest/statetest.hpp>

using namespace evmone;
using namespace evmone::state;
using namespace evmone::test;

TEST(statetest_withdrawals, withdrawals_root_hash)
{
// Input taken from https://etherscan.io/block/17826409
constexpr std::string_view input =
R"([{"address":"0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f","amount":"0xe162d9","index":"0xc13ad8","validatorIndex":"0xa2f00"},{"address":"0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f","amount":"0xe1c5c2","index":"0xc13ad9","validatorIndex":"0xa2f01"},{"address":"0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f","amount":"0xe14f28","index":"0xc13ada","validatorIndex":"0xa2f02"},{"address":"0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f","amount":"0xe190f2","index":"0xc13adb","validatorIndex":"0xa2f03"},{"address":"0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f","amount":"0xe1e59c","index":"0xc13adc","validatorIndex":"0xa2f04"},{"address":"0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f","amount":"0xe1bbfe","index":"0xc13add","validatorIndex":"0xa2f05"},{"address":"0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f","amount":"0xe20974","index":"0xc13ade","validatorIndex":"0xa2f06"},{"address":"0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f","amount":"0xe145b7","index":"0xc13adf","validatorIndex":"0xa2f07"},{"address":"0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f","amount":"0xe11e5d","index":"0xc13ae0","validatorIndex":"0xa2f08"},{"address":"0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f","amount":"0xe221e0","index":"0xc13ae1","validatorIndex":"0xa2f09"},{"address":"0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f","amount":"0xe2061a","index":"0xc13ae2","validatorIndex":"0xa2f0a"},{"address":"0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f","amount":"0xe23d22","index":"0xc13ae3","validatorIndex":"0xa2f0b"},{"address":"0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f","amount":"0xe1ab3a","index":"0xc13ae4","validatorIndex":"0xa2f0c"},{"address":"0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f","amount":"0xe19535","index":"0xc13ae5","validatorIndex":"0xa2f0d"},{"address":"0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f","amount":"0x317c537","index":"0xc13ae6","validatorIndex":"0xa2f0e"},{"address":"0xb9d7934878b5fb9610b3fe8a5e441e8fad7e293f","amount":"0xe1965f","index":"0xc13ae7","validatorIndex":"0xa2f0f"}])";

const auto j = json::json::parse(input);

std::vector<Withdrawal> withdrawals;
for (const auto& withdrawal : j)
withdrawals.push_back(from_json<Withdrawal>(withdrawal));

EXPECT_EQ(mpt_hash(withdrawals),
0x38cd9ae992a22b94a1582e7d0691dbef56a90cdb36bf7b11d98373f80b102c8f_bytes32);
}

TEST(statetest_withdrawals, withdrawals_warmup_test_case)
{
// Input taken from
// https://github.com/ethereum/tests/blob/develop/BlockchainTests/InvalidBlocks/bc4895-withdrawals/warmup.json
constexpr std::string_view input =
R"([
{
"index" : "0x0",
"validatorIndex" : "0x0",
"amount" : "0x186a0",
"address" : "0xc000000000000000000000000000000000000001"
},
{
"index" : "0x1",
"validatorIndex" : "0x0",
"amount" : "0x186a0",
"address" : "0xc000000000000000000000000000000000000002"
},
{
"index" : "0x2",
"validatorIndex" : "0x0",
"amount" : "0x186a0",
"address" : "0xc000000000000000000000000000000000000003"
},
{
"index" : "0x3",
"validatorIndex" : "0x0",
"amount" : "0x186a0",
"address" : "0xc000000000000000000000000000000000000004"
},
{
"index" : "0x4",
"validatorIndex" : "0x0",
"amount" : "0x186a0",
"address" : "0xc000000000000000000000000000000000000005"
},
{
"index" : "0x5",
"validatorIndex" : "0x0",
"amount" : "0x186a0",
"address" : "0xc000000000000000000000000000000000000006"
},
{
"index" : "0x6",
"validatorIndex" : "0x0",
"amount" : "0x186a0",
"address" : "0xc000000000000000000000000000000000000007"
},
{
"index" : "0x7",
"validatorIndex" : "0x0",
"amount" : "0x186a0",
"address" : "0xc000000000000000000000000000000000000008"
},
{
"index" : "0x8",
"validatorIndex" : "0x0",
"amount" : "0x186a0",
"address" : "0xc000000000000000000000000000000000000009"
},
{
"index" : "0x9",
"validatorIndex" : "0x0",
"amount" : "0x186a0",
"address" : "0xc000000000000000000000000000000000000010"
}
])";
const auto j = json::json::parse(input);

std::vector<Withdrawal> withdrawals;
for (const auto& withdrawal : j)
withdrawals.push_back(from_json<Withdrawal>(withdrawal));

EXPECT_EQ(mpt_hash(withdrawals),
0xaa45c53e9f7d6a8362f80876029915da00b1441ef39eb9bbb74f98465ff433ad_bytes32);
}

0 comments on commit c9afe1f

Please sign in to comment.