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

Merged
merged 59 commits into from
Apr 25, 2025
Merged

refactor(levm): implement cache rollback #2417

merged 59 commits into from
Apr 25, 2025

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 cache_backup, 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 some database related functions that don't need backup to GeneralizedDatabase
  • Move some database related functions that need backup VM. Basically it accesses the database backup up if there's a callframe available for doing so.
  • Stop popping callframe whenever possible

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.
  • Move some things to utils.rs and gen_db.rs so that vm.rs keeps main functionalities.

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: 240
Total lines removed: 263
Total lines changed: 503

Detailed view
+----------------------------------------------------------+-------+------+
| File                                                     | Lines | Diff |
+----------------------------------------------------------+-------+------+
| ethrex/cmd/ef_tests/state/runner/levm_runner.rs          | 382   | +1   |
+----------------------------------------------------------+-------+------+
| ethrex/crates/vm/backends/levm/mod.rs                    | 627   | +1   |
+----------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/call_frame.rs                  | 126   | +3   |
+----------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/db/gen_db.rs                   | 209   | +209 |
+----------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/db/mod.rs                      | 16    | +1   |
+----------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/hooks/default_hook.rs          | 262   | -28  |
+----------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/hooks/hook.rs                  | 12    | -6   |
+----------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/hooks/l2_hook.rs               | 192   | -28  |
+----------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/opcode_handlers/block.rs       | 140   | +2   |
+----------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/opcode_handlers/environment.rs | 318   | +4   |
+----------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/opcode_handlers/system.rs      | 840   | +19  |
+----------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/utils.rs                       | 425   | -103 |
+----------------------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/vm.rs                          | 394   | -98  |
+----------------------------------------------------------+-------+------+

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.3 ± 2.9 234.1 244.1 1.00
levm_Factorial 907.3 ± 65.4 882.4 1092.5 3.84 ± 0.28

Benchmark Results: Factorial - Recursive

Command Mean [s] Min [s] Max [s] Relative
revm_FactorialRecursive 1.461 ± 0.090 1.338 1.584 1.00
levm_FactorialRecursive 14.141 ± 0.135 13.922 14.271 9.68 ± 0.61

Benchmark Results: Fibonacci

Command Mean [ms] Min [ms] Max [ms] Relative
revm_Fibonacci 227.6 ± 16.1 211.3 259.2 1.00
levm_Fibonacci 882.9 ± 2.4 879.6 886.8 3.88 ± 0.28

Benchmark Results: ManyHashes

Command Mean [ms] Min [ms] Max [ms] Relative
revm_ManyHashes 8.7 ± 0.1 8.6 8.8 1.00
levm_ManyHashes 17.6 ± 0.2 17.4 18.0 2.02 ± 0.03

Benchmark Results: BubbleSort

Command Mean [s] Min [s] Max [s] Relative
revm_BubbleSort 3.225 ± 0.031 3.180 3.297 1.00
levm_BubbleSort 11.241 ± 0.129 11.078 11.491 3.49 ± 0.05

Benchmark Results: ERC20 - Transfer

Command Mean [ms] Min [ms] Max [ms] Relative
revm_ERC20Transfer 253.1 ± 1.5 252.0 257.2 1.00
levm_ERC20Transfer 581.5 ± 4.3 575.2 588.0 2.30 ± 0.02

Benchmark Results: ERC20 - Mint

Command Mean [ms] Min [ms] Max [ms] Relative
revm_ERC20Mint 144.5 ± 0.6 143.6 145.7 1.00
levm_ERC20Mint 389.4 ± 4.6 383.1 398.3 2.69 ± 0.03

Benchmark Results: ERC20 - Approval

Command Mean [s] Min [s] Max [s] Relative
revm_ERC20Approval 1.063 ± 0.007 1.054 1.076 1.00
levm_ERC20Approval 2.145 ± 0.048 2.100 2.251 2.02 ± 0.05

Main Results

Benchmark Results: Factorial

Command Mean [ms] Min [ms] Max [ms] Relative
revm_Factorial 230.7 ± 0.8 229.6 232.0 1.00
levm_Factorial 891.7 ± 5.3 882.1 898.6 3.87 ± 0.03

Benchmark Results: Factorial - Recursive

Command Mean [s] Min [s] Max [s] Relative
revm_FactorialRecursive 1.405 ± 0.075 1.332 1.513 1.00
levm_FactorialRecursive 14.047 ± 0.204 13.790 14.289 10.00 ± 0.55

Benchmark Results: Fibonacci

Command Mean [ms] Min [ms] Max [ms] Relative
revm_Fibonacci 203.7 ± 0.9 202.8 205.5 1.00
levm_Fibonacci 883.2 ± 3.8 877.0 890.7 4.34 ± 0.03

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 17.5 ± 0.2 17.3 17.8 2.05 ± 0.03

Benchmark Results: BubbleSort

Command Mean [s] Min [s] Max [s] Relative
revm_BubbleSort 3.208 ± 0.010 3.194 3.232 1.00
levm_BubbleSort 6.078 ± 0.028 6.039 6.122 1.89 ± 0.01

Benchmark Results: ERC20 - Transfer

Command Mean [ms] Min [ms] Max [ms] Relative
revm_ERC20Transfer 249.0 ± 3.6 246.0 256.0 1.00
levm_ERC20Transfer 512.3 ± 2.5 509.1 516.8 2.06 ± 0.03

Benchmark Results: ERC20 - Mint

Command Mean [ms] Min [ms] Max [ms] Relative
revm_ERC20Mint 141.9 ± 0.7 140.9 143.3 1.00
levm_ERC20Mint 332.4 ± 3.4 327.6 339.3 2.34 ± 0.03

Benchmark Results: ERC20 - Approval

Command Mean [s] Min [s] Max [s] Relative
revm_ERC20Approval 1.028 ± 0.008 1.020 1.044 1.00
levm_ERC20Approval 1.981 ± 0.044 1.946 2.099 1.93 ± 0.04

Copy link

github-actions bot commented Apr 7, 2025

EF Tests Comparison

Test Name MAIN PR DIFF
Summary: 32931/38385 (85.79%) 32881/38385 (85.66%) ⬇️️ -50
Prague: 5202/5202 (100.00%) 5202/5202 (100.00%) ➖️
Cancun: 7608/7608 (100.00%) 7608/7608 (100.00%) ➖️
Shanghai: 3214/3214 (100.00%) 3214/3214 (100.00%) ➖️
Paris: 2886/2886 (100.00%) 2886/2886 (100.00%) ➖️
London: 2904/2915 (99.62%) 2898/2915 (99.42%) ⬇️️ -6
Berlin: 215/2755 (7.80%) 215/2755 (7.80%) ➖️
Istanbul: 238/2709 (8.79%) 238/2709 (8.79%) ➖️
Petersburg: 2524/2564 (98.44%) 2512/2564 (97.97%) ⬇️️ -12
Constantinople: 2346/2428 (96.62%) 2334/2428 (96.13%) ⬇️️ -12
Byzantium: 2456/2492 (98.56%) 2444/2492 (98.07%) ⬇️️ -12
SpuriousDragon: 588/598 (98.33%) 580/598 (96.99%) ⬇️️ -8
Tangerine: 592/669 (88.49%) 592/669 (88.49%) ➖️
Homestead: 1373/1465 (93.72%) 1373/1465 (93.72%) ➖️
Frontier: 785/880 (89.20%) 785/880 (89.20%) ➖️

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 and others added 11 commits April 14, 2025 10:28
**Motivation**

<!-- Why does this pull request exist? What are its goals? -->
- merge main changes to my other PR because main had a huge refactor and
my PR was also a huge refactor :)

**Description**

<!-- A clear and concise general description of the changes this PR
introduces -->

<!-- Link to issues: Resolves #111, Resolves #222 -->

Closes #issue_number

---------

Co-authored-by: Julian Ventura <[email protected]>
Co-authored-by: Avila Gastón <[email protected]>
Co-authored-by: fmoletta <[email protected]>
Co-authored-by: Matías Onorato <[email protected]>
Co-authored-by: Edgar <[email protected]>
Co-authored-by: Tomás Arjovsky <[email protected]>
Co-authored-by: Martin Paulucci <[email protected]>
Co-authored-by: Lucas Fiegl <[email protected]>
Co-authored-by: Estéfano Bargas <[email protected]>
Co-authored-by: Javier Rodríguez Chatruc <[email protected]>
Co-authored-by: VolodymyrBg <[email protected]>
Co-authored-by: Tomás Paradelo <[email protected]>
Co-authored-by: Mauro Toscano <[email protected]>
Co-authored-by: Cypher Pepe <[email protected]>
JereSalo and others added 6 commits April 24, 2025 16:36
**Motivation**

<!-- Why does this pull request exist? What are its goals? -->
- stop popping callframe in finalize execution

**Description**

<!-- A clear and concise general description of the changes this PR
introduces -->

<!-- Link to issues: Resolves #111, Resolves #222 -->

Closes #issue_number
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.

Left you some nit comments. We should take a look at the clones, since the recent flamegraph that we made while syning in holesky showed us that we spend too much time cloning data from hashmaps and Account.
Anyways, the code LGTM and it has shown some performance improvements while syncing, so good work!

We can merge and try to remove some data cloning in other PR

// ================== Account related functions =====================
/// Gets account, first checking the cache and then the database
/// (caching in the second case)
pub fn get_account(&mut self, address: Address) -> Result<Account, DatabaseError> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can't we return a reference to the Account here, instead of cloning it? The caller may clone if it needs to.
Cloning too many accounts may be expensive, even more if those Accounts are contracts (contain bytecode and storage).

}

/// Gets account without pushing it to the cache
pub fn get_account_no_push_cache(&self, address: Address) -> Result<Account, DatabaseError> {
Copy link
Contributor

Choose a reason for hiding this comment

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

ditto

&mut self,
accrued_substate: &mut Substate,
address: Address,
) -> Result<(AccountInfo, bool), DatabaseError> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Here we may be ok by cloning the AccountInfo, since it does not contain the bytecode nor storage

// ================== Account related functions =====================

pub fn get_account_mut(&mut self, address: Address) -> Result<&mut Account, VMError> {
if !cache::is_account_cached(&self.db.cache, &address) {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: Instead of interacting with the db.cache, why don't you make a function for the storage which retrieves what you want?
In this case, we could have a get_account_mut on the GeneralizedDatabase which returns a mutable reference to the Account.
You can then clone that if you want to store it in the backup.

@JereSalo
Copy link
Contributor Author

This PR has become too big so the best option is to merge it and address the comments later :)

@JereSalo JereSalo added this pull request to the merge queue Apr 25, 2025
Merged via the queue into main with commit 0739937 Apr 25, 2025
44 checks passed
@JereSalo JereSalo deleted the levm/cache_rollback branch April 25, 2025 22:10
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