Skip to content

refactor(levm): implement cache rollback #2417

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 45 commits into
base: main
Choose a base branch
from
Open

Conversation

JereSalo
Copy link
Contributor

@JereSalo JereSalo commented Apr 7, 2025

Motivation

  • Implement cache rollback for avoiding cloning the cache during the execution of a transaction.

Description

  • Now callframe has previous_cache_state, that stores the pre-write state of the account that the callframe is trying to mutate. If the context reverts that state is restored in the cache. Otherwise, the parent call frame inherits the changes of the child of the accounts that only the child has modified, so that if the parent callframe reverts it can revert what the child did.
  • Move database related functions to GeneralizedDatabase. Now they are methods.

Some other changes that it makes:

  • Simplify finalize_execution. Specifically the reversion of value transfer and removal of check for coinbase transfer of gas fee.

Closes #issue_number

@JereSalo JereSalo added tech debt Refactors, cleanups, etc levm Lambda EVM implementation labels Apr 7, 2025
@JereSalo JereSalo self-assigned this Apr 7, 2025
Copy link

github-actions bot commented Apr 7, 2025

Lines of code report

Total lines added: 271
Total lines removed: 135
Total lines changed: 406

Detailed view
+------------------------------------------------------------------------+-------+------+
| File                                                                   | Lines | Diff |
+------------------------------------------------------------------------+-------+------+
| ethrex/cmd/ef_tests/state/runner/levm_runner.rs                        | 417   | +1   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/backends/levm/mod.rs                                  | 691   | +1   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/call_frame.rs                                | 138   | +3   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/db/gen_db.rs                                 | 165   | +165 |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/db/mod.rs                                    | 24    | +1   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/execution_handlers.rs                        | 238   | +4   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/hooks/default_hook.rs                        | 298   | +6   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/hooks/l2_hook.rs                             | 218   | -2   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/opcode_handlers/environment.rs               | 347   | +4   |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/opcode_handlers/stack_memory_storage_flow.rs | 307   | +11  |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/opcode_handlers/system.rs                    | 727   | +75  |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/utils.rs                                     | 409   | -125 |
+------------------------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/vm.rs                                        | 431   | -8   |
+------------------------------------------------------------------------+-------+------+

Copy link

github-actions bot commented Apr 7, 2025

Benchmark Results Comparison

PR Results

Benchmark Results: Factorial

Command Mean [ms] Min [ms] Max [ms] Relative
revm_Factorial 236.1 ± 1.6 234.9 240.6 1.00
levm_Factorial 800.5 ± 4.6 794.3 807.9 3.39 ± 0.03

Benchmark Results: Factorial - Recursive

Command Mean [s] Min [s] Max [s] Relative
revm_FactorialRecursive 1.417 ± 0.079 1.323 1.536 1.00
levm_FactorialRecursive 13.897 ± 0.232 13.648 14.179 9.81 ± 0.57

Benchmark Results: Fibonacci

Command Mean [ms] Min [ms] Max [ms] Relative
revm_Fibonacci 212.9 ± 1.5 211.0 215.8 1.00
levm_Fibonacci 788.9 ± 9.9 776.8 805.8 3.71 ± 0.05

Benchmark Results: ManyHashes

Command Mean [ms] Min [ms] Max [ms] Relative
revm_ManyHashes 8.7 ± 0.0 8.6 8.7 1.00
levm_ManyHashes 16.3 ± 0.2 16.1 16.6 1.88 ± 0.02

Benchmark Results: BubbleSort

Command Mean [s] Min [s] Max [s] Relative
revm_BubbleSort 3.235 ± 0.017 3.216 3.267 1.00
levm_BubbleSort 5.761 ± 0.033 5.713 5.838 1.78 ± 0.01

Benchmark Results: ERC20 - Transfer

Command Mean [ms] Min [ms] Max [ms] Relative
revm_ERC20Transfer 248.2 ± 1.0 247.1 250.1 1.00
levm_ERC20Transfer 497.6 ± 5.5 491.2 508.5 2.01 ± 0.02

Benchmark Results: ERC20 - Mint

Command Mean [ms] Min [ms] Max [ms] Relative
revm_ERC20Mint 142.0 ± 1.6 140.3 144.8 1.00
levm_ERC20Mint 325.0 ± 2.1 322.5 328.6 2.29 ± 0.03

Benchmark Results: ERC20 - Approval

Command Mean [s] Min [s] Max [s] Relative
revm_ERC20Approval 1.037 ± 0.008 1.025 1.050 1.00
levm_ERC20Approval 1.878 ± 0.016 1.856 1.898 1.81 ± 0.02

Main Results

Benchmark Results: Factorial

Command Mean [ms] Min [ms] Max [ms] Relative
revm_Factorial 239.8 ± 0.5 239.3 240.8 1.00
levm_Factorial 815.5 ± 31.3 800.8 904.1 3.40 ± 0.13

Benchmark Results: Factorial - Recursive

Command Mean [s] Min [s] Max [s] Relative
revm_FactorialRecursive 1.355 ± 0.070 1.301 1.497 1.00
levm_FactorialRecursive 13.907 ± 0.032 13.851 13.953 10.26 ± 0.53

Benchmark Results: Fibonacci

Command Mean [ms] Min [ms] Max [ms] Relative
revm_Fibonacci 214.9 ± 0.4 214.6 215.9 1.00
levm_Fibonacci 794.7 ± 10.4 783.3 812.9 3.70 ± 0.05

Benchmark Results: ManyHashes

Command Mean [ms] Min [ms] Max [ms] Relative
revm_ManyHashes 8.6 ± 0.1 8.5 8.7 1.00
levm_ManyHashes 16.4 ± 0.2 16.2 16.9 1.92 ± 0.03

Benchmark Results: BubbleSort

Command Mean [s] Min [s] Max [s] Relative
revm_BubbleSort 3.205 ± 0.025 3.190 3.270 1.00
levm_BubbleSort 5.687 ± 0.024 5.649 5.727 1.77 ± 0.02

Benchmark Results: ERC20 - Transfer

Command Mean [ms] Min [ms] Max [ms] Relative
revm_ERC20Transfer 245.8 ± 4.7 244.1 259.3 1.00
levm_ERC20Transfer 486.6 ± 5.1 479.2 497.6 1.98 ± 0.04

Benchmark Results: ERC20 - Mint

Command Mean [ms] Min [ms] Max [ms] Relative
revm_ERC20Mint 138.9 ± 1.1 137.6 140.8 1.00
levm_ERC20Mint 316.3 ± 5.8 310.8 331.7 2.28 ± 0.05

Benchmark Results: ERC20 - Approval

Command Mean [s] Min [s] Max [s] Relative
revm_ERC20Approval 1.022 ± 0.008 1.014 1.041 1.00
levm_ERC20Approval 1.847 ± 0.012 1.830 1.861 1.81 ± 0.02

Copy link

github-actions bot commented Apr 7, 2025

EF Tests Comparison

Test Name MAIN PR DIFF
Summary: 19711/20129 (97.92%) 19667/20129 (97.70%) ⬇️️ -44
Prague: 2574/2574 (100.00%) 2574/2574 (100.00%) ➖️
Cancun: 4983/4983 (100.00%) 4983/4983 (100.00%) ➖️
Shanghai: 614/614 (100.00%) 614/614 (100.00%) ➖️
Paris: 285/285 (100.00%) 285/285 (100.00%) ➖️
London: 247/247 (100.00%) 247/247 (100.00%) ➖️
Berlin: 205/239 (85.77%) 205/239 (85.77%) ➖️
Istanbul: 229/230 (99.57%) 229/230 (99.57%) ➖️
Petersburg: 2516/2541 (99.02%) 2504/2541 (98.54%) ⬇️️ -12
Constantinople: 2338/2405 (97.21%) 2326/2405 (96.72%) ⬇️️ -12
Byzantium: 2448/2472 (99.03%) 2436/2472 (98.54%) ⬇️️ -12
SpuriousDragon: 579/579 (100.00%) 571/579 (98.62%) ⬇️️ -8
Tangerine: 573/650 (88.15%) 573/650 (88.15%) ➖️
Homestead: 1352/1447 (93.43%) 1352/1447 (93.43%) ➖️
Frontier: 768/863 (88.99%) 768/863 (88.99%) ➖️

Copy link
Contributor

@JulianVentura JulianVentura left a comment

Choose a reason for hiding this comment

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

Code looks good, just two comments


/// Gets mutable account, first checking the cache and then the database
/// (caching in the second case)
/// This isn't a method of VM because it allows us to use it during VM initialization.
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this line comment adds much to this method documentation. Having this method on the GeneralizedDatabase makes more sense than having it on the VM

Copy link
Contributor Author

Choose a reason for hiding this comment

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

changed here

pub fn get_account_mut<'a>(
&'a mut self,
address: Address,
call_frame: Option<&mut CallFrame>,
Copy link
Contributor

Choose a reason for hiding this comment

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

I think that it is not super clear here why you are passing an Option<CallFrame>.
If you want to make the cache backup optional, then maybe we need something a bit more expressive.
The best I came up with right now is to have

type CacheBackup = HashMap<Address, Option<Account>>;

pub fn get_account_mut<'a>(
        &'a mut self,
        address: Address,
        cache_backup: Option<&mut CacheBackup>);
        
// The call would be        
self.db.get_account_mut(address, Some(call_frame.previous_cache_state))?; 
self.db.get_account_mut(address, None)?;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I like it!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Forgot to say, I changed it here

@JereSalo JereSalo marked this pull request as ready for review April 9, 2025 21:32
@JereSalo JereSalo requested a review from a team as a code owner April 9, 2025 21:32
@@ -571,4 +555,17 @@ impl<'a> VM<'a> {

Ok(())
}

/// Restores the cache state to the state before changes made during a callframe.
fn restore_cache_state(&mut self, call_frame: &CallFrame) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Here maybe we can just pass the cache_backup instead of the whole call frame. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You are right! Good catch!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor

@tomip01 tomip01 left a comment

Choose a reason for hiding this comment

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

LGTM, I left some small comments.

Comment on lines 402 to 403
// clear callframe backup because prepare_execution succeeded
initial_call_frame.cache_backup = HashMap::new();
Copy link
Contributor

Choose a reason for hiding this comment

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

I understand this but I think maybe a clearer comment on why we do this would be better

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
levm Lambda EVM implementation tech debt Refactors, cleanups, etc
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants