Skip to content

Commit

Permalink
mpt: Remove explicit extended node kind
Browse files Browse the repository at this point in the history
Notice that the formally specified extended node in
the Merkle Patricia Trie always leads to a branch node.
Therefore, we can treat branch nodes as having potentially non-empty
"extended path" and remove the explicit extended node kind.
This significantly simplifies the implementation.
  • Loading branch information
chfast committed Dec 20, 2023
1 parent c4e9edf commit 9e53b95
Showing 1 changed file with 47 additions and 89 deletions.
136 changes: 47 additions & 89 deletions test/state/mpt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,9 @@ namespace evmone::state
namespace
{
/// The MPT node kind.
enum class Kind : uint8_t
enum class Kind : bool
{
leaf,
ext,
branch
};

Expand Down Expand Up @@ -53,12 +52,15 @@ struct Path

[[nodiscard]] bytes encode(Kind kind) const
{
if (kind == Kind::branch && length == 0)
return {};

const auto is_even = length % 2 == 0;
bytes bs{static_cast<uint8_t>(
(is_even ? 0x00 : (0x10 | nibbles[0])) | (kind == Kind::leaf ? 0x20 : 0x00))};
for (size_t i = is_even ? 0 : 1; i < length; i += 2)
bs.push_back(static_cast<uint8_t>((nibbles[i] << 4) | nibbles[i + 1]));
return bs;
return rlp::encode(bs);
}
};
} // namespace
Expand All @@ -81,39 +83,19 @@ class MPTNode
: m_kind{kind}, m_path{path}, m_value{std::move(value)}
{}

/// Creates an extended node.
static MPTNode ext(const Path& path, std::unique_ptr<MPTNode> child) noexcept
{
assert(child->m_kind == Kind::branch);
MPTNode node{Kind::ext, path};
node.m_children[0] = std::move(child);
return node;
}

/// Optionally wraps the child node with newly created extended node in case
/// the provided path is not empty.
static std::unique_ptr<MPTNode> optional_ext(
const Path& path, std::unique_ptr<MPTNode> child) noexcept
{
return (path.length != 0) ? std::make_unique<MPTNode>(ext(path, std::move(child))) :
std::move(child);
}

/// Creates a branch node out of two children and optionally extends it with an extended
/// node in case the path is not empty.
static MPTNode ext_branch(const Path& path, size_t idx1, std::unique_ptr<MPTNode> child1,
static MPTNode branch(const Path& path, size_t idx1, std::unique_ptr<MPTNode> child1,
size_t idx2, std::unique_ptr<MPTNode> child2) noexcept
{
assert(idx1 != idx2);
assert(idx1 < num_children);
assert(idx2 < num_children);

MPTNode br{Kind::branch};
MPTNode br{Kind::branch, path};
br.m_children[idx1] = std::move(child1);
br.m_children[idx2] = std::move(child2);

return (path.length != 0) ? ext(path, std::make_unique<MPTNode>(std::move(br))) :
std::move(br);
return br;
}

/// The information how two paths are different.
Expand Down Expand Up @@ -167,97 +149,73 @@ void MPTNode::insert(const Path& path, bytes&& value) // NOLINT(misc-no-recursi
{
// The insertion is all about branch nodes. In happy case we will find an empty slot
// in an existing branch node. Otherwise, we need to create new branch node
// (possibly with an adjusted extended node) and transform existing nodes around it.
// (possibly with an extended path) and transform existing nodes around it.

// Let's consider the following branch node with extended path "ab".
//
// |
// |a ↙③
// |b
// |
// [a|b|c|d]
// | ②
//
//
// If the insert path prefix matches the "ab" we insert to one of the children:
// - e.g. for "aba" insert into existing child ①,
// - e.g. for "abd" create new leaf node ②.
// If the insert path prefix doesn't match "ab" we split the extended path by
// a new branch node of the "this" branch node and a new leaf node.
// E.g. for "acd" insert new branch node "a" at ③ with:
// - at "b" : the "this" branch node with empty extended path "",
// - at "c" : the new leaf node with path "d".

const auto [common, this_idx, insert_idx, this_tail, insert_tail] = mismatch(m_path, path);

switch (m_kind)
if (m_kind == Kind::branch && common.length == m_path.length) // Go into the child.
{
case Kind::branch:
{
assert(m_path.length == 0); // Branch has no path.
if (auto& child = m_children[insert_idx]; child)
child->insert(insert_tail, std::move(value));
child->insert(insert_tail, std::move(value)); //
else
child = leaf(insert_tail, std::move(value));
break;
child = leaf(insert_tail, std::move(value)); //
}

case Kind::ext:
else // ③: Shorten path of this node and insert it to the new branch node.
{
assert(m_path.length != 0); // Ext must have non-empty path.
if (common.length == m_path.length) // Paths match: go into the child.
return m_children[0]->insert(path.tail(common.length), std::move(value));

// The original branch node must be pushed down, possible extended with
// the adjusted extended node if the path split point is not directly at the branch node.
// Clang Analyzer bug: https://github.com/llvm/llvm-project/issues/47814
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
auto this_branch = optional_ext(this_tail, std::move(m_children[0]));
auto new_leaf = leaf(insert_tail, std::move(value));
*this =
ext_branch(common, this_idx, std::move(this_branch), insert_idx, std::move(new_leaf));
break;
}

case Kind::leaf:
{
assert(m_path.length != 0); // Leaf must have non-empty path.
assert(common.length != m_path.length); // Paths must be different.
auto this_leaf = leaf(this_tail, std::move(m_value));
auto new_leaf = leaf(insert_tail, std::move(value));
*this = ext_branch(common, this_idx, std::move(this_leaf), insert_idx, std::move(new_leaf));
break;
}

default:
assert(false);
m_path = this_tail;
*this = branch(common, this_idx, std::make_unique<MPTNode>(std::move(*this)), insert_idx,
leaf(insert_tail, std::move(value)));
}
}

/// Encodes a node and optionally hashes the encoded bytes
/// if their length exceeds the specified threshold.
static bytes encode_child(const MPTNode& child) noexcept // NOLINT(misc-no-recursion)
{
if (auto e = child.encode(); e.size() < 32)
return e; // "short" node
else
return rlp::encode(keccak256(e));
}

bytes MPTNode::encode() const // NOLINT(misc-no-recursion)
{
bytes encoded;
static constexpr auto shorten = [](bytes&& b) {
return (b.size() < 32) ? std::move(b) : rlp::encode(keccak256(b));
};

bytes payload; // the encoded content of the node without its path
switch (m_kind)
{
case Kind::leaf:
{
encoded = rlp::encode(m_path.encode(m_kind)) + rlp::encode(m_value);
payload = rlp::encode(m_value);
break;
}
case Kind::branch:
{
assert(m_path.length == 0);
static constexpr uint8_t empty = 0x80; // encoded empty child

for (const auto& child : m_children)
{
if (child)
encoded += encode_child(*child);
else
encoded += empty;
}
encoded += empty; // end indicator
break;
}
case Kind::ext:
{
encoded = rlp::encode(m_path.encode(m_kind)) + encode_child(*m_children[0]);
payload += child ? shorten(child->encode()) : bytes{empty};
payload += empty; // end indicator

if (m_path.length != 0) // extended node
payload = shorten(rlp::internal::wrap_list(payload));
break;
}
}

return rlp::internal::wrap_list(encoded);
return rlp::internal::wrap_list(m_path.encode(m_kind) + payload);
}


Expand Down

0 comments on commit 9e53b95

Please sign in to comment.