Skip to content

Commit 20437d6

Browse files
authored
perf(core): make TrieDb use NodeHash as key (#2517)
**Motivation** Followup on #2516 using the fact that NodeHash is Copy to use it as the key for the trie db instead of a Vec **Description** Changes the trait TrieDb to use a NodeHash as key instead of a generic vec, allowing less expensive clones when passing around keys since NodeHash is copy and doesn't do any allocation.
1 parent 6370ccb commit 20437d6

File tree

13 files changed

+92
-70
lines changed

13 files changed

+92
-70
lines changed

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-28
6+
7+
- Make TrieDb trait use NodeHash as key [2517](https://github.com/lambdaclass/ethrex/pull/2517)
8+
59
### 2025-04-22
610

711
- Avoid calculating state transitions after every block in bulk mode [2519](https://github.com/lambdaclass/ethrex/pull/2519)

crates/common/trie/db.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
1-
use crate::error::TrieError;
1+
use crate::{error::TrieError, NodeHash};
22
use std::{
33
collections::HashMap,
44
sync::{Arc, Mutex},
55
};
66

77
pub trait TrieDB: Send + Sync {
8-
fn get(&self, key: Vec<u8>) -> Result<Option<Vec<u8>>, TrieError>;
9-
fn put(&self, key: Vec<u8>, value: Vec<u8>) -> Result<(), TrieError>;
8+
fn get(&self, key: NodeHash) -> Result<Option<Vec<u8>>, TrieError>;
9+
fn put(&self, key: NodeHash, value: Vec<u8>) -> Result<(), TrieError>;
1010
// fn put_batch(&self, key: Vec<u8>, value: Vec<u8>) -> Result<(), TrieError>;
11-
fn put_batch(&self, key_values: Vec<(Vec<u8>, Vec<u8>)>) -> Result<(), TrieError>;
11+
fn put_batch(&self, key_values: Vec<(NodeHash, Vec<u8>)>) -> Result<(), TrieError>;
1212
}
1313

1414
/// InMemory implementation for the TrieDB trait, with get and put operations.
1515
pub struct InMemoryTrieDB {
16-
inner: Arc<Mutex<HashMap<Vec<u8>, Vec<u8>>>>,
16+
inner: Arc<Mutex<HashMap<NodeHash, Vec<u8>>>>,
1717
}
1818

1919
impl InMemoryTrieDB {
20-
pub const fn new(map: Arc<Mutex<HashMap<Vec<u8>, Vec<u8>>>>) -> Self {
20+
pub const fn new(map: Arc<Mutex<HashMap<NodeHash, Vec<u8>>>>) -> Self {
2121
Self { inner: map }
2222
}
2323
pub fn new_empty() -> Self {
@@ -28,7 +28,7 @@ impl InMemoryTrieDB {
2828
}
2929

3030
impl TrieDB for InMemoryTrieDB {
31-
fn get(&self, key: Vec<u8>) -> Result<Option<Vec<u8>>, TrieError> {
31+
fn get(&self, key: NodeHash) -> Result<Option<Vec<u8>>, TrieError> {
3232
Ok(self
3333
.inner
3434
.lock()
@@ -37,15 +37,15 @@ impl TrieDB for InMemoryTrieDB {
3737
.cloned())
3838
}
3939

40-
fn put(&self, key: Vec<u8>, value: Vec<u8>) -> Result<(), TrieError> {
40+
fn put(&self, key: NodeHash, value: Vec<u8>) -> Result<(), TrieError> {
4141
self.inner
4242
.lock()
4343
.map_err(|_| TrieError::LockError)?
4444
.insert(key, value);
4545
Ok(())
4646
}
4747

48-
fn put_batch(&self, key_values: Vec<(Vec<u8>, Vec<u8>)>) -> Result<(), TrieError> {
48+
fn put_batch(&self, key_values: Vec<(NodeHash, Vec<u8>)>) -> Result<(), TrieError> {
4949
let mut db = self.inner.lock().map_err(|_| TrieError::LockError)?;
5050

5151
for (key, value) in key_values {

crates/common/trie/node_hash.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,20 @@ impl NodeHash {
8787
}
8888
encoder
8989
}
90+
91+
pub fn len(&self) -> usize {
92+
match self {
93+
NodeHash::Hashed(h256) => h256.as_bytes().len(),
94+
NodeHash::Inline(value) => value.1 as usize,
95+
}
96+
}
97+
98+
pub fn is_empty(&self) -> bool {
99+
match self {
100+
NodeHash::Hashed(h256) => h256.as_bytes().is_empty(),
101+
NodeHash::Inline(value) => value.1 == 0,
102+
}
103+
}
90104
}
91105

92106
impl From<Vec<u8>> for NodeHash {

crates/common/trie/state.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ impl TrieState {
3333
return Ok(Some(node.clone()));
3434
};
3535
self.db
36-
.get(hash.into())?
36+
.get(hash)?
3737
.map(|rlp| Node::decode(&rlp).map_err(TrieError::RLPDecode))
3838
.transpose()
3939
}
@@ -68,7 +68,7 @@ impl TrieState {
6868
fn commit_node_tail_recursive(
6969
&mut self,
7070
node_hash: &NodeHash,
71-
acc: &mut Vec<(Vec<u8>, Vec<u8>)>,
71+
acc: &mut Vec<(NodeHash, Vec<u8>)>,
7272
) -> Result<(), TrieError> {
7373
let Some(node) = self.cache.remove(node_hash) else {
7474
// If the node is not in the cache then it means it is already stored in the DB
@@ -87,7 +87,7 @@ impl TrieState {
8787
Node::Leaf(_) => {}
8888
}
8989
// Commit self
90-
acc.push((node_hash.into(), node.encode_to_vec()));
90+
acc.push((*node_hash, node.encode_to_vec()));
9191

9292
Ok(())
9393
}
@@ -96,7 +96,7 @@ impl TrieState {
9696
pub fn write_node(&mut self, node: Node, hash: NodeHash) -> Result<(), TrieError> {
9797
// Don't insert the node if it is already inlined on the parent
9898
if matches!(hash, NodeHash::Hashed(_)) {
99-
self.db.put(hash.into(), node.encode_to_vec())?;
99+
self.db.put(hash, node.encode_to_vec())?;
100100
}
101101
Ok(())
102102
}
@@ -108,7 +108,7 @@ impl TrieState {
108108
.iter()
109109
.filter_map(|node| {
110110
let hash = node.compute_hash();
111-
matches!(hash, NodeHash::Hashed(_)).then(|| (hash.into(), node.encode_to_vec()))
111+
matches!(hash, NodeHash::Hashed(_)).then(|| (hash, node.encode_to_vec()))
112112
})
113113
.collect();
114114
self.db.put_batch(key_values)?;

crates/common/trie/trie.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,13 @@ mod trie_iter;
1111
mod verify_range;
1212
use ethereum_types::H256;
1313
use ethrex_rlp::constants::RLP_NULL;
14-
use node_hash::NodeHash;
1514
use sha3::{Digest, Keccak256};
1615
use std::collections::HashSet;
1716

1817
pub use self::db::{InMemoryTrieDB, TrieDB};
1918
pub use self::nibbles::Nibbles;
2019
pub use self::verify_range::verify_range;
21-
pub use self::{node::Node, state::TrieState};
20+
pub use self::{node::Node, node_hash::NodeHash, state::TrieState};
2221

2322
pub use self::error::TrieError;
2423
use self::{node::LeafNode, trie_iter::TrieIterator};
@@ -241,15 +240,15 @@ impl Trie {
241240
struct NullTrieDB;
242241

243242
impl TrieDB for NullTrieDB {
244-
fn get(&self, _key: Vec<u8>) -> Result<Option<Vec<u8>>, TrieError> {
243+
fn get(&self, _key: NodeHash) -> Result<Option<Vec<u8>>, TrieError> {
245244
Ok(None)
246245
}
247246

248-
fn put(&self, _key: Vec<u8>, _value: Vec<u8>) -> Result<(), TrieError> {
247+
fn put(&self, _key: NodeHash, _value: Vec<u8>) -> Result<(), TrieError> {
249248
Ok(())
250249
}
251250

252-
fn put_batch(&self, _key_values: Vec<(Vec<u8>, Vec<u8>)>) -> Result<(), TrieError> {
251+
fn put_batch(&self, _key_values: Vec<(NodeHash, Vec<u8>)>) -> Result<(), TrieError> {
253252
Ok(())
254253
}
255254
}
@@ -340,7 +339,7 @@ impl Trie {
340339
use std::sync::Arc;
341340
use std::sync::Mutex;
342341

343-
let hmap: HashMap<Vec<u8>, Vec<u8>> = HashMap::new();
342+
let hmap: HashMap<NodeHash, Vec<u8>> = HashMap::new();
344343
let map = Arc::new(Mutex::new(hmap));
345344
let db = InMemoryTrieDB::new(map);
346345
Trie::new(Box::new(db))

crates/storage/store_db/in_memory.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ use ethrex_common::types::{
99
payload::PayloadBundle, AccountState, Block, BlockBody, BlockHash, BlockHeader, BlockNumber,
1010
ChainConfig, Index, Receipt,
1111
};
12-
use ethrex_trie::{InMemoryTrieDB, Nibbles, Trie};
12+
use ethrex_trie::{InMemoryTrieDB, Nibbles, NodeHash, Trie};
1313
use std::{
1414
collections::{BTreeMap, HashMap},
1515
fmt::Debug,
1616
sync::{Arc, Mutex, MutexGuard},
1717
};
1818

19-
pub type NodeMap = Arc<Mutex<HashMap<Vec<u8>, Vec<u8>>>>;
19+
pub type NodeMap = Arc<Mutex<HashMap<NodeHash, Vec<u8>>>>;
2020

2121
#[derive(Default, Clone)]
2222
pub struct Store(Arc<Mutex<StoreInner>>);

crates/storage/store_db/libmdbx.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use ethrex_common::types::{
1919
use ethrex_rlp::decode::RLPDecode;
2020
use ethrex_rlp::encode::RLPEncode;
2121
use ethrex_rlp::error::RLPDecodeError;
22-
use ethrex_trie::{Nibbles, Trie};
22+
use ethrex_trie::{Nibbles, NodeHash, Trie};
2323
use libmdbx::orm::{Decodable, DupSort, Encodable, Table};
2424
use libmdbx::{
2525
dupsort,
@@ -1150,7 +1150,7 @@ table!(
11501150

11511151
table!(
11521152
/// state trie nodes
1153-
( StateTrieNodes ) Vec<u8> => Vec<u8>
1153+
( StateTrieNodes ) NodeHash => Vec<u8>
11541154
);
11551155

11561156
// Local Blocks

crates/storage/trie_db/libmdbx.rs

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use ethrex_trie::error::TrieError;
1+
use ethrex_trie::{error::TrieError, NodeHash};
22
use libmdbx::orm::{Database, Table};
33
use std::{marker::PhantomData, sync::Arc};
44
/// Libmdbx implementation for the TrieDB trait, with get and put operations.
@@ -11,7 +11,7 @@ use ethrex_trie::TrieDB;
1111

1212
impl<T> LibmdbxTrieDB<T>
1313
where
14-
T: Table<Key = Vec<u8>, Value = Vec<u8>>,
14+
T: Table<Key = NodeHash, Value = Vec<u8>>,
1515
{
1616
pub fn new(db: Arc<Database>) -> Self {
1717
Self {
@@ -23,20 +23,20 @@ where
2323

2424
impl<T> TrieDB for LibmdbxTrieDB<T>
2525
where
26-
T: Table<Key = Vec<u8>, Value = Vec<u8>>,
26+
T: Table<Key = NodeHash, Value = Vec<u8>>,
2727
{
28-
fn get(&self, key: Vec<u8>) -> Result<Option<Vec<u8>>, TrieError> {
28+
fn get(&self, key: NodeHash) -> Result<Option<Vec<u8>>, TrieError> {
2929
let txn = self.db.begin_read().map_err(TrieError::DbError)?;
3030
txn.get::<T>(key).map_err(TrieError::DbError)
3131
}
3232

33-
fn put(&self, key: Vec<u8>, value: Vec<u8>) -> Result<(), TrieError> {
33+
fn put(&self, key: NodeHash, value: Vec<u8>) -> Result<(), TrieError> {
3434
let txn = self.db.begin_readwrite().map_err(TrieError::DbError)?;
3535
txn.upsert::<T>(key, value).map_err(TrieError::DbError)?;
3636
txn.commit().map_err(TrieError::DbError)
3737
}
3838

39-
fn put_batch(&self, key_values: Vec<(Vec<u8>, Vec<u8>)>) -> Result<(), TrieError> {
39+
fn put_batch(&self, key_values: Vec<(NodeHash, Vec<u8>)>) -> Result<(), TrieError> {
4040
let txn = self.db.begin_readwrite().map_err(TrieError::DbError)?;
4141
for (key, value) in key_values {
4242
txn.upsert::<T>(key, value).map_err(TrieError::DbError)?;
@@ -49,6 +49,7 @@ where
4949
mod test {
5050
use super::LibmdbxTrieDB;
5151
use crate::trie_db::test_utils::libmdbx::{new_db, TestNodes};
52+
use ethrex_trie::NodeHash;
5253
use ethrex_trie::Trie;
5354
use ethrex_trie::TrieDB;
5455
use libmdbx::{
@@ -62,24 +63,25 @@ mod test {
6263
fn simple_addition() {
6364
table!(
6465
/// NodeHash to Node table
65-
( Nodes ) Vec<u8> => Vec<u8>
66+
( Nodes ) NodeHash => Vec<u8>
6667
);
6768
let inner_db = new_db::<Nodes>();
69+
let key = NodeHash::from_encoded_raw(b"hello");
6870
let db = LibmdbxTrieDB::<Nodes>::new(inner_db);
69-
assert_eq!(db.get("hello".into()).unwrap(), None);
70-
db.put("hello".into(), "value".into()).unwrap();
71-
assert_eq!(db.get("hello".into()).unwrap(), Some("value".into()));
71+
assert_eq!(db.get(key).unwrap(), None);
72+
db.put(key, "value".into()).unwrap();
73+
assert_eq!(db.get(key).unwrap(), Some("value".into()));
7274
}
7375

7476
#[test]
7577
fn different_tables() {
7678
table!(
7779
/// vec to vec
78-
( TableA ) Vec<u8> => Vec<u8>
80+
( TableA ) NodeHash => Vec<u8>
7981
);
8082
table!(
8183
/// vec to vec
82-
( TableB ) Vec<u8> => Vec<u8>
84+
( TableB ) NodeHash => Vec<u8>
8385
);
8486
let tables = [table_info!(TableA), table_info!(TableB)]
8587
.into_iter()
@@ -88,8 +90,9 @@ mod test {
8890
let inner_db = Arc::new(Database::create(None, &tables).unwrap());
8991
let db_a = LibmdbxTrieDB::<TableA>::new(inner_db.clone());
9092
let db_b = LibmdbxTrieDB::<TableB>::new(inner_db.clone());
91-
db_a.put("hello".into(), "value".into()).unwrap();
92-
assert_eq!(db_b.get("hello".into()).unwrap(), None);
93+
let key = NodeHash::from_encoded_raw(b"hello");
94+
db_a.put(key, "value".into()).unwrap();
95+
assert_eq!(db_b.get(key).unwrap(), None);
9396
}
9497

9598
#[test]

crates/storage/trie_db/libmdbx_dupsort.rs

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use std::{marker::PhantomData, sync::Arc};
22

33
use super::utils::node_hash_to_fixed_size;
4-
use ethrex_trie::error::TrieError;
54
use ethrex_trie::TrieDB;
5+
use ethrex_trie::{error::TrieError, NodeHash};
66
use libmdbx::orm::{Database, DupSort, Encodable};
77

88
/// Libmdbx implementation for the TrieDB trait for a dupsort table with a fixed primary key.
@@ -37,13 +37,13 @@ where
3737
T: DupSort<Key = (SK, [u8; 33]), SeekKey = SK, Value = Vec<u8>>,
3838
SK: Clone + Encodable,
3939
{
40-
fn get(&self, key: Vec<u8>) -> Result<Option<Vec<u8>>, TrieError> {
40+
fn get(&self, key: NodeHash) -> Result<Option<Vec<u8>>, TrieError> {
4141
let txn = self.db.begin_read().map_err(TrieError::DbError)?;
4242
txn.get::<T>((self.fixed_key.clone(), node_hash_to_fixed_size(key)))
4343
.map_err(TrieError::DbError)
4444
}
4545

46-
fn put(&self, key: Vec<u8>, value: Vec<u8>) -> Result<(), TrieError> {
46+
fn put(&self, key: NodeHash, value: Vec<u8>) -> Result<(), TrieError> {
4747
let txn = self.db.begin_readwrite().map_err(TrieError::DbError)?;
4848
txn.upsert::<T>(
4949
(self.fixed_key.clone(), node_hash_to_fixed_size(key)),
@@ -53,7 +53,7 @@ where
5353
txn.commit().map_err(TrieError::DbError)
5454
}
5555

56-
fn put_batch(&self, key_values: Vec<(Vec<u8>, Vec<u8>)>) -> Result<(), TrieError> {
56+
fn put_batch(&self, key_values: Vec<(NodeHash, Vec<u8>)>) -> Result<(), TrieError> {
5757
let txn = self.db.begin_readwrite().map_err(TrieError::DbError)?;
5858
for (key, value) in key_values {
5959
txn.upsert::<T>(
@@ -82,19 +82,21 @@ mod test {
8282
fn simple_addition() {
8383
let inner_db = new_db::<Nodes>();
8484
let db = LibmdbxDupsortTrieDB::<Nodes, [u8; 32]>::new(inner_db, [5; 32]);
85-
assert_eq!(db.get("hello".into()).unwrap(), None);
86-
db.put("hello".into(), "value".into()).unwrap();
87-
assert_eq!(db.get("hello".into()).unwrap(), Some("value".into()));
85+
let key = NodeHash::from_encoded_raw(b"hello");
86+
assert_eq!(db.get(key).unwrap(), None);
87+
db.put(key, "value".into()).unwrap();
88+
assert_eq!(db.get(key).unwrap(), Some("value".into()));
8889
}
8990

9091
#[test]
9192
fn different_keys() {
9293
let inner_db = new_db::<Nodes>();
9394
let db_a = LibmdbxDupsortTrieDB::<Nodes, [u8; 32]>::new(inner_db.clone(), [5; 32]);
9495
let db_b = LibmdbxDupsortTrieDB::<Nodes, [u8; 32]>::new(inner_db, [7; 32]);
95-
db_a.put("hello".into(), "hello!".into()).unwrap();
96-
db_b.put("hello".into(), "go away!".into()).unwrap();
97-
assert_eq!(db_a.get("hello".into()).unwrap(), Some("hello!".into()));
98-
assert_eq!(db_b.get("hello".into()).unwrap(), Some("go away!".into()));
96+
let key = NodeHash::from_encoded_raw(b"hello");
97+
db_a.put(key, "hello!".into()).unwrap();
98+
db_b.put(key, "go away!".into()).unwrap();
99+
assert_eq!(db_a.get(key).unwrap(), Some("hello!".into()));
100+
assert_eq!(db_b.get(key).unwrap(), Some("go away!".into()));
99101
}
100102
}

0 commit comments

Comments
 (0)