From 9aace1387e62ce1c2aff24fe93e5c4992dc1040c Mon Sep 17 00:00:00 2001 From: canepat <16927169+canepat@users.noreply.github.com> Date: Wed, 21 Aug 2024 10:19:18 +0200 Subject: [PATCH] core: decode account from E3 encoded storage (#2251) --- silkworm/core/types/account.cpp | 49 +++++++++++++++++++++++ silkworm/core/types/account.hpp | 15 ++++--- silkworm/core/types/account_test.cpp | 60 ++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 5 deletions(-) diff --git a/silkworm/core/types/account.cpp b/silkworm/core/types/account.cpp index f61b79f7b5..5f8dcf376f 100644 --- a/silkworm/core/types/account.cpp +++ b/silkworm/core/types/account.cpp @@ -148,6 +148,55 @@ tl::expected Account::from_encoded_storage(ByteView enco return a; } +tl::expected 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 Account::incarnation_from_encoded_storage(ByteView encoded_payload) noexcept { const tl::expected field_set{validate_encoded_head(encoded_payload)}; if (!field_set) { diff --git a/silkworm/core/types/account.hpp b/silkworm/core/types/account.hpp index 4f43dc3bb4..beba76f42e 100644 --- a/silkworm/core/types/account.hpp +++ b/silkworm/core/types/account.hpp @@ -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 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 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 incarnation_from_encoded_storage( ByteView encoded_payload) noexcept; diff --git a/silkworm/core/types/account_test.cpp b/silkworm/core/types/account_test.cpp index 9db4ec58b7..25a55246f0 100644 --- a/silkworm/core/types/account_test.cpp +++ b/silkworm/core/types/account_test.cpp @@ -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 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 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