Skip to content

Conversation

@Oppen
Copy link
Contributor

@Oppen Oppen commented Nov 17, 2025

This implements a Gravity-like top-down 16-ways parallel Merkleization,
based on our own pipeline work but extending to shard the updates and
join the updated tries at the end.

This implements a Gravity-like top-down 16-ways parallel Merkleization,
based on our own pipeline work but extending to shard the updates and
join the updated tries at the end.
@Oppen Oppen requested a review from a team as a code owner November 17, 2025 20:45
Copilot AI review requested due to automatic review settings November 17, 2025 20:45
@github-actions github-actions bot added L1 Ethereum client L2 Rollup client performance labels Nov 17, 2025
@github-actions
Copy link

github-actions bot commented Nov 17, 2025

Lines of code report

Total lines added: 159
Total lines removed: 0
Total lines changed: 159

Detailed view
+----------------------------------------+-------+------+
| File                                   | Lines | Diff |
+----------------------------------------+-------+------+
| ethrex/crates/blockchain/blockchain.rs | 1515  | +159 |
+----------------------------------------+-------+------+

Copilot finished reviewing on behalf of Oppen November 17, 2025 20:52
Copy link
Contributor

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

This PR implements a 16-way parallel merkleization strategy for Ethereum state trie updates, based on a top-down sharding approach. The goal is to improve performance by distributing the merkleization work across multiple worker threads, each responsible for updating a specific shard of the trie based on the first nibble of hashed addresses.

Key Changes:

  • Introduced parallel merkleization with 16 worker threads, each handling accounts whose hashed addresses start with a specific nibble (0-F)
  • Refactored handle_merkleization to coordinate worker threads and merge results
  • Added handle_merkleization_subtrie to process updates within each shard
  • Modified process_incoming_update_message to accept pre-hashed addresses and return void instead of the state root hash

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 392 to 413
for (choice, worker) in workers_handles.into_iter().enumerate() {
let worker_result = worker
.join()
.map_err(|e| StoreError::Custom(format!("join failed: {e:?}",)))??;
let Some(root_node) = worker_result.state_updates.get(&Nibbles::default()) else {
continue;
};
let root_node = Node::decode(root_node)?;
match root_node {
Node::Branch(mut branch) => {
real_root.choices[choice] = std::mem::take(&mut branch.choices[choice]);
}
Node::Extension(_extension) => {
todo!()
}
Node::Leaf(_leaf) => {
todo!()
}
}
code_updates.extend(worker_result.code_updates);
storage_updates_map.extend(worker_result.storage_updates);
state_updates_map.extend(worker_result.state_updates);
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

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

When a worker receives no updates for its shard, it's skipped with continue, leaving real_root.choices[choice] as the default empty NodeRef. This is incorrect - the subtrie should be the parent's subtrie, not empty.

To fix this, we need to ensure that even when a worker has no updates, we still copy the corresponding child from the parent root node, or ensure workers always produce a root node even with zero updates.

Suggested change
for (choice, worker) in workers_handles.into_iter().enumerate() {
let worker_result = worker
.join()
.map_err(|e| StoreError::Custom(format!("join failed: {e:?}",)))??;
let Some(root_node) = worker_result.state_updates.get(&Nibbles::default()) else {
continue;
};
let root_node = Node::decode(root_node)?;
match root_node {
Node::Branch(mut branch) => {
real_root.choices[choice] = std::mem::take(&mut branch.choices[choice]);
}
Node::Extension(_extension) => {
todo!()
}
Node::Leaf(_leaf) => {
todo!()
}
}
code_updates.extend(worker_result.code_updates);
storage_updates_map.extend(worker_result.storage_updates);
state_updates_map.extend(worker_result.state_updates);
// Decode the parent root node so we can copy unchanged subtries
let parent_root_node = self.state_store.get_node(&parent_header.state_root)?
.ok_or(StoreError::Custom("parent root node not found".to_string()))?;
let parent_root = match Node::decode(&parent_root_node)? {
Node::Branch(branch) => branch,
_ => return Err(StoreError::Custom("parent root is not a branch node".to_string())),
};
for (choice, worker) in workers_handles.into_iter().enumerate() {
let worker_result = worker
.join()
.map_err(|e| StoreError::Custom(format!("join failed: {e:?}",)))??;
if let Some(root_node) = worker_result.state_updates.get(&Nibbles::default()) {
let root_node = Node::decode(root_node)?;
match root_node {
Node::Branch(mut branch) => {
real_root.choices[choice] = std::mem::take(&mut branch.choices[choice]);
}
Node::Extension(_extension) => {
todo!()
}
Node::Leaf(_leaf) => {
todo!()
}
}
code_updates.extend(worker_result.code_updates);
storage_updates_map.extend(worker_result.storage_updates);
state_updates_map.extend(worker_result.state_updates);
} else {
// No updates for this shard; copy from parent root
real_root.choices[choice] = parent_root.choices[choice].clone();
}

Copilot uses AI. Check for mistakes.
Comment on lines 405 to 408
todo!()
}
Node::Leaf(_leaf) => {
todo!()
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

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

Incomplete implementation for Node::Extension case. This will panic at runtime if a worker returns an Extension node as the root. According to the FIXME comment on line 349, this assumes the state root is always a branch node. However, for robustness, this case should be handled (perhaps by returning an error with a clear message rather than panicking with todo!()).

Suggested change
todo!()
}
Node::Leaf(_leaf) => {
todo!()
return Err(StoreError::Custom(
"Worker returned Extension node as root; expected Branch node. This is not supported.".to_string(),
));
}
Node::Leaf(_leaf) => {
return Err(StoreError::Custom(
"Worker returned Leaf node as root; expected Branch node. This is not supported.".to_string(),
));

Copilot uses AI. Check for mistakes.
todo!()
}
Node::Leaf(_leaf) => {
todo!()
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

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

Incomplete implementation for Node::Leaf case. This will panic at runtime if a worker returns a Leaf node as the root. According to the FIXME comment on line 349, this assumes the state root is always a branch node. However, for robustness, this case should be handled (perhaps by returning an error with a clear message rather than panicking with todo!()).

Suggested change
todo!()
return Err(StoreError::Custom(
"Worker returned a Leaf node as the root, which is not supported. Expected a Branch node.".to_string(),
));

Copilot uses AI. Check for mistakes.
@github-actions
Copy link

github-actions bot commented Nov 17, 2025

Benchmark Block Execution Results Comparison Against Main

Command Mean [s] Min [s] Max [s] Relative
base 59.592 ± 0.218 59.383 60.003 1.00
head 60.537 ± 0.191 60.187 60.783 1.02 ± 0.00

@github-project-automation github-project-automation bot moved this to In Review in ethrex_l1 Nov 19, 2025
@jrchatruc jrchatruc enabled auto-merge November 19, 2025 19:46
@jrchatruc jrchatruc added this pull request to the merge queue Nov 19, 2025
Merged via the queue into main with commit d3c0d11 Nov 19, 2025
41 checks passed
@jrchatruc jrchatruc deleted the perf/parallel_merkleization_topdown branch November 19, 2025 20:23
@github-project-automation github-project-automation bot moved this from Todo to Done in ethrex_performance Nov 19, 2025
@github-project-automation github-project-automation bot moved this from In Review to Done in ethrex_l1 Nov 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

L1 Ethereum client L2 Rollup client performance

Projects

Status: Done
Status: Done
Status: Done

Development

Successfully merging this pull request may close these issues.

5 participants