Skip to content

Commit

Permalink
core: decode account from E3 encoded storage (#2251)
Browse files Browse the repository at this point in the history
  • Loading branch information
canepat authored Aug 21, 2024
1 parent cd716cd commit 9aace13
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 5 deletions.
49 changes: 49 additions & 0 deletions silkworm/core/types/account.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,55 @@ tl::expected<Account, DecodingError> Account::from_encoded_storage(ByteView enco
return a;
}

tl::expected<Account, DecodingError> Account::from_encoded_storage_v3(ByteView encoded_payload) noexcept {
Account a;
if (encoded_payload.empty()) {
return a;
}
size_t pos{0};
for (int i{0}; i < 4; ++i) {
uint8_t len = encoded_payload[pos++];
if (len == 0) {
if (encoded_payload.length() == pos && i < 3) {
return tl::unexpected{DecodingError::kUnexpectedLength};
}
continue;
}
if (encoded_payload.length() < pos + len) {
return tl::unexpected{DecodingError::kInputTooShort};
}
const auto encoded_value{encoded_payload.substr(pos, len)};
switch (i) {
case 0:
if (DecodingResult res{endian::from_big_compact(encoded_value, a.nonce)}; !res) {
return tl::unexpected{res.error()};
}
break;
case 1:
if (DecodingResult res{endian::from_big_compact(encoded_value, a.balance)}; !res) {
return tl::unexpected{res.error()};
}
break;
case 2:
if (len != kHashLength) {
return tl::unexpected{DecodingError::kUnexpectedLength};
}
std::memcpy(a.code_hash.bytes, encoded_value.data(), kHashLength);
break;
case 3:
if (DecodingResult res{endian::from_big_compact(encoded_value, a.incarnation)}; !res) {
return tl::unexpected{res.error()};
}
break;
default:
intx::unreachable();
}
pos += len;
}

return a;
}

tl::expected<uint64_t, DecodingError> Account::incarnation_from_encoded_storage(ByteView encoded_payload) noexcept {
const tl::expected<uint8_t, DecodingError> field_set{validate_encoded_head(encoded_payload)};
if (!field_set) {
Expand Down
15 changes: 10 additions & 5 deletions silkworm/core/types/account.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,24 @@ struct Account {
uint64_t incarnation{0};
uint64_t previous_incarnation{0};

//! \remarks Erigon's (*Account)EncodeForStorage
//! \brief Encode the account into its binary representation for data storage
//! \remarks Erigon (*Account)EncodeForStorage
[[nodiscard]] Bytes encode_for_storage(bool omit_code_hash = false) const;

//! \remarks Erigon's (*Account)EncodingLengthForStorage
//! \brief Compute the length of the account binary representation for data storage
//! \remarks Erigon (*Account)EncodingLengthForStorage
[[nodiscard]] size_t encoding_length_for_storage() const;

//! \brief Rlp encodes Account
//! \brief Serialize the account into its Recursive-Length Prefix (RLP) representation
[[nodiscard]] Bytes rlp(const evmc::bytes32& storage_root) const;

//! \brief Returns an Account from it's encoded representation
//! \brief Decode an Account from its binary representation for data storage
[[nodiscard]] static tl::expected<Account, DecodingError> from_encoded_storage(ByteView encoded_payload) noexcept;

//! \brief Returns an Account Incarnation from it's encoded representation
//! \brief Decode an Account from its binary representation for data storage in E3 data format
[[nodiscard]] static tl::expected<Account, DecodingError> from_encoded_storage_v3(ByteView encoded_payload) noexcept;

//! \brief Return an Account Incarnation from its binary representation for data storage
//! \remarks Similar to from_encoded_storage but faster as it parses only incarnation
[[nodiscard]] static tl::expected<uint64_t, DecodingError> incarnation_from_encoded_storage(
ByteView encoded_payload) noexcept;
Expand Down
60 changes: 60 additions & 0 deletions silkworm/core/types/account_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,64 @@ TEST_CASE("Decode account from storage") {
}
}

TEST_CASE("Decode account from storage V3") {
SECTION("Correct payload") {
Bytes encoded{*from_hex("01020203e820f1885eda54b7a053318cd41e2093220dab15d65381b1157a3633a83bfd5c92390105")};
const auto decoded{Account::from_encoded_storage_v3(encoded)};
REQUIRE(decoded);

CHECK(decoded->nonce == 2);
CHECK(decoded->balance == 1000);
CHECK(decoded->code_hash == 0xf1885eda54b7a053318cd41e2093220dab15d65381b1157a3633a83bfd5c9239_bytes32);
CHECK(decoded->incarnation == 5);
}

SECTION("Empty payload") {
Bytes encoded{};
const auto decoded{Account::from_encoded_storage_v3(encoded)};
REQUIRE(decoded);

CHECK(decoded->nonce == 0);
CHECK(decoded->balance == 0);
CHECK(decoded->code_hash == kEmptyHash);
CHECK(decoded->incarnation == 0);
}

SECTION("Insufficient zero byte payload") {
std::vector<std::string_view> encoded_sequence{"00", "0000", "000000"};
for (const auto encoded_payload : encoded_sequence) {
Bytes encoded{*from_hex(encoded_payload)};
CHECK(Account::from_encoded_storage_v3(encoded) == tl::unexpected{DecodingError::kUnexpectedLength});
}
}

SECTION("All zero byte payload") {
Bytes encoded{*from_hex("00000000")};
CHECK(Account::from_encoded_storage_v3(encoded));
}

SECTION("Too short payload") {
std::vector<std::string_view> encoded_sequence{
"0f",
"0102",
"01020203e8",
"0805c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a4",
};
for (const auto encoded_payload : encoded_sequence) {
Bytes encoded{*from_hex(encoded_payload)};
CHECK(Account::from_encoded_storage_v3(encoded) == tl::unexpected{DecodingError::kInputTooShort});
}
}

SECTION("Wrong nonce payload") {
Bytes encoded{*from_hex("020001")};
CHECK(Account::from_encoded_storage_v3(encoded) == tl::unexpected{DecodingError::kLeadingZero});
}

SECTION("Wrong code_hash payload") {
Bytes encoded{*from_hex("01020203e822f1885eda54b7a053318cd41e2093220dab15d65381b1157a3633a83bfd5c92390105")};
CHECK(Account::from_encoded_storage_v3(encoded) == tl::unexpected{DecodingError::kUnexpectedLength});
}
}

} // namespace silkworm

0 comments on commit 9aace13

Please sign in to comment.