Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
a503cdb
prealloc
xqft Sep 25, 2025
cc65ec9
fast rlp encoding for branch
xqft Sep 25, 2025
0d87f5b
write directly to hasher
xqft Sep 29, 2025
2475090
dont use encode_write
xqft Sep 29, 2025
9731165
precalc len, assume no inline
xqft Sep 29, 2025
bf46e33
precalc len and write to hasher
xqft Sep 29, 2025
9433e3c
quick rlp and hashing for the rest of nodes
xqft Sep 29, 2025
7132ea0
fix partial/prefix encoding
xqft Sep 29, 2025
71519ea
fix partial/prefix encoding for case len 1
xqft Sep 29, 2025
04faadd
use correct len on value encoding
xqft Sep 29, 2025
f7195f2
fix extension hash prefix
xqft Sep 29, 2025
3cbe873
fix conditional on < 0x80
xqft Sep 29, 2025
b99f697
fix value prefix match case
xqft Sep 29, 2025
0c3571c
add todo and unreachable
xqft Sep 29, 2025
451b340
bad assumption of leaf with single byte value
xqft Sep 29, 2025
2f6f981
memoize hash
xqft Sep 30, 2025
dfa1d27
use encode_raw for branch
xqft Sep 30, 2025
d8e77d9
remove write, add arrayvec buf
xqft Sep 30, 2025
7d9aaac
lint
xqft Sep 30, 2025
b6cc6f6
remove arrayvec
xqft Sep 30, 2025
0641b1f
remove arrayvec import
xqft Sep 30, 2025
fa6cd12
add comment to memoize_hashes and rename
xqft Sep 30, 2025
24b6804
simplify memoize
xqft Sep 30, 2025
5d3fa97
remove fast rlp
xqft Sep 30, 2025
edf7050
dont use encoder
xqft Sep 30, 2025
44035c4
update len
xqft Sep 30, 2025
4f0e036
remove inline assumption
xqft Sep 30, 2025
3dca224
add paylaod length encoding, comments
xqft Sep 30, 2025
86ae6a0
simplify branch encoding
xqft Oct 1, 2025
c2ed4d0
fix encoded_length()
xqft Oct 1, 2025
1df875c
nit
xqft Oct 1, 2025
538c2ab
revert leaf and extension quick rlp
xqft Oct 1, 2025
caadcd2
inline is already encoded
xqft Oct 1, 2025
deef7e0
fix encoded_len, remove extra line
xqft Oct 1, 2025
cd19ec5
remove explicit lifetime
xqft Oct 1, 2025
4b0af4b
change min to max'
xqft Oct 1, 2025
90b17c2
clearer code
xqft Oct 1, 2025
85e88ae
fix value encoding
xqft Oct 1, 2025
8bd9ca3
remove new_with_capacity
xqft Oct 1, 2025
98da8c1
prettify
xqft Oct 1, 2025
0bc34be
Merge branch 'main' into l2/opt_rlp_buffer
xqft Oct 1, 2025
7723608
Update crates/common/rlp/encode.rs
xqft Oct 1, 2025
f9accd3
Refactor payload length calculation for RLP encoding
xqft Oct 1, 2025
44d134c
fmt
xqft Oct 1, 2025
910d3cb
Refactor buffer initialization in encode_raw method
xqft Oct 1, 2025
5e64c72
Merge branch 'main' into l2/opt_rlp_buffer
edg-l Oct 2, 2025
b1ccd89
Merge branch 'main' into l2/opt_rlp_buffer
xqft Oct 21, 2025
4b9209a
clippy
xqft Oct 21, 2025
2a1bab8
fmt
xqft Oct 23, 2025
66c70c6
Merge branch 'main' into l2/opt_rlp_buffer
xqft Oct 28, 2025
42e638d
implement encode_to_vec for branch node
xqft Oct 30, 2025
68f9875
fix
xqft Oct 30, 2025
e701c6c
Merge branch 'main' into l2/opt_rlp_buffer
xqft Oct 30, 2025
2720954
fmt
xqft Oct 30, 2025
2815096
Merge branch 'main' into l2/opt_rlp_buffer
xqft Oct 31, 2025
c3d8a8d
Merge branch 'main' into l2/opt_rlp_buffer
xqft Nov 5, 2025
d7dadad
Merge branch 'main' into l2/opt_rlp_buffer
xqft Nov 10, 2025
8bccc0c
Update crates/common/trie/rlp.rs
ilitteri Nov 10, 2025
951c6be
Merge branch 'main' into l2/opt_rlp_buffer
xqft Nov 10, 2025
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
19 changes: 19 additions & 0 deletions crates/common/rlp/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,25 @@ impl RLPEncode for [u8] {
buf.put_slice(self);
}
}

fn length(&self) -> usize {
const U8_MAX_PLUS_ONE: usize = u8::MAX as usize + 1;
const U16_MAX_PLUS_ONE: usize = u16::MAX as usize + 1;

match self.len() {
0 => 1, // encodes to RLP_NULL
1 if self[0] < 0x80 => 1, // `self` is its own encoding
1..56 => 1 + self.len(), // single byte prefix
56..U8_MAX_PLUS_ONE => 1 + 1 + self.len(), // single byte prefix + payload len bytes
U8_MAX_PLUS_ONE..U16_MAX_PLUS_ONE => 1 + 2 + self.len(), // single byte prefix + payload len bytes
_ => {
// fallback if `self` is longer than 2^16 - 1 bytes
let payload_len_bytes =
((usize::BITS - self.len().leading_zeros()) as usize).div_ceil(8);
1 + payload_len_bytes + self.len()
}
}
}
}

impl<const N: usize> RLPEncode for [u8; N] {
Expand Down
32 changes: 30 additions & 2 deletions crates/common/trie/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,22 @@ impl NodeRef {
}

pub fn compute_hash(&self) -> NodeHash {
*self.compute_hash_ref()
}

pub fn compute_hash_ref(&self) -> &NodeHash {
match self {
NodeRef::Node(node, hash) => *hash.get_or_init(|| node.compute_hash()),
NodeRef::Hash(hash) => *hash,
NodeRef::Node(node, hash) => hash.get_or_init(|| node.compute_hash()),
NodeRef::Hash(hash) => hash,
}
}

pub fn memoize_hashes(&self) {
if let NodeRef::Node(node, hash) = &self
&& hash.get().is_none()
{
node.memoize_hashes();
let _ = hash.set(node.compute_hash());
}
}

Expand Down Expand Up @@ -294,12 +307,27 @@ impl Node {

/// Computes the node's hash
pub fn compute_hash(&self) -> NodeHash {
self.memoize_hashes();
match self {
Node::Branch(n) => n.compute_hash(),
Node::Extension(n) => n.compute_hash(),
Node::Leaf(n) => n.compute_hash(),
}
}

/// Recursively memoizes the hashes of all nodes of the subtrie that has
/// `self` as root (post-order traversal)
pub fn memoize_hashes(&self) {
match self {
Node::Branch(n) => {
for child in &n.choices {
child.memoize_hashes();
}
}
Node::Extension(n) => n.child.memoize_hashes(),
_ => {}
}
}
}

/// Used as return type for `Node` remove operations that may resolve into either:
Expand Down
8 changes: 8 additions & 0 deletions crates/common/trie/node_hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,14 @@ impl RLPEncode for NodeHash {
fn encode(&self, buf: &mut dyn bytes::BufMut) {
RLPEncode::encode(&Into::<Vec<u8>>::into(self), buf)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we build a vec to encode here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm not sure, maybe to take advantage of NodeHash::as_ref()? might try changing it now that you mentioned it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think I'll do it in a different PR

}

fn length(&self) -> usize {
match self {
NodeHash::Hashed(_) => 33, // 1 byte prefix + 32 bytes
NodeHash::Inline((_, 0)) => 1, // if empty then it's encoded to RLP_NULL
NodeHash::Inline((_, len)) => *len as usize, // already encoded
}
}
}

impl RLPDecode for NodeHash {
Expand Down
46 changes: 37 additions & 9 deletions crates/common/trie/rlp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ use std::array;
// Contains RLP encoding and decoding implementations for Trie Nodes
// This encoding is only used to store the nodes in the DB, it is not the encoding used for hash computation
use ethrex_rlp::{
constants::RLP_NULL,
decode::{RLPDecode, decode_bytes},
encode::RLPEncode,
encode::{RLPEncode, encode_length},
error::RLPDecodeError,
structs::{Decoder, Encoder},
};
Expand All @@ -14,18 +15,45 @@ use crate::{Nibbles, NodeHash};

impl RLPEncode for BranchNode {
fn encode(&self, buf: &mut dyn bytes::BufMut) {
let mut encoder = Encoder::new(buf);
let value_len = <[u8] as RLPEncode>::length(&self.value);
let payload_len = self.choices.iter().fold(value_len, |acc, child| {
acc + RLPEncode::length(child.compute_hash_ref())
});

encode_length(payload_len, buf);
for child in self.choices.iter() {
match child.compute_hash_ref() {
NodeHash::Hashed(hash) => hash.0.encode(buf),
NodeHash::Inline((_, 0)) => buf.put_u8(RLP_NULL),
NodeHash::Inline((encoded, len)) => buf.put_slice(&encoded[..*len as usize]),
}
}
<[u8] as RLPEncode>::encode(&self.value, buf);
}

// Duplicated to prealloc the buffer and avoid calculating the payload length twice
fn encode_to_vec(&self) -> Vec<u8> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we should make encode_to_vec in the generic implementation call the length method instead, wdyt?

Copy link
Contributor Author

@xqft xqft Nov 10, 2025

Choose a reason for hiding this comment

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

The thing with that is that the generic length encodes into a buffer (a zero capacity Vec) and then returns the length of that buffer. Ideally RLPEncode should have a way to hint what the encoded size would be to prealloc the buffer (this is done manually in BranchNode::encode_to_vec), and default to a non-prealloced buffer.

If we call the current length we end up encoding twice

let value_len = <[u8] as RLPEncode>::length(&self.value);
let choices_len = self.choices.iter().fold(0, |acc, child| {
acc + RLPEncode::length(child.compute_hash_ref())
});
let payload_len = choices_len + value_len;

let mut buf: Vec<u8> = Vec::with_capacity(payload_len + 3); // 3 byte prefix headroom

encode_length(payload_len, &mut buf);
for child in self.choices.iter() {
match child.compute_hash() {
NodeHash::Hashed(hash) => encoder = encoder.encode_bytes(&hash.0),
child @ NodeHash::Inline(raw) if raw.1 != 0 => {
encoder = encoder.encode_raw(child.as_ref())
match child.compute_hash_ref() {
NodeHash::Hashed(hash) => hash.0.encode(&mut buf),
NodeHash::Inline((_, 0)) => buf.push(RLP_NULL),
NodeHash::Inline((encoded, len)) => {
buf.extend_from_slice(&encoded[..*len as usize])
}
_ => encoder = encoder.encode_bytes(&[]),
}
}
encoder = encoder.encode_bytes(&self.value);
encoder.finish();
<[u8] as RLPEncode>::encode(&self.value, &mut buf);

buf
}
}

Expand Down