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

RLP decoding #687

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions test/state/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ target_sources(
precompiles_cache.hpp
precompiles_cache.cpp
rlp.hpp
rlp.cpp
state.hpp
state.cpp
)
Expand Down
87 changes: 87 additions & 0 deletions test/state/rlp.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// evmone: Fast Ethereum Virtual Machine implementation
// Copyright 2023 The evmone Authors.
// SPDX-License-Identifier: Apache-2.0

#include "rlp.hpp"

namespace evmone::rlp
{

[[nodiscard]] Header decode_header(bytes_view& input)
{
const auto input_len = input.size();

if (input_len == 0)
throw std::runtime_error("rlp decoding error: input is empty");

const auto prefix = input[0];

if (prefix < 0x80)
return {1, false};
else if (prefix < 0xb8) // [0x80, 0xb7]
{
const uint8_t len = prefix - 0x80;
if (len >= input_len)
throw std::runtime_error("rlp decoding error: input too short");

input.remove_prefix(1);
return {static_cast<uint8_t>(prefix - 0x80), false};
}
else if (prefix < 0xc0) // [0xb8, 0xbf]
{
const uint8_t len_of_str_len = prefix - 0xb7;
if (len_of_str_len >= input_len)
throw std::runtime_error("rlp decoding error: input too short");

const auto str_len = evmone::rlp::load<uint64_t>(input.substr(1, len_of_str_len));
if (str_len + len_of_str_len >= input_len)
throw std::runtime_error("rlp decoding error: input too short");

input.remove_prefix(1 + len_of_str_len);
return {str_len, false};
}
else if (prefix < 0xf8) // [0xc0, 0xf7]
{
const uint8_t list_len = prefix - 0xc0;
if (list_len >= input_len)
throw std::runtime_error("rlp decoding error: input too short");

input.remove_prefix(1);
return {list_len, true};
}
else // [0xf8, 0xff]
{
const uint8_t len_of_list_len = prefix - 0xf7;
if (len_of_list_len >= input_len)
throw std::runtime_error("rlp decoding error: input too short");
const auto list_len = evmone::rlp::load<uint64_t>(input.substr(1, len_of_list_len));
if (list_len + len_of_list_len >= input_len)
throw std::runtime_error("rlp decoding error: input too short");

input.remove_prefix(1 + len_of_list_len);
return {list_len, true};
}
}

void decode(bytes_view& from, evmc::bytes32& to)
{
decode(from, to.bytes);
}

void decode(bytes_view& from, bytes& to)
{
const auto h = decode_header(from);

if (h.is_list)
throw std::runtime_error("rlp decoding error: unexpected list type");

to = from.substr(0, static_cast<size_t>(h.payload_length));
from.remove_prefix(static_cast<size_t>(h.payload_length));
}

void decode(bytes_view& from, evmc::address& to)
{
decode(from, to.bytes);
}

} // namespace evmone::rlp
114 changes: 114 additions & 0 deletions test/state/rlp.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@

#pragma once

#include <evmc/evmc.hpp>
#include <intx/intx.hpp>
#include <cassert>
#include <span>
#include <string>
#include <string_view>
#include <utility>
Expand Down Expand Up @@ -119,4 +121,116 @@ inline bytes internal::encode_container(InputIterator begin, InputIterator end)
content += encode(*it);
return wrap_list(content);
}

template <class T>
concept UnsignedIntegral =
std::unsigned_integral<T> || std::same_as<T, intx::uint128> || std::same_as<T, intx::uint256> ||
std::same_as<T, intx::uint512> || std::same_as<T, intx::uint<2048>>;

// Load unsigned integral from bytes_view. The destination size must not be smaller than input data.
template <UnsignedIntegral T>
[[nodiscard]] inline T load(bytes_view input)
{
if (input.size() > sizeof(T))
throw std::runtime_error("load: input too big");

T x{};
std::memcpy(&intx::as_bytes(x)[sizeof(T) - input.size()], input.data(), input.size());
x = intx::to_big_endian(x);
return x;
}

// RLP decoding implementation based on
// https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/#definition
// and https://github.com/torquem-ch/silkworm/blob/master/silkworm/core/rlp/decode.hpp
template <typename T>
inline void decode(bytes_view& input, T& to)
{
rlp_decode(input, to);
}

struct Header
{
uint64_t payload_length = 0;
bool is_list = false;
};

[[nodiscard]] Header decode_header(bytes_view& input);

template <UnsignedIntegral T>
void decode(bytes_view& from, T& to)
{
constexpr auto to_size = sizeof(T);
const auto h = decode_header(from);

if (h.is_list)
throw std::runtime_error("rlp decoding error: unexpected list type");

if (to_size < h.payload_length)
throw std::runtime_error("rlp decoding error: unexpected type");

to = load<T>(from.substr(0, static_cast<size_t>(h.payload_length)));
from.remove_prefix(static_cast<size_t>(h.payload_length));
}

void decode(bytes_view& from, bytes& to);
void decode(bytes_view& from, evmc::bytes32& to);
void decode(bytes_view& from, evmc::address& to);

template <size_t N>
void decode(bytes_view& from, std::span<uint8_t, N> to)
{
const auto h = decode_header(from);

if (h.is_list)
throw std::runtime_error("rlp decoding error: unexpected list type");

if (to.size() < h.payload_length)
throw std::runtime_error("rlp decoding error: payload too big");

auto d = to.size() - h.payload_length;
std::memcpy(to.data() + d, from.data(), static_cast<size_t>(h.payload_length));
from.remove_prefix(static_cast<size_t>(h.payload_length));
}

template <size_t N>
void decode(bytes_view& from, uint8_t (&to)[N])
{
decode(from, std::span<uint8_t, N>(to));
}

template <typename T1, typename T2>
void decode(bytes_view& from, std::pair<T1, T2>& p);

template <typename T>
void decode(bytes_view& from, std::vector<T>& to)
{
const auto h = decode_header(from);

if (!h.is_list)
throw std::runtime_error("rlp decoding error: unexpected type. list expected");

auto payload_view = from.substr(0, static_cast<size_t>(h.payload_length));

while (!payload_view.empty())
{
to.emplace_back();
decode(payload_view, to.back());
}

from.remove_prefix(static_cast<size_t>(h.payload_length));
}

template <typename T1, typename T2>
void decode(bytes_view& from, std::pair<T1, T2>& p)
{
const auto h = decode_header(from);

if (!h.is_list)
throw std::runtime_error("rlp decoding error: unexpected type. list expected");

decode(from, p.first);
decode(from, p.second);
}

} // namespace evmone::rlp
68 changes: 68 additions & 0 deletions test/state/state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -336,4 +336,72 @@ std::variant<TransactionReceipt, std::error_code> transition(State& state, const
withdrawal.amount_in_gwei);
}

void rlp_decode(bytes_view& from, Transaction& to)
{
using namespace rlp;

const auto h = decode_header(from);

// Legacy type starts with a list
if (h.is_list)
to.type = Transaction::Type::legacy;
else
{
// Decode tx type for type > Transaction::Type::legacy
uint8_t t{};
decode(from, t);

if (t > stdx::to_underlying(Transaction::Type::legacy) &&
t <= stdx::to_underlying(Transaction::Type::eip1559))
to.type = static_cast<Transaction::Type>(t);
else
throw std::runtime_error("rlp decoding error: unexpected transaction type.");

// Decode list after type identifier
if (!decode_header(from).is_list)
throw std::runtime_error("rlp decoding error: unexpected type. list expected");

decode(from, to.chain_id);
}

decode(from, to.nonce);

// Decode max priority fee per gas
if (to.type == Transaction::Type::eip1559)
rlp::decode(from, to.max_priority_gas_price);

decode(from, to.max_gas_price);

// Init max_priority_gas_price as max_gas_price for pre-eip1559
if (to.type != Transaction::Type::eip1559)
to.max_priority_gas_price = to.max_gas_price;

uint64_t gas_limit{};
decode(from, gas_limit);
to.gas_limit = static_cast<int64_t>(gas_limit);

// Init address field. It's std::optional
to.to = evmc::address{};
decode(from, to.to->bytes);
decode(from, to.value);
decode(from, to.data);

// For legacy tx chain id is encoded in `v` value
if (to.type == Transaction::Type::legacy)
{
uint256 v_u256;
decode<uint256>(from, v_u256);
to.v = (v_u256 - 35) % 2 == 0 ? 0 : 1;
to.chain_id = ((v_u256 - 35 - to.v) / 2)[0];
}
else
{
decode(from, to.access_list);
decode(from, to.v);
}

decode(from, to.r);
decode(from, to.s);
}

} // namespace evmone::state
5 changes: 4 additions & 1 deletion test/state/state.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ struct Transaction

Type type = Type::legacy;
bytes data;
int64_t gas_limit;
int64_t gas_limit = 0;
intx::uint256 max_gas_price;
intx::uint256 max_priority_gas_price;
address sender;
Expand Down Expand Up @@ -202,4 +202,7 @@ std::variant<int64_t, std::error_code> validate_transaction(const Account& sende
/// Defines how to RLP-encode a Withdrawal.
[[nodiscard]] bytes rlp_encode(const Withdrawal& withdrawal);

/// Defines how to RLP-decode a Transaction.
void rlp_decode(bytes_view& from, Transaction& to);

} // namespace evmone::state
Loading