Skip to content

Commit 14b6dd3

Browse files
committed
perf(core): transform the inlined variant of NodeHash to a constant sized array
1 parent e7832a1 commit 14b6dd3

11 files changed

Lines changed: 99 additions & 90 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Perf
44

5+
### 2025-04-25
6+
7+
- Transform the inlined variant of NodeHash to a constant sized array [2516](https://github.com/lambdaclass/ethrex/pull/2516)
8+
59
### 2025-04-11
610

711
- Removed some unnecessary clones and made some functions const: [2438](https://github.com/lambdaclass/ethrex/pull/2438)

crates/common/trie/node.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ impl Node {
179179
fn decode_child(rlp: &[u8]) -> NodeHash {
180180
match decode_bytes(rlp) {
181181
Ok((hash, &[])) if hash.len() == 32 => NodeHash::Hashed(H256::from_slice(hash)),
182-
Ok((&[], &[])) => NodeHash::Inline(vec![]),
183-
_ => NodeHash::Inline(rlp.to_vec()),
182+
Ok((&[], &[])) => NodeHash::default(),
183+
_ => NodeHash::inline_from_slice(rlp),
184184
}
185185
}

crates/common/trie/node/branch.rs

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ impl BranchNode {
6161
let child_hash = &self.choices[choice];
6262
if child_hash.is_valid() {
6363
let child_node = state
64-
.get_node(child_hash.clone())?
64+
.get_node(*child_hash)?
6565
.ok_or(TrieError::InconsistentTree)?;
6666
child_node.get(state, path)
6767
} else {
@@ -93,7 +93,7 @@ impl BranchNode {
9393
// Insert into existing child and then update it
9494
choice_hash => {
9595
let child_node = state
96-
.get_node(choice_hash.clone())?
96+
.get_node(*choice_hash)?
9797
.ok_or(TrieError::InconsistentTree)?;
9898

9999
let child_node = child_node.insert(state, path, value)?;
@@ -138,7 +138,7 @@ impl BranchNode {
138138
let value = if let Some(choice_index) = path.next_choice() {
139139
if self.choices[choice_index].is_valid() {
140140
let child_node = state
141-
.get_node(self.choices[choice_index].clone())?
141+
.get_node(self.choices[choice_index])?
142142
.ok_or(TrieError::InconsistentTree)?;
143143
// Remove value from child node
144144
let (child_node, old_value) = child_node.remove(state, path.clone())?;
@@ -179,15 +179,14 @@ impl BranchNode {
179179
(1, false) => {
180180
let (choice_index, child_hash) = children[0];
181181
let child = state
182-
.get_node(child_hash.clone())?
182+
.get_node(*child_hash)?
183183
.ok_or(TrieError::InconsistentTree)?;
184184
match child {
185185
// Replace self with an extension node leading to the child
186-
Node::Branch(_) => ExtensionNode::new(
187-
Nibbles::from_hex(vec![choice_index as u8]),
188-
child_hash.clone(),
189-
)
190-
.into(),
186+
Node::Branch(_) => {
187+
ExtensionNode::new(Nibbles::from_hex(vec![choice_index as u8]), *child_hash)
188+
.into()
189+
}
191190
// Replace self with the child extension node, updating its path in the process
192191
Node::Extension(mut extension_node) => {
193192
extension_node.prefix.prepend(choice_index as u8);
@@ -207,7 +206,7 @@ impl BranchNode {
207206

208207
/// Computes the node's hash
209208
pub fn compute_hash(&self) -> NodeHash {
210-
NodeHash::from_encoded_raw(self.encode_raw())
209+
NodeHash::from_encoded_raw(&self.encode_raw())
211210
}
212211

213212
/// Encodes the node
@@ -217,7 +216,7 @@ impl BranchNode {
217216
for child in self.choices.iter() {
218217
match child {
219218
NodeHash::Hashed(hash) => encoder = encoder.encode_bytes(&hash.0),
220-
NodeHash::Inline(raw) if !raw.is_empty() => encoder = encoder.encode_raw(raw),
219+
NodeHash::Inline(raw) if raw.1 != 0 => encoder = encoder.encode_raw(child.as_ref()),
221220
_ => encoder = encoder.encode_bytes(&[]),
222221
}
223222
}
@@ -229,7 +228,7 @@ impl BranchNode {
229228
/// Inserts the node into the state and returns its hash
230229
pub fn insert_self(self, state: &mut TrieState) -> Result<NodeHash, TrieError> {
231230
let hash = self.compute_hash();
232-
state.insert_node(self.into(), hash.clone());
231+
state.insert_node(self.into(), hash);
233232
Ok(hash)
234233
}
235234

@@ -253,7 +252,7 @@ impl BranchNode {
253252
let child_hash = &self.choices[choice];
254253
if child_hash.is_valid() {
255254
let child_node = state
256-
.get_node(child_hash.clone())?
255+
.get_node(*child_hash)?
257256
.ok_or(TrieError::InconsistentTree)?;
258257
child_node.get_path(state, path, node_path)?;
259258
}

crates/common/trie/node/extension.rs

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ impl ExtensionNode {
2828
// Otherwise, no value is present.
2929
if path.skip_prefix(&self.prefix) {
3030
let child_node = state
31-
.get_node(self.child.clone())?
31+
.get_node(self.child)?
3232
.ok_or(TrieError::InconsistentTree)?;
3333

3434
child_node.get(state, path)
@@ -144,29 +144,22 @@ impl ExtensionNode {
144144

145145
/// Computes the node's hash
146146
pub fn compute_hash(&self) -> NodeHash {
147-
NodeHash::from_encoded_raw(self.encode_raw())
147+
NodeHash::from_encoded_raw(&self.encode_raw())
148148
}
149149

150150
/// Encodes the node
151151
pub fn encode_raw(&self) -> Vec<u8> {
152152
let mut buf = vec![];
153153
let mut encoder = Encoder::new(&mut buf).encode_bytes(&self.prefix.encode_compact());
154-
match &self.child {
155-
NodeHash::Inline(x) => {
156-
encoder = encoder.encode_raw(x);
157-
}
158-
NodeHash::Hashed(x) => {
159-
encoder = encoder.encode_bytes(&x.0);
160-
}
161-
}
154+
encoder = self.child.encode(encoder);
162155
encoder.finish();
163156
buf
164157
}
165158

166159
/// Inserts the node into the state and returns its hash
167160
pub fn insert_self(self, state: &mut TrieState) -> Result<NodeHash, TrieError> {
168161
let hash = self.compute_hash();
169-
state.insert_node(self.into(), hash.clone());
162+
state.insert_node(self.into(), hash);
170163
Ok(hash)
171164
}
172165

@@ -187,7 +180,7 @@ impl ExtensionNode {
187180
// Continue to child
188181
if path.skip_prefix(&self.prefix) {
189182
let child_node = state
190-
.get_node(self.child.clone())?
183+
.get_node(self.child)?
191184
.ok_or(TrieError::InconsistentTree)?;
192185
child_node.get_path(state, path, node_path)?;
193186
}

crates/common/trie/node/leaf.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ impl LeafNode {
101101

102102
/// Computes the node's hash
103103
pub fn compute_hash(&self) -> NodeHash {
104-
NodeHash::from_encoded_raw(self.encode_raw())
104+
NodeHash::from_encoded_raw(&self.encode_raw())
105105
}
106106

107107
/// Encodes the node
@@ -118,7 +118,7 @@ impl LeafNode {
118118
/// Receives the offset that needs to be traversed to reach the leaf node from the canonical root, used to compute the node hash
119119
pub fn insert_self(self, state: &mut TrieState) -> Result<NodeHash, TrieError> {
120120
let hash = self.compute_hash();
121-
state.insert_node(self.into(), hash.clone());
121+
state.insert_node(self.into(), hash);
122122
Ok(hash)
123123
}
124124

crates/common/trie/node_hash.rs

Lines changed: 42 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use ethereum_types::H256;
2-
use ethrex_rlp::{decode::RLPDecode, encode::RLPEncode};
2+
use ethrex_rlp::{decode::RLPDecode, encode::RLPEncode, structs::Encoder};
33
#[cfg(feature = "libmdbx")]
44
use libmdbx::orm::{Decodable, Encodable};
55
use sha3::{Digest, Keccak256};
@@ -8,38 +8,50 @@ use sha3::{Digest, Keccak256};
88
/// If the encoded node is less than 32 bits, contains the encoded node itself
99
// TODO: Check if we can omit the Inline variant, as nodes will always be bigger than 32 bits in our use case
1010
// TODO: Check if making this `Copy` can make the code less verbose at a reasonable performance cost
11-
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
11+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1212
pub enum NodeHash {
1313
Hashed(H256),
14-
Inline(Vec<u8>),
14+
// Inline is always len < 32. We need to store the length of the data, a u8 is enough.
15+
Inline(([u8; 31], u8)),
1516
}
1617

1718
impl AsRef<[u8]> for NodeHash {
1819
fn as_ref(&self) -> &[u8] {
1920
match self {
20-
NodeHash::Inline(x) => x.as_ref(),
21+
NodeHash::Inline((slice, len)) => &slice[0..(*len as usize)],
2122
NodeHash::Hashed(x) => x.as_bytes(),
2223
}
2324
}
2425
}
2526

2627
impl NodeHash {
2728
/// Returns the `NodeHash` of an encoded node (encoded using the NodeEncoder)
28-
pub fn from_encoded_raw(encoded: Vec<u8>) -> NodeHash {
29+
pub fn from_encoded_raw(encoded: &[u8]) -> NodeHash {
2930
if encoded.len() >= 32 {
30-
let hash = Keccak256::new_with_prefix(&encoded).finalize();
31+
let hash = Keccak256::new_with_prefix(encoded).finalize();
3132
NodeHash::Hashed(H256::from_slice(hash.as_slice()))
3233
} else {
33-
NodeHash::Inline(encoded)
34+
NodeHash::inline_from_slice(encoded)
3435
}
3536
}
37+
38+
pub(crate) fn inline_from_slice(slice: &[u8]) -> NodeHash {
39+
assert!(slice.len() < 32, "Slice must have a len < 32 to be inlined");
40+
let mut buffer = [0; 31];
41+
buffer[0..slice.len()].copy_from_slice(slice);
42+
NodeHash::Inline((buffer, slice.len() as u8))
43+
}
44+
3645
/// Returns the finalized hash
3746
/// NOTE: This will hash smaller nodes, only use to get the final root hash, not for intermediate node hashes
3847
pub fn finalize(self) -> H256 {
3948
match self {
40-
NodeHash::Inline(x) => {
41-
H256::from_slice(Keccak256::new().chain_update(&*x).finalize().as_slice())
42-
}
49+
NodeHash::Inline(_) => H256::from_slice(
50+
Keccak256::new()
51+
.chain_update(self.as_ref())
52+
.finalize()
53+
.as_slice(),
54+
),
4355
NodeHash::Hashed(x) => x,
4456
}
4557
}
@@ -48,20 +60,33 @@ impl NodeHash {
4860
/// The hash will only be considered invalid if it is empty
4961
/// Aka if it has a default value instead of being a product of hash computation
5062
pub fn is_valid(&self) -> bool {
51-
!matches!(self, NodeHash::Inline(v) if v.is_empty())
63+
!matches!(self, NodeHash::Inline(v) if v.1 == 0)
5264
}
5365

5466
/// Const version of `Default` trait impl
5567
pub const fn const_default() -> Self {
56-
Self::Inline(vec![])
68+
Self::Inline(([0; 31], 0))
69+
}
70+
71+
/// Encodes this NodeHash with the given encoder.
72+
pub fn encode<'a>(&self, mut encoder: Encoder<'a>) -> Encoder<'a> {
73+
match self {
74+
NodeHash::Inline(_) => {
75+
encoder = encoder.encode_raw(self.as_ref());
76+
}
77+
NodeHash::Hashed(_) => {
78+
encoder = encoder.encode_bytes(self.as_ref());
79+
}
80+
}
81+
encoder
5782
}
5883
}
5984

6085
impl From<Vec<u8>> for NodeHash {
6186
fn from(value: Vec<u8>) -> Self {
6287
match value.len() {
6388
32 => NodeHash::Hashed(H256::from_slice(&value)),
64-
_ => NodeHash::Inline(value),
89+
_ => NodeHash::inline_from_slice(&value),
6590
}
6691
}
6792
}
@@ -74,19 +99,13 @@ impl From<H256> for NodeHash {
7499

75100
impl From<NodeHash> for Vec<u8> {
76101
fn from(val: NodeHash) -> Self {
77-
match val {
78-
NodeHash::Hashed(x) => x.0.to_vec(),
79-
NodeHash::Inline(x) => x,
80-
}
102+
val.as_ref().to_vec()
81103
}
82104
}
83105

84106
impl From<&NodeHash> for Vec<u8> {
85107
fn from(val: &NodeHash) -> Self {
86-
match val {
87-
NodeHash::Hashed(x) => x.0.to_vec(),
88-
NodeHash::Inline(x) => x.clone(),
89-
}
108+
val.as_ref().to_vec()
90109
}
91110
}
92111

@@ -104,14 +123,14 @@ impl Decodable for NodeHash {
104123
fn decode(b: &[u8]) -> anyhow::Result<Self> {
105124
Ok(match b.len() {
106125
32 => NodeHash::Hashed(H256::from_slice(b)),
107-
_ => NodeHash::Inline(b.into()),
126+
_ => NodeHash::inline_from_slice(b),
108127
})
109128
}
110129
}
111130

112131
impl Default for NodeHash {
113132
fn default() -> Self {
114-
NodeHash::Inline(Vec::new())
133+
NodeHash::Inline(([0; 31], 0))
115134
}
116135
}
117136

crates/common/trie/state.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ impl TrieState {
2626
/// Retrieves a node based on its hash
2727
pub fn get_node(&self, hash: NodeHash) -> Result<Option<Node>, TrieError> {
2828
// Decode the node if it is inlined
29-
if let NodeHash::Inline(encoded) = hash {
30-
return Ok(Some(Node::decode_raw(&encoded)?));
29+
if let NodeHash::Inline(_) = hash {
30+
return Ok(Some(Node::decode_raw(hash.as_ref())?));
3131
}
3232
if let Some(node) = self.cache.get(&hash) {
3333
return Ok(Some(node.clone()));

0 commit comments

Comments
 (0)