LCOV - differential code coverage report
Current view: top level - test/state - mpt.cpp (source / functions) Coverage Total Hit UBC GBC CBC
Current: DIFF_COVERAGE Lines: 93.2 % 117 109 8 16 93
Current Date: 2024-03-20 16:29:22 Functions: 100.0 % 20 20 2 18
Baseline: coverage_BASE.lcov
Baseline Date: 2024-03-20 14:19:08

           TLA  Line data    Source code
       1                 : // evmone: Fast Ethereum Virtual Machine implementation
       2                 : // Copyright 2022 The evmone Authors.
       3                 : // SPDX-License-Identifier: Apache-2.0
       4                 : 
       5                 : #include "mpt.hpp"
       6                 : #include "rlp.hpp"
       7                 : #include <algorithm>
       8                 : #include <cassert>
       9                 : 
      10                 : namespace evmone::state
      11                 : {
      12                 : namespace
      13                 : {
      14                 : /// The MPT node kind.
      15                 : enum class Kind : uint8_t
      16                 : {
      17                 :     leaf,
      18                 :     ext,
      19                 :     branch
      20                 : };
      21                 : 
      22                 : /// The collection of nibbles (4-bit values) representing a path in a MPT.
      23                 : ///
      24                 : /// TODO(c++26): This is an instance of std::inplace_vector.
      25                 : class Path
      26                 : {
      27                 :     static constexpr size_t max_size = 64;
      28                 : 
      29                 :     size_t m_size = 0;  // TODO: Can be converted to uint8_t.
      30                 :     uint8_t m_nibbles[max_size]{};
      31                 : 
      32                 : public:
      33 CBC         109 :     Path() = default;
      34                 : 
      35                 :     /// Constructs a path from a pair of iterators.
      36             771 :     Path(const uint8_t* first, const uint8_t* last) noexcept
      37             771 :       : m_size(static_cast<size_t>(last - first))
      38                 :     {
      39             771 :         assert(m_size <= std::size(m_nibbles));
      40             771 :         std::copy(first, last, m_nibbles);
      41             771 :     }
      42                 : 
      43                 :     /// Constructs a path from bytes - each byte will produce 2 nibbles in the path.
      44             544 :     explicit Path(bytes_view key) noexcept : m_size{2 * key.size()}
      45                 :     {
      46             544 :         assert(m_size <= std::size(m_nibbles) && "a keys must not be longer than 32 bytes");
      47             544 :         size_t i = 0;
      48           14666 :         for (const auto b : key)
      49                 :         {
      50           14122 :             m_nibbles[i++] = b >> 4;
      51           14122 :             m_nibbles[i++] = b & 0x0f;
      52                 :         }
      53             544 :     }
      54                 : 
      55             544 :     [[nodiscard]] static constexpr size_t capacity() noexcept { return max_size; }
      56             550 :     [[nodiscard]] bool empty() const noexcept { return m_size == 0; }
      57             993 :     [[nodiscard]] const uint8_t* begin() const noexcept { return m_nibbles; }
      58            1542 :     [[nodiscard]] const uint8_t* end() const noexcept { return m_nibbles + m_size; }
      59                 : 
      60             544 :     [[nodiscard]] bytes encode(Kind kind) const
      61                 :     {
      62             544 :         assert(kind == Kind::leaf || kind == Kind::ext);
      63             544 :         const auto kind_prefix = kind == Kind::leaf ? 0x20 : 0x00;
      64             544 :         const auto has_odd_size = m_size % 2 != 0;
      65             544 :         const auto nibble_prefix = has_odd_size ? (0x10 | m_nibbles[0]) : 0x00;
      66                 : 
      67             544 :         bytes encoded{static_cast<uint8_t>(kind_prefix | nibble_prefix)};
      68           14229 :         for (auto i = size_t{has_odd_size}; i < m_size; i += 2)
      69           13685 :             encoded.push_back(static_cast<uint8_t>((m_nibbles[i] << 4) | m_nibbles[i + 1]));
      70             544 :         return encoded;
      71 UBC           0 :     }
      72                 : };
      73                 : }  // namespace
      74                 : 
      75                 : /// The MPT Node.
      76                 : ///
      77                 : /// The implementation is based on StackTrie from go-ethereum.
      78                 : // TODO(clang-tidy-17): bug https://github.com/llvm/llvm-project/issues/50006
      79                 : // NOLINTNEXTLINE(bugprone-reserved-identifier)
      80                 : class MPTNode
      81                 : {
      82                 :     static constexpr size_t num_children = 16;
      83                 : 
      84                 :     Kind m_kind = Kind::leaf;
      85                 :     Path m_path;
      86                 :     bytes m_value;
      87                 :     std::unique_ptr<MPTNode> m_children[num_children];
      88                 : 
      89 CBC         762 :     explicit MPTNode(Kind kind, const Path& path = {}, bytes&& value = {}) noexcept
      90             762 :       : m_kind{kind}, m_path{path}, m_value{std::move(value)}
      91             762 :     {}
      92                 : 
      93                 :     /// Creates an extended node.
      94 GBC           1 :     static MPTNode ext(const Path& path, std::unique_ptr<MPTNode> child) noexcept
      95                 :     {
      96               1 :         assert(child->m_kind == Kind::branch);
      97               1 :         MPTNode node{Kind::ext, path};
      98               1 :         node.m_children[0] = std::move(child);
      99               1 :         return node;
     100                 :     }
     101                 : 
     102                 :     /// Optionally wraps the child node with newly created extended node in case
     103                 :     /// the provided path is not empty.
     104               1 :     static std::unique_ptr<MPTNode> optional_ext(
     105                 :         const Path& path, std::unique_ptr<MPTNode> child) noexcept
     106                 :     {
     107               2 :         return (!path.empty()) ? std::make_unique<MPTNode>(ext(path, std::move(child))) :
     108               1 :                                  std::move(child);
     109                 :     }
     110                 : 
     111                 :     /// Creates a branch node out of two children and optionally extends it with an extended
     112                 :     /// node in case the path is not empty.
     113 CBC         109 :     static MPTNode ext_branch(const Path& path, size_t idx1, std::unique_ptr<MPTNode> child1,
     114                 :         size_t idx2, std::unique_ptr<MPTNode> child2) noexcept
     115                 :     {
     116             109 :         assert(idx1 != idx2);
     117             109 :         assert(idx1 < num_children);
     118             109 :         assert(idx2 < num_children);
     119                 : 
     120             109 :         MPTNode br{Kind::branch};
     121             109 :         br.m_children[idx1] = std::move(child1);
     122             109 :         br.m_children[idx2] = std::move(child2);
     123                 : 
     124             219 :         return (!path.empty()) ? ext(path, std::make_unique<MPTNode>(std::move(br))) :
     125             110 :                                  std::move(br);
     126             109 :     }
     127                 : 
     128                 : public:
     129                 :     MPTNode() = default;
     130                 : 
     131                 :     /// Creates new leaf node.
     132             652 :     static std::unique_ptr<MPTNode> leaf(const Path& path, bytes&& value) noexcept
     133                 :     {
     134             652 :         return std::make_unique<MPTNode>(MPTNode{Kind::leaf, path, std::move(value)});
     135                 :     }
     136                 : 
     137                 :     void insert(const Path& path, bytes&& value);
     138                 : 
     139                 :     [[nodiscard]] bytes encode() const;
     140                 : };
     141                 : 
     142             331 : void MPTNode::insert(const Path& path, bytes&& value)  // NOLINT(misc-no-recursion)
     143                 : {
     144                 :     // The insertion is all about branch nodes. In happy case we will find an empty slot
     145                 :     // in an existing branch node. Otherwise, we need to create new branch node
     146                 :     // (possibly with an adjusted extended node) and transform existing nodes around it.
     147                 : 
     148             331 :     const auto [this_idx, insert_idx] = std::ranges::mismatch(m_path, path);
     149                 : 
     150                 :     // insert_idx is always valid if requirements are fulfilled:
     151                 :     // - if m_path is not shorter than path they must have mismatched nibbles,
     152                 :     //   given the requirement of key uniqueness and not being a prefix if existing key,
     153                 :     // - if m_path is shorter and matches the path prefix
     154                 :     //   then insert_idx points at path[m_path.size()].
     155             331 :     assert(insert_idx != path.end() && "a key must not be a prefix of another key");
     156                 : 
     157             331 :     const Path common{m_path.begin(), this_idx};
     158             331 :     const Path insert_tail{insert_idx + 1, path.end()};
     159                 : 
     160             331 :     switch (m_kind)
     161                 :     {
     162             222 :     case Kind::branch:
     163                 :     {
     164             222 :         assert(m_path.empty());  // Branch has no path.
     165             222 :         if (auto& child = m_children[*insert_idx]; child)
     166               1 :             child->insert(insert_tail, std::move(value));
     167                 :         else
     168             221 :             child = leaf(insert_tail, std::move(value));
     169             222 :         break;
     170                 :     }
     171                 : 
     172 GBC           1 :     case Kind::ext:
     173                 :     {
     174               1 :         assert(!m_path.empty());       // Ext must have non-empty path.
     175               1 :         if (this_idx == m_path.end())  // Paths match: go into the child.
     176 UBC           0 :             return m_children[0]->insert({insert_idx, path.end()}, std::move(value));
     177                 : 
     178                 :         // The original branch node must be pushed down, possible extended with
     179                 :         // the adjusted extended node if the path split point is not directly at the branch node.
     180                 :         // Clang Analyzer bug: https://github.com/llvm/llvm-project/issues/47814
     181                 :         // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
     182 GBC           1 :         auto this_branch = optional_ext({this_idx + 1, m_path.end()}, std::move(m_children[0]));
     183               1 :         auto new_leaf = leaf(insert_tail, std::move(value));
     184                 :         *this =
     185               1 :             ext_branch(common, *this_idx, std::move(this_branch), *insert_idx, std::move(new_leaf));
     186               1 :         break;
     187               1 :     }
     188                 : 
     189 CBC         108 :     case Kind::leaf:
     190                 :     {
     191             108 :         assert(!m_path.empty());  // Leaf must have non-empty path.
     192             108 :         assert(this_idx != m_path.end() && "a key must be unique");
     193             108 :         auto this_leaf = leaf({this_idx + 1, m_path.end()}, std::move(m_value));
     194             108 :         auto new_leaf = leaf(insert_tail, std::move(value));
     195                 :         *this =
     196             108 :             ext_branch(common, *this_idx, std::move(this_leaf), *insert_idx, std::move(new_leaf));
     197             108 :         break;
     198             108 :     }
     199                 : 
     200 UBC           0 :     default:
     201               0 :         assert(false);
     202                 :     }
     203                 : }
     204                 : 
     205                 : /// Encodes a node and optionally hashes the encoded bytes
     206                 : /// if their length exceeds the specified threshold.
     207 CBC         439 : static bytes encode_child(const MPTNode& child) noexcept  // NOLINT(misc-no-recursion)
     208                 : {
     209             439 :     if (auto e = child.encode(); e.size() < 32)
     210 UBC           0 :         return e;  // "short" node
     211                 :     else
     212 CBC         439 :         return rlp::encode(keccak256(e));
     213                 : }
     214                 : 
     215             653 : bytes MPTNode::encode() const  // NOLINT(misc-no-recursion)
     216                 : {
     217             653 :     bytes encoded;
     218             653 :     switch (m_kind)
     219                 :     {
     220             544 :     case Kind::leaf:
     221                 :     {
     222             544 :         encoded = rlp::encode(m_path.encode(m_kind)) + rlp::encode(m_value);
     223             544 :         break;
     224                 :     }
     225             109 :     case Kind::branch:
     226                 :     {
     227             109 :         assert(m_path.empty());
     228                 :         static constexpr uint8_t empty = 0x80;  // encoded empty child
     229                 : 
     230            1853 :         for (const auto& child : m_children)
     231                 :         {
     232            1744 :             if (child)
     233             439 :                 encoded += encode_child(*child);
     234                 :             else
     235            1305 :                 encoded += empty;
     236                 :         }
     237             109 :         encoded += empty;  // end indicator
     238             109 :         break;
     239                 :     }
     240 UBC           0 :     case Kind::ext:
     241                 :     {
     242               0 :         encoded = rlp::encode(m_path.encode(m_kind)) + encode_child(*m_children[0]);
     243               0 :         break;
     244                 :     }
     245                 :     }
     246                 : 
     247 CBC        1306 :     return rlp::internal::wrap_list(encoded);
     248             653 : }
     249                 : 
     250                 : 
     251             424 : MPT::MPT() noexcept = default;
     252             424 : MPT::~MPT() noexcept = default;
     253                 : 
     254             544 : void MPT::insert(bytes_view key, bytes&& value)
     255                 : {
     256             544 :     assert(key.size() <= Path::capacity() / 2);  // must fit the path impl. length limit
     257             544 :     const Path path{key};
     258                 : 
     259             544 :     if (m_root == nullptr)
     260             214 :         m_root = MPTNode::leaf(path, std::move(value));
     261                 :     else
     262             330 :         m_root->insert(path, std::move(value));
     263             544 : }
     264                 : 
     265             424 : [[nodiscard]] hash256 MPT::hash() const
     266                 : {
     267             424 :     if (m_root == nullptr)
     268             210 :         return emptyMPTHash;
     269             214 :     return keccak256(m_root->encode());
     270                 : }
     271                 : 
     272                 : }  // namespace evmone::state
        

Generated by: LCOV version 2.0-1