Skip to content

Conversation

@fmoletta
Copy link
Contributor

@fmoletta fmoletta commented Nov 26, 2025

Motivation
Addressing #5344 by using the existing block_hash_cache to store block hashes looked up during BLOCKHASH opcode execution to speed up subsequent lookups

Description

  • Add interior mutability to block_hash_cache (HashMap -> Arc<Mutex>)
  • Insert block hashes obtained during lookup to block_hash_cache (BLOCKHASH opcode)
  • Use oldest cached block successor to shortcut ancestor lookup (BLOCKHASH opcode)

Benchmark Results: x33,6 improvement on BlockHash EEST gas benchmark: 1080.92 Mgas/s vs 32.18 Gas/s on main (7b4a0ba)

Closes #5344

@github-actions
Copy link

github-actions bot commented Nov 26, 2025

Lines of code report

Total lines added: 14
Total lines removed: 0
Total lines changed: 14

Detailed view
+--------------------------------+-------+------+
| File                           | Lines | Diff |
+--------------------------------+-------+------+
| ethrex/crates/blockchain/vm.rs | 138   | +14  |
+--------------------------------+-------+------+

.get_canonical_block_hash_sync(block_number)
.map_err(|err| EvmError::DB(err.to_string()))?
{
block_hash_cache.insert(block_number, hash);
Copy link
Collaborator

Choose a reason for hiding this comment

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

this grows infinitely?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is only used for the BLOCKHASH opcode implementation, which checks that the requested block number is less than 256 blocks away from the current block, so it should only grow up to 256

@fmoletta fmoletta changed the title feat: cache block hash lookup for BLOCKHASH opcode feat(l1): cache block hash lookup for BLOCKHASH opcode Nov 26, 2025
@github-actions github-actions bot added the L1 Ethereum client label Nov 26, 2025
Comment on lines 20 to 21
// We use this when executing blocks in batches, as we will only add the blocks at the end
// And may need to access hashes of blocks previously executed in the batch
Copy link
Contributor

Choose a reason for hiding this comment

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

I know the PR is not open yet but we should update this comment, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

nice catch!

@fmoletta fmoletta marked this pull request as ready for review November 27, 2025 14:09
@fmoletta fmoletta requested a review from a team as a code owner November 27, 2025 14:09
@ethrex-project-sync ethrex-project-sync bot moved this to In Review in ethrex_l1 Nov 27, 2025
@Oppen
Copy link
Contributor

Oppen commented Nov 27, 2025

I suggest we try preloading the 256 hashes in an array/vector first. For the most part they could live in memory (they take 8kiB for the canonical chain, with some extra for reorgs and forks) already, and even when they don't, the common case is blocks in the canonical chain, meaning they can be resolved with a single query to the canonical hashes table, and the rest require a bounded (65, I think, for forks starting at a non-finalized block that should be at most 64 blocks long, plus one for the intersection with the canonical chain).
That would avoid the internal management of block hashes, including the mutex, and I think would lead to overall simpler code. Iff it shows to perform poorly, I would consider something more complex than that.

@fmoletta
Copy link
Contributor Author

I suggest we try preloading the 256 hashes in an array/vector first. For the most part they could live in memory (they take 8kiB for the canonical chain, with some extra for reorgs and forks) already, and even when they don't, the common case is blocks in the canonical chain, meaning they can be resolved with a single query to the canonical hashes table, and the rest require a bounded (65, I think, for forks starting at a non-finalized block that should be at most 64 blocks long, plus one for the intersection with the canonical chain). That would avoid the internal management of block hashes, including the mutex, and I think would lead to overall simpler code. Iff it shows to perform poorly, I would consider something more complex than that.

I implemented this on #5443 but the benchmark results are worse than this solution.
The benchmark only requests a single block hash multiple times so it makes sense that preloading all 256 won't be an improvement, but I also don't think it is very common for a contract to request so many different block hashes

@Oppen
Copy link
Contributor

Oppen commented Nov 28, 2025

I suggest we try preloading the 256 hashes in an array/vector first. For the most part they could live in memory (they take 8kiB for the canonical chain, with some extra for reorgs and forks) already, and even when they don't, the common case is blocks in the canonical chain, meaning they can be resolved with a single query to the canonical hashes table, and the rest require a bounded (65, I think, for forks starting at a non-finalized block that should be at most 64 blocks long, plus one for the intersection with the canonical chain). That would avoid the internal management of block hashes, including the mutex, and I think would lead to overall simpler code. Iff it shows to perform poorly, I would consider something more complex than that.

I implemented this on #5443 but the benchmark results are worse than this solution. The benchmark only requests a single block hash multiple times so it makes sense that preloading all 256 won't be an improvement, but I also don't think it is very common for a contract to request so many different block hashes

I'd argue it doesn't really implement what I said, as it's making a get to the DB for each block, which I'm saying we should avoid. It's also using a hashmap rather than a vector, and hashing is not free either.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

L1 Ethereum client

Projects

Status: In Review

Development

Successfully merging this pull request may close these issues.

gas benchmarks: blockhash

7 participants