-
Notifications
You must be signed in to change notification settings - Fork 298
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
Add RLP encoding implementation to evmone::state library #463
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
# evmone: Fast Ethereum Virtual Machine implementation | ||
# Copyright 2022 The evmone Authors. | ||
# SPDX-License-Identifier: Apache-2.0 | ||
|
||
add_library(evmone-state INTERFACE) | ||
add_library(evmone::state ALIAS evmone-state) | ||
target_link_libraries(evmone-state INTERFACE ethash::keccak) | ||
target_sources( | ||
evmone-state INTERFACE | ||
hash_utils.hpp | ||
rlp.hpp | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// evmone: Fast Ethereum Virtual Machine implementation | ||
// Copyright 2022 The evmone Authors. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
#pragma once | ||
|
||
#include <ethash/keccak.hpp> | ||
#include <evmc/evmc.hpp> | ||
#include <evmc/hex.hpp> | ||
#include <cstring> | ||
|
||
namespace evmone | ||
{ | ||
using evmc::bytes; | ||
using evmc::bytes_view; | ||
|
||
/// Default type for 256-bit hash. | ||
/// | ||
/// Better than ethash::hash256 because has some additional handy constructors. | ||
using hash256 = evmc::bytes32; | ||
|
||
/// Computes Keccak hash out of input bytes (wrapper of ethash::keccak256). | ||
inline hash256 keccak256(bytes_view data) noexcept | ||
{ | ||
const auto eh = ethash::keccak256(data.data(), data.size()); | ||
hash256 h; | ||
std::memcpy(h.bytes, eh.bytes, sizeof(h)); // TODO: Use std::bit_cast. | ||
return h; | ||
} | ||
} // namespace evmone |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
// evmone: Fast Ethereum Virtual Machine implementation | ||
// Copyright 2021 The evmone Authors. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
#pragma once | ||
|
||
#include <intx/intx.hpp> | ||
#include <cassert> | ||
#include <string> | ||
#include <string_view> | ||
#include <utility> | ||
#include <vector> | ||
|
||
namespace evmone::rlp | ||
{ | ||
using bytes = std::basic_string<uint8_t>; | ||
using bytes_view = std::basic_string_view<uint8_t>; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Weren't these defined somewhere? Or only in Fizzy? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is already defined in multiple places (2x in EVMC). But as long as the definition is the same this is fine. Notice this RLP implementation does not include any evmone/evmc headers. Also intx include can be easily dropped. |
||
|
||
namespace internal | ||
{ | ||
template <uint8_t ShortBase, uint8_t LongBase> | ||
inline bytes encode_length(size_t l) | ||
{ | ||
static constexpr auto short_cutoff = 55; | ||
static_assert(ShortBase + short_cutoff <= 0xff); | ||
assert(l <= 0xffffff); | ||
|
||
if (l <= short_cutoff) | ||
return {static_cast<uint8_t>(ShortBase + l)}; | ||
else if (const auto l0 = static_cast<uint8_t>(l); l <= 0xff) | ||
return {LongBase + 1, l0}; | ||
else if (const auto l1 = static_cast<uint8_t>(l >> 8); l <= 0xffff) | ||
return {LongBase + 2, l1, l0}; | ||
else | ||
return {LongBase + 3, static_cast<uint8_t>(l >> 16), l1, l0}; | ||
} | ||
|
||
inline bytes wrap_list(const bytes& content) | ||
{ | ||
return internal::encode_length<192, 247>(content.size()) + content; | ||
} | ||
|
||
template <typename InputIterator> | ||
inline bytes encode_container(InputIterator begin, InputIterator end); | ||
} // namespace internal | ||
|
||
inline bytes_view trim(bytes_view b) noexcept | ||
{ | ||
b.remove_prefix(std::min(b.find_first_not_of(uint8_t{0x00}), b.size())); | ||
return b; | ||
} | ||
|
||
template <typename T> | ||
inline decltype(rlp_encode(std::declval<T>())) encode(const T& v) | ||
{ | ||
return rlp_encode(v); | ||
gumb0 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
inline bytes encode(bytes_view data) | ||
{ | ||
static constexpr uint8_t short_base = 128; | ||
if (data.size() == 1 && data[0] < short_base) | ||
return {data[0]}; | ||
|
||
return internal::encode_length<short_base, 183>(data.size()) += data; // Op + not available. | ||
} | ||
|
||
inline bytes encode(uint64_t x) | ||
{ | ||
uint8_t b[sizeof(x)]; | ||
intx::be::store(b, x); | ||
return encode(trim({b, sizeof(b)})); | ||
} | ||
|
||
inline bytes encode(const intx::uint256& x) | ||
{ | ||
uint8_t b[sizeof(x)]; | ||
intx::be::store(b, x); | ||
return encode(trim({b, sizeof(b)})); | ||
} | ||
|
||
template <typename T> | ||
inline bytes encode(const std::vector<T>& v) | ||
{ | ||
return internal::encode_container(v.begin(), v.end()); | ||
} | ||
|
||
template <typename T, size_t N> | ||
inline bytes encode(const T (&v)[N]) | ||
{ | ||
return internal::encode_container(std::begin(v), std::end(v)); | ||
} | ||
|
||
/// Encodes the fixed-size collection of heterogeneous values as RLP list. | ||
template <typename... Types> | ||
inline bytes encode_tuple(const Types&... elements) | ||
{ | ||
return internal::wrap_list((encode(elements) + ...)); | ||
} | ||
|
||
/// Encodes the container as RLP list. | ||
/// | ||
/// @tparam InputIterator Type of the input iterator. | ||
/// @param begin Begin iterator. | ||
/// @param end End iterator. | ||
/// @return Bytes of the RLP list. | ||
template <typename InputIterator> | ||
inline bytes internal::encode_container(InputIterator begin, InputIterator end) | ||
{ | ||
bytes content; | ||
for (auto it = begin; it != end; ++it) | ||
content += encode(*it); | ||
return wrap_list(content); | ||
} | ||
} // namespace evmone::rlp |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
// evmone: Fast Ethereum Virtual Machine implementation | ||
// Copyright 2022 The evmone Authors. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
#include <gtest/gtest.h> | ||
#include <test/state/hash_utils.hpp> | ||
#include <test/state/rlp.hpp> | ||
#include <test/utils/utils.hpp> | ||
|
||
using namespace evmone; | ||
using namespace evmc::literals; | ||
using namespace intx; | ||
|
||
static constexpr auto emptyBytesHash = | ||
0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470_bytes32; | ||
|
||
static constexpr auto emptyMPTHash = | ||
0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421_bytes32; | ||
|
||
TEST(state_rlp, empty_bytes_hash) | ||
{ | ||
EXPECT_EQ(keccak256({}), emptyBytesHash); | ||
} | ||
|
||
TEST(state_rlp, empty_mpt_hash) | ||
{ | ||
const auto rlp_null = rlp::encode(0); | ||
EXPECT_EQ(rlp_null, bytes{0x80}); | ||
EXPECT_EQ(keccak256(rlp_null), emptyMPTHash); | ||
} | ||
|
||
TEST(state_rlp, encode_string_short) | ||
{ | ||
EXPECT_EQ(rlp::encode(0x01), "01"_hex); | ||
EXPECT_EQ(rlp::encode(0x31), "31"_hex); | ||
EXPECT_EQ(rlp::encode(0x7f), "7f"_hex); | ||
} | ||
|
||
TEST(state_rlp, encode_string_long) | ||
{ | ||
const auto buffer = std::make_unique<uint8_t[]>(0xffffff); | ||
|
||
const auto r1 = rlp::encode({buffer.get(), 0xaabb}); | ||
EXPECT_EQ(r1.size(), 0xaabb + 3); | ||
EXPECT_EQ(hex({r1.data(), 10}), "b9aabb00000000000000"); | ||
|
||
const auto r2 = rlp::encode({buffer.get(), 0xffff}); | ||
EXPECT_EQ(r2.size(), 0xffff + 3); | ||
EXPECT_EQ(hex({r2.data(), 10}), "b9ffff00000000000000"); | ||
|
||
const auto r3 = rlp::encode({buffer.get(), 0xaabbcc}); | ||
EXPECT_EQ(r3.size(), 0xaabbcc + 4); | ||
EXPECT_EQ(hex({r3.data(), 10}), "baaabbcc000000000000"); | ||
|
||
const auto r4 = rlp::encode({buffer.get(), 0xffffff}); | ||
EXPECT_EQ(r4.size(), 0xffffff + 4); | ||
EXPECT_EQ(hex({r4.data(), 10}), "baffffff000000000000"); | ||
} | ||
|
||
TEST(state_rlp, encode_c_array) | ||
{ | ||
uint64_t a[]{1, 2, 3}; | ||
EXPECT_EQ(hex(rlp::encode(a)), "c3010203"); | ||
} | ||
|
||
TEST(state_rlp, encode_vector) | ||
{ | ||
const auto x = 0xe1e2e3e4e5e6e7d0d1d2d3d4d5d6d7c0c1c2c3c4c5c6c7b0b1b2b3b4b5b6b7_u256; | ||
EXPECT_EQ( | ||
rlp::encode(x), "9fe1e2e3e4e5e6e7d0d1d2d3d4d5d6d7c0c1c2c3c4c5c6c7b0b1b2b3b4b5b6b7"_hex); | ||
std::vector<uint256> v(0xffffff / 32, x); | ||
const auto r = rlp::encode(v); | ||
EXPECT_EQ(r.size(), v.size() * 32 + 4); | ||
} | ||
|
||
TEST(state_rlp, encode_account_with_balance) | ||
{ | ||
const auto expected = | ||
"f8 44" | ||
"80" | ||
"01" | ||
"a0 56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" | ||
"a0 c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"_hex; | ||
|
||
const auto r = rlp::encode_tuple(uint64_t{0}, 1_u256, emptyMPTHash, emptyBytesHash); | ||
EXPECT_EQ(r, expected); | ||
} | ||
|
||
TEST(state_rlp, encode_storage_value) | ||
{ | ||
const auto value = 0x00000000000000000000000000000000000000000000000000000000000001ff_bytes32; | ||
const auto xvalue = rlp::encode(rlp::trim(value)); | ||
EXPECT_EQ(xvalue, "8201ff"_hex); | ||
} | ||
|
||
TEST(state_rlp, encode_mpt_node) | ||
{ | ||
const auto path = "2041"_hex; | ||
const auto value = "765f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f31"_hex; | ||
const auto node = rlp::encode_tuple(path, value); | ||
EXPECT_EQ(node, "e18220419d765f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f31"_hex); | ||
} | ||
|
||
struct CustomStruct | ||
{ | ||
uint64_t a; | ||
bytes b; | ||
}; | ||
|
||
inline bytes rlp_encode(const CustomStruct& t) | ||
{ | ||
return rlp::encode_tuple(t.a, t.b); | ||
} | ||
|
||
TEST(state_rlp, encode_custom_struct) | ||
{ | ||
const CustomStruct t{1, {0x02, 0x03}}; | ||
EXPECT_EQ(rlp::encode(t), "c4 01 820203"_hex); | ||
} | ||
|
||
TEST(state_rlp, encode_custom_struct_list) | ||
{ | ||
std::vector<CustomStruct> v{{1, {0x02, 0x03}}, {4, {0x05, 0x06}}}; | ||
EXPECT_EQ(rlp::encode(v), "ca c401820203 c404820506"_hex); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👀
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I started this last year :(