Skip to content

Commit 6e724b5

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

File tree

11 files changed

+107
-98
lines changed

11 files changed

+107
-98
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Perf
44

5+
### 2025-04-22
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

+3-4
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ mod leaf;
55
use std::array;
66

77
pub use branch::BranchNode;
8-
use ethereum_types::H256;
98
use ethrex_rlp::{decode::decode_bytes, error::RLPDecodeError, structs::Decoder};
109
pub use extension::ExtensionNode;
1110
pub use leaf::LeafNode;
@@ -178,8 +177,8 @@ impl Node {
178177

179178
fn decode_child(rlp: &[u8]) -> NodeHash {
180179
match decode_bytes(rlp) {
181-
Ok((hash, &[])) if hash.len() == 32 => NodeHash::Hashed(H256::from_slice(hash)),
182-
Ok((&[], &[])) => NodeHash::Inline(vec![]),
183-
_ => NodeHash::Inline(rlp.to_vec()),
180+
Ok((hash, &[])) if hash.len() == 32 => NodeHash::from_slice(hash),
181+
Ok((&[], &[])) => NodeHash::default(),
182+
_ => NodeHash::from_slice(rlp),
184183
}
185184
}

crates/common/trie/node/branch.rs

+12-13
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

+5-12
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

+2-2
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

+49-29
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,57 @@ 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::from_slice(encoded)
3435
}
3536
}
37+
38+
/// Converts a slice of an already hashed data (in case it's not inlineable) to a NodeHash.
39+
///
40+
/// If you need to hash it in case its len >= 32 see `from_encoded_raw`
41+
pub(crate) fn from_slice(slice: &[u8]) -> NodeHash {
42+
match slice.len() {
43+
0..32 => {
44+
let mut buffer = [0; 31];
45+
buffer[0..slice.len()].copy_from_slice(slice);
46+
NodeHash::Inline((buffer, slice.len() as u8))
47+
}
48+
_ => NodeHash::Hashed(H256::from_slice(slice)),
49+
}
50+
}
51+
3652
/// Returns the finalized hash
3753
/// NOTE: This will hash smaller nodes, only use to get the final root hash, not for intermediate node hashes
3854
pub fn finalize(self) -> H256 {
3955
match self {
40-
NodeHash::Inline(x) => {
41-
H256::from_slice(Keccak256::new().chain_update(&*x).finalize().as_slice())
42-
}
56+
NodeHash::Inline(_) => H256::from_slice(
57+
Keccak256::new()
58+
.chain_update(self.as_ref())
59+
.finalize()
60+
.as_slice(),
61+
),
4362
NodeHash::Hashed(x) => x,
4463
}
4564
}
@@ -48,21 +67,31 @@ impl NodeHash {
4867
/// The hash will only be considered invalid if it is empty
4968
/// Aka if it has a default value instead of being a product of hash computation
5069
pub fn is_valid(&self) -> bool {
51-
!matches!(self, NodeHash::Inline(v) if v.is_empty())
70+
!matches!(self, NodeHash::Inline(v) if v.1 == 0)
5271
}
5372

5473
/// Const version of `Default` trait impl
5574
pub const fn const_default() -> Self {
56-
Self::Inline(vec![])
75+
Self::Inline(([0; 31], 0))
76+
}
77+
78+
/// Encodes this NodeHash with the given encoder.
79+
pub fn encode<'a>(&self, mut encoder: Encoder<'a>) -> Encoder<'a> {
80+
match self {
81+
NodeHash::Inline(_) => {
82+
encoder = encoder.encode_raw(self.as_ref());
83+
}
84+
NodeHash::Hashed(_) => {
85+
encoder = encoder.encode_bytes(self.as_ref());
86+
}
87+
}
88+
encoder
5789
}
5890
}
5991

6092
impl From<Vec<u8>> for NodeHash {
6193
fn from(value: Vec<u8>) -> Self {
62-
match value.len() {
63-
32 => NodeHash::Hashed(H256::from_slice(&value)),
64-
_ => NodeHash::Inline(value),
65-
}
94+
NodeHash::from_slice(&value)
6695
}
6796
}
6897

@@ -74,19 +103,13 @@ impl From<H256> for NodeHash {
74103

75104
impl From<NodeHash> for Vec<u8> {
76105
fn from(val: NodeHash) -> Self {
77-
match val {
78-
NodeHash::Hashed(x) => x.0.to_vec(),
79-
NodeHash::Inline(x) => x,
80-
}
106+
val.as_ref().to_vec()
81107
}
82108
}
83109

84110
impl From<&NodeHash> for Vec<u8> {
85111
fn from(val: &NodeHash) -> Self {
86-
match val {
87-
NodeHash::Hashed(x) => x.0.to_vec(),
88-
NodeHash::Inline(x) => x.clone(),
89-
}
112+
val.as_ref().to_vec()
90113
}
91114
}
92115

@@ -102,16 +125,13 @@ impl Encodable for NodeHash {
102125
#[cfg(feature = "libmdbx")]
103126
impl Decodable for NodeHash {
104127
fn decode(b: &[u8]) -> anyhow::Result<Self> {
105-
Ok(match b.len() {
106-
32 => NodeHash::Hashed(H256::from_slice(b)),
107-
_ => NodeHash::Inline(b.into()),
108-
})
128+
Ok(NodeHash::from_slice(b))
109129
}
110130
}
111131

112132
impl Default for NodeHash {
113133
fn default() -> Self {
114-
NodeHash::Inline(Vec::new())
134+
NodeHash::Inline(([0; 31], 0))
115135
}
116136
}
117137

crates/common/trie/state.rs

+2-2
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)