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

core: decode account from E3 encoded storage #2251

Merged
merged 1 commit into from
Aug 21, 2024
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
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) {
Comment on lines +157 to +169
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this loop with a switch is a smart hack to structure the code
it took me a while to figure it out

a more basic way would be to extract this preambule into a helper function or a local lambda,
and then call it before each decoding attempt

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did it this way for similarity with the code already present in Account::from_encoded_storage

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
Loading