Skip to content

feat(mpt): add TrieNode variant + NodeEncoder (M2.2)#17

Open
kyonRay wants to merge 1 commit into
feat/mpt-pr03-nibble-typesfrom
feat/mpt-pr04-trie-node-encoder
Open

feat(mpt): add TrieNode variant + NodeEncoder (M2.2)#17
kyonRay wants to merge 1 commit into
feat/mpt-pr03-nibble-typesfrom
feat/mpt-pr04-trie-node-encoder

Conversation

@kyonRay
Copy link
Copy Markdown
Owner

@kyonRay kyonRay commented May 12, 2026

Summary

  • Adds the RLP encoding layer for the MPT module:
    • TrieNode.hvariant<EmptyNode, LeafNode, ExtensionNode, BranchNode> plus NodeRef { kind: Inline | Hash, inlineBytes, hash }. A default-constructed child (Inline + empty inlineBytes) is the explicit "absent" state and encodes as RLP-empty (0x80).
    • NodeEncoder.{h,cpp}encodeRaw(TrieNode) produces canonical Ethereum RLP for each node kind; encodeAndRef(TrieNode) returns (raw bytes, NodeRef) with the spec's <32B inline / >=32B hash boundary. Visitor uses if constexpr with a static_assert(!sizeof(T*), ...) fallback to force a compile error if a new TrieNode alternative is added without an explicit handler.
    • keccak256(span<bcos::byte const>) free function wrapping bcos::crypto::hasher::openssl::OpenSSL_Keccak256_Hasher (Ethereum 0x01 padding, not NIST SHA3-256's 0x06).
  • Adds bcos-crypto to ledger's target_link_libraries so the hasher is reachable from the library.

Stacking

Test plan

  • ./bcos-ledger/test/test-bcos-ledger --run_test=NodeEncoderSuite — 4 cases:
    • EncodeLeafShort — short leaf produces Inline ref with inlineBytes == raw
    • EncodeBranchHashedWhenLarge — branch carrying a 32-byte hash child crosses the inline boundary and yields Hash ref
    • EmptyTrieRootMatchesEthereumConstantkeccak256(encodeRaw(EmptyNode{})) == emptyRootHash() validates the Keccak 0x01-padding patch (proves the hasher is Ethereum-flavoured, not NIST SHA3-256)
    • InlineThresholdAt31Bytes — leaf encoded at exactly 31 bytes → Inline; at exactly 32 bytes → Hash
  • Regression: NibbleSuite (7) and HexPrefixSuite (6) from M2.1 still green
  • clang-format --dry-run --Werror clean on all new files
  • License header in the first 20 lines of each new file

- TrieNode = variant<Empty, Leaf, Extension, Branch>; NodeRef = inline-bytes or 32B hash
- encodeRaw() -> RLP bytes; encodeAndRef() -> (raw, ref) with <32B inline rule
- keccak256() wrapper around bcos-crypto OpenSSLHasher<Keccak256> (0x01 padding)
- EmptyTrieRoot test verifies keccak256(0x80) == emptyRootHash() (Ethereum constant)

Refs: spec §5.5

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 12, 2026 10:11
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds Ethereum-canonical RLP encoding + reference (inline vs hash) logic for MPT trie nodes, including Keccak256 hashing and unit tests that pin down key spec constants and the 32-byte inline threshold.

Changes:

  • Introduces TrieNode (std::variant) node model plus NodeRef for branch children.
  • Implements NodeEncoder for raw RLP encoding and (raw, NodeRef) computation with Keccak256 hashing.
  • Adds NodeEncoderSuite tests and wires bcos-crypto into ledger build/link.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
bcos-ledger/test/unittests/mpt/NodeEncoderTest.cpp Adds unit tests for RLP encoding, inline/hash threshold, and the empty-trie-root Keccak validation.
bcos-ledger/bcos-ledger/mpt/TrieNode.h Defines trie node types (Empty/Leaf/Extension/Branch) and NodeRef representation.
bcos-ledger/bcos-ledger/mpt/NodeEncoder.h Declares NodeEncoder API and keccak256(span<...>) helper.
bcos-ledger/bcos-ledger/mpt/NodeEncoder.cpp Implements Keccak256 wrapper plus RLP encoding for each trie node kind and inline/hash reference selection.
bcos-ledger/CMakeLists.txt Adds NodeEncoder.cpp to ledger target and links bcos-crypto.

#include <bcos-codec/rlp/Common.h>
#include <bcos-codec/rlp/RLPEncode.h>
#include <bcos-crypto/hasher/OpenSSLHasher.h>
#include <bcos-utilities/Common.h>
#include <bcos-ledger/mpt/NodeEncoder.h>
#include <bcos-ledger/mpt/TrieNode.h>
#include <bcos-utilities/Common.h>
#include <boost/test/unit_test.hpp>
Comment on lines +43 to +48
/// An extension node: stores a shared nibble prefix and an already-RLP-encoded child reference.
struct ExtensionNode
{
std::vector<uint8_t> sharedNibbles; ///< Shared nibble prefix (no HP terminator)
bcos::bytes child; ///< Already RLP-encoded child ref (inline bytes or 33-byte hash string)
};
Comment on lines +88 to +102
// Encode ExtensionNode → RLP list [HP(sharedNibbles, leaf=false), child_ref_raw]
// ExtensionNode.child is ALREADY a complete RLP-encoded child reference, so we must
// splice it raw rather than re-encode it as a byte-string (which would double-wrap).
static bcos::bytes encodeExtension(ExtensionNode const& node)
{
using namespace bcos::codec::rlp;
bcos::bytes out;
auto hp = hexPrefixEncode(node.sharedNibbles, /*terminator=*/false);
// child contributes its own bytes directly (not as an RLP-wrapped byte-string).
const size_t payloadLen = length(hp) + node.child.size();
encodeHeader(out, {.isList = true, .payloadLength = payloadLen});
encode(out, hp);
out.insert(out.end(), node.child.begin(), node.child.end());
return out;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants